mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
Merge branch 'master' into rlp-review
This commit is contained in:
commit
b79a17004e
86 changed files with 2755 additions and 2794 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
|
@ -10,6 +10,7 @@ core/ @karalabe @holiman @rjl493456442
|
||||||
eth/ @karalabe @holiman @rjl493456442
|
eth/ @karalabe @holiman @rjl493456442
|
||||||
eth/catalyst/ @gballet
|
eth/catalyst/ @gballet
|
||||||
eth/tracers/ @s1na
|
eth/tracers/ @s1na
|
||||||
|
core/tracing/ @s1na
|
||||||
graphql/ @s1na
|
graphql/ @s1na
|
||||||
les/ @zsfelfoldi @rjl493456442
|
les/ @zsfelfoldi @rjl493456442
|
||||||
light/ @zsfelfoldi @rjl493456442
|
light/ @zsfelfoldi @rjl493456442
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ run:
|
||||||
# default is true. Enables skipping of directories:
|
# default is true. Enables skipping of directories:
|
||||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||||
skip-dirs-use-default: true
|
skip-dirs-use-default: true
|
||||||
skip-files:
|
|
||||||
- core/genesis_alloc.go
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
|
|
@ -26,6 +24,8 @@ linters:
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
|
### linters we tried and will not be using:
|
||||||
|
###
|
||||||
# - structcheck # lots of false positives
|
# - structcheck # lots of false positives
|
||||||
# - errcheck #lot of false positives
|
# - errcheck #lot of false positives
|
||||||
# - contextcheck
|
# - contextcheck
|
||||||
|
|
@ -40,6 +40,8 @@ linters-settings:
|
||||||
simplify: true
|
simplify: true
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
|
exclude-files:
|
||||||
|
- core/genesis_alloc.go
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: crypto/bn256/cloudflare/optate.go
|
- path: crypto/bn256/cloudflare/optate.go
|
||||||
linters:
|
linters:
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,11 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
|
|
||||||
// Create a temporary keystore to test with
|
// Create a temporary keystore to test with
|
||||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int()))
|
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int()))
|
||||||
|
|
||||||
|
// Create the directory
|
||||||
|
os.MkdirAll(dir, 0700)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
list := ks.Accounts()
|
list := ks.Accounts()
|
||||||
|
|
@ -335,9 +340,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
if !waitWatcherStart(ks) {
|
if !waitWatcherStart(ks) {
|
||||||
t.Fatal("keystore watcher didn't start in time")
|
t.Fatal("keystore watcher didn't start in time")
|
||||||
}
|
}
|
||||||
// Create the directory and copy a key file into it.
|
// Copy a key file into it
|
||||||
os.MkdirAll(dir, 0700)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
file := filepath.Join(dir, "aaa")
|
file := filepath.Join(dir, "aaa")
|
||||||
|
|
||||||
// Place one of our testfiles in there
|
// Place one of our testfiles in there
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
|
||||||
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
||||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
||||||
}
|
}
|
||||||
var blobHashes []common.Hash
|
var blobHashes = make([]common.Hash, 0, len(txs))
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
|
||||||
|
|
||||||
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
|
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
|
||||||
if hash := block.Hash(); hash != expectedHash {
|
if hash := block.Hash(); hash != expectedHash {
|
||||||
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
return nil, fmt.Errorf("sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
||||||
}
|
}
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,36 +48,37 @@ cab2af6951a6e2115824263f6df13ff069c47270f5788714fa1d776f7f60cb39 go1.22.3.windo
|
||||||
40b37f4b068fc759f3a0dd61176a0f7570a4ba48bed8561c31d3967a3583981a go1.22.3.windows-arm.zip
|
40b37f4b068fc759f3a0dd61176a0f7570a4ba48bed8561c31d3967a3583981a go1.22.3.windows-arm.zip
|
||||||
59b76ee22b9b1c3afbf7f50e3cb4edb954d6c0d25e5e029ab5483a6804d61e71 go1.22.3.windows-arm64.zip
|
59b76ee22b9b1c3afbf7f50e3cb4edb954d6c0d25e5e029ab5483a6804d61e71 go1.22.3.windows-arm64.zip
|
||||||
|
|
||||||
# version:golangci 1.55.2
|
# version:golangci 1.59.0
|
||||||
# https://github.com/golangci/golangci-lint/releases/
|
# https://github.com/golangci/golangci-lint/releases/
|
||||||
# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/
|
# https://github.com/golangci/golangci-lint/releases/download/v1.59.0/
|
||||||
632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz
|
418acf7e255ddc0783e97129c9b03d9311b77826a5311d425a01c708a86417e7 golangci-lint-1.59.0-darwin-amd64.tar.gz
|
||||||
234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz
|
5f6a1d95a6dd69f6e328eb56dd311a38e04cfab79a1305fbf4957f4e203f47b6 golangci-lint-1.59.0-darwin-arm64.tar.gz
|
||||||
2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz
|
8899bf589185d49f747f3e5db9f0bde8a47245a100c64a3dd4d65e8e92cfc4f2 golangci-lint-1.59.0-freebsd-386.tar.gz
|
||||||
e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz
|
658212f138d9df2ac89427e22115af34bf387c0871d70f2a25101718946a014f golangci-lint-1.59.0-freebsd-amd64.tar.gz
|
||||||
5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz
|
4c6395ea40f314d3b6fa17d8997baab93464d5d1deeaab513155e625473bd03a golangci-lint-1.59.0-freebsd-armv6.tar.gz
|
||||||
7f21ab1008d05f32c954f99470fc86a83a059e530fe2add1d0b7d8ed4d8992a7 golangci-lint-1.55.2-freebsd-armv7.tar.gz
|
ff37da4fbaacdb6bbae70fdbdbb1ba932a859956f788c82822fa06bef5b7c6b3 golangci-lint-1.59.0-freebsd-armv7.tar.gz
|
||||||
33ab06139b9219a28251f10821da94423db30285cc2af97494cbb2a281927de9 golangci-lint-1.55.2-illumos-amd64.tar.gz
|
439739469ed2bda182b1ec276d40c40e02f195537f78e3672996741ad223d6b6 golangci-lint-1.59.0-illumos-amd64.tar.gz
|
||||||
57ce6f8ce3ad6ee45d7cc3d9a047545a851c2547637834a3fcb086c7b40b1e6b golangci-lint-1.55.2-linux-386.tar.gz
|
940801d46790e40d0a097d8fee34e2606f0ef148cd039654029b0b8750a15ed6 golangci-lint-1.59.0-linux-386.tar.gz
|
||||||
ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c golangci-lint-1.55.2-linux-amd64.tar.gz
|
3b14a439f33c4fff83dbe0349950d984042b9a1feb6c62f82787b598fc3ab5f4 golangci-lint-1.59.0-linux-amd64.tar.gz
|
||||||
8eb0cee9b1dbf0eaa49871798c7f8a5b35f2960c52d776a5f31eb7d886b92746 golangci-lint-1.55.2-linux-arm64.tar.gz
|
c57e6c0b0fa03089a2611dceddd5bc5d206716cccdff8b149da8baac598719a1 golangci-lint-1.59.0-linux-arm64.tar.gz
|
||||||
3195f3e0f37d353fd5bd415cabcd4e263f5c29d3d0ffb176c26ff3d2c75eb3bb golangci-lint-1.55.2-linux-armv6.tar.gz
|
93149e2d3b25ac754df9a23172403d8aa6d021a7e0d9c090a12f51897f68c9a0 golangci-lint-1.59.0-linux-armv6.tar.gz
|
||||||
c823ee36eb1a719e171de1f2f5ca3068033dce8d9817232fd10ed71fd6650406 golangci-lint-1.55.2-linux-armv7.tar.gz
|
d10ac38239d9efee3ee87b55c96cdf3fa09e1a525babe3ffdaaf65ccc48cf3dc golangci-lint-1.59.0-linux-armv7.tar.gz
|
||||||
758a5d2a356dc494bd13ed4c0d4bf5a54a4dc91267ea5ecdd87b86c7ca0624e7 golangci-lint-1.55.2-linux-loong64.tar.gz
|
047338114b4f0d5f08f0fb9a397b03cc171916ed0960be7dfb355c2320cd5e9c golangci-lint-1.59.0-linux-loong64.tar.gz
|
||||||
2c7b9abdce7cae802a67d583cd7c6dca520bff6d0e17c8535a918e2f2b437aa0 golangci-lint-1.55.2-linux-mips64.tar.gz
|
5632df0f7f8fc03a80a266130faef0b5902d280cf60621f1b2bdc1aef6d97ee9 golangci-lint-1.59.0-linux-mips64.tar.gz
|
||||||
024e0a15b85352cc27271285526e16a4ab66d3e67afbbe446c9808c06cb8dbed golangci-lint-1.55.2-linux-mips64le.tar.gz
|
71dd638c82fa4439171e7126d2c7a32b5d103bfdef282cea40c83632cb3d1f4b golangci-lint-1.59.0-linux-mips64le.tar.gz
|
||||||
6b00f89ba5506c1de1efdd9fa17c54093013a294fefd8b9b31534db626a672ee golangci-lint-1.55.2-linux-ppc64le.tar.gz
|
6cf9ea0d34e91669948483f9ae7f07da319a879344373a1981099fbd890cde00 golangci-lint-1.59.0-linux-ppc64le.tar.gz
|
||||||
0faa0d047d9bf7b703ed3ea65b6117043c93504f9ca1de25ae929d3901c73d4a golangci-lint-1.55.2-linux-riscv64.tar.gz
|
af0205fa6fbab197cee613c359947711231739095d21b5c837086233b36ad971 golangci-lint-1.59.0-linux-riscv64.tar.gz
|
||||||
30dec9b22e7d5bb4e9d5ccea96da20f71cd7db3c8cf30b8ddc7cb9174c4d742a golangci-lint-1.55.2-linux-s390x.tar.gz
|
a9d2fb93f3c688ebccef94f5dc96c0b07c4d20bf6556cddebd8442159b0c80f6 golangci-lint-1.59.0-linux-s390x.tar.gz
|
||||||
5a0ede48f79ad707902fdb29be8cd2abd8302dc122b65ebae3fdfc86751c7698 golangci-lint-1.55.2-netbsd-386.tar.gz
|
68ab4c57a847b8ace9679887f2f8b2b6760e57ee29dcde8c3f40dd8bb2654fa2 golangci-lint-1.59.0-netbsd-386.tar.gz
|
||||||
95af20a2e617126dd5b08122ece7819101070e1582a961067ce8c41172f901ad golangci-lint-1.55.2-netbsd-amd64.tar.gz
|
d277b8b435c19406d00de4d509eadf5a024a5782878332e9a1b7c02bb76e87a7 golangci-lint-1.59.0-netbsd-amd64.tar.gz
|
||||||
94fb7dacb7527847cc95d7120904e19a2a0a81a0d50d61766c9e0251da72ab9d golangci-lint-1.55.2-netbsd-armv6.tar.gz
|
83211656be8dcfa1545af4f92894409f412d1f37566798cb9460a526593ad62c golangci-lint-1.59.0-netbsd-arm64.tar.gz
|
||||||
ca906bce5fee9619400e4a321c56476fe4a4efb6ac4fc989d340eb5563348873 golangci-lint-1.55.2-netbsd-armv7.tar.gz
|
6c6866d28bf79fa9817a0f7d2b050890ed109cae80bdb4dfa39536a7226da237 golangci-lint-1.59.0-netbsd-armv6.tar.gz
|
||||||
45b442f69fc8915c4500201c0247b7f3f69544dbc9165403a61f9095f2c57355 golangci-lint-1.55.2-windows-386.zip
|
11587566363bd03ca586b7df9776ccaed569fcd1f3489930ac02f9375b307503 golangci-lint-1.59.0-netbsd-armv7.tar.gz
|
||||||
f57d434d231d43417dfa631587522f8c1991220b43c8ffadb9c7bd279508bf81 golangci-lint-1.55.2-windows-amd64.zip
|
466181a8967bafa495e41494f93a0bec829c2cf715de874583b0460b3b8ae2b8 golangci-lint-1.59.0-windows-386.zip
|
||||||
fd7dc8f4c6829ee6fafb252a4d81d2155cd35da7833665cbb25d53ce7cecd990 golangci-lint-1.55.2-windows-arm64.zip
|
3317d8a87a99a49a0a1321d295c010790e6dbf43ee96b318f4b8bb23eae7a565 golangci-lint-1.59.0-windows-amd64.zip
|
||||||
1892c3c24f9e7ef44b02f6750c703864b6dc350129f3ec39510300007b2376f1 golangci-lint-1.55.2-windows-armv6.zip
|
b3af955c7fceac8220a36fc799e1b3f19d3b247d32f422caac5f9845df8f7316 golangci-lint-1.59.0-windows-arm64.zip
|
||||||
a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-1.55.2-windows-armv7.zip
|
6f083c7d0c764e5a0e5bde46ee3e91ae357d80c194190fe1d9754392e9064c7e golangci-lint-1.59.0-windows-armv6.zip
|
||||||
|
3709b4dd425deadab27748778d08e03c0f804d7748f7dd5b6bb488d98aa031c7 golangci-lint-1.59.0-windows-armv7.zip
|
||||||
|
|
||||||
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
|
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -552,7 +552,7 @@ func listWallets(c *cli.Context) error {
|
||||||
// accountImport imports a raw hexadecimal private key via CLI.
|
// accountImport imports a raw hexadecimal private key via CLI.
|
||||||
func accountImport(c *cli.Context) error {
|
func accountImport(c *cli.Context) error {
|
||||||
if c.Args().Len() != 1 {
|
if c.Args().Len() != 1 {
|
||||||
return errors.New("<keyfile> must be given as first argument.")
|
return errors.New("<keyfile> must be given as first argument")
|
||||||
}
|
}
|
||||||
internalApi, ui, err := initInternalApi(c)
|
internalApi, ui, err := initInternalApi(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func newTestEnv(remote string, listen1, listen2 string) *testenv {
|
||||||
if tcpPort = node.TCP(); tcpPort == 0 {
|
if tcpPort = node.TCP(); tcpPort == 0 {
|
||||||
tcpPort = 30303
|
tcpPort = 30303
|
||||||
}
|
}
|
||||||
if udpPort = node.TCP(); udpPort == 0 {
|
if udpPort = node.UDP(); udpPort == 0 {
|
||||||
udpPort = 30303
|
udpPort = 30303
|
||||||
}
|
}
|
||||||
node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort)
|
node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort)
|
||||||
|
|
@ -110,7 +110,7 @@ func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *testenv) remoteEndpoint() v4wire.Endpoint {
|
func (te *testenv) remoteEndpoint() v4wire.Endpoint {
|
||||||
return v4wire.NewEndpoint(te.remoteAddr, 0)
|
return v4wire.NewEndpoint(te.remoteAddr.AddrPort(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(ns []v4wire.Node, key v4wire.Pubkey) bool {
|
func contains(ns []v4wire.Node, key v4wire.Pubkey) bool {
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if env.ParentBaseFee == nil || env.Number == 0 {
|
if env.ParentBaseFee == nil || env.Number == 0 {
|
||||||
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
|
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'parentBaseFee' in env section"))
|
||||||
}
|
}
|
||||||
env.BaseFee = eip1559.CalcBaseFee(chainConfig, &types.Header{
|
env.BaseFee = eip1559.CalcBaseFee(chainConfig, &types.Header{
|
||||||
Number: new(big.Int).SetUint64(env.Number - 1),
|
Number: new(big.Int).SetUint64(env.Number - 1),
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ data, and verifies that all snapshot storage data has a corresponding account.
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "inspect-account",
|
Name: "inspect-account",
|
||||||
Usage: "Check all snapshot layers for the a specific account",
|
Usage: "Check all snapshot layers for the specific account",
|
||||||
ArgsUsage: "<address | hash>",
|
ArgsUsage: "<address | hash>",
|
||||||
Action: checkAccount,
|
Action: checkAccount,
|
||||||
Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags),
|
Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
|
|
|
||||||
32
cmd/geth/testdata/vcheck/vulnerabilities.json
vendored
32
cmd/geth/testdata/vcheck/vulnerabilities.json
vendored
|
|
@ -166,5 +166,37 @@
|
||||||
"severity": "Low",
|
"severity": "Low",
|
||||||
"CVE": "CVE-2022-29177",
|
"CVE": "CVE-2022-29177",
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
|
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DoS via malicious p2p message",
|
||||||
|
"uid": "GETH-2023-01",
|
||||||
|
"summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
|
||||||
|
"description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.",
|
||||||
|
"links": [
|
||||||
|
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm",
|
||||||
|
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
|
||||||
|
],
|
||||||
|
"introduced": "v1.10.0",
|
||||||
|
"fixed": "v1.12.1",
|
||||||
|
"published": "2023-09-06",
|
||||||
|
"severity": "High",
|
||||||
|
"CVE": "CVE-2023-40591",
|
||||||
|
"check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DoS via malicious p2p message",
|
||||||
|
"uid": "GETH-2024-01",
|
||||||
|
"summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
|
||||||
|
"description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)",
|
||||||
|
"links": [
|
||||||
|
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652",
|
||||||
|
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
|
||||||
|
],
|
||||||
|
"introduced": "v1.10.0",
|
||||||
|
"fixed": "v1.13.15",
|
||||||
|
"published": "2024-05-06",
|
||||||
|
"severity": "High",
|
||||||
|
"CVE": "CVE-2024-32972",
|
||||||
|
"check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -180,9 +180,9 @@ func BenchmarkByteAtOld(b *testing.B) {
|
||||||
func TestReadBits(t *testing.T) {
|
func TestReadBits(t *testing.T) {
|
||||||
check := func(input string) {
|
check := func(input string) {
|
||||||
want, _ := hex.DecodeString(input)
|
want, _ := hex.DecodeString(input)
|
||||||
int, _ := new(big.Int).SetString(input, 16)
|
n, _ := new(big.Int).SetString(input, 16)
|
||||||
buf := make([]byte, len(want))
|
buf := make([]byte, len(want))
|
||||||
ReadBits(int, buf)
|
ReadBits(n, buf)
|
||||||
if !bytes.Equal(buf, want) {
|
if !bytes.Equal(buf, want) {
|
||||||
t.Errorf("have: %x\nwant: %x", buf, want)
|
t.Errorf("have: %x\nwant: %x", buf, want)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ func (i *HexOrDecimal64) UnmarshalJSON(input []byte) error {
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
|
func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
|
||||||
int, ok := ParseUint64(string(input))
|
n, ok := ParseUint64(string(input))
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid hex or decimal integer %q", input)
|
return fmt.Errorf("invalid hex or decimal integer %q", input)
|
||||||
}
|
}
|
||||||
*i = HexOrDecimal64(int)
|
*i = HexOrDecimal64(n)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
// request represents a bloom retrieval task to prioritize and pull from the local
|
// request represents a bloom retrieval task to prioritize and pull from the local
|
||||||
// database or remotely from the network.
|
// database or remotely from the network.
|
||||||
type request struct {
|
type request struct {
|
||||||
section uint64 // Section index to retrieve the a bit-vector from
|
section uint64 // Section index to retrieve the bit-vector from
|
||||||
bit uint // Bit index within the section to retrieve the vector of
|
bit uint // Bit index within the section to retrieve the vector of
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Heade
|
||||||
b.t.Error("Unexpected call to Process")
|
b.t.Error("Unexpected call to Process")
|
||||||
// Can't use Fatal since this is not the test's goroutine.
|
// Can't use Fatal since this is not the test's goroutine.
|
||||||
// Returning error stops the chainIndexer's updateLoop
|
// Returning error stops the chainIndexer's updateLoop
|
||||||
return errors.New("Unexpected call to Process")
|
return errors.New("unexpected call to Process")
|
||||||
case b.processCh <- header.Number.Uint64():
|
case b.processCh <- header.Number.Uint64():
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
|
||||||
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
||||||
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
||||||
// Pre-deploy EIP-4788 system contract
|
// Pre-deploy EIP-4788 system contract
|
||||||
params.BeaconRootsAddress: types.Account{Nonce: 1, Code: params.BeaconRootsCode},
|
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if faucet != nil {
|
if faucet != nil {
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := common.Hex2Bytes("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
|
expected := common.FromHex("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
|
||||||
got := genesis.ToBlock().Root().Bytes()
|
got := genesis.ToBlock().Root().Bytes()
|
||||||
if !bytes.Equal(got, expected) {
|
if !bytes.Equal(got, expected) {
|
||||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
||||||
|
|
@ -314,7 +314,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
||||||
triedb := triedb.NewDatabase(db, &triedb.Config{IsVerkle: true, PathDB: pathdb.Defaults})
|
triedb := triedb.NewDatabase(db, &triedb.Config{IsVerkle: true, PathDB: pathdb.Defaults})
|
||||||
block := genesis.MustCommit(db, triedb)
|
block := genesis.MustCommit(db, triedb)
|
||||||
if !bytes.Equal(block.Root().Bytes(), expected) {
|
if !bytes.Equal(block.Root().Bytes(), expected) {
|
||||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that the trie is verkle
|
// Test that the trie is verkle
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ type freezerOpenFunc = func() (*Freezer, error)
|
||||||
// resettableFreezer is a wrapper of the freezer which makes the
|
// resettableFreezer is a wrapper of the freezer which makes the
|
||||||
// freezer resettable.
|
// freezer resettable.
|
||||||
type resettableFreezer struct {
|
type resettableFreezer struct {
|
||||||
|
readOnly bool
|
||||||
freezer *Freezer
|
freezer *Freezer
|
||||||
opener freezerOpenFunc
|
opener freezerOpenFunc
|
||||||
datadir string
|
datadir string
|
||||||
|
|
@ -60,6 +61,7 @@ func newResettableFreezer(datadir string, namespace string, readonly bool, maxTa
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &resettableFreezer{
|
return &resettableFreezer{
|
||||||
|
readOnly: readonly,
|
||||||
freezer: freezer,
|
freezer: freezer,
|
||||||
opener: opener,
|
opener: opener,
|
||||||
datadir: datadir,
|
datadir: datadir,
|
||||||
|
|
@ -74,6 +76,9 @@ func (f *resettableFreezer) Reset() error {
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
if f.readOnly {
|
||||||
|
return errReadOnly
|
||||||
|
}
|
||||||
if err := f.freezer.Close(); err != nil {
|
if err := f.freezer.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ package state
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"maps"
|
"maps"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -34,14 +32,6 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// hasherPool holds a pool of hashers used by state objects during concurrent
|
|
||||||
// trie updates.
|
|
||||||
var hasherPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return crypto.NewKeccakState()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type Storage map[common.Hash]common.Hash
|
type Storage map[common.Hash]common.Hash
|
||||||
|
|
||||||
func (s Storage) Copy() Storage {
|
func (s Storage) Copy() Storage {
|
||||||
|
|
@ -65,9 +55,20 @@ type stateObject struct {
|
||||||
trie Trie // storage trie, which becomes non-nil on first access
|
trie Trie // storage trie, which becomes non-nil on first access
|
||||||
code []byte // contract bytecode, which gets set when code is loaded
|
code []byte // contract bytecode, which gets set when code is loaded
|
||||||
|
|
||||||
originStorage Storage // Storage cache of original entries to dedup rewrites
|
originStorage Storage // Storage entries that have been accessed within the current block
|
||||||
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
|
dirtyStorage Storage // Storage entries that have been modified within the current transaction
|
||||||
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction
|
pendingStorage Storage // Storage entries that have been modified within the current block
|
||||||
|
|
||||||
|
// uncommittedStorage tracks a set of storage entries that have been modified
|
||||||
|
// but not yet committed since the "last commit operation", along with their
|
||||||
|
// original values before mutation.
|
||||||
|
//
|
||||||
|
// Specifically, the commit will be performed after each transaction before
|
||||||
|
// the byzantium fork, therefore the map is already reset at the transaction
|
||||||
|
// boundary; however post the byzantium fork, the commit will only be performed
|
||||||
|
// at the end of block, this set essentially tracks all the modifications
|
||||||
|
// made within the block.
|
||||||
|
uncommittedStorage Storage
|
||||||
|
|
||||||
// Cache flags.
|
// Cache flags.
|
||||||
dirtyCode bool // true if the code was updated
|
dirtyCode bool // true if the code was updated
|
||||||
|
|
@ -102,16 +103,12 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
||||||
origin: origin,
|
origin: origin,
|
||||||
data: *acct,
|
data: *acct,
|
||||||
originStorage: make(Storage),
|
originStorage: make(Storage),
|
||||||
pendingStorage: make(Storage),
|
|
||||||
dirtyStorage: make(Storage),
|
dirtyStorage: make(Storage),
|
||||||
|
pendingStorage: make(Storage),
|
||||||
|
uncommittedStorage: make(Storage),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeRLP implements rlp.Encoder.
|
|
||||||
func (s *stateObject) EncodeRLP(w io.Writer) error {
|
|
||||||
return rlp.Encode(w, &s.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stateObject) markSelfdestructed() {
|
func (s *stateObject) markSelfdestructed() {
|
||||||
s.selfDestructed = true
|
s.selfDestructed = true
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +124,7 @@ func (s *stateObject) touch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTrie returns the associated storage trie. The trie will be opened if it'
|
// getTrie returns the associated storage trie. The trie will be opened if it's
|
||||||
// not loaded previously. An error will be returned if trie can't be loaded.
|
// not loaded previously. An error will be returned if trie can't be loaded.
|
||||||
//
|
//
|
||||||
// If a new trie is opened, it will be cached within the state object to allow
|
// If a new trie is opened, it will be cached within the state object to allow
|
||||||
|
|
@ -150,17 +147,17 @@ func (s *stateObject) getTrie() (Trie, error) {
|
||||||
// trie in the state object. The caller might want to do that, but it's cleaner
|
// trie in the state object. The caller might want to do that, but it's cleaner
|
||||||
// to break the hidden interdependency between retrieving tries from the db or
|
// to break the hidden interdependency between retrieving tries from the db or
|
||||||
// from the prefetcher.
|
// from the prefetcher.
|
||||||
func (s *stateObject) getPrefetchedTrie() (Trie, error) {
|
func (s *stateObject) getPrefetchedTrie() Trie {
|
||||||
// If there's nothing to meaningfully return, let the user figure it out by
|
// If there's nothing to meaningfully return, let the user figure it out by
|
||||||
// pulling the trie from disk.
|
// pulling the trie from disk.
|
||||||
if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil {
|
if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
// Attempt to retrieve the trie from the pretecher
|
// Attempt to retrieve the trie from the prefetcher
|
||||||
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState retrieves a value from the account storage trie.
|
// GetState retrieves a value associated with the given storage key.
|
||||||
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
||||||
value, _ := s.getState(key)
|
value, _ := s.getState(key)
|
||||||
return value
|
return value
|
||||||
|
|
@ -177,7 +174,8 @@ func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
|
||||||
return origin, origin
|
return origin, origin
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommittedState retrieves a value from the committed account storage trie.
|
// GetCommittedState retrieves the value associated with the specific key
|
||||||
|
// without any mutations caused in the current execution.
|
||||||
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||||
// If we have a pending write or clean cached, return that
|
// If we have a pending write or clean cached, return that
|
||||||
if value, pending := s.pendingStorage[key]; pending {
|
if value, pending := s.pendingStorage[key]; pending {
|
||||||
|
|
@ -193,6 +191,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||||
// have been handles via pendingStorage above.
|
// have been handles via pendingStorage above.
|
||||||
// 2) we don't have new values, and can deliver empty response back
|
// 2) we don't have new values, and can deliver empty response back
|
||||||
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
|
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
|
||||||
|
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
// If no live objects are available, attempt to use snapshots
|
// If no live objects are available, attempt to use snapshots
|
||||||
|
|
@ -272,17 +271,26 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common
|
||||||
func (s *stateObject) finalise() {
|
func (s *stateObject) finalise() {
|
||||||
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
|
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
|
||||||
for key, value := range s.dirtyStorage {
|
for key, value := range s.dirtyStorage {
|
||||||
// If the slot is different from its original value, move it into the
|
if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
|
||||||
// pending area to be committed at the end of the block (and prefetch
|
// The slot is reverted to its original value, delete the entry
|
||||||
// the pathways).
|
// to avoid thrashing the data structures.
|
||||||
if value != s.originStorage[key] {
|
delete(s.uncommittedStorage, key)
|
||||||
s.pendingStorage[key] = value
|
} else if exist {
|
||||||
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
|
// The slot is modified to another value and the slot has been
|
||||||
|
// tracked for commit, do nothing here.
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, the slot was reverted to its original value, remove it
|
// The slot is different from its original value and hasn't been
|
||||||
// from the pending area to avoid thrashing the data structure.
|
// tracked for commit yet.
|
||||||
delete(s.pendingStorage, key)
|
s.uncommittedStorage[key] = s.GetCommittedState(key)
|
||||||
|
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
|
||||||
}
|
}
|
||||||
|
// Aggregate the dirty storage slots into the pending area. It might
|
||||||
|
// be possible that the value of tracked slot here is same with the
|
||||||
|
// one in originStorage (e.g. the slot was modified in tx_a and then
|
||||||
|
// modified back in tx_b). We can't blindly remove it from pending
|
||||||
|
// map as the dirty slot might have been committed already (before the
|
||||||
|
// byzantium fork) and entry is necessary to modify the value back.
|
||||||
|
s.pendingStorage[key] = value
|
||||||
}
|
}
|
||||||
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
||||||
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
|
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
|
||||||
|
|
@ -308,40 +316,23 @@ func (s *stateObject) finalise() {
|
||||||
// It assumes all the dirty storage slots have been finalized before.
|
// It assumes all the dirty storage slots have been finalized before.
|
||||||
func (s *stateObject) updateTrie() (Trie, error) {
|
func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
// Short circuit if nothing changed, don't bother with hashing anything
|
// Short circuit if nothing changed, don't bother with hashing anything
|
||||||
if len(s.pendingStorage) == 0 {
|
if len(s.uncommittedStorage) == 0 {
|
||||||
return s.trie, nil
|
return s.trie, nil
|
||||||
}
|
}
|
||||||
// Retrieve a pretecher populated trie, or fall back to the database
|
// Retrieve a pretecher populated trie, or fall back to the database
|
||||||
tr, err := s.getPrefetchedTrie()
|
tr := s.getPrefetchedTrie()
|
||||||
switch {
|
if tr != nil {
|
||||||
case err != nil:
|
// Prefetcher returned a live trie, swap it out for the current one
|
||||||
// Fetcher retrieval failed, something's very wrong, abort
|
s.trie = tr
|
||||||
s.db.setError(err)
|
} else {
|
||||||
return nil, err
|
|
||||||
|
|
||||||
case tr == nil:
|
|
||||||
// Fetcher not running or empty trie, fallback to the database trie
|
// Fetcher not running or empty trie, fallback to the database trie
|
||||||
|
var err error
|
||||||
tr, err = s.getTrie()
|
tr, err = s.getTrie()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.db.setError(err)
|
s.db.setError(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
// Prefetcher returned a live trie, swap it out for the current one
|
|
||||||
s.trie = tr
|
|
||||||
}
|
}
|
||||||
// The snapshot storage map for the object
|
|
||||||
var (
|
|
||||||
storage map[common.Hash][]byte
|
|
||||||
origin map[common.Hash][]byte
|
|
||||||
)
|
|
||||||
// Insert all the pending storage updates into the trie
|
|
||||||
usedStorage := make([][]byte, 0, len(s.pendingStorage))
|
|
||||||
|
|
||||||
hasher := hasherPool.Get().(crypto.KeccakState)
|
|
||||||
defer hasherPool.Put(hasher)
|
|
||||||
|
|
||||||
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
|
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
|
||||||
// in circumstances similar to the following:
|
// in circumstances similar to the following:
|
||||||
//
|
//
|
||||||
|
|
@ -352,21 +343,23 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
// If the deletion is handled first, then `P` would be left with only one child, thus collapsed
|
// If the deletion is handled first, then `P` would be left with only one child, thus collapsed
|
||||||
// into a shortnode. This requires `B` to be resolved from disk.
|
// into a shortnode. This requires `B` to be resolved from disk.
|
||||||
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
|
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
|
||||||
var deletions []common.Hash
|
var (
|
||||||
for key, value := range s.pendingStorage {
|
deletions []common.Hash
|
||||||
|
used = make([][]byte, 0, len(s.uncommittedStorage))
|
||||||
|
)
|
||||||
|
for key, origin := range s.uncommittedStorage {
|
||||||
// Skip noop changes, persist actual changes
|
// Skip noop changes, persist actual changes
|
||||||
if value == s.originStorage[key] {
|
value, exist := s.pendingStorage[key]
|
||||||
|
if value == origin {
|
||||||
|
log.Error("Storage update was noop", "address", s.address, "slot", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
log.Error("Storage slot is not found in pending area", s.address, "slot", key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prev := s.originStorage[key]
|
|
||||||
s.originStorage[key] = value
|
|
||||||
|
|
||||||
var encoded []byte // rlp-encoded value to be used by the snapshot
|
|
||||||
if (value != common.Hash{}) {
|
if (value != common.Hash{}) {
|
||||||
// Encoding []byte cannot fail, ok to ignore the error.
|
if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil {
|
||||||
trimmed := common.TrimLeftZeroes(value[:])
|
|
||||||
encoded, _ = rlp.EncodeToBytes(trimmed)
|
|
||||||
if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil {
|
|
||||||
s.db.setError(err)
|
s.db.setError(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -374,39 +367,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
} else {
|
} else {
|
||||||
deletions = append(deletions, key)
|
deletions = append(deletions, key)
|
||||||
}
|
}
|
||||||
// Cache the mutated storage slots until commit
|
|
||||||
if storage == nil {
|
|
||||||
s.db.storagesLock.Lock()
|
|
||||||
if storage = s.db.storages[s.addrHash]; storage == nil {
|
|
||||||
storage = make(map[common.Hash][]byte)
|
|
||||||
s.db.storages[s.addrHash] = storage
|
|
||||||
}
|
|
||||||
s.db.storagesLock.Unlock()
|
|
||||||
}
|
|
||||||
khash := crypto.HashData(hasher, key[:])
|
|
||||||
storage[khash] = encoded // encoded will be nil if it's deleted
|
|
||||||
|
|
||||||
// Cache the original value of mutated storage slots
|
|
||||||
if origin == nil {
|
|
||||||
s.db.storagesLock.Lock()
|
|
||||||
if origin = s.db.storagesOrigin[s.address]; origin == nil {
|
|
||||||
origin = make(map[common.Hash][]byte)
|
|
||||||
s.db.storagesOrigin[s.address] = origin
|
|
||||||
}
|
|
||||||
s.db.storagesLock.Unlock()
|
|
||||||
}
|
|
||||||
// Track the original value of slot only if it's mutated first time
|
|
||||||
if _, ok := origin[khash]; !ok {
|
|
||||||
if prev == (common.Hash{}) {
|
|
||||||
origin[khash] = nil // nil if it was not present previously
|
|
||||||
} else {
|
|
||||||
// Encoding []byte cannot fail, ok to ignore the error.
|
|
||||||
b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:]))
|
|
||||||
origin[khash] = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Cache the items for preloading
|
// Cache the items for preloading
|
||||||
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
|
used = append(used, common.CopyBytes(key[:])) // Copy needed for closure
|
||||||
}
|
}
|
||||||
for _, key := range deletions {
|
for _, key := range deletions {
|
||||||
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
|
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
|
||||||
|
|
@ -415,15 +377,10 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
}
|
}
|
||||||
s.db.StorageDeleted.Add(1)
|
s.db.StorageDeleted.Add(1)
|
||||||
}
|
}
|
||||||
// If no slots were touched, issue a warning as we shouldn't have done all
|
|
||||||
// the above work in the first place
|
|
||||||
if len(usedStorage) == 0 {
|
|
||||||
log.Error("State object update was noop", "addr", s.address, "slots", len(s.pendingStorage))
|
|
||||||
}
|
|
||||||
if s.db.prefetcher != nil {
|
if s.db.prefetcher != nil {
|
||||||
s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
|
s.db.prefetcher.used(s.addrHash, s.data.Root, used)
|
||||||
}
|
}
|
||||||
s.pendingStorage = make(Storage) // reset pending map
|
s.uncommittedStorage = make(Storage) // empties the commit markers
|
||||||
return tr, nil
|
return tr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,30 +396,79 @@ func (s *stateObject) updateRoot() {
|
||||||
s.data.Root = tr.Hash()
|
s.data.Root = tr.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit obtains a set of dirty storage trie nodes and updates the account data.
|
// commitStorage overwrites the clean storage with the storage changes and
|
||||||
// The returned set can be nil if nothing to commit. This function assumes all
|
// fulfills the storage diffs into the given accountUpdate struct.
|
||||||
// storage mutations have already been flushed into trie by updateRoot.
|
func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||||
|
var (
|
||||||
|
buf = crypto.NewKeccakState()
|
||||||
|
encode = func(val common.Hash) []byte {
|
||||||
|
if val == (common.Hash{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for key, val := range s.pendingStorage {
|
||||||
|
// Skip the noop storage changes, it might be possible the value
|
||||||
|
// of tracked slot is same in originStorage and pendingStorage
|
||||||
|
// map, e.g. the storage slot is modified in tx_a and then reset
|
||||||
|
// back in tx_b.
|
||||||
|
if val == s.originStorage[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hash := crypto.HashData(buf, key[:])
|
||||||
|
if op.storages == nil {
|
||||||
|
op.storages = make(map[common.Hash][]byte)
|
||||||
|
}
|
||||||
|
op.storages[hash] = encode(val)
|
||||||
|
if op.storagesOrigin == nil {
|
||||||
|
op.storagesOrigin = make(map[common.Hash][]byte)
|
||||||
|
}
|
||||||
|
op.storagesOrigin[hash] = encode(s.originStorage[key])
|
||||||
|
|
||||||
|
// Overwrite the clean value of storage slots
|
||||||
|
s.originStorage[key] = val
|
||||||
|
}
|
||||||
|
s.pendingStorage = make(Storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit obtains the account changes (metadata, storage slots, code) caused by
|
||||||
|
// state execution along with the dirty storage trie nodes.
|
||||||
//
|
//
|
||||||
// Note, commit may run concurrently across all the state objects. Do not assume
|
// Note, commit may run concurrently across all the state objects. Do not assume
|
||||||
// thread-safe access to the statedb.
|
// thread-safe access to the statedb.
|
||||||
func (s *stateObject) commit() (*trienode.NodeSet, error) {
|
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
||||||
// Short circuit if trie is not even loaded, don't bother with committing anything
|
// commit the account metadata changes
|
||||||
if s.trie == nil {
|
op := &accountUpdate{
|
||||||
s.origin = s.data.Copy()
|
address: s.address,
|
||||||
return nil, nil
|
data: types.SlimAccountRLP(s.data),
|
||||||
|
}
|
||||||
|
if s.origin != nil {
|
||||||
|
op.origin = types.SlimAccountRLP(*s.origin)
|
||||||
|
}
|
||||||
|
// commit the contract code if it's modified
|
||||||
|
if s.dirtyCode {
|
||||||
|
op.code = &contractCode{
|
||||||
|
hash: common.BytesToHash(s.CodeHash()),
|
||||||
|
blob: s.code,
|
||||||
|
}
|
||||||
|
s.dirtyCode = false // reset the dirty flag
|
||||||
|
}
|
||||||
|
// Commit storage changes and the associated storage trie
|
||||||
|
s.commitStorage(op)
|
||||||
|
if len(op.storages) == 0 {
|
||||||
|
// nothing changed, don't bother to commit the trie
|
||||||
|
s.origin = s.data.Copy()
|
||||||
|
return op, nil, nil
|
||||||
}
|
}
|
||||||
// The trie is currently in an open state and could potentially contain
|
|
||||||
// cached mutations. Call commit to acquire a set of nodes that have been
|
|
||||||
// modified, the set can be nil if nothing to commit.
|
|
||||||
root, nodes, err := s.trie.Commit(false)
|
root, nodes, err := s.trie.Commit(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
s.data.Root = root
|
s.data.Root = root
|
||||||
|
|
||||||
// Update original account data after commit
|
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return nodes, nil
|
return op, nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBalance adds amount to s's balance.
|
// AddBalance adds amount to s's balance.
|
||||||
|
|
@ -514,6 +520,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
originStorage: s.originStorage.Copy(),
|
originStorage: s.originStorage.Copy(),
|
||||||
pendingStorage: s.pendingStorage.Copy(),
|
pendingStorage: s.pendingStorage.Copy(),
|
||||||
dirtyStorage: s.dirtyStorage.Copy(),
|
dirtyStorage: s.dirtyStorage.Copy(),
|
||||||
|
uncommittedStorage: s.uncommittedStorage.Copy(),
|
||||||
dirtyCode: s.dirtyCode,
|
dirtyCode: s.dirtyCode,
|
||||||
selfDestructed: s.selfDestructed,
|
selfDestructed: s.selfDestructed,
|
||||||
newContract: s.newContract,
|
newContract: s.newContract,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -95,15 +96,6 @@ type StateDB struct {
|
||||||
// It will be updated when the Commit is called.
|
// It will be updated when the Commit is called.
|
||||||
originalRoot common.Hash
|
originalRoot common.Hash
|
||||||
|
|
||||||
// These maps hold the state changes (including the corresponding
|
|
||||||
// original value) that occurred in this **block**.
|
|
||||||
accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
|
|
||||||
accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
|
|
||||||
|
|
||||||
storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format
|
|
||||||
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
|
|
||||||
storagesLock sync.Mutex // Mutex protecting the maps during concurrent updates/commits
|
|
||||||
|
|
||||||
// This map holds 'live' objects, which will get modified while
|
// This map holds 'live' objects, which will get modified while
|
||||||
// processing a state transition.
|
// processing a state transition.
|
||||||
stateObjects map[common.Address]*stateObject
|
stateObjects map[common.Address]*stateObject
|
||||||
|
|
@ -171,9 +163,6 @@ type StateDB struct {
|
||||||
StorageUpdated atomic.Int64
|
StorageUpdated atomic.Int64
|
||||||
AccountDeleted int
|
AccountDeleted int
|
||||||
StorageDeleted atomic.Int64
|
StorageDeleted atomic.Int64
|
||||||
|
|
||||||
// Testing hooks
|
|
||||||
onCommit func(states *triestate.Set) // Hook invoked when commit is performed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// New creates a new state from a given trie.
|
||||||
|
|
@ -187,10 +176,6 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||||
trie: tr,
|
trie: tr,
|
||||||
originalRoot: root,
|
originalRoot: root,
|
||||||
snaps: snaps,
|
snaps: snaps,
|
||||||
accounts: make(map[common.Hash][]byte),
|
|
||||||
storages: make(map[common.Hash]map[common.Hash][]byte),
|
|
||||||
accountsOrigin: make(map[common.Address][]byte),
|
|
||||||
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
|
|
||||||
stateObjects: make(map[common.Address]*stateObject),
|
stateObjects: make(map[common.Address]*stateObject),
|
||||||
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
|
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
|
||||||
mutations: make(map[common.Address]*mutation),
|
mutations: make(map[common.Address]*mutation),
|
||||||
|
|
@ -351,7 +336,7 @@ func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxIndex returns the current transaction index set by Prepare.
|
// TxIndex returns the current transaction index set by SetTxContext.
|
||||||
func (s *StateDB) TxIndex() int {
|
func (s *StateDB) TxIndex() int {
|
||||||
return s.txIndex
|
return s.txIndex
|
||||||
}
|
}
|
||||||
|
|
@ -380,7 +365,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState retrieves a value from the given account's storage trie.
|
// GetState retrieves the value associated with the specific key.
|
||||||
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
|
@ -389,7 +374,8 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
// GetCommittedState retrieves the value associated with the specific key
|
||||||
|
// without any mutations caused in the current execution.
|
||||||
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
|
|
@ -557,22 +543,6 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
||||||
if obj.dirtyCode {
|
if obj.dirtyCode {
|
||||||
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
|
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
|
||||||
}
|
}
|
||||||
// Cache the data until commit. Note, this update mechanism is not symmetric
|
|
||||||
// to the deletion, because whereas it is enough to track account updates
|
|
||||||
// at commit time, deletions need tracking at transaction boundary level to
|
|
||||||
// ensure we capture state clearing.
|
|
||||||
s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data)
|
|
||||||
|
|
||||||
// Track the original value of mutated account, nil means it was not present.
|
|
||||||
// Skip if it has been tracked (because updateStateObject may be called
|
|
||||||
// multiple times in a block).
|
|
||||||
if _, ok := s.accountsOrigin[obj.address]; !ok {
|
|
||||||
if obj.origin == nil {
|
|
||||||
s.accountsOrigin[obj.address] = nil
|
|
||||||
} else {
|
|
||||||
s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteStateObject removes the given object from the state trie.
|
// deleteStateObject removes the given object from the state trie.
|
||||||
|
|
@ -691,10 +661,6 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
trie: s.db.CopyTrie(s.trie),
|
trie: s.db.CopyTrie(s.trie),
|
||||||
hasher: crypto.NewKeccakState(),
|
hasher: crypto.NewKeccakState(),
|
||||||
originalRoot: s.originalRoot,
|
originalRoot: s.originalRoot,
|
||||||
accounts: copySet(s.accounts),
|
|
||||||
storages: copy2DSet(s.storages),
|
|
||||||
accountsOrigin: copySet(s.accountsOrigin),
|
|
||||||
storagesOrigin: copy2DSet(s.storagesOrigin),
|
|
||||||
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
|
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
|
||||||
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
|
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
|
||||||
mutations: make(map[common.Address]*mutation, len(s.mutations)),
|
mutations: make(map[common.Address]*mutation, len(s.mutations)),
|
||||||
|
|
@ -803,13 +769,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||||
s.stateObjectsDestruct[obj.address] = obj.origin
|
s.stateObjectsDestruct[obj.address] = obj.origin
|
||||||
}
|
}
|
||||||
// Note, we can't do this only at the end of a block because multiple
|
|
||||||
// transactions within the same block might self destruct and then
|
|
||||||
// resurrect an account; but the snapshotter needs both events.
|
|
||||||
delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
|
|
||||||
delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
|
||||||
delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect)
|
|
||||||
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
|
||||||
} else {
|
} else {
|
||||||
obj.finalise()
|
obj.finalise()
|
||||||
s.markUpdate(addr)
|
s.markUpdate(addr)
|
||||||
|
|
@ -878,9 +837,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
|
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
if trie, err := s.prefetcher.trie(common.Hash{}, s.originalRoot); err != nil {
|
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
||||||
log.Error("Failed to retrieve account pre-fetcher trie", "err", err)
|
log.Error("Failed to retrieve account pre-fetcher trie")
|
||||||
} else if trie != nil {
|
} else {
|
||||||
s.trie = trie
|
s.trie = trie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1020,10 +979,9 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
// deleteStorage is designed to delete the storage trie of a designated account.
|
||||||
// It could potentially be terminated if the storage size is excessively large,
|
// The function will make an attempt to utilize an efficient strategy if the
|
||||||
// potentially leading to an out-of-memory panic. The function will make an attempt
|
// associated state snapshot is reachable; otherwise, it will resort to a less
|
||||||
// to utilize an efficient strategy if the associated state snapshot is reachable;
|
// efficient approach.
|
||||||
// otherwise, it will resort to a less-efficient approach.
|
|
||||||
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) {
|
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
|
|
@ -1058,75 +1016,61 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDestruction processes all destruction markers and deletes the account
|
// handleDestruction processes all destruction markers and deletes the account
|
||||||
// and associated storage slots if necessary. There are four possible situations
|
// and associated storage slots if necessary. There are four potential scenarios
|
||||||
// here:
|
// as following:
|
||||||
//
|
//
|
||||||
// - the account was not existent and be marked as destructed
|
// (a) the account was not existent and be marked as destructed
|
||||||
//
|
// (b) the account was not existent and be marked as destructed,
|
||||||
// - the account was not existent and be marked as destructed,
|
|
||||||
// however, it's resurrected later in the same block.
|
// however, it's resurrected later in the same block.
|
||||||
//
|
// (c) the account was existent and be marked as destructed
|
||||||
// - the account was existent and be marked as destructed
|
// (d) the account was existent and be marked as destructed,
|
||||||
//
|
|
||||||
// - the account was existent and be marked as destructed,
|
|
||||||
// however it's resurrected later in the same block.
|
// however it's resurrected later in the same block.
|
||||||
//
|
//
|
||||||
// In case (a), nothing needs be deleted, nil to nil transition can be ignored.
|
// In case (a), nothing needs be deleted, nil to nil transition can be ignored.
|
||||||
//
|
|
||||||
// In case (b), nothing needs be deleted, nil is used as the original value for
|
// In case (b), nothing needs be deleted, nil is used as the original value for
|
||||||
// newly created account and storages
|
// newly created account and storages
|
||||||
//
|
|
||||||
// In case (c), **original** account along with its storages should be deleted,
|
// In case (c), **original** account along with its storages should be deleted,
|
||||||
// with their values be tracked as original value.
|
// with their values be tracked as original value.
|
||||||
//
|
|
||||||
// In case (d), **original** account along with its storages should be deleted,
|
// In case (d), **original** account along with its storages should be deleted,
|
||||||
// with their values be tracked as original value.
|
// with their values be tracked as original value.
|
||||||
func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) error {
|
func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
|
||||||
// Short circuit if geth is running with hash mode. This procedure can consume
|
var (
|
||||||
// considerable time and storage deletion isn't supported in hash mode, thus
|
nodes []*trienode.NodeSet
|
||||||
// preemptively avoiding unnecessary expenses.
|
buf = crypto.NewKeccakState()
|
||||||
if s.db.TrieDB().Scheme() == rawdb.HashScheme {
|
deletes = make(map[common.Hash]*accountDelete)
|
||||||
return nil
|
)
|
||||||
}
|
|
||||||
for addr, prev := range s.stateObjectsDestruct {
|
for addr, prev := range s.stateObjectsDestruct {
|
||||||
// The original account was non-existing, and it's marked as destructed
|
// The account was non-existent, and it's marked as destructed in the scope
|
||||||
// in the scope of block. It can be case (a) or (b).
|
// of block. It can be either case (a) or (b) and will be interpreted as
|
||||||
// - for (a), skip it without doing anything.
|
// null->null state transition.
|
||||||
// - for (b), track account's original value as nil. It may overwrite
|
// - for (a), skip it without doing anything
|
||||||
// the data cached in s.accountsOrigin set by 'updateStateObject'.
|
// - for (b), the resurrected account with nil as original will be handled afterwards
|
||||||
addrHash := crypto.Keccak256Hash(addr[:])
|
|
||||||
if prev == nil {
|
if prev == nil {
|
||||||
if _, ok := s.accounts[addrHash]; ok {
|
|
||||||
s.accountsOrigin[addr] = nil // case (b)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// It can overwrite the data in s.accountsOrigin set by 'updateStateObject'.
|
// The account was existent, it can be either case (c) or (d).
|
||||||
s.accountsOrigin[addr] = types.SlimAccountRLP(*prev) // case (c) or (d)
|
addrHash := crypto.HashData(buf, addr.Bytes())
|
||||||
|
op := &accountDelete{
|
||||||
|
address: addr,
|
||||||
|
origin: types.SlimAccountRLP(*prev),
|
||||||
|
}
|
||||||
|
deletes[addrHash] = op
|
||||||
|
|
||||||
// Short circuit if the storage was empty.
|
// Short circuit if the origin storage was empty.
|
||||||
if prev.Root == types.EmptyRootHash {
|
if prev.Root == types.EmptyRootHash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Remove storage slots belong to the account.
|
// Remove storage slots belonging to the account.
|
||||||
slots, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
slots, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete storage, err: %w", err)
|
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
}
|
||||||
if s.storagesOrigin[addr] == nil {
|
op.storagesOrigin = slots
|
||||||
s.storagesOrigin[addr] = slots
|
|
||||||
} else {
|
// Aggregate the associated trie node changes.
|
||||||
// It can overwrite the data in s.storagesOrigin[addrHash] set by
|
nodes = append(nodes, set)
|
||||||
// 'object.updateTrie'.
|
|
||||||
for key, val := range slots {
|
|
||||||
s.storagesOrigin[addr][key] = val
|
|
||||||
}
|
}
|
||||||
}
|
return deletes, nodes, nil
|
||||||
if err := nodes.Merge(set); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTrie returns the account trie.
|
// GetTrie returns the account trie.
|
||||||
|
|
@ -1134,18 +1078,12 @@ func (s *StateDB) GetTrie() Trie {
|
||||||
return s.trie
|
return s.trie
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit writes the state to the underlying in-memory trie database.
|
// commit gathers the state mutations accumulated along with the associated
|
||||||
// Once the state is committed, tries cached in stateDB (including account
|
// trie changes, resetting all internal flags with the new state as the base.
|
||||||
// trie, storage tries) will no longer be functional. A new state instance
|
func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
||||||
// must be created with new root and updated database for accessing post-
|
|
||||||
// commit states.
|
|
||||||
//
|
|
||||||
// The associated block number of the state transition is also provided
|
|
||||||
// for more chain context.
|
|
||||||
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) {
|
|
||||||
// Short circuit in case any database failure occurred earlier.
|
// Short circuit in case any database failure occurred earlier.
|
||||||
if s.dbErr != nil {
|
if s.dbErr != nil {
|
||||||
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||||
}
|
}
|
||||||
// Finalize any pending changes and merge everything into the tries
|
// Finalize any pending changes and merge everything into the tries
|
||||||
s.IntermediateRoot(deleteEmptyObjects)
|
s.IntermediateRoot(deleteEmptyObjects)
|
||||||
|
|
@ -1156,19 +1094,56 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
accountTrieNodesDeleted int
|
accountTrieNodesDeleted int
|
||||||
storageTrieNodesUpdated int
|
storageTrieNodesUpdated int
|
||||||
storageTrieNodesDeleted int
|
storageTrieNodesDeleted int
|
||||||
nodes = trienode.NewMergedNodeSet()
|
|
||||||
|
lock sync.Mutex // protect two maps below
|
||||||
|
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
|
||||||
|
updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
|
||||||
|
|
||||||
|
// merge aggregates the dirty trie nodes into the global set.
|
||||||
|
//
|
||||||
|
// Given that some accounts may be destroyed and then recreated within
|
||||||
|
// the same block, it's possible that a node set with the same owner
|
||||||
|
// may already exists. In such cases, these two sets are combined, with
|
||||||
|
// the later one overwriting the previous one if any nodes are modified
|
||||||
|
// or deleted in both sets.
|
||||||
|
//
|
||||||
|
// merge run concurrently across all the state objects and account trie.
|
||||||
|
merge = func(set *trienode.NodeSet) error {
|
||||||
|
if set == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
updates, deletes := set.Size()
|
||||||
|
if set.Owner == (common.Hash{}) {
|
||||||
|
accountTrieNodesUpdated += updates
|
||||||
|
accountTrieNodesDeleted += deletes
|
||||||
|
} else {
|
||||||
|
storageTrieNodesUpdated += updates
|
||||||
|
storageTrieNodesDeleted += deletes
|
||||||
|
}
|
||||||
|
return nodes.Merge(set)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
// Handle all state deletions first
|
// Given that some accounts could be destroyed and then recreated within
|
||||||
if err := s.handleDestruction(nodes); err != nil {
|
// the same block, account deletions must be processed first. This ensures
|
||||||
return common.Hash{}, err
|
// that the storage trie nodes deleted during destruction and recreated
|
||||||
|
// during subsequent resurrection can be combined correctly.
|
||||||
|
deletes, delNodes, err := s.handleDestruction()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, set := range delNodes {
|
||||||
|
if err := merge(set); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Handle all state updates afterwards, concurrently to one another to shave
|
// Handle all state updates afterwards, concurrently to one another to shave
|
||||||
// off some milliseconds from the commit operation. Also accumulate the code
|
// off some milliseconds from the commit operation. Also accumulate the code
|
||||||
// writes to run in parallel with the computations.
|
// writes to run in parallel with the computations.
|
||||||
start := time.Now()
|
|
||||||
var (
|
var (
|
||||||
code = s.db.DiskDB().NewBatch()
|
start = time.Now()
|
||||||
lock sync.Mutex
|
|
||||||
root common.Hash
|
root common.Hash
|
||||||
workers errgroup.Group
|
workers errgroup.Group
|
||||||
)
|
)
|
||||||
|
|
@ -1189,16 +1164,9 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
}
|
}
|
||||||
root = newroot
|
root = newroot
|
||||||
|
|
||||||
// Merge the dirty nodes of account trie into global set
|
if err := merge(set); err != nil {
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
|
|
||||||
if set != nil {
|
|
||||||
if err = nodes.Merge(set); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
|
|
||||||
}
|
|
||||||
s.AccountCommits = time.Since(start)
|
s.AccountCommits = time.Since(start)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
@ -1215,49 +1183,29 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
}
|
}
|
||||||
// Write any contract code associated with the state object
|
// Write any contract code associated with the state object
|
||||||
obj := s.stateObjects[addr]
|
obj := s.stateObjects[addr]
|
||||||
if obj.code != nil && obj.dirtyCode {
|
if obj == nil {
|
||||||
rawdb.WriteCode(code, common.BytesToHash(obj.CodeHash()), obj.code)
|
return nil, errors.New("missing state object")
|
||||||
obj.dirtyCode = false
|
|
||||||
}
|
}
|
||||||
// Run the storage updates concurrently to one another
|
// Run the storage updates concurrently to one another
|
||||||
workers.Go(func() error {
|
workers.Go(func() error {
|
||||||
// Write any storage changes in the state object to its storage trie
|
// Write any storage changes in the state object to its storage trie
|
||||||
set, err := obj.commit()
|
update, set, err := obj.commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Merge the dirty nodes of storage trie into global set. It is possible
|
if err := merge(set); err != nil {
|
||||||
// that the account was destructed and then resurrected in the same block.
|
|
||||||
// In this case, the node set is shared by both accounts.
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
|
|
||||||
if set != nil {
|
|
||||||
if err = nodes.Merge(set); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updates, deleted := set.Size()
|
lock.Lock()
|
||||||
storageTrieNodesUpdated += updates
|
updates[obj.addrHash] = update
|
||||||
storageTrieNodesDeleted += deleted
|
lock.Unlock()
|
||||||
}
|
|
||||||
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Schedule the code commits to run concurrently too. This shouldn't really
|
|
||||||
// take much since we don't often commit code, but since it's disk access,
|
|
||||||
// it's always yolo.
|
|
||||||
workers.Go(func() error {
|
|
||||||
if code.ValueSize() > 0 {
|
|
||||||
if err := code.Write(); err != nil {
|
|
||||||
log.Crit("Failed to commit dirty codes", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
// Wait for everything to finish and update the metrics
|
// Wait for everything to finish and update the metrics
|
||||||
if err := workers.Wait(); err != nil {
|
if err := workers.Wait(); err != nil {
|
||||||
return common.Hash{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
|
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
|
||||||
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
|
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
|
||||||
|
|
@ -1271,53 +1219,78 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
s.StorageUpdated.Store(0)
|
s.StorageUpdated.Store(0)
|
||||||
s.StorageDeleted.Store(0)
|
s.StorageDeleted.Store(0)
|
||||||
|
|
||||||
|
// Clear all internal flags and update state root at the end.
|
||||||
|
s.mutations = make(map[common.Address]*mutation)
|
||||||
|
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
||||||
|
|
||||||
|
origin := s.originalRoot
|
||||||
|
s.originalRoot = root
|
||||||
|
return newStateUpdate(origin, root, deletes, updates, nodes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// commitAndFlush is a wrapper of commit which also commits the state mutations
|
||||||
|
// to the configured data stores.
|
||||||
|
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateUpdate, error) {
|
||||||
|
ret, err := s.commit(deleteEmptyObjects)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Commit dirty contract code if any exists
|
||||||
|
if db := s.db.DiskDB(); db != nil && len(ret.codes) > 0 {
|
||||||
|
batch := db.NewBatch()
|
||||||
|
for _, code := range ret.codes {
|
||||||
|
rawdb.WriteCode(batch, code.hash, code.blob)
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ret.empty() {
|
||||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
start = time.Now()
|
s.snap = nil
|
||||||
// Only update if there's a state transition (skip empty Clique blocks)
|
|
||||||
if parent := s.snap.Root(); parent != root {
|
start := time.Now()
|
||||||
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil {
|
if err := s.snaps.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil {
|
||||||
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
|
log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)
|
||||||
}
|
}
|
||||||
// Keep TriesInMemory diff layers in the memory, persistent layer is 129th.
|
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||||
// - head layer is paired with HEAD state
|
// - head layer is paired with HEAD state
|
||||||
// - head-1 layer is paired with HEAD-1 state
|
// - head-1 layer is paired with HEAD-1 state
|
||||||
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||||
if err := s.snaps.Cap(root, TriesInMemory); err != nil {
|
if err := s.snaps.Cap(ret.root, TriesInMemory); err != nil {
|
||||||
log.Warn("Failed to cap snapshot tree", "root", root, "layers", TriesInMemory, "err", err)
|
log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.SnapshotCommits += time.Since(start)
|
s.SnapshotCommits += time.Since(start)
|
||||||
s.snap = nil
|
|
||||||
}
|
}
|
||||||
if root == (common.Hash{}) {
|
// If trie database is enabled, commit the state update as a new layer
|
||||||
root = types.EmptyRootHash
|
if db := s.db.TrieDB(); db != nil {
|
||||||
|
start := time.Now()
|
||||||
|
set := triestate.New(ret.accountsOrigin, ret.storagesOrigin)
|
||||||
|
if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, set); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
origin := s.originalRoot
|
s.TrieDBCommits += time.Since(start)
|
||||||
if origin == (common.Hash{}) {
|
|
||||||
origin = types.EmptyRootHash
|
|
||||||
}
|
}
|
||||||
if root != origin {
|
}
|
||||||
start = time.Now()
|
return ret, err
|
||||||
set := triestate.New(s.accountsOrigin, s.storagesOrigin)
|
}
|
||||||
if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
|
|
||||||
|
// Commit writes the state mutations into the configured data stores.
|
||||||
|
//
|
||||||
|
// Once the state is committed, tries cached in stateDB (including account
|
||||||
|
// trie, storage tries) will no longer be functional. A new state instance
|
||||||
|
// must be created with new root and updated database for accessing post-
|
||||||
|
// commit states.
|
||||||
|
//
|
||||||
|
// The associated block number of the state transition is also provided
|
||||||
|
// for more chain context.
|
||||||
|
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) {
|
||||||
|
ret, err := s.commitAndFlush(block, deleteEmptyObjects)
|
||||||
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
s.originalRoot = root
|
return ret.root, nil
|
||||||
s.TrieDBCommits += time.Since(start)
|
|
||||||
|
|
||||||
if s.onCommit != nil {
|
|
||||||
s.onCommit(set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clear all internal flags at the end of commit operation.
|
|
||||||
s.accounts = make(map[common.Hash][]byte)
|
|
||||||
s.storages = make(map[common.Hash]map[common.Hash][]byte)
|
|
||||||
s.accountsOrigin = make(map[common.Address][]byte)
|
|
||||||
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
|
||||||
s.mutations = make(map[common.Address]*mutation)
|
|
||||||
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
|
||||||
return root, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare handles the preparatory steps for executing a state transition with.
|
// Prepare handles the preparatory steps for executing a state transition with.
|
||||||
|
|
@ -1399,41 +1372,9 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
|
||||||
return s.accessList.Contains(addr, slot)
|
return s.accessList.Contains(addr, slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertAccountSet converts a provided account set from address keyed to hash keyed.
|
// markDelete is invoked when an account is deleted but the deletion is
|
||||||
func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} {
|
// not yet committed. The pending mutation is cached and will be applied
|
||||||
ret := make(map[common.Hash]struct{}, len(set))
|
// all together
|
||||||
for addr := range set {
|
|
||||||
obj, exist := s.stateObjects[addr]
|
|
||||||
if !exist {
|
|
||||||
ret[crypto.Keccak256Hash(addr[:])] = struct{}{}
|
|
||||||
} else {
|
|
||||||
ret[obj.addrHash] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// copySet returns a deep-copied set.
|
|
||||||
func copySet[k comparable](set map[k][]byte) map[k][]byte {
|
|
||||||
copied := make(map[k][]byte, len(set))
|
|
||||||
for key, val := range set {
|
|
||||||
copied[key] = common.CopyBytes(val)
|
|
||||||
}
|
|
||||||
return copied
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy2DSet returns a two-dimensional deep-copied set.
|
|
||||||
func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.Hash][]byte {
|
|
||||||
copied := make(map[k]map[common.Hash][]byte, len(set))
|
|
||||||
for addr, subset := range set {
|
|
||||||
copied[addr] = make(map[common.Hash][]byte, len(subset))
|
|
||||||
for key, val := range subset {
|
|
||||||
copied[addr][key] = common.CopyBytes(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return copied
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StateDB) markDelete(addr common.Address) {
|
func (s *StateDB) markDelete(addr common.Address) {
|
||||||
if _, ok := s.mutations[addr]; !ok {
|
if _, ok := s.mutations[addr]; !ok {
|
||||||
s.mutations[addr] = &mutation{}
|
s.mutations[addr] = &mutation{}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
@ -180,9 +179,21 @@ func (test *stateTest) run() bool {
|
||||||
roots []common.Hash
|
roots []common.Hash
|
||||||
accountList []map[common.Address][]byte
|
accountList []map[common.Address][]byte
|
||||||
storageList []map[common.Address]map[common.Hash][]byte
|
storageList []map[common.Address]map[common.Hash][]byte
|
||||||
onCommit = func(states *triestate.Set) {
|
copyUpdate = func(update *stateUpdate) {
|
||||||
accountList = append(accountList, copySet(states.Accounts))
|
accounts := make(map[common.Address][]byte, len(update.accountsOrigin))
|
||||||
storageList = append(storageList, copy2DSet(states.Storages))
|
for key, val := range update.accountsOrigin {
|
||||||
|
accounts[key] = common.CopyBytes(val)
|
||||||
|
}
|
||||||
|
accountList = append(accountList, accounts)
|
||||||
|
|
||||||
|
storages := make(map[common.Address]map[common.Hash][]byte, len(update.storagesOrigin))
|
||||||
|
for addr, subset := range update.storagesOrigin {
|
||||||
|
storages[addr] = make(map[common.Hash][]byte, len(subset))
|
||||||
|
for key, val := range subset {
|
||||||
|
storages[addr][key] = common.CopyBytes(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storageList = append(storageList, storages)
|
||||||
}
|
}
|
||||||
disk = rawdb.NewMemoryDatabase()
|
disk = rawdb.NewMemoryDatabase()
|
||||||
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
||||||
|
|
@ -210,8 +221,6 @@ func (test *stateTest) run() bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
state.onCommit = onCommit
|
|
||||||
|
|
||||||
for i, action := range actions {
|
for i, action := range actions {
|
||||||
if i%test.chunk == 0 && i != 0 {
|
if i%test.chunk == 0 && i != 0 {
|
||||||
if byzantium {
|
if byzantium {
|
||||||
|
|
@ -227,14 +236,15 @@ func (test *stateTest) run() bool {
|
||||||
} else {
|
} else {
|
||||||
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
|
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
|
||||||
}
|
}
|
||||||
nroot, err := state.Commit(0, true) // call commit at the block boundary
|
ret, err := state.commitAndFlush(0, true) // call commit at the block boundary
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if nroot == root {
|
if ret.empty() {
|
||||||
return true // filter out non-change state transition
|
return true
|
||||||
}
|
}
|
||||||
roots = append(roots, nroot)
|
copyUpdate(ret)
|
||||||
|
roots = append(roots, ret.root)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(test.actions); i++ {
|
for i := 0; i < len(test.actions); i++ {
|
||||||
root := types.EmptyRootHash
|
root := types.EmptyRootHash
|
||||||
|
|
|
||||||
133
core/state/stateupdate.go
Normal file
133
core/state/stateupdate.go
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// contractCode represents a contract code with associated metadata.
|
||||||
|
type contractCode struct {
|
||||||
|
hash common.Hash // hash is the cryptographic hash of the contract code.
|
||||||
|
blob []byte // blob is the binary representation of the contract code.
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountDelete represents an operation for deleting an Ethereum account.
|
||||||
|
type accountDelete struct {
|
||||||
|
address common.Address // address is the unique account identifier
|
||||||
|
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
||||||
|
storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format.
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountUpdate represents an operation for updating an Ethereum account.
|
||||||
|
type accountUpdate struct {
|
||||||
|
address common.Address // address is the unique account identifier
|
||||||
|
data []byte // data is the slim-RLP encoded account data.
|
||||||
|
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
||||||
|
code *contractCode // code represents mutated contract code; nil means it's not modified.
|
||||||
|
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
|
||||||
|
storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format.
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateUpdate represents the difference between two states resulting from state
|
||||||
|
// execution. It contains information about mutated contract codes, accounts,
|
||||||
|
// and storage slots, along with their original values.
|
||||||
|
type stateUpdate struct {
|
||||||
|
originRoot common.Hash // hash of the state before applying mutation
|
||||||
|
root common.Hash // hash of the state after applying mutation
|
||||||
|
destructs map[common.Hash]struct{} // destructs contains the list of destructed accounts
|
||||||
|
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
|
||||||
|
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
|
||||||
|
storages map[common.Hash]map[common.Hash][]byte // storages stores mutated slots in 'prefix-zero-trimmed' RLP format
|
||||||
|
storagesOrigin map[common.Address]map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in 'prefix-zero-trimmed' RLP format
|
||||||
|
codes map[common.Address]contractCode // codes contains the set of dirty codes
|
||||||
|
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty returns a flag indicating the state transition is empty or not.
|
||||||
|
func (sc *stateUpdate) empty() bool {
|
||||||
|
return sc.originRoot == sc.root
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStateUpdate constructs a state update object, representing the differences
|
||||||
|
// between two states by performing state execution. It aggregates the given
|
||||||
|
// account deletions and account updates to form a comprehensive state update.
|
||||||
|
func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
|
||||||
|
var (
|
||||||
|
destructs = make(map[common.Hash]struct{})
|
||||||
|
accounts = make(map[common.Hash][]byte)
|
||||||
|
accountsOrigin = make(map[common.Address][]byte)
|
||||||
|
storages = make(map[common.Hash]map[common.Hash][]byte)
|
||||||
|
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||||
|
codes = make(map[common.Address]contractCode)
|
||||||
|
)
|
||||||
|
// Due to the fact that some accounts could be destructed and resurrected
|
||||||
|
// within the same block, the deletions must be aggregated first.
|
||||||
|
for addrHash, op := range deletes {
|
||||||
|
addr := op.address
|
||||||
|
destructs[addrHash] = struct{}{}
|
||||||
|
accountsOrigin[addr] = op.origin
|
||||||
|
if len(op.storagesOrigin) > 0 {
|
||||||
|
storagesOrigin[addr] = op.storagesOrigin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Aggregate account updates then.
|
||||||
|
for addrHash, op := range updates {
|
||||||
|
// Aggregate dirty contract codes if they are available.
|
||||||
|
addr := op.address
|
||||||
|
if op.code != nil {
|
||||||
|
codes[addr] = *op.code
|
||||||
|
}
|
||||||
|
// Aggregate the account changes. The original account value will only
|
||||||
|
// be tracked if it's not present yet.
|
||||||
|
accounts[addrHash] = op.data
|
||||||
|
if _, found := accountsOrigin[addr]; !found {
|
||||||
|
accountsOrigin[addr] = op.origin
|
||||||
|
}
|
||||||
|
// Aggregate the storage changes. The original storage slot value will
|
||||||
|
// only be tracked if it's not present yet.
|
||||||
|
if len(op.storages) > 0 {
|
||||||
|
storages[addrHash] = op.storages
|
||||||
|
}
|
||||||
|
if len(op.storagesOrigin) > 0 {
|
||||||
|
origin := storagesOrigin[addr]
|
||||||
|
if origin == nil {
|
||||||
|
storagesOrigin[addr] = op.storagesOrigin
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for key, slot := range op.storagesOrigin {
|
||||||
|
if _, found := origin[key]; !found {
|
||||||
|
origin[key] = slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storagesOrigin[addr] = origin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &stateUpdate{
|
||||||
|
originRoot: types.TrieRootHash(originRoot),
|
||||||
|
root: types.TrieRootHash(root),
|
||||||
|
destructs: destructs,
|
||||||
|
accounts: accounts,
|
||||||
|
accountsOrigin: accountsOrigin,
|
||||||
|
storages: storages,
|
||||||
|
storagesOrigin: storagesOrigin,
|
||||||
|
codes: codes,
|
||||||
|
nodes: nodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminate iterates over all the subfetchers and issues a terminateion request
|
// terminate iterates over all the subfetchers and issues a termination request
|
||||||
// to all of them. Depending on the async parameter, the method will either block
|
// to all of them. Depending on the async parameter, the method will either block
|
||||||
// until all subfetchers spin down, or return immediately.
|
// until all subfetchers spin down, or return immediately.
|
||||||
func (p *triePrefetcher) terminate(async bool) {
|
func (p *triePrefetcher) terminate(async bool) {
|
||||||
|
|
@ -145,16 +145,16 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm
|
||||||
// trie returns the trie matching the root hash, blocking until the fetcher of
|
// trie returns the trie matching the root hash, blocking until the fetcher of
|
||||||
// the given trie terminates. If no fetcher exists for the request, nil will be
|
// the given trie terminates. If no fetcher exists for the request, nil will be
|
||||||
// returned.
|
// returned.
|
||||||
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) (Trie, error) {
|
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
|
||||||
// Bail if no trie was prefetched for this root
|
// Bail if no trie was prefetched for this root
|
||||||
fetcher := p.fetchers[p.trieID(owner, root)]
|
fetcher := p.fetchers[p.trieID(owner, root)]
|
||||||
if fetcher == nil {
|
if fetcher == nil {
|
||||||
log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
|
log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
|
||||||
p.deliveryMissMeter.Mark(1)
|
p.deliveryMissMeter.Mark(1)
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
// Subfetcher exists, retrieve its trie
|
// Subfetcher exists, retrieve its trie
|
||||||
return fetcher.peek(), nil
|
return fetcher.peek()
|
||||||
}
|
}
|
||||||
|
|
||||||
// used marks a batch of state items used to allow creating statistics as to
|
// used marks a batch of state items used to allow creating statistics as to
|
||||||
|
|
@ -234,7 +234,7 @@ func (sf *subfetcher) schedule(keys [][]byte) error {
|
||||||
case sf.wake <- struct{}{}:
|
case sf.wake <- struct{}{}:
|
||||||
// Wake signal sent
|
// Wake signal sent
|
||||||
default:
|
default:
|
||||||
// Wake signal not sent as a previous is already queued
|
// Wake signal not sent as a previous one is already queued
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +250,7 @@ func (sf *subfetcher) wait() {
|
||||||
// peeks for the original data. The method will block until all the scheduled
|
// peeks for the original data. The method will block until all the scheduled
|
||||||
// data has been loaded and the fethcer terminated.
|
// data has been loaded and the fethcer terminated.
|
||||||
func (sf *subfetcher) peek() Trie {
|
func (sf *subfetcher) peek() Trie {
|
||||||
// Block until the fertcher terminates, then retrieve the trie
|
// Block until the fetcher terminates, then retrieve the trie
|
||||||
sf.wait()
|
sf.wait()
|
||||||
return sf.trie
|
return sf.trie
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +296,7 @@ func (sf *subfetcher) loop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-sf.wake:
|
case <-sf.wake:
|
||||||
// Execute all remaining tasks in single run
|
// Execute all remaining tasks in a single run
|
||||||
sf.lock.Lock()
|
sf.lock.Lock()
|
||||||
tasks := sf.tasks
|
tasks := sf.tasks
|
||||||
sf.tasks = nil
|
sf.tasks = nil
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func TestUseAfterTerminate(t *testing.T) {
|
||||||
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
|
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
|
||||||
t.Errorf("Prefetch succeeded after terminate: %v", err)
|
t.Errorf("Prefetch succeeded after terminate: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := prefetcher.trie(common.Hash{}, db.originalRoot); err != nil {
|
if tr := prefetcher.trie(common.Hash{}, db.originalRoot); tr == nil {
|
||||||
t.Errorf("Trie retrieval failed after terminate: %v", err)
|
t.Errorf("Prefetcher returned nil trie after terminate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
core/tracing/gen_balance_change_reason_stringer.go
Normal file
37
core/tracing/gen_balance_change_reason_stringer.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Code generated by "stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tracing
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[BalanceChangeUnspecified-0]
|
||||||
|
_ = x[BalanceIncreaseRewardMineUncle-1]
|
||||||
|
_ = x[BalanceIncreaseRewardMineBlock-2]
|
||||||
|
_ = x[BalanceIncreaseWithdrawal-3]
|
||||||
|
_ = x[BalanceIncreaseGenesisBalance-4]
|
||||||
|
_ = x[BalanceIncreaseRewardTransactionFee-5]
|
||||||
|
_ = x[BalanceDecreaseGasBuy-6]
|
||||||
|
_ = x[BalanceIncreaseGasReturn-7]
|
||||||
|
_ = x[BalanceIncreaseDaoContract-8]
|
||||||
|
_ = x[BalanceDecreaseDaoAccount-9]
|
||||||
|
_ = x[BalanceChangeTransfer-10]
|
||||||
|
_ = x[BalanceChangeTouchAccount-11]
|
||||||
|
_ = x[BalanceIncreaseSelfdestruct-12]
|
||||||
|
_ = x[BalanceDecreaseSelfdestruct-13]
|
||||||
|
_ = x[BalanceDecreaseSelfdestructBurn-14]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn"
|
||||||
|
|
||||||
|
var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400}
|
||||||
|
|
||||||
|
func (i BalanceChangeReason) String() string {
|
||||||
|
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
|
||||||
|
return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]]
|
||||||
|
}
|
||||||
|
|
@ -199,6 +199,8 @@ type Hooks struct {
|
||||||
// for tracing and reporting.
|
// for tracing and reporting.
|
||||||
type BalanceChangeReason byte
|
type BalanceChangeReason byte
|
||||||
|
|
||||||
|
//go:generate stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BalanceChangeUnspecified BalanceChangeReason = 0
|
BalanceChangeUnspecified BalanceChangeReason = 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -556,7 +556,7 @@ func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxDifference returns a new set which is the difference between a and b.
|
// TxDifference returns a new set of transactions that are present in a but not in b.
|
||||||
func TxDifference(a, b Transactions) Transactions {
|
func TxDifference(a, b Transactions) Transactions {
|
||||||
keep := make(Transactions, 0, len(a))
|
keep := make(Transactions, 0, len(a))
|
||||||
|
|
||||||
|
|
@ -574,7 +574,7 @@ func TxDifference(a, b Transactions) Transactions {
|
||||||
return keep
|
return keep
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashDifference returns a new set which is the difference between a and b.
|
// HashDifference returns a new set of hashes that are present in a but not in b.
|
||||||
func HashDifference(a, b []common.Hash) []common.Hash {
|
func HashDifference(a, b []common.Hash) []common.Hash {
|
||||||
keep := make([]common.Hash, 0, len(a))
|
keep := make([]common.Hash, 0, len(a))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ func assertEqual(orig *Transaction, cpy *Transaction) error {
|
||||||
}
|
}
|
||||||
if orig.AccessList() != nil {
|
if orig.AccessList() != nil {
|
||||||
if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) {
|
if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) {
|
||||||
return errors.New("access list wrong!")
|
return errors.New("access list wrong")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,10 @@ type Config struct {
|
||||||
// sets defaults on the config
|
// sets defaults on the config
|
||||||
func setDefaults(cfg *Config) {
|
func setDefaults(cfg *Config) {
|
||||||
if cfg.ChainConfig == nil {
|
if cfg.ChainConfig == nil {
|
||||||
|
var (
|
||||||
|
shanghaiTime = uint64(0)
|
||||||
|
cancunTime = uint64(0)
|
||||||
|
)
|
||||||
cfg.ChainConfig = ¶ms.ChainConfig{
|
cfg.ChainConfig = ¶ms.ChainConfig{
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
HomesteadBlock: new(big.Int),
|
HomesteadBlock: new(big.Int),
|
||||||
|
|
@ -72,9 +76,14 @@ func setDefaults(cfg *Config) {
|
||||||
MuirGlacierBlock: new(big.Int),
|
MuirGlacierBlock: new(big.Int),
|
||||||
BerlinBlock: new(big.Int),
|
BerlinBlock: new(big.Int),
|
||||||
LondonBlock: new(big.Int),
|
LondonBlock: new(big.Int),
|
||||||
|
ArrowGlacierBlock: nil,
|
||||||
|
GrayGlacierBlock: nil,
|
||||||
|
TerminalTotalDifficulty: big.NewInt(0),
|
||||||
|
TerminalTotalDifficultyPassed: true,
|
||||||
|
MergeNetsplitBlock: nil,
|
||||||
|
ShanghaiTime: &shanghaiTime,
|
||||||
|
CancunTime: &cancunTime}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Difficulty == nil {
|
if cfg.Difficulty == nil {
|
||||||
cfg.Difficulty = new(big.Int)
|
cfg.Difficulty = new(big.Int)
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +110,10 @@ func setDefaults(cfg *Config) {
|
||||||
if cfg.BlobBaseFee == nil {
|
if cfg.BlobBaseFee == nil {
|
||||||
cfg.BlobBaseFee = big.NewInt(params.BlobTxMinBlobGasprice)
|
cfg.BlobBaseFee = big.NewInt(params.BlobTxMinBlobGasprice)
|
||||||
}
|
}
|
||||||
|
// Merge indicators
|
||||||
|
if t := cfg.ChainConfig.ShanghaiTime; cfg.ChainConfig.TerminalTotalDifficultyPassed || (t != nil && *t == 0) {
|
||||||
|
cfg.Random = &(common.Hash{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute executes the code using the input as call data during the execution.
|
// Execute executes the code using the input as call data during the execution.
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ func TestExecute(t *testing.T) {
|
||||||
|
|
||||||
func TestCall(t *testing.T) {
|
func TestCall(t *testing.T) {
|
||||||
state, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
state, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||||
address := common.HexToAddress("0x0a")
|
address := common.HexToAddress("0xaa")
|
||||||
state.SetCode(address, []byte{
|
state.SetCode(address, []byte{
|
||||||
byte(vm.PUSH1), 10,
|
byte(vm.PUSH1), 10,
|
||||||
byte(vm.PUSH1), 0,
|
byte(vm.PUSH1), 0,
|
||||||
|
|
@ -725,7 +725,7 @@ func TestRuntimeJSTracer(t *testing.T) {
|
||||||
byte(vm.CREATE),
|
byte(vm.CREATE),
|
||||||
byte(vm.POP),
|
byte(vm.POP),
|
||||||
},
|
},
|
||||||
results: []string{`"1,1,952855,6,12"`, `"1,1,952855,6,0"`},
|
results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0"`},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// CREATE2
|
// CREATE2
|
||||||
|
|
@ -741,7 +741,7 @@ func TestRuntimeJSTracer(t *testing.T) {
|
||||||
byte(vm.CREATE2),
|
byte(vm.CREATE2),
|
||||||
byte(vm.POP),
|
byte(vm.POP),
|
||||||
},
|
},
|
||||||
results: []string{`"1,1,952846,6,13"`, `"1,1,952846,6,0"`},
|
results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0"`},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// CALL
|
// CALL
|
||||||
|
|
|
||||||
|
|
@ -79,52 +79,52 @@ type BitCurve struct {
|
||||||
BitSize int // the size of the underlying field
|
BitSize int // the size of the underlying field
|
||||||
}
|
}
|
||||||
|
|
||||||
func (BitCurve *BitCurve) Params() *elliptic.CurveParams {
|
func (bitCurve *BitCurve) Params() *elliptic.CurveParams {
|
||||||
return &elliptic.CurveParams{
|
return &elliptic.CurveParams{
|
||||||
P: BitCurve.P,
|
P: bitCurve.P,
|
||||||
N: BitCurve.N,
|
N: bitCurve.N,
|
||||||
B: BitCurve.B,
|
B: bitCurve.B,
|
||||||
Gx: BitCurve.Gx,
|
Gx: bitCurve.Gx,
|
||||||
Gy: BitCurve.Gy,
|
Gy: bitCurve.Gy,
|
||||||
BitSize: BitCurve.BitSize,
|
BitSize: bitCurve.BitSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOnCurve returns true if the given (x,y) lies on the BitCurve.
|
// IsOnCurve returns true if the given (x,y) lies on the BitCurve.
|
||||||
func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
|
func (bitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
|
||||||
// y² = x³ + b
|
// y² = x³ + b
|
||||||
y2 := new(big.Int).Mul(y, y) //y²
|
y2 := new(big.Int).Mul(y, y) //y²
|
||||||
y2.Mod(y2, BitCurve.P) //y²%P
|
y2.Mod(y2, bitCurve.P) //y²%P
|
||||||
|
|
||||||
x3 := new(big.Int).Mul(x, x) //x²
|
x3 := new(big.Int).Mul(x, x) //x²
|
||||||
x3.Mul(x3, x) //x³
|
x3.Mul(x3, x) //x³
|
||||||
|
|
||||||
x3.Add(x3, BitCurve.B) //x³+B
|
x3.Add(x3, bitCurve.B) //x³+B
|
||||||
x3.Mod(x3, BitCurve.P) //(x³+B)%P
|
x3.Mod(x3, bitCurve.P) //(x³+B)%P
|
||||||
|
|
||||||
return x3.Cmp(y2) == 0
|
return x3.Cmp(y2) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// affineFromJacobian reverses the Jacobian transform. See the comment at the
|
// affineFromJacobian reverses the Jacobian transform. See the comment at the
|
||||||
// top of the file.
|
// top of the file.
|
||||||
func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
|
func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
|
||||||
if z.Sign() == 0 {
|
if z.Sign() == 0 {
|
||||||
return new(big.Int), new(big.Int)
|
return new(big.Int), new(big.Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
zinv := new(big.Int).ModInverse(z, BitCurve.P)
|
zinv := new(big.Int).ModInverse(z, bitCurve.P)
|
||||||
zinvsq := new(big.Int).Mul(zinv, zinv)
|
zinvsq := new(big.Int).Mul(zinv, zinv)
|
||||||
|
|
||||||
xOut = new(big.Int).Mul(x, zinvsq)
|
xOut = new(big.Int).Mul(x, zinvsq)
|
||||||
xOut.Mod(xOut, BitCurve.P)
|
xOut.Mod(xOut, bitCurve.P)
|
||||||
zinvsq.Mul(zinvsq, zinv)
|
zinvsq.Mul(zinvsq, zinv)
|
||||||
yOut = new(big.Int).Mul(y, zinvsq)
|
yOut = new(big.Int).Mul(y, zinvsq)
|
||||||
yOut.Mod(yOut, BitCurve.P)
|
yOut.Mod(yOut, bitCurve.P)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add returns the sum of (x1,y1) and (x2,y2)
|
// Add returns the sum of (x1,y1) and (x2,y2)
|
||||||
func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
|
func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
|
||||||
// If one point is at infinity, return the other point.
|
// If one point is at infinity, return the other point.
|
||||||
// Adding the point at infinity to any point will preserve the other point.
|
// Adding the point at infinity to any point will preserve the other point.
|
||||||
if x1.Sign() == 0 && y1.Sign() == 0 {
|
if x1.Sign() == 0 && y1.Sign() == 0 {
|
||||||
|
|
@ -135,27 +135,27 @@ func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
|
||||||
}
|
}
|
||||||
z := new(big.Int).SetInt64(1)
|
z := new(big.Int).SetInt64(1)
|
||||||
if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 {
|
if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 {
|
||||||
return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z))
|
return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z))
|
||||||
}
|
}
|
||||||
return BitCurve.affineFromJacobian(BitCurve.addJacobian(x1, y1, z, x2, y2, z))
|
return bitCurve.affineFromJacobian(bitCurve.addJacobian(x1, y1, z, x2, y2, z))
|
||||||
}
|
}
|
||||||
|
|
||||||
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
|
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
|
||||||
// (x2, y2, z2) and returns their sum, also in Jacobian form.
|
// (x2, y2, z2) and returns their sum, also in Jacobian form.
|
||||||
func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
|
func (bitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
|
||||||
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
|
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
|
||||||
z1z1 := new(big.Int).Mul(z1, z1)
|
z1z1 := new(big.Int).Mul(z1, z1)
|
||||||
z1z1.Mod(z1z1, BitCurve.P)
|
z1z1.Mod(z1z1, bitCurve.P)
|
||||||
z2z2 := new(big.Int).Mul(z2, z2)
|
z2z2 := new(big.Int).Mul(z2, z2)
|
||||||
z2z2.Mod(z2z2, BitCurve.P)
|
z2z2.Mod(z2z2, bitCurve.P)
|
||||||
|
|
||||||
u1 := new(big.Int).Mul(x1, z2z2)
|
u1 := new(big.Int).Mul(x1, z2z2)
|
||||||
u1.Mod(u1, BitCurve.P)
|
u1.Mod(u1, bitCurve.P)
|
||||||
u2 := new(big.Int).Mul(x2, z1z1)
|
u2 := new(big.Int).Mul(x2, z1z1)
|
||||||
u2.Mod(u2, BitCurve.P)
|
u2.Mod(u2, bitCurve.P)
|
||||||
h := new(big.Int).Sub(u2, u1)
|
h := new(big.Int).Sub(u2, u1)
|
||||||
if h.Sign() == -1 {
|
if h.Sign() == -1 {
|
||||||
h.Add(h, BitCurve.P)
|
h.Add(h, bitCurve.P)
|
||||||
}
|
}
|
||||||
i := new(big.Int).Lsh(h, 1)
|
i := new(big.Int).Lsh(h, 1)
|
||||||
i.Mul(i, i)
|
i.Mul(i, i)
|
||||||
|
|
@ -163,13 +163,13 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int
|
||||||
|
|
||||||
s1 := new(big.Int).Mul(y1, z2)
|
s1 := new(big.Int).Mul(y1, z2)
|
||||||
s1.Mul(s1, z2z2)
|
s1.Mul(s1, z2z2)
|
||||||
s1.Mod(s1, BitCurve.P)
|
s1.Mod(s1, bitCurve.P)
|
||||||
s2 := new(big.Int).Mul(y2, z1)
|
s2 := new(big.Int).Mul(y2, z1)
|
||||||
s2.Mul(s2, z1z1)
|
s2.Mul(s2, z1z1)
|
||||||
s2.Mod(s2, BitCurve.P)
|
s2.Mod(s2, bitCurve.P)
|
||||||
r := new(big.Int).Sub(s2, s1)
|
r := new(big.Int).Sub(s2, s1)
|
||||||
if r.Sign() == -1 {
|
if r.Sign() == -1 {
|
||||||
r.Add(r, BitCurve.P)
|
r.Add(r, bitCurve.P)
|
||||||
}
|
}
|
||||||
r.Lsh(r, 1)
|
r.Lsh(r, 1)
|
||||||
v := new(big.Int).Mul(u1, i)
|
v := new(big.Int).Mul(u1, i)
|
||||||
|
|
@ -179,7 +179,7 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int
|
||||||
x3.Sub(x3, j)
|
x3.Sub(x3, j)
|
||||||
x3.Sub(x3, v)
|
x3.Sub(x3, v)
|
||||||
x3.Sub(x3, v)
|
x3.Sub(x3, v)
|
||||||
x3.Mod(x3, BitCurve.P)
|
x3.Mod(x3, bitCurve.P)
|
||||||
|
|
||||||
y3 := new(big.Int).Set(r)
|
y3 := new(big.Int).Set(r)
|
||||||
v.Sub(v, x3)
|
v.Sub(v, x3)
|
||||||
|
|
@ -187,33 +187,33 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int
|
||||||
s1.Mul(s1, j)
|
s1.Mul(s1, j)
|
||||||
s1.Lsh(s1, 1)
|
s1.Lsh(s1, 1)
|
||||||
y3.Sub(y3, s1)
|
y3.Sub(y3, s1)
|
||||||
y3.Mod(y3, BitCurve.P)
|
y3.Mod(y3, bitCurve.P)
|
||||||
|
|
||||||
z3 := new(big.Int).Add(z1, z2)
|
z3 := new(big.Int).Add(z1, z2)
|
||||||
z3.Mul(z3, z3)
|
z3.Mul(z3, z3)
|
||||||
z3.Sub(z3, z1z1)
|
z3.Sub(z3, z1z1)
|
||||||
if z3.Sign() == -1 {
|
if z3.Sign() == -1 {
|
||||||
z3.Add(z3, BitCurve.P)
|
z3.Add(z3, bitCurve.P)
|
||||||
}
|
}
|
||||||
z3.Sub(z3, z2z2)
|
z3.Sub(z3, z2z2)
|
||||||
if z3.Sign() == -1 {
|
if z3.Sign() == -1 {
|
||||||
z3.Add(z3, BitCurve.P)
|
z3.Add(z3, bitCurve.P)
|
||||||
}
|
}
|
||||||
z3.Mul(z3, h)
|
z3.Mul(z3, h)
|
||||||
z3.Mod(z3, BitCurve.P)
|
z3.Mod(z3, bitCurve.P)
|
||||||
|
|
||||||
return x3, y3, z3
|
return x3, y3, z3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double returns 2*(x,y)
|
// Double returns 2*(x,y)
|
||||||
func (BitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
|
func (bitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
|
||||||
z1 := new(big.Int).SetInt64(1)
|
z1 := new(big.Int).SetInt64(1)
|
||||||
return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z1))
|
return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
|
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
|
||||||
// returns its double, also in Jacobian form.
|
// returns its double, also in Jacobian form.
|
||||||
func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
|
func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
|
||||||
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
||||||
|
|
||||||
a := new(big.Int).Mul(x, x) //X1²
|
a := new(big.Int).Mul(x, x) //X1²
|
||||||
|
|
@ -231,30 +231,30 @@ func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int,
|
||||||
|
|
||||||
x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D
|
x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D
|
||||||
x3.Sub(f, x3) //F-2*D
|
x3.Sub(f, x3) //F-2*D
|
||||||
x3.Mod(x3, BitCurve.P)
|
x3.Mod(x3, bitCurve.P)
|
||||||
|
|
||||||
y3 := new(big.Int).Sub(d, x3) //D-X3
|
y3 := new(big.Int).Sub(d, x3) //D-X3
|
||||||
y3.Mul(e, y3) //E*(D-X3)
|
y3.Mul(e, y3) //E*(D-X3)
|
||||||
y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C
|
y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C
|
||||||
y3.Mod(y3, BitCurve.P)
|
y3.Mod(y3, bitCurve.P)
|
||||||
|
|
||||||
z3 := new(big.Int).Mul(y, z) //Y1*Z1
|
z3 := new(big.Int).Mul(y, z) //Y1*Z1
|
||||||
z3.Mul(big.NewInt(2), z3) //3*Y1*Z1
|
z3.Mul(big.NewInt(2), z3) //3*Y1*Z1
|
||||||
z3.Mod(z3, BitCurve.P)
|
z3.Mod(z3, bitCurve.P)
|
||||||
|
|
||||||
return x3, y3, z3
|
return x3, y3, z3
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScalarBaseMult returns k*G, where G is the base point of the group and k is
|
// ScalarBaseMult returns k*G, where G is the base point of the group and k is
|
||||||
// an integer in big-endian form.
|
// an integer in big-endian form.
|
||||||
func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
||||||
return BitCurve.ScalarMult(BitCurve.Gx, BitCurve.Gy, k)
|
return bitCurve.ScalarMult(bitCurve.Gx, bitCurve.Gy, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal converts a point into the form specified in section 4.3.6 of ANSI
|
// Marshal converts a point into the form specified in section 4.3.6 of ANSI
|
||||||
// X9.62.
|
// X9.62.
|
||||||
func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
|
func (bitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
|
||||||
byteLen := (BitCurve.BitSize + 7) >> 3
|
byteLen := (bitCurve.BitSize + 7) >> 3
|
||||||
ret := make([]byte, 1+2*byteLen)
|
ret := make([]byte, 1+2*byteLen)
|
||||||
ret[0] = 4 // uncompressed point flag
|
ret[0] = 4 // uncompressed point flag
|
||||||
readBits(x, ret[1:1+byteLen])
|
readBits(x, ret[1:1+byteLen])
|
||||||
|
|
@ -264,8 +264,8 @@ func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
|
||||||
|
|
||||||
// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On
|
// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On
|
||||||
// error, x = nil.
|
// error, x = nil.
|
||||||
func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
|
func (bitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
|
||||||
byteLen := (BitCurve.BitSize + 7) >> 3
|
byteLen := (bitCurve.BitSize + 7) >> 3
|
||||||
if len(data) != 1+2*byteLen {
|
if len(data) != 1+2*byteLen {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
|
func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
|
||||||
// Ensure scalar is exactly 32 bytes. We pad always, even if
|
// Ensure scalar is exactly 32 bytes. We pad always, even if
|
||||||
// scalar is 32 bytes long, to avoid a timing side channel.
|
// scalar is 32 bytes long, to avoid a timing side channel.
|
||||||
if len(scalar) > 32 {
|
if len(scalar) > 32 {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ package secp256k1
|
||||||
|
|
||||||
import "math/big"
|
import "math/big"
|
||||||
|
|
||||||
func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
|
func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
|
||||||
panic("ScalarMult is not available when secp256k1 is built without cgo")
|
panic("ScalarMult is not available when secp256k1 is built without cgo")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -105,9 +104,6 @@ type Ethereum struct {
|
||||||
// whose lifecycle will be managed by the provided node.
|
// whose lifecycle will be managed by the provided node.
|
||||||
func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
// Ensure configuration values are compatible and sane
|
// Ensure configuration values are compatible and sane
|
||||||
if config.SyncMode == downloader.LightSync {
|
|
||||||
return nil, errors.New("can't run eth.Ethereum in light sync mode, light mode has been deprecated")
|
|
||||||
}
|
|
||||||
if !config.SyncMode.IsValid() {
|
if !config.SyncMode.IsValid() {
|
||||||
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
|
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +204,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
}
|
}
|
||||||
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to create tracer %s: %v", config.VMTrace, err)
|
return nil, fmt.Errorf("failed to create tracer %s: %v", config.VMTrace, err)
|
||||||
}
|
}
|
||||||
vmConfig.Tracer = t
|
vmConfig.Tracer = t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -979,11 +979,11 @@ func TestSimultaneousNewBlock(t *testing.T) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if newResp, err := api.NewPayloadV1(*execData); err != nil {
|
if newResp, err := api.NewPayloadV1(*execData); err != nil {
|
||||||
errMu.Lock()
|
errMu.Lock()
|
||||||
testErr = fmt.Errorf("Failed to insert block: %w", err)
|
testErr = fmt.Errorf("failed to insert block: %w", err)
|
||||||
errMu.Unlock()
|
errMu.Unlock()
|
||||||
} else if newResp.Status != "VALID" {
|
} else if newResp.Status != "VALID" {
|
||||||
errMu.Lock()
|
errMu.Lock()
|
||||||
testErr = fmt.Errorf("Failed to insert block: %v", newResp.Status)
|
testErr = fmt.Errorf("failed to insert block: %v", newResp.Status)
|
||||||
errMu.Unlock()
|
errMu.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -1018,7 +1018,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||||
errMu.Lock()
|
errMu.Lock()
|
||||||
testErr = fmt.Errorf("Failed to insert block: %w", err)
|
testErr = fmt.Errorf("failed to insert block: %w", err)
|
||||||
errMu.Unlock()
|
errMu.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -279,9 +279,12 @@ func (c *SimulatedBeacon) Rollback() {
|
||||||
|
|
||||||
// Fork sets the head to the provided hash.
|
// Fork sets the head to the provided hash.
|
||||||
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
||||||
|
// Ensure no pending transactions.
|
||||||
|
c.eth.TxPool().Sync()
|
||||||
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
|
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
|
||||||
return errors.New("pending block dirty")
|
return errors.New("pending block dirty")
|
||||||
}
|
}
|
||||||
|
|
||||||
parent := c.eth.BlockChain().GetBlockByHash(parentHash)
|
parent := c.eth.BlockChain().GetBlockByHash(parentHash)
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
return errors.New("parent not found")
|
return errors.New("parent not found")
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ func (api *DownloaderAPI) eventLoop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
|
// Syncing provides information when this node starts synchronising with the Ethereum network and when it's finished.
|
||||||
func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
|
func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||||
if !supported {
|
if !supported {
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
||||||
case SnapSync:
|
case SnapSync:
|
||||||
chainHead = d.blockchain.CurrentSnapBlock()
|
chainHead = d.blockchain.CurrentSnapBlock()
|
||||||
default:
|
default:
|
||||||
chainHead = d.lightchain.CurrentHeader()
|
panic("unknown sync mode")
|
||||||
}
|
}
|
||||||
number := chainHead.Number.Uint64()
|
number := chainHead.Number.Uint64()
|
||||||
|
|
||||||
|
|
@ -222,7 +222,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
||||||
case SnapSync:
|
case SnapSync:
|
||||||
linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
|
linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
|
||||||
default:
|
default:
|
||||||
linked = d.blockchain.HasHeader(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
|
panic("unknown sync mode")
|
||||||
}
|
}
|
||||||
if !linked {
|
if !linked {
|
||||||
// This is a programming error. The chain backfiller was called with a
|
// This is a programming error. The chain backfiller was called with a
|
||||||
|
|
@ -257,7 +257,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
||||||
case SnapSync:
|
case SnapSync:
|
||||||
known = d.blockchain.HasFastBlock(h.Hash(), n)
|
known = d.blockchain.HasFastBlock(h.Hash(), n)
|
||||||
default:
|
default:
|
||||||
known = d.lightchain.HasHeader(h.Hash(), n)
|
panic("unknown sync mode")
|
||||||
}
|
}
|
||||||
if !known {
|
if !known {
|
||||||
end = check
|
end = check
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ var (
|
||||||
errCancelContentProcessing = errors.New("content processing canceled (requested)")
|
errCancelContentProcessing = errors.New("content processing canceled (requested)")
|
||||||
errCanceled = errors.New("syncing canceled (requested)")
|
errCanceled = errors.New("syncing canceled (requested)")
|
||||||
errNoPivotHeader = errors.New("pivot header is not found")
|
errNoPivotHeader = errors.New("pivot header is not found")
|
||||||
ErrMergeTransition = errors.New("legacy sync reached the merge")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
||||||
|
|
@ -98,7 +97,6 @@ type Downloader struct {
|
||||||
syncStatsChainHeight uint64 // Highest block number known when syncing started
|
syncStatsChainHeight uint64 // Highest block number known when syncing started
|
||||||
syncStatsLock sync.RWMutex // Lock protecting the sync stats fields
|
syncStatsLock sync.RWMutex // Lock protecting the sync stats fields
|
||||||
|
|
||||||
lightchain LightChain
|
|
||||||
blockchain BlockChain
|
blockchain BlockChain
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
|
|
@ -143,8 +141,8 @@ type Downloader struct {
|
||||||
syncLogTime time.Time // Time instance when status was last reported
|
syncLogTime time.Time // Time instance when status was last reported
|
||||||
}
|
}
|
||||||
|
|
||||||
// LightChain encapsulates functions required to synchronise a light chain.
|
// BlockChain encapsulates functions required to sync a (full or snap) blockchain.
|
||||||
type LightChain interface {
|
type BlockChain interface {
|
||||||
// HasHeader verifies a header's presence in the local chain.
|
// HasHeader verifies a header's presence in the local chain.
|
||||||
HasHeader(common.Hash, uint64) bool
|
HasHeader(common.Hash, uint64) bool
|
||||||
|
|
||||||
|
|
@ -162,11 +160,6 @@ type LightChain interface {
|
||||||
|
|
||||||
// SetHead rewinds the local chain to a new head.
|
// SetHead rewinds the local chain to a new head.
|
||||||
SetHead(uint64) error
|
SetHead(uint64) error
|
||||||
}
|
|
||||||
|
|
||||||
// BlockChain encapsulates functions required to sync a (full or snap) blockchain.
|
|
||||||
type BlockChain interface {
|
|
||||||
LightChain
|
|
||||||
|
|
||||||
// HasBlock verifies a block's presence in the local chain.
|
// HasBlock verifies a block's presence in the local chain.
|
||||||
HasBlock(common.Hash, uint64) bool
|
HasBlock(common.Hash, uint64) bool
|
||||||
|
|
@ -201,17 +194,13 @@ type BlockChain interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||||
func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader {
|
func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||||
if lightchain == nil {
|
|
||||||
lightchain = chain
|
|
||||||
}
|
|
||||||
dl := &Downloader{
|
dl := &Downloader{
|
||||||
stateDB: stateDb,
|
stateDB: stateDb,
|
||||||
mux: mux,
|
mux: mux,
|
||||||
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
|
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
|
||||||
peers: newPeerSet(),
|
peers: newPeerSet(),
|
||||||
blockchain: chain,
|
blockchain: chain,
|
||||||
lightchain: lightchain,
|
|
||||||
dropPeer: dropPeer,
|
dropPeer: dropPeer,
|
||||||
headerProcCh: make(chan *headerTask, 1),
|
headerProcCh: make(chan *headerTask, 1),
|
||||||
quitCh: make(chan struct{}),
|
quitCh: make(chan struct{}),
|
||||||
|
|
@ -240,15 +229,13 @@ func (d *Downloader) Progress() ethereum.SyncProgress {
|
||||||
|
|
||||||
current := uint64(0)
|
current := uint64(0)
|
||||||
mode := d.getMode()
|
mode := d.getMode()
|
||||||
switch {
|
switch mode {
|
||||||
case d.blockchain != nil && mode == FullSync:
|
case FullSync:
|
||||||
current = d.blockchain.CurrentBlock().Number.Uint64()
|
current = d.blockchain.CurrentBlock().Number.Uint64()
|
||||||
case d.blockchain != nil && mode == SnapSync:
|
case SnapSync:
|
||||||
current = d.blockchain.CurrentSnapBlock().Number.Uint64()
|
current = d.blockchain.CurrentSnapBlock().Number.Uint64()
|
||||||
case d.lightchain != nil:
|
|
||||||
current = d.lightchain.CurrentHeader().Number.Uint64()
|
|
||||||
default:
|
default:
|
||||||
log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode)
|
log.Error("Unknown downloader mode", "mode", mode)
|
||||||
}
|
}
|
||||||
progress, pending := d.SnapSyncer.Progress()
|
progress, pending := d.SnapSyncer.Progress()
|
||||||
|
|
||||||
|
|
@ -402,7 +389,7 @@ func (d *Downloader) syncToHead() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.mux.Post(FailedEvent{err})
|
d.mux.Post(FailedEvent{err})
|
||||||
} else {
|
} else {
|
||||||
latest := d.lightchain.CurrentHeader()
|
latest := d.blockchain.CurrentHeader()
|
||||||
d.mux.Post(DoneEvent{latest})
|
d.mux.Post(DoneEvent{latest})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -520,7 +507,7 @@ func (d *Downloader) syncToHead() (err error) {
|
||||||
}
|
}
|
||||||
// Rewind the ancient store and blockchain if reorg happens.
|
// Rewind the ancient store and blockchain if reorg happens.
|
||||||
if origin+1 < frozen {
|
if origin+1 < frozen {
|
||||||
if err := d.lightchain.SetHead(origin); err != nil {
|
if err := d.blockchain.SetHead(origin); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("Truncated excess ancient chain segment", "oldhead", frozen-1, "newhead", origin)
|
log.Info("Truncated excess ancient chain segment", "oldhead", frozen-1, "newhead", origin)
|
||||||
|
|
@ -690,19 +677,17 @@ func (d *Downloader) processHeaders(origin uint64) error {
|
||||||
chunkHashes := hashes[:limit]
|
chunkHashes := hashes[:limit]
|
||||||
|
|
||||||
// In case of header only syncing, validate the chunk immediately
|
// In case of header only syncing, validate the chunk immediately
|
||||||
if mode == SnapSync || mode == LightSync {
|
if mode == SnapSync {
|
||||||
// Although the received headers might be all valid, a legacy
|
// Although the received headers might be all valid, a legacy
|
||||||
// PoW/PoA sync must not accept post-merge headers. Make sure
|
// PoW/PoA sync must not accept post-merge headers. Make sure
|
||||||
// that any transition is rejected at this point.
|
// that any transition is rejected at this point.
|
||||||
if len(chunkHeaders) > 0 {
|
if len(chunkHeaders) > 0 {
|
||||||
if n, err := d.lightchain.InsertHeaderChain(chunkHeaders); err != nil {
|
if n, err := d.blockchain.InsertHeaderChain(chunkHeaders); err != nil {
|
||||||
log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err)
|
log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err)
|
||||||
return fmt.Errorf("%w: %v", errInvalidChain, err)
|
return fmt.Errorf("%w: %v", errInvalidChain, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unless we're doing light chains, schedule the headers for associated content retrieval
|
|
||||||
if mode == FullSync || mode == SnapSync {
|
|
||||||
// If we've reached the allowed number of pending headers, stall a bit
|
// If we've reached the allowed number of pending headers, stall a bit
|
||||||
for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders {
|
for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders {
|
||||||
timer.Reset(time.Second)
|
timer.Reset(time.Second)
|
||||||
|
|
@ -717,7 +702,7 @@ func (d *Downloader) processHeaders(origin uint64) error {
|
||||||
if len(inserts) != len(chunkHeaders) {
|
if len(inserts) != len(chunkHeaders) {
|
||||||
return fmt.Errorf("%w: stale headers", errBadPeer)
|
return fmt.Errorf("%w: stale headers", errBadPeer)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
headers = headers[limit:]
|
headers = headers[limit:]
|
||||||
hashes = hashes[limit:]
|
hashes = hashes[limit:]
|
||||||
origin += uint64(limit)
|
origin += uint64(limit)
|
||||||
|
|
@ -1056,7 +1041,7 @@ func (d *Downloader) readHeaderRange(last *types.Header, count int) []*types.Hea
|
||||||
headers []*types.Header
|
headers []*types.Header
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
parent := d.lightchain.GetHeaderByHash(current.ParentHash)
|
parent := d.blockchain.GetHeaderByHash(current.ParentHash)
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
break // The chain is not continuous, or the chain is exhausted
|
break // The chain is not continuous, or the chain is exhausted
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
|
||||||
chain: chain,
|
chain: chain,
|
||||||
peers: make(map[string]*downloadTesterPeer),
|
peers: make(map[string]*downloadTesterPeer),
|
||||||
}
|
}
|
||||||
tester.downloader = New(db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, success)
|
tester.downloader = New(db, new(event.TypeMux), tester.chain, tester.dropPeer, success)
|
||||||
return tester
|
return tester
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,9 +384,6 @@ func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
headers, blocks, receipts := length, length, length
|
headers, blocks, receipts := length, length, length
|
||||||
if tester.downloader.getMode() == LightSync {
|
|
||||||
blocks, receipts = 1, 1
|
|
||||||
}
|
|
||||||
if hs := int(tester.chain.CurrentHeader().Number.Uint64()) + 1; hs != headers {
|
if hs := int(tester.chain.CurrentHeader().Number.Uint64()) + 1; hs != headers {
|
||||||
t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers)
|
t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers)
|
||||||
}
|
}
|
||||||
|
|
@ -400,7 +397,6 @@ func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
|
||||||
|
|
||||||
func TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) }
|
func TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) }
|
||||||
func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) }
|
func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
success := make(chan struct{})
|
success := make(chan struct{})
|
||||||
|
|
@ -507,7 +503,6 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// Tests that a canceled download wipes all previously accumulated state.
|
// Tests that a canceled download wipes all previously accumulated state.
|
||||||
func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
||||||
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
||||||
func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
complete := make(chan struct{})
|
complete := make(chan struct{})
|
||||||
|
|
@ -540,7 +535,6 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// and not wreak havoc on other nodes in the network.
|
// and not wreak havoc on other nodes in the network.
|
||||||
func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
|
func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
|
||||||
func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) }
|
func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
complete := make(chan struct{})
|
complete := make(chan struct{})
|
||||||
|
|
@ -580,7 +574,6 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// made, and instead the header should be assembled into a whole block in itself.
|
// made, and instead the header should be assembled into a whole block in itself.
|
||||||
func TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) }
|
func TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) }
|
||||||
func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) }
|
func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) }
|
||||||
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
success := make(chan struct{})
|
success := make(chan struct{})
|
||||||
|
|
@ -619,7 +612,7 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// Validate the number of block bodies that should have been requested
|
// Validate the number of block bodies that should have been requested
|
||||||
bodiesNeeded, receiptsNeeded := 0, 0
|
bodiesNeeded, receiptsNeeded := 0, 0
|
||||||
for _, block := range chain.blocks[1:] {
|
for _, block := range chain.blocks[1:] {
|
||||||
if mode != LightSync && (len(block.Transactions()) > 0 || len(block.Uncles()) > 0) {
|
if len(block.Transactions()) > 0 || len(block.Uncles()) > 0 {
|
||||||
bodiesNeeded++
|
bodiesNeeded++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -696,7 +689,6 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// and highest block number) is tracked and updated correctly.
|
// and highest block number) is tracked and updated correctly.
|
||||||
func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
|
func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
|
||||||
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
||||||
func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
success := make(chan struct{})
|
success := make(chan struct{})
|
||||||
|
|
@ -734,17 +726,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||||
t.Fatalf("failed to beacon-sync chain: %v", err)
|
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||||
}
|
}
|
||||||
var startingBlock uint64
|
startingBlock := uint64(len(chain.blocks)/2 - 1)
|
||||||
if mode == LightSync {
|
|
||||||
// in light-sync mode:
|
|
||||||
// * the starting block is 0 on the second sync cycle because blocks
|
|
||||||
// are never downloaded.
|
|
||||||
// * The current/highest blocks reported in the progress reflect the
|
|
||||||
// current/highest header.
|
|
||||||
startingBlock = 0
|
|
||||||
} else {
|
|
||||||
startingBlock = uint64(len(chain.blocks)/2 - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-success:
|
case <-success:
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,10 @@ type SyncMode uint32
|
||||||
const (
|
const (
|
||||||
FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks
|
FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks
|
||||||
SnapSync // Download the chain and the state via compact snapshots
|
SnapSync // Download the chain and the state via compact snapshots
|
||||||
LightSync // Download only the headers and terminate afterwards
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mode SyncMode) IsValid() bool {
|
func (mode SyncMode) IsValid() bool {
|
||||||
return mode >= FullSync && mode <= LightSync
|
return mode == FullSync || mode == SnapSync
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements the stringer interface.
|
// String implements the stringer interface.
|
||||||
|
|
@ -39,8 +38,6 @@ func (mode SyncMode) String() string {
|
||||||
return "full"
|
return "full"
|
||||||
case SnapSync:
|
case SnapSync:
|
||||||
return "snap"
|
return "snap"
|
||||||
case LightSync:
|
|
||||||
return "light"
|
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
@ -52,8 +49,6 @@ func (mode SyncMode) MarshalText() ([]byte, error) {
|
||||||
return []byte("full"), nil
|
return []byte("full"), nil
|
||||||
case SnapSync:
|
case SnapSync:
|
||||||
return []byte("snap"), nil
|
return []byte("snap"), nil
|
||||||
case LightSync:
|
|
||||||
return []byte("light"), nil
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown sync mode %d", mode)
|
return nil, fmt.Errorf("unknown sync mode %d", mode)
|
||||||
}
|
}
|
||||||
|
|
@ -65,10 +60,8 @@ func (mode *SyncMode) UnmarshalText(text []byte) error {
|
||||||
*mode = FullSync
|
*mode = FullSync
|
||||||
case "snap":
|
case "snap":
|
||||||
*mode = SnapSync
|
*mode = SnapSync
|
||||||
case "light":
|
|
||||||
*mode = LightSync
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(`unknown sync mode %q, want "full", "snap" or "light"`, text)
|
return fmt.Errorf(`unknown sync mode %q, want "full" or "snap"`, text)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ 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/eth/protocols/eth"
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -376,20 +377,9 @@ func TestSkeletonSyncInit(t *testing.T) {
|
||||||
skeleton.Terminate()
|
skeleton.Terminate()
|
||||||
|
|
||||||
// Ensure the correct resulting sync status
|
// Ensure the correct resulting sync status
|
||||||
var progress skeletonProgress
|
expect := skeletonExpect{state: tt.newstate}
|
||||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
if err := checkSkeletonProgress(db, false, nil, expect); err != nil {
|
||||||
|
t.Errorf("test %d: %v", i, err)
|
||||||
if len(progress.Subchains) != len(tt.newstate) {
|
|
||||||
t.Errorf("test %d: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.newstate))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for j := 0; j < len(progress.Subchains); j++ {
|
|
||||||
if progress.Subchains[j].Head != tt.newstate[j].Head {
|
|
||||||
t.Errorf("test %d: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.newstate[j].Head)
|
|
||||||
}
|
|
||||||
if progress.Subchains[j].Tail != tt.newstate[j].Tail {
|
|
||||||
t.Errorf("test %d: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.newstate[j].Tail)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -493,28 +483,36 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
skeleton.Terminate()
|
skeleton.Terminate()
|
||||||
|
|
||||||
// Ensure the correct resulting sync status
|
// Ensure the correct resulting sync status
|
||||||
var progress skeletonProgress
|
expect := skeletonExpect{state: tt.newstate}
|
||||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
if err := checkSkeletonProgress(db, false, nil, expect); err != nil {
|
||||||
|
t.Errorf("test %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(progress.Subchains) != len(tt.newstate) {
|
type skeletonExpect struct {
|
||||||
t.Errorf("test %d: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.newstate))
|
state []*subchain // Expected sync state after the post-init event
|
||||||
continue
|
serve uint64 // Expected number of header retrievals after initial cycle
|
||||||
}
|
drop uint64 // Expected number of peers dropped after initial cycle
|
||||||
for j := 0; j < len(progress.Subchains); j++ {
|
}
|
||||||
if progress.Subchains[j].Head != tt.newstate[j].Head {
|
|
||||||
t.Errorf("test %d: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.newstate[j].Head)
|
type skeletonTest struct {
|
||||||
}
|
fill bool // Whether to run a real backfiller in this test case
|
||||||
if progress.Subchains[j].Tail != tt.newstate[j].Tail {
|
unpredictable bool // Whether to ignore drops/serves due to uncertain packet assignments
|
||||||
t.Errorf("test %d: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.newstate[j].Tail)
|
|
||||||
}
|
head *types.Header // New head header to announce to reorg to
|
||||||
}
|
peers []*skeletonTestPeer // Initial peer set to start the sync with
|
||||||
}
|
mid skeletonExpect
|
||||||
|
|
||||||
|
newHead *types.Header // New header to anoint on top of the old one
|
||||||
|
newPeer *skeletonTestPeer // New peer to join the skeleton syncer
|
||||||
|
end skeletonExpect
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the skeleton sync correctly retrieves headers from one or more
|
// Tests that the skeleton sync correctly retrieves headers from one or more
|
||||||
// peers without duplicates or other strange side effects.
|
// peers without duplicates or other strange side effects.
|
||||||
func TestSkeletonSyncRetrievals(t *testing.T) {
|
func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
//log.SetDefault(log.NewLogger(log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))))
|
||||||
|
|
||||||
// Since skeleton headers don't need to be meaningful, beyond a parent hash
|
// Since skeleton headers don't need to be meaningful, beyond a parent hash
|
||||||
// progression, create a long fake chain to test with.
|
// progression, create a long fake chain to test with.
|
||||||
|
|
@ -537,22 +535,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
Extra: []byte("B"), // force a different hash
|
Extra: []byte("B"), // force a different hash
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []skeletonTest{
|
||||||
fill bool // Whether to run a real backfiller in this test case
|
|
||||||
unpredictable bool // Whether to ignore drops/serves due to uncertain packet assignments
|
|
||||||
|
|
||||||
head *types.Header // New head header to announce to reorg to
|
|
||||||
peers []*skeletonTestPeer // Initial peer set to start the sync with
|
|
||||||
midstate []*subchain // Expected sync state after initial cycle
|
|
||||||
midserve uint64 // Expected number of header retrievals after initial cycle
|
|
||||||
middrop uint64 // Expected number of peers dropped after initial cycle
|
|
||||||
|
|
||||||
newHead *types.Header // New header to anoint on top of the old one
|
|
||||||
newPeer *skeletonTestPeer // New peer to join the skeleton syncer
|
|
||||||
endstate []*subchain // Expected sync state after the post-init event
|
|
||||||
endserve uint64 // Expected number of header retrievals after the post-init event
|
|
||||||
enddrop uint64 // Expected number of peers dropped after the post-init event
|
|
||||||
}{
|
|
||||||
// Completely empty database with only the genesis set. The sync is expected
|
// Completely empty database with only the genesis set. The sync is expected
|
||||||
// to create a single subchain with the requested head. No peers however, so
|
// to create a single subchain with the requested head. No peers however, so
|
||||||
// the sync should be stuck without any progression.
|
// the sync should be stuck without any progression.
|
||||||
|
|
@ -561,11 +544,15 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
// to the genesis block.
|
// to the genesis block.
|
||||||
{
|
{
|
||||||
head: chain[len(chain)-1],
|
head: chain[len(chain)-1],
|
||||||
midstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: uint64(len(chain) - 1)}},
|
mid: skeletonExpect{
|
||||||
|
state: []*subchain{{Head: uint64(len(chain) - 1), Tail: uint64(len(chain) - 1)}},
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("test-peer", chain),
|
newPeer: newSkeletonTestPeer("test-peer", chain),
|
||||||
endstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: uint64(len(chain) - 2), // len - head - genesis
|
state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||||
|
serve: uint64(len(chain) - 2), // len - head - genesis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Completely empty database with only the genesis set. The sync is expected
|
// Completely empty database with only the genesis set. The sync is expected
|
||||||
// to create a single subchain with the requested head. With one valid peer,
|
// to create a single subchain with the requested head. With one valid peer,
|
||||||
|
|
@ -575,12 +562,16 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
{
|
{
|
||||||
head: chain[len(chain)-1],
|
head: chain[len(chain)-1],
|
||||||
peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-1", chain)},
|
peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-1", chain)},
|
||||||
midstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
mid: skeletonExpect{
|
||||||
midserve: uint64(len(chain) - 2), // len - head - genesis
|
state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||||
|
serve: uint64(len(chain) - 2), // len - head - genesis
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("test-peer-2", chain),
|
newPeer: newSkeletonTestPeer("test-peer-2", chain),
|
||||||
endstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: uint64(len(chain) - 2), // len - head - genesis
|
state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||||
|
serve: uint64(len(chain) - 2), // len - head - genesis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Completely empty database with only the genesis set. The sync is expected
|
// Completely empty database with only the genesis set. The sync is expected
|
||||||
// to create a single subchain with the requested head. With many valid peers,
|
// to create a single subchain with the requested head. With many valid peers,
|
||||||
|
|
@ -594,12 +585,16 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
newSkeletonTestPeer("test-peer-2", chain),
|
newSkeletonTestPeer("test-peer-2", chain),
|
||||||
newSkeletonTestPeer("test-peer-3", chain),
|
newSkeletonTestPeer("test-peer-3", chain),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
mid: skeletonExpect{
|
||||||
midserve: uint64(len(chain) - 2), // len - head - genesis
|
state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||||
|
serve: uint64(len(chain) - 2), // len - head - genesis
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("test-peer-4", chain),
|
newPeer: newSkeletonTestPeer("test-peer-4", chain),
|
||||||
endstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: uint64(len(chain) - 2), // len - head - genesis
|
state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||||
|
serve: uint64(len(chain) - 2), // len - head - genesis
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test checks if a peer tries to withhold a header - *on* the sync
|
// This test checks if a peer tries to withhold a header - *on* the sync
|
||||||
// boundary - instead of sending the requested amount. The malicious short
|
// boundary - instead of sending the requested amount. The malicious short
|
||||||
|
|
@ -611,14 +606,18 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
peers: []*skeletonTestPeer{
|
peers: []*skeletonTestPeer{
|
||||||
newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:99]...), nil), chain[100:]...)),
|
newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:99]...), nil), chain[100:]...)),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
mid: skeletonExpect{
|
||||||
midserve: requestHeaders + 101 - 3, // len - head - genesis - missing
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||||
middrop: 1, // penalize shortened header deliveries
|
serve: requestHeaders + 101 - 3, // len - head - genesis - missing
|
||||||
|
drop: 1, // penalize shortened header deliveries
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||||
enddrop: 1, // no new drops
|
serve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis
|
||||||
|
drop: 1, // no new drops
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test checks if a peer tries to withhold a header - *off* the sync
|
// This test checks if a peer tries to withhold a header - *off* the sync
|
||||||
// boundary - instead of sending the requested amount. The malicious short
|
// boundary - instead of sending the requested amount. The malicious short
|
||||||
|
|
@ -630,14 +629,18 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
peers: []*skeletonTestPeer{
|
peers: []*skeletonTestPeer{
|
||||||
newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:50]...), nil), chain[51:]...)),
|
newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:50]...), nil), chain[51:]...)),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
mid: skeletonExpect{
|
||||||
midserve: requestHeaders + 101 - 3, // len - head - genesis - missing
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||||
middrop: 1, // penalize shortened header deliveries
|
serve: requestHeaders + 101 - 3, // len - head - genesis - missing
|
||||||
|
drop: 1, // penalize shortened header deliveries
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||||
enddrop: 1, // no new drops
|
serve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis
|
||||||
|
drop: 1, // no new drops
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test checks if a peer tries to duplicate a header - *on* the sync
|
// This test checks if a peer tries to duplicate a header - *on* the sync
|
||||||
// boundary - instead of sending the correct sequence. The malicious duped
|
// boundary - instead of sending the correct sequence. The malicious duped
|
||||||
|
|
@ -649,14 +652,18 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
peers: []*skeletonTestPeer{
|
peers: []*skeletonTestPeer{
|
||||||
newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:99]...), chain[98]), chain[100:]...)),
|
newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:99]...), chain[98]), chain[100:]...)),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
mid: skeletonExpect{
|
||||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||||
middrop: 1, // penalize invalid header sequences
|
serve: requestHeaders + 101 - 2, // len - head - genesis
|
||||||
|
drop: 1, // penalize invalid header sequences
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||||
enddrop: 1, // no new drops
|
serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||||
|
drop: 1, // no new drops
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test checks if a peer tries to duplicate a header - *off* the sync
|
// This test checks if a peer tries to duplicate a header - *off* the sync
|
||||||
// boundary - instead of sending the correct sequence. The malicious duped
|
// boundary - instead of sending the correct sequence. The malicious duped
|
||||||
|
|
@ -668,14 +675,18 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
peers: []*skeletonTestPeer{
|
peers: []*skeletonTestPeer{
|
||||||
newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:50]...), chain[49]), chain[51:]...)),
|
newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:50]...), chain[49]), chain[51:]...)),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
mid: skeletonExpect{
|
||||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||||
middrop: 1, // penalize invalid header sequences
|
serve: requestHeaders + 101 - 2, // len - head - genesis
|
||||||
|
drop: 1, // penalize invalid header sequences
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||||
enddrop: 1, // no new drops
|
serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||||
|
drop: 1, // no new drops
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test checks if a peer tries to inject a different header - *on*
|
// This test checks if a peer tries to inject a different header - *on*
|
||||||
// the sync boundary - instead of sending the correct sequence. The bad
|
// the sync boundary - instead of sending the correct sequence. The bad
|
||||||
|
|
@ -698,14 +709,18 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
mid: skeletonExpect{
|
||||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||||
middrop: 1, // different set of headers, drop // TODO(karalabe): maybe just diff sync?
|
serve: requestHeaders + 101 - 2, // len - head - genesis
|
||||||
|
drop: 1, // different set of headers, drop // TODO(karalabe): maybe just diff sync?
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||||
enddrop: 1, // no new drops
|
serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||||
|
drop: 1, // no new drops
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test checks if a peer tries to inject a different header - *off*
|
// This test checks if a peer tries to inject a different header - *off*
|
||||||
// the sync boundary - instead of sending the correct sequence. The bad
|
// the sync boundary - instead of sending the correct sequence. The bad
|
||||||
|
|
@ -728,14 +743,18 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
mid: skeletonExpect{
|
||||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||||
middrop: 1, // different set of headers, drop
|
serve: requestHeaders + 101 - 2, // len - head - genesis
|
||||||
|
drop: 1, // different set of headers, drop
|
||||||
|
},
|
||||||
|
|
||||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
state: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||||
enddrop: 1, // no new drops
|
serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||||
|
drop: 1, // no new drops
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test reproduces a bug caught during review (kudos to @holiman)
|
// This test reproduces a bug caught during review (kudos to @holiman)
|
||||||
// where a subchain is merged with a previously interrupted one, causing
|
// where a subchain is merged with a previously interrupted one, causing
|
||||||
|
|
@ -765,12 +784,16 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
return nil // Fallback to default behavior, just delayed
|
return nil // Fallback to default behavior, just delayed
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
midstate: []*subchain{{Head: 2 * requestHeaders, Tail: 1}},
|
mid: skeletonExpect{
|
||||||
midserve: 2*requestHeaders - 1, // len - head - genesis
|
state: []*subchain{{Head: 2 * requestHeaders, Tail: 1}},
|
||||||
|
serve: 2*requestHeaders - 1, // len - head - genesis
|
||||||
|
},
|
||||||
|
|
||||||
newHead: chain[2*requestHeaders+2],
|
newHead: chain[2*requestHeaders+2],
|
||||||
endstate: []*subchain{{Head: 2*requestHeaders + 2, Tail: 1}},
|
end: skeletonExpect{
|
||||||
endserve: 4 * requestHeaders,
|
state: []*subchain{{Head: 2*requestHeaders + 2, Tail: 1}},
|
||||||
|
serve: 4 * requestHeaders,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This test reproduces a bug caught by (@rjl493456442) where a skeleton
|
// This test reproduces a bug caught by (@rjl493456442) where a skeleton
|
||||||
// header goes missing, causing the sync to get stuck and/or panic.
|
// header goes missing, causing the sync to get stuck and/or panic.
|
||||||
|
|
@ -794,11 +817,15 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
|
|
||||||
head: chain[len(chain)/2+1], // Sync up until the sidechain common ancestor + 2
|
head: chain[len(chain)/2+1], // Sync up until the sidechain common ancestor + 2
|
||||||
peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-oldchain", chain)},
|
peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-oldchain", chain)},
|
||||||
midstate: []*subchain{{Head: uint64(len(chain)/2 + 1), Tail: 1}},
|
mid: skeletonExpect{
|
||||||
|
state: []*subchain{{Head: uint64(len(chain)/2 + 1), Tail: 1}},
|
||||||
|
},
|
||||||
|
|
||||||
newHead: sidechain[len(sidechain)/2+3], // Sync up until the sidechain common ancestor + 4
|
newHead: sidechain[len(sidechain)/2+3], // Sync up until the sidechain common ancestor + 4
|
||||||
newPeer: newSkeletonTestPeer("test-peer-newchain", sidechain),
|
newPeer: newSkeletonTestPeer("test-peer-newchain", sidechain),
|
||||||
endstate: []*subchain{{Head: uint64(len(sidechain)/2 + 3), Tail: uint64(len(chain) / 2)}},
|
end: skeletonExpect{
|
||||||
|
state: []*subchain{{Head: uint64(len(sidechain)/2 + 3), Tail: uint64(len(chain) / 2)}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
|
@ -861,115 +888,83 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
skeleton := newSkeleton(db, peerset, drop, filler)
|
skeleton := newSkeleton(db, peerset, drop, filler)
|
||||||
skeleton.Sync(tt.head, nil, true)
|
skeleton.Sync(tt.head, nil, true)
|
||||||
|
|
||||||
var progress skeletonProgress
|
|
||||||
// Wait a bit (bleah) for the initial sync loop to go to idle. This might
|
// Wait a bit (bleah) for the initial sync loop to go to idle. This might
|
||||||
// be either a finish or a never-start hence why there's no event to hook.
|
// be either a finish or a never-start hence why there's no event to hook.
|
||||||
check := func() error {
|
|
||||||
if len(progress.Subchains) != len(tt.midstate) {
|
|
||||||
return fmt.Errorf("test %d, mid state: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.midstate))
|
|
||||||
}
|
|
||||||
for j := 0; j < len(progress.Subchains); j++ {
|
|
||||||
if progress.Subchains[j].Head != tt.midstate[j].Head {
|
|
||||||
return fmt.Errorf("test %d, mid state: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.midstate[j].Head)
|
|
||||||
}
|
|
||||||
if progress.Subchains[j].Tail != tt.midstate[j].Tail {
|
|
||||||
return fmt.Errorf("test %d, mid state: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.midstate[j].Tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
waitStart := time.Now()
|
waitStart := time.Now()
|
||||||
for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 {
|
for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 {
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
// Check the post-init end state if it matches the required results
|
if err := checkSkeletonProgress(db, tt.unpredictable, tt.peers, tt.mid); err == nil {
|
||||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
|
||||||
if err := check(); err == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := check(); err != nil {
|
if err := checkSkeletonProgress(db, tt.unpredictable, tt.peers, tt.mid); err != nil {
|
||||||
t.Error(err)
|
t.Errorf("test %d, mid: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !tt.unpredictable {
|
|
||||||
var served uint64
|
|
||||||
for _, peer := range tt.peers {
|
|
||||||
served += peer.served.Load()
|
|
||||||
}
|
|
||||||
if served != tt.midserve {
|
|
||||||
t.Errorf("test %d, mid state: served headers mismatch: have %d, want %d", i, served, tt.midserve)
|
|
||||||
}
|
|
||||||
var drops uint64
|
|
||||||
for _, peer := range tt.peers {
|
|
||||||
drops += peer.dropped.Load()
|
|
||||||
}
|
|
||||||
if drops != tt.middrop {
|
|
||||||
t.Errorf("test %d, mid state: dropped peers mismatch: have %d, want %d", i, drops, tt.middrop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply the post-init events if there's any
|
// Apply the post-init events if there's any
|
||||||
if tt.newHead != nil {
|
endpeers := tt.peers
|
||||||
skeleton.Sync(tt.newHead, nil, true)
|
|
||||||
}
|
|
||||||
if tt.newPeer != nil {
|
if tt.newPeer != nil {
|
||||||
if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH68, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil {
|
if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH68, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil {
|
||||||
t.Errorf("test %d: failed to register new peer: %v", i, err)
|
t.Errorf("test %d: failed to register new peer: %v", i, err)
|
||||||
}
|
}
|
||||||
|
time.Sleep(time.Millisecond * 50) // given time for peer registration
|
||||||
|
endpeers = append(tt.peers, tt.newPeer)
|
||||||
}
|
}
|
||||||
|
if tt.newHead != nil {
|
||||||
|
skeleton.Sync(tt.newHead, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
// Wait a bit (bleah) for the second sync loop to go to idle. This might
|
// Wait a bit (bleah) for the second sync loop to go to idle. This might
|
||||||
// be either a finish or a never-start hence why there's no event to hook.
|
// be either a finish or a never-start hence why there's no event to hook.
|
||||||
check = func() error {
|
|
||||||
if len(progress.Subchains) != len(tt.endstate) {
|
|
||||||
return fmt.Errorf("test %d, end state: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.endstate))
|
|
||||||
}
|
|
||||||
for j := 0; j < len(progress.Subchains); j++ {
|
|
||||||
if progress.Subchains[j].Head != tt.endstate[j].Head {
|
|
||||||
return fmt.Errorf("test %d, end state: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.endstate[j].Head)
|
|
||||||
}
|
|
||||||
if progress.Subchains[j].Tail != tt.endstate[j].Tail {
|
|
||||||
return fmt.Errorf("test %d, end state: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.endstate[j].Tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
waitStart = time.Now()
|
waitStart = time.Now()
|
||||||
for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 {
|
for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 {
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
// Check the post-init end state if it matches the required results
|
if err := checkSkeletonProgress(db, tt.unpredictable, endpeers, tt.end); err == nil {
|
||||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
|
||||||
if err := check(); err == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := check(); err != nil {
|
if err := checkSkeletonProgress(db, tt.unpredictable, endpeers, tt.end); err != nil {
|
||||||
t.Error(err)
|
t.Errorf("test %d, end: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Check that the peers served no more headers than we actually needed
|
// Check that the peers served no more headers than we actually needed
|
||||||
if !tt.unpredictable {
|
|
||||||
served := uint64(0)
|
|
||||||
for _, peer := range tt.peers {
|
|
||||||
served += peer.served.Load()
|
|
||||||
}
|
|
||||||
if tt.newPeer != nil {
|
|
||||||
served += tt.newPeer.served.Load()
|
|
||||||
}
|
|
||||||
if served != tt.endserve {
|
|
||||||
t.Errorf("test %d, end state: served headers mismatch: have %d, want %d", i, served, tt.endserve)
|
|
||||||
}
|
|
||||||
drops := uint64(0)
|
|
||||||
for _, peer := range tt.peers {
|
|
||||||
drops += peer.dropped.Load()
|
|
||||||
}
|
|
||||||
if tt.newPeer != nil {
|
|
||||||
drops += tt.newPeer.dropped.Load()
|
|
||||||
}
|
|
||||||
if drops != tt.enddrop {
|
|
||||||
t.Errorf("test %d, end state: dropped peers mismatch: have %d, want %d", i, drops, tt.middrop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clean up any leftover skeleton sync resources
|
// Clean up any leftover skeleton sync resources
|
||||||
skeleton.Terminate()
|
skeleton.Terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkSkeletonProgress(db ethdb.KeyValueReader, unpredictable bool, peers []*skeletonTestPeer, expected skeletonExpect) error {
|
||||||
|
var progress skeletonProgress
|
||||||
|
// Check the post-init end state if it matches the required results
|
||||||
|
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
||||||
|
|
||||||
|
if len(progress.Subchains) != len(expected.state) {
|
||||||
|
return fmt.Errorf("subchain count mismatch: have %d, want %d", len(progress.Subchains), len(expected.state))
|
||||||
|
}
|
||||||
|
for j := 0; j < len(progress.Subchains); j++ {
|
||||||
|
if progress.Subchains[j].Head != expected.state[j].Head {
|
||||||
|
return fmt.Errorf("subchain %d head mismatch: have %d, want %d", j, progress.Subchains[j].Head, expected.state[j].Head)
|
||||||
|
}
|
||||||
|
if progress.Subchains[j].Tail != expected.state[j].Tail {
|
||||||
|
return fmt.Errorf("subchain %d tail mismatch: have %d, want %d", j, progress.Subchains[j].Tail, expected.state[j].Tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !unpredictable {
|
||||||
|
var served uint64
|
||||||
|
for _, peer := range peers {
|
||||||
|
served += peer.served.Load()
|
||||||
|
}
|
||||||
|
if served != expected.serve {
|
||||||
|
return fmt.Errorf("served headers mismatch: have %d, want %d", served, expected.serve)
|
||||||
|
}
|
||||||
|
var drops uint64
|
||||||
|
for _, peer := range peers {
|
||||||
|
drops += peer.dropped.Load()
|
||||||
|
}
|
||||||
|
if drops != expected.drop {
|
||||||
|
return fmt.Errorf("dropped peers mismatch: have %d, want %d", drops, expected.drop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ const (
|
||||||
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
|
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
|
||||||
// for the fee history calculation (mostly relevant for LES).
|
// for the fee history calculation (mostly relevant for LES).
|
||||||
maxBlockFetchers = 4
|
maxBlockFetchers = 4
|
||||||
|
// maxQueryLimit is the max number of requested percentiles.
|
||||||
maxQueryLimit = 100
|
maxQueryLimit = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
return nil, errors.New("snap sync not supported with snapshots disabled")
|
return nil, errors.New("snap sync not supported with snapshots disabled")
|
||||||
}
|
}
|
||||||
// Construct the downloader (long sync)
|
// Construct the downloader (long sync)
|
||||||
h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, h.enableSyncedFeatures)
|
h.downloader = downloader.New(config.Database, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures)
|
||||||
|
|
||||||
fetchTx := func(peer string, hashes []common.Hash) error {
|
fetchTx := func(peer string, hashes []common.Hash) error {
|
||||||
p := h.peers.peer(peer)
|
p := h.peers.peer(peer)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func makeLegacyProgress() legacyProgress {
|
||||||
Next: common.Hash{},
|
Next: common.Hash{},
|
||||||
Last: common.Hash{0x77},
|
Last: common.Hash{0x77},
|
||||||
SubTasks: map[common.Hash][]*legacyStorageTask{
|
SubTasks: map[common.Hash][]*legacyStorageTask{
|
||||||
common.Hash{0x1}: {
|
{0x1}: {
|
||||||
{
|
{
|
||||||
Next: common.Hash{},
|
Next: common.Hash{},
|
||||||
Last: common.Hash{0xff},
|
Last: common.Hash{0xff},
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ var (
|
||||||
// to allow concurrent retrievals.
|
// to allow concurrent retrievals.
|
||||||
accountConcurrency = 16
|
accountConcurrency = 16
|
||||||
|
|
||||||
// storageConcurrency is the number of chunks to split the a large contract
|
// storageConcurrency is the number of chunks to split a large contract
|
||||||
// storage trie into to allow concurrent retrievals.
|
// storage trie into to allow concurrent retrievals.
|
||||||
storageConcurrency = 16
|
storageConcurrency = 16
|
||||||
)
|
)
|
||||||
|
|
@ -3250,9 +3250,9 @@ func (t *healRequestSort) Merge() []TrieNodePathSet {
|
||||||
// sortByAccountPath takes hashes and paths, and sorts them. After that, it generates
|
// sortByAccountPath takes hashes and paths, and sorts them. After that, it generates
|
||||||
// the TrieNodePaths and merges paths which belongs to the same account path.
|
// the TrieNodePaths and merges paths which belongs to the same account path.
|
||||||
func sortByAccountPath(paths []string, hashes []common.Hash) ([]string, []common.Hash, []trie.SyncPath, []TrieNodePathSet) {
|
func sortByAccountPath(paths []string, hashes []common.Hash) ([]string, []common.Hash, []trie.SyncPath, []TrieNodePathSet) {
|
||||||
var syncPaths []trie.SyncPath
|
syncPaths := make([]trie.SyncPath, len(paths))
|
||||||
for _, path := range paths {
|
for i, path := range paths {
|
||||||
syncPaths = append(syncPaths, trie.NewSyncPath([]byte(path)))
|
syncPaths[i] = trie.NewSyncPath([]byte(path))
|
||||||
}
|
}
|
||||||
n := &healRequestSort{paths, hashes, syncPaths}
|
n := &healRequestSort{paths, hashes, syncPaths}
|
||||||
sort.Sort(n)
|
sort.Sort(n)
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ func TestTraceCall(t *testing.T) {
|
||||||
config: &TraceCallConfig{TxIndex: uintPtr(1)},
|
config: &TraceCallConfig{TxIndex: uintPtr(1)},
|
||||||
expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr),
|
expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr),
|
||||||
},
|
},
|
||||||
// After the target transaction, should be succeed
|
// After the target transaction, should be succeeded
|
||||||
{
|
{
|
||||||
blockNumber: rpc.BlockNumber(genBlocks - 1),
|
blockNumber: rpc.BlockNumber(genBlocks - 1),
|
||||||
call: ethapi.TransactionArgs{
|
call: ethapi.TransactionArgs{
|
||||||
|
|
|
||||||
613
eth/tracers/internal/tracetest/supply_test.go
Normal file
613
eth/tracers/internal/tracetest/supply_test.go
Normal file
|
|
@ -0,0 +1,613 @@
|
||||||
|
// Copyright 2021 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package tracetest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
||||||
|
// Force-load live packages, to trigger registration
|
||||||
|
_ "github.com/ethereum/go-ethereum/eth/tracers/live"
|
||||||
|
)
|
||||||
|
|
||||||
|
type supplyInfoIssuance struct {
|
||||||
|
GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"`
|
||||||
|
Reward *hexutil.Big `json:"reward,omitempty"`
|
||||||
|
Withdrawals *hexutil.Big `json:"withdrawals,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyInfoBurn struct {
|
||||||
|
EIP1559 *hexutil.Big `json:"1559,omitempty"`
|
||||||
|
Blob *hexutil.Big `json:"blob,omitempty"`
|
||||||
|
Misc *hexutil.Big `json:"misc,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyInfo struct {
|
||||||
|
Issuance *supplyInfoIssuance `json:"issuance,omitempty"`
|
||||||
|
Burn *supplyInfoBurn `json:"burn,omitempty"`
|
||||||
|
|
||||||
|
// Block info
|
||||||
|
Number uint64 `json:"blockNumber"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
|
ParentHash common.Hash `json:"parentHash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyBlockGenerationFunc(b *core.BlockGen) {}
|
||||||
|
|
||||||
|
func TestSupplyOmittedFields(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = *params.MergedTestChainConfig
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
|
||||||
|
|
||||||
|
out, _, err := testSupplyTracer(t, gspec, func(b *core.BlockGen) {
|
||||||
|
b.SetPoS()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := supplyInfo{
|
||||||
|
Number: 0,
|
||||||
|
Hash: common.HexToHash("0x52f276d96f0afaaf2c3cb358868bdc2779c4b0cb8de3e7e5302e247c0b66a703"),
|
||||||
|
ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
}
|
||||||
|
actual := out[expected.Number]
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupplyGenesisAlloc(t *testing.T) {
|
||||||
|
var (
|
||||||
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||||
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||||
|
eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||||
|
|
||||||
|
config = *params.AllEthashProtocolChanges
|
||||||
|
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {Balance: eth1},
|
||||||
|
addr2: {Balance: eth1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expected := supplyInfo{
|
||||||
|
Issuance: &supplyInfoIssuance{
|
||||||
|
GenesisAlloc: (*hexutil.Big)(new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))),
|
||||||
|
},
|
||||||
|
Number: 0,
|
||||||
|
Hash: common.HexToHash("0xbcc9466e9fc6a8b56f4b29ca353a421ff8b51a0c1a58ca4743b427605b08f2ca"),
|
||||||
|
ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _, err := testSupplyTracer(t, gspec, emptyBlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := out[expected.Number]
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupplyRewards(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = *params.AllEthashProtocolChanges
|
||||||
|
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expected := supplyInfo{
|
||||||
|
Issuance: &supplyInfoIssuance{
|
||||||
|
Reward: (*hexutil.Big)(new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))),
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
Hash: common.HexToHash("0xcbb08370505be503dafedc4e96d139ea27aba3cbc580148568b8a307b3f51052"),
|
||||||
|
ParentHash: common.HexToHash("0xadeda0a83e337b6c073e3f0e9a17531a04009b397a9588c093b628f21b8bc5a3"),
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _, err := testSupplyTracer(t, gspec, emptyBlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := out[expected.Number]
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupplyEip1559Burn(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = *params.AllEthashProtocolChanges
|
||||||
|
|
||||||
|
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||||
|
// A sender who makes transactions, has some eth1
|
||||||
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei))
|
||||||
|
eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||||
|
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {Balance: eth1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
signer := types.LatestSigner(gspec.Config)
|
||||||
|
|
||||||
|
eip1559BlockGenerationFunc := func(b *core.BlockGen) {
|
||||||
|
txdata := &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 0,
|
||||||
|
To: &aa,
|
||||||
|
Gas: 21000,
|
||||||
|
GasFeeCap: gwei5,
|
||||||
|
GasTipCap: big.NewInt(2),
|
||||||
|
}
|
||||||
|
tx := types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key1)
|
||||||
|
|
||||||
|
b.AddTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, chain, err := testSupplyTracer(t, gspec, eip1559BlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
head = chain.CurrentBlock()
|
||||||
|
reward = new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))
|
||||||
|
burn = new(big.Int).Mul(big.NewInt(21000), head.BaseFee)
|
||||||
|
expected = supplyInfo{
|
||||||
|
Issuance: &supplyInfoIssuance{
|
||||||
|
Reward: (*hexutil.Big)(reward),
|
||||||
|
},
|
||||||
|
Burn: &supplyInfoBurn{
|
||||||
|
EIP1559: (*hexutil.Big)(burn),
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
Hash: head.Hash(),
|
||||||
|
ParentHash: head.ParentHash,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
actual := out[expected.Number]
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupplyWithdrawals(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = *params.MergedTestChainConfig
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
withdrawalsBlockGenerationFunc := func(b *core.BlockGen) {
|
||||||
|
b.SetPoS()
|
||||||
|
|
||||||
|
b.AddWithdrawal(&types.Withdrawal{
|
||||||
|
Validator: 42,
|
||||||
|
Address: common.Address{0xee},
|
||||||
|
Amount: 1337,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
out, chain, err := testSupplyTracer(t, gspec, withdrawalsBlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
head = chain.CurrentBlock()
|
||||||
|
expected = supplyInfo{
|
||||||
|
Issuance: &supplyInfoIssuance{
|
||||||
|
Withdrawals: (*hexutil.Big)(big.NewInt(1337000000000)),
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
Hash: head.Hash(),
|
||||||
|
ParentHash: head.ParentHash,
|
||||||
|
}
|
||||||
|
actual = out[expected.Number]
|
||||||
|
)
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests fund retrieval after contract's selfdestruct.
|
||||||
|
// Contract A calls contract B which selfdestructs, but B receives eth1
|
||||||
|
// after the selfdestruct opcode executes from Contract A.
|
||||||
|
// Because Contract B is removed only at the end of the transaction
|
||||||
|
// the ether sent in between is burnt before Cancun hard fork.
|
||||||
|
func TestSupplySelfdestruct(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = *params.TestChainConfig
|
||||||
|
|
||||||
|
aa = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||||
|
bb = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||||
|
dad = common.HexToAddress("0x0000000000000000000000000000000000000dad")
|
||||||
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei))
|
||||||
|
eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||||
|
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {Balance: eth1},
|
||||||
|
aa: {
|
||||||
|
Code: common.FromHex("0x61face60f01b6000527322222222222222222222222222222222222222226000806002600080855af160008103603457600080fd5b60008060008034865af1905060008103604c57600080fd5b5050"),
|
||||||
|
// Nonce: 0,
|
||||||
|
Balance: big.NewInt(0),
|
||||||
|
},
|
||||||
|
bb: {
|
||||||
|
Code: common.FromHex("0x6000357fface000000000000000000000000000000000000000000000000000000000000808203602f57610dad80ff5b5050"),
|
||||||
|
Nonce: 0,
|
||||||
|
Balance: eth1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
|
||||||
|
|
||||||
|
signer := types.LatestSigner(gspec.Config)
|
||||||
|
|
||||||
|
testBlockGenerationFunc := func(b *core.BlockGen) {
|
||||||
|
b.SetPoS()
|
||||||
|
|
||||||
|
txdata := &types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
To: &aa,
|
||||||
|
Value: gwei5,
|
||||||
|
Gas: 150000,
|
||||||
|
GasPrice: gwei5,
|
||||||
|
Data: []byte{},
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key1)
|
||||||
|
|
||||||
|
b.AddTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Test pre Cancun
|
||||||
|
preCancunOutput, preCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Pre-cancun failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check balance at state:
|
||||||
|
// 1. 0x0000...000dad has 1 ether
|
||||||
|
// 2. A has 0 ether
|
||||||
|
// 3. B has 0 ether
|
||||||
|
statedb, _ := preCancunChain.State()
|
||||||
|
if got, exp := statedb.GetBalance(dad), eth1; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", dad, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(aa), big.NewInt(0); got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", aa, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(bb), big.NewInt(0); got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", bb, got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
head := preCancunChain.CurrentBlock()
|
||||||
|
// Check live trace output
|
||||||
|
expected := supplyInfo{
|
||||||
|
Burn: &supplyInfoBurn{
|
||||||
|
EIP1559: (*hexutil.Big)(big.NewInt(55289500000000)),
|
||||||
|
Misc: (*hexutil.Big)(big.NewInt(5000000000)),
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
Hash: head.Hash(),
|
||||||
|
ParentHash: head.ParentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := preCancunOutput[expected.Number]
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
|
||||||
|
// 2. Test post Cancun
|
||||||
|
cancunTime := uint64(0)
|
||||||
|
gspec.Config.ShanghaiTime = &cancunTime
|
||||||
|
gspec.Config.CancunTime = &cancunTime
|
||||||
|
|
||||||
|
postCancunOutput, postCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Post-cancun failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check balance at state:
|
||||||
|
// 1. 0x0000...000dad has 1 ether
|
||||||
|
// 3. A has 0 ether
|
||||||
|
// 3. B has 5 gwei
|
||||||
|
statedb, _ = postCancunChain.State()
|
||||||
|
if got, exp := statedb.GetBalance(dad), eth1; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", dad, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(aa), big.NewInt(0); got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", aa, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(bb), gwei5; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", bb, got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check live trace output
|
||||||
|
head = postCancunChain.CurrentBlock()
|
||||||
|
expected = supplyInfo{
|
||||||
|
Burn: &supplyInfoBurn{
|
||||||
|
EIP1559: (*hexutil.Big)(big.NewInt(55289500000000)),
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
Hash: head.Hash(),
|
||||||
|
ParentHash: head.ParentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = postCancunOutput[expected.Number]
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests selfdestructing contract to send its balance to itself (burn).
|
||||||
|
// It tests both cases of selfdestructing succeding and being reverted.
|
||||||
|
// - Contract A calls B and D.
|
||||||
|
// - Contract B selfdestructs and sends the eth1 to itself (Burn amount to be counted).
|
||||||
|
// - Contract C selfdestructs and sends the eth1 to itself.
|
||||||
|
// - Contract D calls C and reverts (Burn amount of C
|
||||||
|
// has to be reverted as well).
|
||||||
|
func TestSupplySelfdestructItselfAndRevert(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = *params.TestChainConfig
|
||||||
|
|
||||||
|
aa = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||||
|
bb = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||||
|
cc = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
||||||
|
dd = common.HexToAddress("0x4444444444444444444444444444444444444444")
|
||||||
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei))
|
||||||
|
eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||||
|
eth2 = new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))
|
||||||
|
eth5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.Ether))
|
||||||
|
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: &config,
|
||||||
|
// BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {Balance: eth1},
|
||||||
|
aa: {
|
||||||
|
// Contract code in YUL:
|
||||||
|
//
|
||||||
|
// object "ContractA" {
|
||||||
|
// code {
|
||||||
|
// let B := 0x2222222222222222222222222222222222222222
|
||||||
|
// let D := 0x4444444444444444444444444444444444444444
|
||||||
|
|
||||||
|
// // Call to Contract B
|
||||||
|
// let resB:= call(gas(), B, 0, 0x0, 0x0, 0, 0)
|
||||||
|
|
||||||
|
// // Call to Contract D
|
||||||
|
// let resD := call(gas(), D, 0, 0x0, 0x0, 0, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Code: common.FromHex("0x73222222222222222222222222222222222222222273444444444444444444444444444444444444444460006000600060006000865af160006000600060006000865af150505050"),
|
||||||
|
Balance: common.Big0,
|
||||||
|
},
|
||||||
|
bb: {
|
||||||
|
// Contract code in YUL:
|
||||||
|
//
|
||||||
|
// object "ContractB" {
|
||||||
|
// code {
|
||||||
|
// let self := address()
|
||||||
|
// selfdestruct(self)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Code: common.FromHex("0x3080ff50"),
|
||||||
|
Balance: eth5,
|
||||||
|
},
|
||||||
|
cc: {
|
||||||
|
Code: common.FromHex("0x3080ff50"),
|
||||||
|
Balance: eth1,
|
||||||
|
},
|
||||||
|
dd: {
|
||||||
|
// Contract code in YUL:
|
||||||
|
//
|
||||||
|
// object "ContractD" {
|
||||||
|
// code {
|
||||||
|
// let C := 0x3333333333333333333333333333333333333333
|
||||||
|
|
||||||
|
// // Call to Contract C
|
||||||
|
// let resC := call(gas(), C, 0, 0x0, 0x0, 0, 0)
|
||||||
|
|
||||||
|
// // Revert
|
||||||
|
// revert(0, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Code: common.FromHex("0x73333333333333333333333333333333333333333360006000600060006000855af160006000fd5050"),
|
||||||
|
Balance: eth2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
|
||||||
|
|
||||||
|
signer := types.LatestSigner(gspec.Config)
|
||||||
|
|
||||||
|
testBlockGenerationFunc := func(b *core.BlockGen) {
|
||||||
|
b.SetPoS()
|
||||||
|
|
||||||
|
txdata := &types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
To: &aa,
|
||||||
|
Value: common.Big0,
|
||||||
|
Gas: 150000,
|
||||||
|
GasPrice: gwei5,
|
||||||
|
Data: []byte{},
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := types.NewTx(txdata)
|
||||||
|
tx, _ = types.SignTx(tx, signer, key1)
|
||||||
|
|
||||||
|
b.AddTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, chain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to test supply tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check balance at state:
|
||||||
|
// 1. A has 0 ether
|
||||||
|
// 2. B has 0 ether, burned
|
||||||
|
// 3. C has 2 ether, selfdestructed but parent D reverted
|
||||||
|
// 4. D has 1 ether, reverted
|
||||||
|
statedb, _ := chain.State()
|
||||||
|
if got, exp := statedb.GetBalance(aa), common.Big0; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("address \"%v\" balance, got %v exp %v\n", aa, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(bb), common.Big0; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(cc), eth1; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp)
|
||||||
|
}
|
||||||
|
if got, exp := statedb.GetBalance(dd), eth2; got.CmpBig(exp) != 0 {
|
||||||
|
t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check live trace output
|
||||||
|
block := chain.GetBlockByNumber(1)
|
||||||
|
|
||||||
|
expected := supplyInfo{
|
||||||
|
Burn: &supplyInfoBurn{
|
||||||
|
EIP1559: (*hexutil.Big)(new(big.Int).Mul(block.BaseFee(), big.NewInt(int64(block.GasUsed())))),
|
||||||
|
Misc: (*hexutil.Big)(eth5), // 5ETH burned from contract B
|
||||||
|
},
|
||||||
|
Number: 1,
|
||||||
|
Hash: block.Hash(),
|
||||||
|
ParentHash: block.ParentHash(),
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := output[expected.Number]
|
||||||
|
|
||||||
|
compareAsJSON(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSupplyTracer(t *testing.T, genesis *core.Genesis, gen func(*core.BlockGen)) ([]supplyInfo, *core.BlockChain, error) {
|
||||||
|
var (
|
||||||
|
engine = beacon.New(ethash.NewFaker())
|
||||||
|
)
|
||||||
|
|
||||||
|
traceOutputPath := filepath.ToSlash(t.TempDir())
|
||||||
|
traceOutputFilename := path.Join(traceOutputPath, "supply.jsonl")
|
||||||
|
|
||||||
|
// Load supply tracer
|
||||||
|
tracer, err := tracers.LiveDirectory.New("supply", json.RawMessage(fmt.Sprintf(`{"path":"%s"}`, traceOutputPath)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create call tracer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), core.DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, engine, vm.Config{Tracer: tracer}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create tester chain: %v", err)
|
||||||
|
}
|
||||||
|
defer chain.Stop()
|
||||||
|
|
||||||
|
_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 1, func(i int, b *core.BlockGen) {
|
||||||
|
b.SetCoinbase(common.Address{1})
|
||||||
|
gen(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
if n, err := chain.InsertChain(blocks); err != nil {
|
||||||
|
return nil, chain, fmt.Errorf("block %d: failed to insert into chain: %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and compare the results
|
||||||
|
file, err := os.OpenFile(traceOutputFilename, os.O_RDONLY, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, chain, fmt.Errorf("failed to open output file: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var output []supplyInfo
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
blockBytes := scanner.Bytes()
|
||||||
|
|
||||||
|
var info supplyInfo
|
||||||
|
if err := json.Unmarshal(blockBytes, &info); err != nil {
|
||||||
|
return nil, chain, fmt.Errorf("failed to unmarshal result: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output = append(output, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareAsJSON(t *testing.T, expected interface{}, actual interface{}) {
|
||||||
|
want, err := json.Marshal(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal expected value to JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
have, err := json.Marshal(actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal actual value to JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(want, have) {
|
||||||
|
t.Fatalf("incorrect supply info: expected %s, got %s", string(want), string(have))
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
49
eth/tracers/live/gen_supplyinfoburn.go
Normal file
49
eth/tracers/live/gen_supplyinfoburn.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package live
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*supplyInfoBurnMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (s supplyInfoBurn) MarshalJSON() ([]byte, error) {
|
||||||
|
type supplyInfoBurn struct {
|
||||||
|
EIP1559 *hexutil.Big `json:"1559,omitempty"`
|
||||||
|
Blob *hexutil.Big `json:"blob,omitempty"`
|
||||||
|
Misc *hexutil.Big `json:"misc,omitempty"`
|
||||||
|
}
|
||||||
|
var enc supplyInfoBurn
|
||||||
|
enc.EIP1559 = (*hexutil.Big)(s.EIP1559)
|
||||||
|
enc.Blob = (*hexutil.Big)(s.Blob)
|
||||||
|
enc.Misc = (*hexutil.Big)(s.Misc)
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (s *supplyInfoBurn) UnmarshalJSON(input []byte) error {
|
||||||
|
type supplyInfoBurn struct {
|
||||||
|
EIP1559 *hexutil.Big `json:"1559,omitempty"`
|
||||||
|
Blob *hexutil.Big `json:"blob,omitempty"`
|
||||||
|
Misc *hexutil.Big `json:"misc,omitempty"`
|
||||||
|
}
|
||||||
|
var dec supplyInfoBurn
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.EIP1559 != nil {
|
||||||
|
s.EIP1559 = (*big.Int)(dec.EIP1559)
|
||||||
|
}
|
||||||
|
if dec.Blob != nil {
|
||||||
|
s.Blob = (*big.Int)(dec.Blob)
|
||||||
|
}
|
||||||
|
if dec.Misc != nil {
|
||||||
|
s.Misc = (*big.Int)(dec.Misc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
49
eth/tracers/live/gen_supplyinfoissuance.go
Normal file
49
eth/tracers/live/gen_supplyinfoissuance.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package live
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*supplyInfoIssuanceMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (s supplyInfoIssuance) MarshalJSON() ([]byte, error) {
|
||||||
|
type supplyInfoIssuance struct {
|
||||||
|
GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"`
|
||||||
|
Reward *hexutil.Big `json:"reward,omitempty"`
|
||||||
|
Withdrawals *hexutil.Big `json:"withdrawals,omitempty"`
|
||||||
|
}
|
||||||
|
var enc supplyInfoIssuance
|
||||||
|
enc.GenesisAlloc = (*hexutil.Big)(s.GenesisAlloc)
|
||||||
|
enc.Reward = (*hexutil.Big)(s.Reward)
|
||||||
|
enc.Withdrawals = (*hexutil.Big)(s.Withdrawals)
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (s *supplyInfoIssuance) UnmarshalJSON(input []byte) error {
|
||||||
|
type supplyInfoIssuance struct {
|
||||||
|
GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"`
|
||||||
|
Reward *hexutil.Big `json:"reward,omitempty"`
|
||||||
|
Withdrawals *hexutil.Big `json:"withdrawals,omitempty"`
|
||||||
|
}
|
||||||
|
var dec supplyInfoIssuance
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.GenesisAlloc != nil {
|
||||||
|
s.GenesisAlloc = (*big.Int)(dec.GenesisAlloc)
|
||||||
|
}
|
||||||
|
if dec.Reward != nil {
|
||||||
|
s.Reward = (*big.Int)(dec.Reward)
|
||||||
|
}
|
||||||
|
if dec.Withdrawals != nil {
|
||||||
|
s.Withdrawals = (*big.Int)(dec.Withdrawals)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
310
eth/tracers/live/supply.go
Normal file
310
eth/tracers/live/supply.go
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
package live
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tracers.LiveDirectory.Register("supply", newSupply)
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyInfoIssuance struct {
|
||||||
|
GenesisAlloc *big.Int `json:"genesisAlloc,omitempty"`
|
||||||
|
Reward *big.Int `json:"reward,omitempty"`
|
||||||
|
Withdrawals *big.Int `json:"withdrawals,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type supplyInfoIssuance -field-override supplyInfoIssuanceMarshaling -out gen_supplyinfoissuance.go
|
||||||
|
type supplyInfoIssuanceMarshaling struct {
|
||||||
|
GenesisAlloc *hexutil.Big
|
||||||
|
Reward *hexutil.Big
|
||||||
|
Withdrawals *hexutil.Big
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyInfoBurn struct {
|
||||||
|
EIP1559 *big.Int `json:"1559,omitempty"`
|
||||||
|
Blob *big.Int `json:"blob,omitempty"`
|
||||||
|
Misc *big.Int `json:"misc,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type supplyInfoBurn -field-override supplyInfoBurnMarshaling -out gen_supplyinfoburn.go
|
||||||
|
type supplyInfoBurnMarshaling struct {
|
||||||
|
EIP1559 *hexutil.Big
|
||||||
|
Blob *hexutil.Big
|
||||||
|
Misc *hexutil.Big
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyInfo struct {
|
||||||
|
Issuance *supplyInfoIssuance `json:"issuance,omitempty"`
|
||||||
|
Burn *supplyInfoBurn `json:"burn,omitempty"`
|
||||||
|
|
||||||
|
// Block info
|
||||||
|
Number uint64 `json:"blockNumber"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
|
ParentHash common.Hash `json:"parentHash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyTxCallstack struct {
|
||||||
|
calls []supplyTxCallstack
|
||||||
|
burn *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type supply struct {
|
||||||
|
delta supplyInfo
|
||||||
|
txCallstack []supplyTxCallstack // Callstack for current transaction
|
||||||
|
logger *lumberjack.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type supplyTracerConfig struct {
|
||||||
|
Path string `json:"path"` // Path to the directory where the tracer logs will be stored
|
||||||
|
MaxSize int `json:"maxSize"` // MaxSize is the maximum size in megabytes of the tracer log file before it gets rotated. It defaults to 100 megabytes.
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSupply(cfg json.RawMessage) (*tracing.Hooks, error) {
|
||||||
|
var config supplyTracerConfig
|
||||||
|
if cfg != nil {
|
||||||
|
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse config: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.Path == "" {
|
||||||
|
return nil, errors.New("supply tracer output path is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store traces in a rotating file
|
||||||
|
logger := &lumberjack.Logger{
|
||||||
|
Filename: filepath.Join(config.Path, "supply.jsonl"),
|
||||||
|
}
|
||||||
|
if config.MaxSize > 0 {
|
||||||
|
logger.MaxSize = config.MaxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &supply{
|
||||||
|
delta: newSupplyInfo(),
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
return &tracing.Hooks{
|
||||||
|
OnBlockStart: t.OnBlockStart,
|
||||||
|
OnBlockEnd: t.OnBlockEnd,
|
||||||
|
OnGenesisBlock: t.OnGenesisBlock,
|
||||||
|
OnTxStart: t.OnTxStart,
|
||||||
|
OnBalanceChange: t.OnBalanceChange,
|
||||||
|
OnEnter: t.OnEnter,
|
||||||
|
OnExit: t.OnExit,
|
||||||
|
OnClose: t.OnClose,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSupplyInfo() supplyInfo {
|
||||||
|
return supplyInfo{
|
||||||
|
Issuance: &supplyInfoIssuance{
|
||||||
|
GenesisAlloc: big.NewInt(0),
|
||||||
|
Reward: big.NewInt(0),
|
||||||
|
Withdrawals: big.NewInt(0),
|
||||||
|
},
|
||||||
|
Burn: &supplyInfoBurn{
|
||||||
|
EIP1559: big.NewInt(0),
|
||||||
|
Blob: big.NewInt(0),
|
||||||
|
Misc: big.NewInt(0),
|
||||||
|
},
|
||||||
|
|
||||||
|
Number: 0,
|
||||||
|
Hash: common.Hash{},
|
||||||
|
ParentHash: common.Hash{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) resetDelta() {
|
||||||
|
s.delta = newSupplyInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnBlockStart(ev tracing.BlockEvent) {
|
||||||
|
s.resetDelta()
|
||||||
|
|
||||||
|
s.delta.Number = ev.Block.NumberU64()
|
||||||
|
s.delta.Hash = ev.Block.Hash()
|
||||||
|
s.delta.ParentHash = ev.Block.ParentHash()
|
||||||
|
|
||||||
|
// Calculate Burn for this block
|
||||||
|
if ev.Block.BaseFee() != nil {
|
||||||
|
burn := new(big.Int).Mul(new(big.Int).SetUint64(ev.Block.GasUsed()), ev.Block.BaseFee())
|
||||||
|
s.delta.Burn.EIP1559 = burn
|
||||||
|
}
|
||||||
|
// Blob burnt gas
|
||||||
|
if blobGas := ev.Block.BlobGasUsed(); blobGas != nil && *blobGas > 0 && ev.Block.ExcessBlobGas() != nil {
|
||||||
|
var (
|
||||||
|
excess = *ev.Block.ExcessBlobGas()
|
||||||
|
baseFee = eip4844.CalcBlobFee(excess)
|
||||||
|
burn = new(big.Int).Mul(new(big.Int).SetUint64(*blobGas), baseFee)
|
||||||
|
)
|
||||||
|
s.delta.Burn.Blob = burn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnBlockEnd(err error) {
|
||||||
|
s.write(s.delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) {
|
||||||
|
s.resetDelta()
|
||||||
|
|
||||||
|
s.delta.Number = b.NumberU64()
|
||||||
|
s.delta.Hash = b.Hash()
|
||||||
|
s.delta.ParentHash = b.ParentHash()
|
||||||
|
|
||||||
|
// Initialize supply with total allocation in genesis block
|
||||||
|
for _, account := range alloc {
|
||||||
|
s.delta.Issuance.GenesisAlloc.Add(s.delta.Issuance.GenesisAlloc, account.Balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.write(s.delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnBalanceChange(a common.Address, prevBalance, newBalance *big.Int, reason tracing.BalanceChangeReason) {
|
||||||
|
diff := new(big.Int).Sub(newBalance, prevBalance)
|
||||||
|
|
||||||
|
// NOTE: don't handle "BalanceIncreaseGenesisBalance" because it is handled in OnGenesisBlock
|
||||||
|
switch reason {
|
||||||
|
case tracing.BalanceIncreaseRewardMineUncle:
|
||||||
|
case tracing.BalanceIncreaseRewardMineBlock:
|
||||||
|
s.delta.Issuance.Reward.Add(s.delta.Issuance.Reward, diff)
|
||||||
|
case tracing.BalanceIncreaseWithdrawal:
|
||||||
|
s.delta.Issuance.Withdrawals.Add(s.delta.Issuance.Withdrawals, diff)
|
||||||
|
case tracing.BalanceDecreaseSelfdestructBurn:
|
||||||
|
// BalanceDecreaseSelfdestructBurn is non-reversible as it happens
|
||||||
|
// at the end of the transaction.
|
||||||
|
s.delta.Burn.Misc.Sub(s.delta.Burn.Misc, diff)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
|
||||||
|
s.txCallstack = make([]supplyTxCallstack, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internalTxsHandler handles internal transactions burned amount
|
||||||
|
func (s *supply) internalTxsHandler(call *supplyTxCallstack) {
|
||||||
|
// Handle Burned amount
|
||||||
|
if call.burn != nil {
|
||||||
|
s.delta.Burn.Misc.Add(s.delta.Burn.Misc, call.burn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(call.calls) > 0 {
|
||||||
|
// Recursivelly handle internal calls
|
||||||
|
for _, call := range call.calls {
|
||||||
|
callCopy := call
|
||||||
|
s.internalTxsHandler(&callCopy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||||
|
call := supplyTxCallstack{
|
||||||
|
calls: make([]supplyTxCallstack, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a special case of burned amount which has to be handled here
|
||||||
|
// which happens when type == selfdestruct and from == to.
|
||||||
|
if vm.OpCode(typ) == vm.SELFDESTRUCT && from == to && value.Cmp(common.Big0) == 1 {
|
||||||
|
call.burn = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append call to the callstack, so we can fill the details in CaptureExit
|
||||||
|
s.txCallstack = append(s.txCallstack, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
|
||||||
|
if depth == 0 {
|
||||||
|
// No need to handle Burned amount if transaction is reverted
|
||||||
|
if !reverted {
|
||||||
|
s.internalTxsHandler(&s.txCallstack[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
size := len(s.txCallstack)
|
||||||
|
if size <= 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Pop call
|
||||||
|
call := s.txCallstack[size-1]
|
||||||
|
s.txCallstack = s.txCallstack[:size-1]
|
||||||
|
size -= 1
|
||||||
|
|
||||||
|
// In case of a revert, we can drop the call and all its subcalls.
|
||||||
|
// Caution, that this has to happen after popping the call from the stack.
|
||||||
|
if reverted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.txCallstack[size-1].calls = append(s.txCallstack[size-1].calls, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) OnClose() {
|
||||||
|
if err := s.logger.Close(); err != nil {
|
||||||
|
log.Warn("failed to close supply tracer log file", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *supply) write(data any) {
|
||||||
|
supply, ok := data.(supplyInfo)
|
||||||
|
if !ok {
|
||||||
|
log.Warn("failed to cast supply tracer data on write to log file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty fields
|
||||||
|
if supply.Issuance.GenesisAlloc.Sign() == 0 {
|
||||||
|
supply.Issuance.GenesisAlloc = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Issuance.Reward.Sign() == 0 {
|
||||||
|
supply.Issuance.Reward = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Issuance.Withdrawals.Sign() == 0 {
|
||||||
|
supply.Issuance.Withdrawals = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Issuance.GenesisAlloc == nil && supply.Issuance.Reward == nil && supply.Issuance.Withdrawals == nil {
|
||||||
|
supply.Issuance = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Burn.EIP1559.Sign() == 0 {
|
||||||
|
supply.Burn.EIP1559 = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Burn.Blob.Sign() == 0 {
|
||||||
|
supply.Burn.Blob = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Burn.Misc.Sign() == 0 {
|
||||||
|
supply.Burn.Misc = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if supply.Burn.EIP1559 == nil && supply.Burn.Blob == nil && supply.Burn.Misc == nil {
|
||||||
|
supply.Burn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _ := json.Marshal(supply)
|
||||||
|
if _, err := s.logger.Write(out); err != nil {
|
||||||
|
log.Warn("failed to write to supply tracer log file", "error", err)
|
||||||
|
}
|
||||||
|
if _, err := s.logger.Write([]byte{'\n'}); err != nil {
|
||||||
|
log.Warn("failed to write to supply tracer log file", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -74,6 +74,11 @@ func (f callFrame) failed() bool {
|
||||||
|
|
||||||
func (f *callFrame) processOutput(output []byte, err error, reverted bool) {
|
func (f *callFrame) processOutput(output []byte, err error, reverted bool) {
|
||||||
output = common.CopyBytes(output)
|
output = common.CopyBytes(output)
|
||||||
|
// Clear error if tx wasn't reverted. This happened
|
||||||
|
// for pre-homestead contract storage OOG.
|
||||||
|
if err != nil && !reverted {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.Output = output
|
f.Output = output
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -530,7 +530,7 @@ func makeDataset(size, ksize, vsize int, order bool) ([][]byte, [][]byte) {
|
||||||
vals = append(vals, randBytes(vsize))
|
vals = append(vals, randBytes(vsize))
|
||||||
}
|
}
|
||||||
if order {
|
if order {
|
||||||
slices.SortFunc(keys, func(a, b []byte) int { return bytes.Compare(a, b) })
|
slices.SortFunc(keys, bytes.Compare)
|
||||||
}
|
}
|
||||||
return keys, vals
|
return keys, vals
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ func (b *batch) Put(key, value []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete inserts the a key removal into the batch for later committing.
|
// Delete inserts the key removal into the batch for later committing.
|
||||||
func (b *batch) Delete(key []byte) error {
|
func (b *batch) Delete(key []byte) error {
|
||||||
b.b.Delete(key)
|
b.b.Delete(key)
|
||||||
b.size += len(key)
|
b.size += len(key)
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ func (b *batch) Put(key, value []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete inserts the a key removal into the batch for later committing.
|
// Delete inserts the key removal into the batch for later committing.
|
||||||
func (b *batch) Delete(key []byte) error {
|
func (b *batch) Delete(key []byte) error {
|
||||||
b.writes = append(b.writes, keyvalue{string(key), nil, true})
|
b.writes = append(b.writes, keyvalue{string(key), nil, true})
|
||||||
b.size += len(key)
|
b.size += len(key)
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool, e
|
||||||
|
|
||||||
// The default compaction concurrency(1 thread),
|
// The default compaction concurrency(1 thread),
|
||||||
// Here use all available CPUs for faster compaction.
|
// Here use all available CPUs for faster compaction.
|
||||||
MaxConcurrentCompactions: func() int { return runtime.NumCPU() },
|
MaxConcurrentCompactions: runtime.NumCPU,
|
||||||
|
|
||||||
// Per-level options. Options for at least one level must be specified. The
|
// Per-level options. Options for at least one level must be specified. The
|
||||||
// options for the last level are used for all subsequent levels.
|
// options for the last level are used for all subsequent levels.
|
||||||
|
|
@ -575,7 +575,7 @@ func (b *batch) Put(key, value []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete inserts the a key removal into the batch for later committing.
|
// Delete inserts the key removal into the batch for later committing.
|
||||||
func (b *batch) Delete(key []byte) error {
|
func (b *batch) Delete(key []byte) error {
|
||||||
b.b.Delete(key, nil)
|
b.b.Delete(key, nil)
|
||||||
b.size += len(key)
|
b.size += len(key)
|
||||||
|
|
|
||||||
16
go.mod
16
go.mod
|
|
@ -4,8 +4,8 @@ go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
|
||||||
github.com/Microsoft/go-winio v0.6.1
|
github.com/Microsoft/go-winio v0.6.2
|
||||||
github.com/VictoriaMetrics/fastcache v1.12.1
|
github.com/VictoriaMetrics/fastcache v1.12.2
|
||||||
github.com/aws/aws-sdk-go-v2 v1.21.2
|
github.com/aws/aws-sdk-go-v2 v1.21.2
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.18.45
|
github.com/aws/aws-sdk-go-v2/config v1.18.45
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43
|
github.com/aws/aws-sdk-go-v2/credentials v1.13.43
|
||||||
|
|
@ -18,12 +18,12 @@ require (
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c
|
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c
|
||||||
github.com/crate-crypto/go-kzg-4844 v1.0.0
|
github.com/crate-crypto/go-kzg-4844 v1.0.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0
|
github.com/deckarep/golang-set/v2 v2.6.0
|
||||||
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
|
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
|
||||||
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
|
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
|
||||||
github.com/ethereum/c-kzg-4844 v1.0.0
|
github.com/ethereum/c-kzg-4844 v1.0.0
|
||||||
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0
|
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/ferranbt/fastssz v0.1.2
|
github.com/ferranbt/fastssz v0.1.2
|
||||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
|
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
|
||||||
github.com/fjl/memsize v0.0.2
|
github.com/fjl/memsize v0.0.2
|
||||||
|
|
@ -51,7 +51,7 @@ require (
|
||||||
github.com/kilic/bls12-381 v0.1.0
|
github.com/kilic/bls12-381 v0.1.0
|
||||||
github.com/kylelemons/godebug v1.1.0
|
github.com/kylelemons/godebug v1.1.0
|
||||||
github.com/mattn/go-colorable v0.1.13
|
github.com/mattn/go-colorable v0.1.13
|
||||||
github.com/mattn/go-isatty v0.0.17
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
|
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
|
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
|
||||||
|
|
@ -69,11 +69,11 @@ require (
|
||||||
go.uber.org/automaxprocs v1.5.2
|
go.uber.org/automaxprocs v1.5.2
|
||||||
golang.org/x/crypto v0.22.0
|
golang.org/x/crypto v0.22.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.7.0
|
||||||
golang.org/x/sys v0.19.0
|
golang.org/x/sys v0.20.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
golang.org/x/tools v0.20.0
|
golang.org/x/tools v0.20.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ require (
|
||||||
github.com/aws/smithy-go v1.15.0 // indirect
|
github.com/aws/smithy-go v1.15.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.10.0 // indirect
|
github.com/bits-and-blooms/bitset v1.10.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cockroachdb/errors v1.11.1 // indirect
|
github.com/cockroachdb/errors v1.11.1 // indirect
|
||||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||||
|
|
|
||||||
39
go.sum
39
go.sum
|
|
@ -44,17 +44,15 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgx
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||||
github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40=
|
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||||
github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o=
|
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
|
@ -103,8 +101,9 @@ github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
|
||||||
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
|
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
|
@ -142,8 +141,8 @@ github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
|
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||||
|
|
@ -171,8 +170,8 @@ github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHE
|
||||||
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||||
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4=
|
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4=
|
||||||
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w=
|
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
|
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
|
||||||
github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
|
github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
|
||||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY=
|
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY=
|
||||||
|
|
@ -377,16 +376,14 @@ github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIG
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
|
|
@ -682,7 +679,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -690,11 +686,12 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
|
@ -851,8 +848,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -751,7 +751,7 @@ func TestEstimateGas(t *testing.T) {
|
||||||
From: &accounts[0].addr,
|
From: &accounts[0].addr,
|
||||||
To: &accounts[1].addr,
|
To: &accounts[1].addr,
|
||||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||||
BlobHashes: []common.Hash{common.Hash{0x01, 0x22}},
|
BlobHashes: []common.Hash{{0x01, 0x22}},
|
||||||
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
|
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
|
||||||
},
|
},
|
||||||
want: 21000,
|
want: 21000,
|
||||||
|
|
@ -939,7 +939,7 @@ func TestCall(t *testing.T) {
|
||||||
call: TransactionArgs{
|
call: TransactionArgs{
|
||||||
From: &accounts[1].addr,
|
From: &accounts[1].addr,
|
||||||
To: &randomAccounts[2].addr,
|
To: &randomAccounts[2].addr,
|
||||||
BlobHashes: []common.Hash{common.Hash{0x01, 0x22}},
|
BlobHashes: []common.Hash{{0x01, 0x22}},
|
||||||
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
|
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
|
||||||
},
|
},
|
||||||
overrides: StateOverride{
|
overrides: StateOverride{
|
||||||
|
|
@ -1063,7 +1063,7 @@ func TestSendBlobTransaction(t *testing.T) {
|
||||||
From: &b.acc.Address,
|
From: &b.acc.Address,
|
||||||
To: &to,
|
To: &to,
|
||||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||||
BlobHashes: []common.Hash{common.Hash{0x01, 0x22}},
|
BlobHashes: []common.Hash{{0x01, 0x22}},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to fill tx defaults: %v\n", err)
|
t.Fatalf("failed to fill tx defaults: %v\n", err)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func TestLoggingWithVmodule(t *testing.T) {
|
||||||
logger.Trace("a message", "foo", "bar")
|
logger.Trace("a message", "foo", "bar")
|
||||||
have := out.String()
|
have := out.String()
|
||||||
// The timestamp is locale-dependent, so we want to trim that off
|
// The timestamp is locale-dependent, so we want to trim that off
|
||||||
// "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
|
// "INFO [01-01|00:00:00.000] a message ..." -> "a message..."
|
||||||
have = strings.Split(have, "]")[1]
|
have = strings.Split(have, "]")[1]
|
||||||
want := " a message foo=bar\n"
|
want := " a message foo=bar\n"
|
||||||
if have != want {
|
if have != want {
|
||||||
|
|
@ -42,7 +42,7 @@ func TestTerminalHandlerWithAttrs(t *testing.T) {
|
||||||
logger.Trace("a message", "foo", "bar")
|
logger.Trace("a message", "foo", "bar")
|
||||||
have := out.String()
|
have := out.String()
|
||||||
// The timestamp is locale-dependent, so we want to trim that off
|
// The timestamp is locale-dependent, so we want to trim that off
|
||||||
// "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
|
// "INFO [01-01|00:00:00.000] a message ..." -> "a message..."
|
||||||
have = strings.Split(have, "]")[1]
|
have = strings.Split(have, "]")[1]
|
||||||
want := " a message baz=bat foo=bar\n"
|
want := " a message baz=bat foo=bar\n"
|
||||||
if have != want {
|
if have != want {
|
||||||
|
|
@ -97,7 +97,7 @@ func benchmarkLogger(b *testing.B, l Logger) {
|
||||||
tt = time.Now()
|
tt = time.Now()
|
||||||
bigint = big.NewInt(100)
|
bigint = big.NewInt(100)
|
||||||
nilbig *big.Int
|
nilbig *big.Int
|
||||||
err = errors.New("Oh nooes it's crap")
|
err = errors.New("oh nooes it's crap")
|
||||||
)
|
)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
@ -126,7 +126,7 @@ func TestLoggerOutput(t *testing.T) {
|
||||||
tt = time.Time{}
|
tt = time.Time{}
|
||||||
bigint = big.NewInt(100)
|
bigint = big.NewInt(100)
|
||||||
nilbig *big.Int
|
nilbig *big.Int
|
||||||
err = errors.New("Oh nooes it's crap")
|
err = errors.New("oh nooes it's crap")
|
||||||
smallUint = uint256.NewInt(500_000)
|
smallUint = uint256.NewInt(500_000)
|
||||||
bigUint = &uint256.Int{0xff, 0xff, 0xff, 0xff}
|
bigUint = &uint256.Int{0xff, 0xff, 0xff, 0xff}
|
||||||
)
|
)
|
||||||
|
|
@ -150,7 +150,7 @@ func TestLoggerOutput(t *testing.T) {
|
||||||
|
|
||||||
have := out.String()
|
have := out.String()
|
||||||
t.Logf("output %v", out.String())
|
t.Logf("output %v", out.String())
|
||||||
want := `INFO [11-07|19:14:33.821] This is a message foo=123 bytes="[0 0 0 0 0 0 0 0 0 0]" bonk="a string with text" time=0001-01-01T00:00:00+0000 bigint=100 nilbig=<nil> err="Oh nooes it's crap" struct="{A:Foo B:12}" struct="{A:Foo\nLinebreak B:122}" ptrstruct="&{A:Foo B:12}" smalluint=500,000 bigUint=1,600,660,942,523,603,594,864,898,306,482,794,244,293,965,082,972,225,630,372,095
|
want := `INFO [11-07|19:14:33.821] This is a message foo=123 bytes="[0 0 0 0 0 0 0 0 0 0]" bonk="a string with text" time=0001-01-01T00:00:00+0000 bigint=100 nilbig=<nil> err="oh nooes it's crap" struct="{A:Foo B:12}" struct="{A:Foo\nLinebreak B:122}" ptrstruct="&{A:Foo B:12}" smalluint=500,000 bigUint=1,600,660,942,523,603,594,864,898,306,482,794,244,293,965,082,972,225,630,372,095
|
||||||
`
|
`
|
||||||
if !bytes.Equal([]byte(have)[25:], []byte(want)[25:]) {
|
if !bytes.Equal([]byte(have)[25:], []byte(want)[25:]) {
|
||||||
t.Errorf("Error\nhave: %q\nwant: %q", have, want)
|
t.Errorf("Error\nhave: %q\nwant: %q", have, want)
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,18 @@ var (
|
||||||
gcStats debug.GCStats
|
gcStats debug.GCStats
|
||||||
)
|
)
|
||||||
|
|
||||||
// Capture new values for the Go garbage collector statistics exported in
|
// CaptureDebugGCStats captures new values for the Go garbage collector statistics
|
||||||
// debug.GCStats. This is designed to be called as a goroutine.
|
// exported in debug.GCStats. This is designed to be called as a goroutine.
|
||||||
func CaptureDebugGCStats(r Registry, d time.Duration) {
|
func CaptureDebugGCStats(r Registry, d time.Duration) {
|
||||||
for range time.Tick(d) {
|
for range time.Tick(d) {
|
||||||
CaptureDebugGCStatsOnce(r)
|
CaptureDebugGCStatsOnce(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture new values for the Go garbage collector statistics exported in
|
// CaptureDebugGCStatsOnce captures new values for the Go garbage collector
|
||||||
// debug.GCStats. This is designed to be called in a background goroutine.
|
// statistics exported in debug.GCStats. This is designed to be called in
|
||||||
// Giving a registry which has not been given to RegisterDebugGCStats will
|
// a background goroutine. Giving a registry which has not been given to
|
||||||
// panic.
|
// RegisterDebugGCStats will panic.
|
||||||
//
|
//
|
||||||
// Be careful (but much less so) with this because debug.ReadGCStats calls
|
// Be careful (but much less so) with this because debug.ReadGCStats calls
|
||||||
// the C function runtime·lock(runtime·mheap) which, while not a stop-the-world
|
// the C function runtime·lock(runtime·mheap) which, while not a stop-the-world
|
||||||
|
|
@ -50,9 +50,9 @@ func CaptureDebugGCStatsOnce(r Registry) {
|
||||||
debugMetrics.GCStats.PauseTotal.Update(int64(gcStats.PauseTotal))
|
debugMetrics.GCStats.PauseTotal.Update(int64(gcStats.PauseTotal))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register metrics for the Go garbage collector statistics exported in
|
// RegisterDebugGCStats registers metrics for the Go garbage collector statistics
|
||||||
// debug.GCStats. The metrics are named by their fully-qualified Go symbols,
|
// exported in debug.GCStats. The metrics are named by their fully-qualified Go
|
||||||
// i.e. debug.GCStats.PauseTotal.
|
// symbols, i.e. debug.GCStats.PauseTotal.
|
||||||
func RegisterDebugGCStats(r Registry) {
|
func RegisterDebugGCStats(r Registry) {
|
||||||
debugMetrics.GCStats.LastGC = NewGauge()
|
debugMetrics.GCStats.LastGC = NewGauge()
|
||||||
debugMetrics.GCStats.NumGC = NewGauge()
|
debugMetrics.GCStats.NumGC = NewGauge()
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ type Config struct {
|
||||||
// DefaultConfig contains default settings for miner.
|
// DefaultConfig contains default settings for miner.
|
||||||
var DefaultConfig = Config{
|
var DefaultConfig = Config{
|
||||||
GasCeil: 30_000_000,
|
GasCeil: 30_000_000,
|
||||||
GasPrice: big.NewInt(params.GWei),
|
GasPrice: big.NewInt(params.GWei / 1000),
|
||||||
|
|
||||||
// The default recommit time is chosen as two seconds since
|
// The default recommit time is chosen as two seconds since
|
||||||
// consensus-layer usually will wait a half slot of time(6s)
|
// consensus-layer usually will wait a half slot of time(6s)
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ func (b *testWorkerBackend) TxPool() *txpool.TxPool { return b.txPool }
|
||||||
|
|
||||||
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*Miner, *testWorkerBackend) {
|
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*Miner, *testWorkerBackend) {
|
||||||
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
|
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
|
||||||
backend.txPool.Add(pendingTxs, true, false)
|
backend.txPool.Add(pendingTxs, true, true)
|
||||||
w := New(backend, testConfig, engine)
|
w := New(backend, testConfig, engine)
|
||||||
return w, backend
|
return w, backend
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -34,8 +35,8 @@ import (
|
||||||
|
|
||||||
// UDPConn is a network connection on which discovery can operate.
|
// UDPConn is a network connection on which discovery can operate.
|
||||||
type UDPConn interface {
|
type UDPConn interface {
|
||||||
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
|
ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error)
|
||||||
WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
|
WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (n int, err error)
|
||||||
Close() error
|
Close() error
|
||||||
LocalAddr() net.Addr
|
LocalAddr() net.Addr
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +95,7 @@ func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
|
||||||
// channel if configured.
|
// channel if configured.
|
||||||
type ReadPacket struct {
|
type ReadPacket struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
Addr *net.UDPAddr
|
Addr netip.AddrPort
|
||||||
}
|
}
|
||||||
|
|
||||||
type randomSource interface {
|
type randomSource interface {
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,16 @@ import (
|
||||||
// not need to be an actual node identifier.
|
// not need to be an actual node identifier.
|
||||||
type lookup struct {
|
type lookup struct {
|
||||||
tab *Table
|
tab *Table
|
||||||
queryfunc func(*node) ([]*node, error)
|
queryfunc queryFunc
|
||||||
replyCh chan []*node
|
replyCh chan []*enode.Node
|
||||||
cancelCh <-chan struct{}
|
cancelCh <-chan struct{}
|
||||||
asked, seen map[enode.ID]bool
|
asked, seen map[enode.ID]bool
|
||||||
result nodesByDistance
|
result nodesByDistance
|
||||||
replyBuffer []*node
|
replyBuffer []*enode.Node
|
||||||
queries int
|
queries int
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryFunc func(*node) ([]*node, error)
|
type queryFunc func(*enode.Node) ([]*enode.Node, error)
|
||||||
|
|
||||||
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
|
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
|
||||||
it := &lookup{
|
it := &lookup{
|
||||||
|
|
@ -47,7 +47,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
|
||||||
asked: make(map[enode.ID]bool),
|
asked: make(map[enode.ID]bool),
|
||||||
seen: make(map[enode.ID]bool),
|
seen: make(map[enode.ID]bool),
|
||||||
result: nodesByDistance{target: target},
|
result: nodesByDistance{target: target},
|
||||||
replyCh: make(chan []*node, alpha),
|
replyCh: make(chan []*enode.Node, alpha),
|
||||||
cancelCh: ctx.Done(),
|
cancelCh: ctx.Done(),
|
||||||
queries: -1,
|
queries: -1,
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
|
||||||
func (it *lookup) run() []*enode.Node {
|
func (it *lookup) run() []*enode.Node {
|
||||||
for it.advance() {
|
for it.advance() {
|
||||||
}
|
}
|
||||||
return unwrapNodes(it.result.entries)
|
return it.result.entries
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance advances the lookup until any new nodes have been found.
|
// advance advances the lookup until any new nodes have been found.
|
||||||
|
|
@ -139,7 +139,7 @@ func (it *lookup) slowdown() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *lookup) query(n *node, reply chan<- []*node) {
|
func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) {
|
||||||
r, err := it.queryfunc(n)
|
r, err := it.queryfunc(n)
|
||||||
if !errors.Is(err, errClosed) { // avoid recording failures on shutdown.
|
if !errors.Is(err, errClosed) { // avoid recording failures on shutdown.
|
||||||
success := len(r) > 0
|
success := len(r) > 0
|
||||||
|
|
@ -154,7 +154,7 @@ func (it *lookup) query(n *node, reply chan<- []*node) {
|
||||||
// lookupIterator performs lookup operations and iterates over all seen nodes.
|
// lookupIterator performs lookup operations and iterates over all seen nodes.
|
||||||
// When a lookup finishes, a new one is created through nextLookup.
|
// When a lookup finishes, a new one is created through nextLookup.
|
||||||
type lookupIterator struct {
|
type lookupIterator struct {
|
||||||
buffer []*node
|
buffer []*enode.Node
|
||||||
nextLookup lookupFunc
|
nextLookup lookupFunc
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel func()
|
cancel func()
|
||||||
|
|
@ -173,7 +173,7 @@ func (it *lookupIterator) Node() *enode.Node {
|
||||||
if len(it.buffer) == 0 {
|
if len(it.buffer) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return unwrapNode(it.buffer[0])
|
return it.buffer[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next moves to the next node.
|
// Next moves to the next node.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package discover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
)
|
)
|
||||||
|
|
@ -58,16 +58,16 @@ func newMeteredConn(conn UDPConn) UDPConn {
|
||||||
return &meteredUdpConn{UDPConn: conn}
|
return &meteredUdpConn{UDPConn: conn}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFromUDP delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
|
// ReadFromUDPAddrPort delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
|
||||||
func (c *meteredUdpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
func (c *meteredUdpConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||||
n, addr, err = c.UDPConn.ReadFromUDP(b)
|
n, addr, err = c.UDPConn.ReadFromUDPAddrPort(b)
|
||||||
ingressTrafficMeter.Mark(int64(n))
|
ingressTrafficMeter.Mark(int64(n))
|
||||||
return n, addr, err
|
return n, addr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
|
// WriteToUDP delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
|
||||||
func (c *meteredUdpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
|
func (c *meteredUdpConn) WriteToUDP(b []byte, addr netip.AddrPort) (n int, err error) {
|
||||||
n, err = c.UDPConn.WriteToUDP(b, addr)
|
n, err = c.UDPConn.WriteToUDPAddrPort(b, addr)
|
||||||
egressTrafficMeter.Mark(int64(n))
|
egressTrafficMeter.Mark(int64(n))
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"slices"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -37,10 +38,10 @@ type BucketNode struct {
|
||||||
Live bool `json:"live"`
|
Live bool `json:"live"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// node represents a host on the network.
|
// tableNode is an entry in Table.
|
||||||
// The fields of Node may not be modified.
|
type tableNode struct {
|
||||||
type node struct {
|
|
||||||
*enode.Node
|
*enode.Node
|
||||||
|
revalList *revalidationList
|
||||||
addedToTable time.Time // first time node was added to bucket or replacement list
|
addedToTable time.Time // first time node was added to bucket or replacement list
|
||||||
addedToBucket time.Time // time it was added in the actual bucket
|
addedToBucket time.Time // time it was added in the actual bucket
|
||||||
livenessChecks uint // how often liveness was checked
|
livenessChecks uint // how often liveness was checked
|
||||||
|
|
@ -74,34 +75,59 @@ func (e encPubkey) id() enode.ID {
|
||||||
return enode.ID(crypto.Keccak256Hash(e[:]))
|
return enode.ID(crypto.Keccak256Hash(e[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapNode(n *enode.Node) *node {
|
func unwrapNodes(ns []*tableNode) []*enode.Node {
|
||||||
return &node{Node: n}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapNodes(ns []*enode.Node) []*node {
|
|
||||||
result := make([]*node, len(ns))
|
|
||||||
for i, n := range ns {
|
|
||||||
result[i] = wrapNode(n)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func unwrapNode(n *node) *enode.Node {
|
|
||||||
return n.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func unwrapNodes(ns []*node) []*enode.Node {
|
|
||||||
result := make([]*enode.Node, len(ns))
|
result := make([]*enode.Node, len(ns))
|
||||||
for i, n := range ns {
|
for i, n := range ns {
|
||||||
result[i] = unwrapNode(n)
|
result[i] = n.Node
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) addr() *net.UDPAddr {
|
func (n *tableNode) String() string {
|
||||||
return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *node) String() string {
|
|
||||||
return n.Node.String()
|
return n.Node.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodesByDistance is a list of nodes, ordered by distance to target.
|
||||||
|
type nodesByDistance struct {
|
||||||
|
entries []*enode.Node
|
||||||
|
target enode.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// push adds the given node to the list, keeping the total size below maxElems.
|
||||||
|
func (h *nodesByDistance) push(n *enode.Node, maxElems int) {
|
||||||
|
ix := sort.Search(len(h.entries), func(i int) bool {
|
||||||
|
return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
end := len(h.entries)
|
||||||
|
if len(h.entries) < maxElems {
|
||||||
|
h.entries = append(h.entries, n)
|
||||||
|
}
|
||||||
|
if ix < end {
|
||||||
|
// Slide existing entries down to make room.
|
||||||
|
// This will overwrite the entry we just appended.
|
||||||
|
copy(h.entries[ix+1:], h.entries[ix:])
|
||||||
|
h.entries[ix] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeType interface {
|
||||||
|
ID() enode.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsID reports whether ns contains a node with the given ID.
|
||||||
|
func containsID[N nodeType](ns []N, id enode.ID) bool {
|
||||||
|
for _, n := range ns {
|
||||||
|
if n.ID() == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteNode removes a node from the list.
|
||||||
|
func deleteNode[N nodeType](list []N, id enode.ID) []N {
|
||||||
|
return slices.DeleteFunc(list, func(n N) bool {
|
||||||
|
return n.ID() == id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -65,7 +64,7 @@ const (
|
||||||
type Table struct {
|
type Table struct {
|
||||||
mutex sync.Mutex // protects buckets, bucket content, nursery, rand
|
mutex sync.Mutex // protects buckets, bucket content, nursery, rand
|
||||||
buckets [nBuckets]*bucket // index of known nodes by distance
|
buckets [nBuckets]*bucket // index of known nodes by distance
|
||||||
nursery []*node // bootstrap nodes
|
nursery []*enode.Node // bootstrap nodes
|
||||||
rand reseedingRandom // source of randomness, periodically reseeded
|
rand reseedingRandom // source of randomness, periodically reseeded
|
||||||
ips netutil.DistinctNetSet
|
ips netutil.DistinctNetSet
|
||||||
revalidation tableRevalidation
|
revalidation tableRevalidation
|
||||||
|
|
@ -85,8 +84,8 @@ type Table struct {
|
||||||
closeReq chan struct{}
|
closeReq chan struct{}
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
|
|
||||||
nodeAddedHook func(*bucket, *node)
|
nodeAddedHook func(*bucket, *tableNode)
|
||||||
nodeRemovedHook func(*bucket, *node)
|
nodeRemovedHook func(*bucket, *tableNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// transport is implemented by the UDP transports.
|
// transport is implemented by the UDP transports.
|
||||||
|
|
@ -101,20 +100,21 @@ type transport interface {
|
||||||
// bucket contains nodes, ordered by their last activity. the entry
|
// bucket contains nodes, ordered by their last activity. the entry
|
||||||
// that was most recently active is the first element in entries.
|
// that was most recently active is the first element in entries.
|
||||||
type bucket struct {
|
type bucket struct {
|
||||||
entries []*node // live entries, sorted by time of last contact
|
entries []*tableNode // live entries, sorted by time of last contact
|
||||||
replacements []*node // recently seen nodes to be used if revalidation fails
|
replacements []*tableNode // recently seen nodes to be used if revalidation fails
|
||||||
ips netutil.DistinctNetSet
|
ips netutil.DistinctNetSet
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
type addNodeOp struct {
|
type addNodeOp struct {
|
||||||
node *node
|
node *enode.Node
|
||||||
isInbound bool
|
isInbound bool
|
||||||
|
forceSetLive bool // for tests
|
||||||
}
|
}
|
||||||
|
|
||||||
type trackRequestOp struct {
|
type trackRequestOp struct {
|
||||||
node *node
|
node *enode.Node
|
||||||
foundNodes []*node
|
foundNodes []*enode.Node
|
||||||
success bool
|
success bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +186,7 @@ func (tab *Table) getNode(id enode.ID) *enode.Node {
|
||||||
b := tab.bucket(id)
|
b := tab.bucket(id)
|
||||||
for _, e := range b.entries {
|
for _, e := range b.entries {
|
||||||
if e.ID() == id {
|
if e.ID() == id {
|
||||||
return unwrapNode(e)
|
return e.Node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -202,7 +202,7 @@ func (tab *Table) close() {
|
||||||
// are used to connect to the network if the table is empty and there
|
// are used to connect to the network if the table is empty and there
|
||||||
// are no known nodes in the database.
|
// are no known nodes in the database.
|
||||||
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
||||||
nursery := make([]*node, 0, len(nodes))
|
nursery := make([]*enode.Node, 0, len(nodes))
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
if err := n.ValidateComplete(); err != nil {
|
if err := n.ValidateComplete(); err != nil {
|
||||||
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
||||||
|
|
@ -211,7 +211,7 @@ func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
||||||
tab.log.Error("Bootstrap node filtered by netrestrict", "id", n.ID(), "ip", n.IP())
|
tab.log.Error("Bootstrap node filtered by netrestrict", "id", n.ID(), "ip", n.IP())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nursery = append(nursery, wrapNode(n))
|
nursery = append(nursery, n)
|
||||||
}
|
}
|
||||||
tab.nursery = nursery
|
tab.nursery = nursery
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -255,9 +255,9 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *
|
||||||
liveNodes := &nodesByDistance{target: target}
|
liveNodes := &nodesByDistance{target: target}
|
||||||
for _, b := range &tab.buckets {
|
for _, b := range &tab.buckets {
|
||||||
for _, n := range b.entries {
|
for _, n := range b.entries {
|
||||||
nodes.push(n, nresults)
|
nodes.push(n.Node, nresults)
|
||||||
if preferLive && n.isValidatedLive {
|
if preferLive && n.isValidatedLive {
|
||||||
liveNodes.push(n, nresults)
|
liveNodes.push(n.Node, nresults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,8 +309,8 @@ func (tab *Table) len() (n int) {
|
||||||
// list.
|
// list.
|
||||||
//
|
//
|
||||||
// The caller must not hold tab.mutex.
|
// The caller must not hold tab.mutex.
|
||||||
func (tab *Table) addFoundNode(n *node) bool {
|
func (tab *Table) addFoundNode(n *enode.Node, forceSetLive bool) bool {
|
||||||
op := addNodeOp{node: n, isInbound: false}
|
op := addNodeOp{node: n, isInbound: false, forceSetLive: forceSetLive}
|
||||||
select {
|
select {
|
||||||
case tab.addNodeCh <- op:
|
case tab.addNodeCh <- op:
|
||||||
return <-tab.addNodeHandled
|
return <-tab.addNodeHandled
|
||||||
|
|
@ -327,7 +327,7 @@ func (tab *Table) addFoundNode(n *node) bool {
|
||||||
// repeatedly.
|
// repeatedly.
|
||||||
//
|
//
|
||||||
// The caller must not hold tab.mutex.
|
// The caller must not hold tab.mutex.
|
||||||
func (tab *Table) addInboundNode(n *node) bool {
|
func (tab *Table) addInboundNode(n *enode.Node) bool {
|
||||||
op := addNodeOp{node: n, isInbound: true}
|
op := addNodeOp{node: n, isInbound: true}
|
||||||
select {
|
select {
|
||||||
case tab.addNodeCh <- op:
|
case tab.addNodeCh <- op:
|
||||||
|
|
@ -337,7 +337,7 @@ func (tab *Table) addInboundNode(n *node) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tab *Table) trackRequest(n *node, success bool, foundNodes []*node) {
|
func (tab *Table) trackRequest(n *enode.Node, success bool, foundNodes []*enode.Node) {
|
||||||
op := trackRequestOp{n, foundNodes, success}
|
op := trackRequestOp{n, foundNodes, success}
|
||||||
select {
|
select {
|
||||||
case tab.trackRequestCh <- op:
|
case tab.trackRequestCh <- op:
|
||||||
|
|
@ -443,13 +443,14 @@ func (tab *Table) doRefresh(done chan struct{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tab *Table) loadSeedNodes() {
|
func (tab *Table) loadSeedNodes() {
|
||||||
seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
|
seeds := tab.db.QuerySeeds(seedCount, seedMaxAge)
|
||||||
seeds = append(seeds, tab.nursery...)
|
seeds = append(seeds, tab.nursery...)
|
||||||
for i := range seeds {
|
for i := range seeds {
|
||||||
seed := seeds[i]
|
seed := seeds[i]
|
||||||
if tab.log.Enabled(context.Background(), log.LevelTrace) {
|
if tab.log.Enabled(context.Background(), log.LevelTrace) {
|
||||||
age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP()))
|
age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP()))
|
||||||
tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
|
addr, _ := seed.UDPEndpoint()
|
||||||
|
tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", addr, "age", age)
|
||||||
}
|
}
|
||||||
tab.handleAddNode(addNodeOp{node: seed, isInbound: false})
|
tab.handleAddNode(addNodeOp{node: seed, isInbound: false})
|
||||||
}
|
}
|
||||||
|
|
@ -513,8 +514,9 @@ func (tab *Table) handleAddNode(req addNodeOp) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
b := tab.bucket(req.node.ID())
|
b := tab.bucket(req.node.ID())
|
||||||
if tab.bumpInBucket(b, req.node.Node) {
|
n, _ := tab.bumpInBucket(b, req.node, req.isInbound)
|
||||||
// Already in bucket, update record.
|
if n != nil {
|
||||||
|
// Already in bucket.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(b.entries) >= bucketSize {
|
if len(b.entries) >= bucketSize {
|
||||||
|
|
@ -528,15 +530,20 @@ func (tab *Table) handleAddNode(req addNodeOp) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to bucket.
|
// Add to bucket.
|
||||||
b.entries = append(b.entries, req.node)
|
wn := &tableNode{Node: req.node}
|
||||||
b.replacements = deleteNode(b.replacements, req.node)
|
if req.forceSetLive {
|
||||||
tab.nodeAdded(b, req.node)
|
wn.livenessChecks = 1
|
||||||
|
wn.isValidatedLive = true
|
||||||
|
}
|
||||||
|
b.entries = append(b.entries, wn)
|
||||||
|
b.replacements = deleteNode(b.replacements, wn.ID())
|
||||||
|
tab.nodeAdded(b, wn)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// addReplacement adds n to the replacement cache of bucket b.
|
// addReplacement adds n to the replacement cache of bucket b.
|
||||||
func (tab *Table) addReplacement(b *bucket, n *node) {
|
func (tab *Table) addReplacement(b *bucket, n *enode.Node) {
|
||||||
if contains(b.replacements, n.ID()) {
|
if containsID(b.replacements, n.ID()) {
|
||||||
// TODO: update ENR
|
// TODO: update ENR
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -544,15 +551,15 @@ func (tab *Table) addReplacement(b *bucket, n *node) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n.addedToTable = time.Now()
|
wn := &tableNode{Node: n, addedToTable: time.Now()}
|
||||||
var removed *node
|
var removed *tableNode
|
||||||
b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
|
b.replacements, removed = pushNode(b.replacements, wn, maxReplacements)
|
||||||
if removed != nil {
|
if removed != nil {
|
||||||
tab.removeIP(b, removed.IP())
|
tab.removeIP(b, removed.IP())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tab *Table) nodeAdded(b *bucket, n *node) {
|
func (tab *Table) nodeAdded(b *bucket, n *tableNode) {
|
||||||
if n.addedToTable == (time.Time{}) {
|
if n.addedToTable == (time.Time{}) {
|
||||||
n.addedToTable = time.Now()
|
n.addedToTable = time.Now()
|
||||||
}
|
}
|
||||||
|
|
@ -566,7 +573,7 @@ func (tab *Table) nodeAdded(b *bucket, n *node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tab *Table) nodeRemoved(b *bucket, n *node) {
|
func (tab *Table) nodeRemoved(b *bucket, n *tableNode) {
|
||||||
tab.revalidation.nodeRemoved(n)
|
tab.revalidation.nodeRemoved(n)
|
||||||
if tab.nodeRemovedHook != nil {
|
if tab.nodeRemovedHook != nil {
|
||||||
tab.nodeRemovedHook(b, n)
|
tab.nodeRemovedHook(b, n)
|
||||||
|
|
@ -578,8 +585,8 @@ func (tab *Table) nodeRemoved(b *bucket, n *node) {
|
||||||
|
|
||||||
// deleteInBucket removes node n from the table.
|
// deleteInBucket removes node n from the table.
|
||||||
// If there are replacement nodes in the bucket, the node is replaced.
|
// If there are replacement nodes in the bucket, the node is replaced.
|
||||||
func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *node {
|
func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *tableNode {
|
||||||
index := slices.IndexFunc(b.entries, func(e *node) bool { return e.ID() == id })
|
index := slices.IndexFunc(b.entries, func(e *tableNode) bool { return e.ID() == id })
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
// Entry has been removed already.
|
// Entry has been removed already.
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -605,26 +612,45 @@ func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *node {
|
||||||
return rep
|
return rep
|
||||||
}
|
}
|
||||||
|
|
||||||
// bumpInBucket updates the node record of n in the bucket.
|
// bumpInBucket updates a node record if it exists in the bucket.
|
||||||
func (tab *Table) bumpInBucket(b *bucket, newRecord *enode.Node) bool {
|
// The second return value reports whether the node's endpoint (IP/port) was updated.
|
||||||
i := slices.IndexFunc(b.entries, func(elem *node) bool {
|
func (tab *Table) bumpInBucket(b *bucket, newRecord *enode.Node, isInbound bool) (n *tableNode, endpointChanged bool) {
|
||||||
|
i := slices.IndexFunc(b.entries, func(elem *tableNode) bool {
|
||||||
return elem.ID() == newRecord.ID()
|
return elem.ID() == newRecord.ID()
|
||||||
})
|
})
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
return false
|
return nil, false // not in bucket
|
||||||
|
}
|
||||||
|
n = b.entries[i]
|
||||||
|
|
||||||
|
// For inbound updates (from the node itself) we accept any change, even if it sets
|
||||||
|
// back the sequence number. For found nodes (!isInbound), seq has to advance. Note
|
||||||
|
// this check also ensures found discv4 nodes (which always have seq=0) can't be
|
||||||
|
// updated.
|
||||||
|
if newRecord.Seq() <= n.Seq() && !isInbound {
|
||||||
|
return n, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !newRecord.IP().Equal(b.entries[i].IP()) {
|
// Check endpoint update against IP limits.
|
||||||
// Endpoint has changed, ensure that the new IP fits into table limits.
|
ipchanged := newRecord.IPAddr() != n.IPAddr()
|
||||||
tab.removeIP(b, b.entries[i].IP())
|
portchanged := newRecord.UDP() != n.UDP()
|
||||||
|
if ipchanged {
|
||||||
|
tab.removeIP(b, n.IP())
|
||||||
if !tab.addIP(b, newRecord.IP()) {
|
if !tab.addIP(b, newRecord.IP()) {
|
||||||
// It doesn't, put the previous one back.
|
// It doesn't fit with the limit, put the previous record back.
|
||||||
tab.addIP(b, b.entries[i].IP())
|
tab.addIP(b, n.IP())
|
||||||
return false
|
return n, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.entries[i].Node = newRecord
|
|
||||||
return true
|
// Apply update.
|
||||||
|
n.Node = newRecord
|
||||||
|
if ipchanged || portchanged {
|
||||||
|
// Ensure node is revalidated quickly for endpoint changes.
|
||||||
|
tab.revalidation.nodeEndpointChanged(tab, n)
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
return n, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tab *Table) handleTrackRequest(op trackRequestOp) {
|
func (tab *Table) handleTrackRequest(op trackRequestOp) {
|
||||||
|
|
@ -652,21 +678,12 @@ func (tab *Table) handleTrackRequest(op trackRequestOp) {
|
||||||
|
|
||||||
// Add found nodes.
|
// Add found nodes.
|
||||||
for _, n := range op.foundNodes {
|
for _, n := range op.foundNodes {
|
||||||
tab.handleAddNode(addNodeOp{n, false})
|
tab.handleAddNode(addNodeOp{n, false, false})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(ns []*node, id enode.ID) bool {
|
|
||||||
for _, n := range ns {
|
|
||||||
if n.ID() == id {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushNode adds n to the front of list, keeping at most max items.
|
// pushNode adds n to the front of list, keeping at most max items.
|
||||||
func pushNode(list []*node, n *node, max int) ([]*node, *node) {
|
func pushNode(list []*tableNode, n *tableNode, max int) ([]*tableNode, *tableNode) {
|
||||||
if len(list) < max {
|
if len(list) < max {
|
||||||
list = append(list, nil)
|
list = append(list, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -675,37 +692,3 @@ func pushNode(list []*node, n *node, max int) ([]*node, *node) {
|
||||||
list[0] = n
|
list[0] = n
|
||||||
return list, removed
|
return list, removed
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteNode removes n from list.
|
|
||||||
func deleteNode(list []*node, n *node) []*node {
|
|
||||||
for i := range list {
|
|
||||||
if list[i].ID() == n.ID() {
|
|
||||||
return append(list[:i], list[i+1:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodesByDistance is a list of nodes, ordered by distance to target.
|
|
||||||
type nodesByDistance struct {
|
|
||||||
entries []*node
|
|
||||||
target enode.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// push adds the given node to the list, keeping the total size below maxElems.
|
|
||||||
func (h *nodesByDistance) push(n *node, maxElems int) {
|
|
||||||
ix := sort.Search(len(h.entries), func(i int) bool {
|
|
||||||
return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
end := len(h.entries)
|
|
||||||
if len(h.entries) < maxElems {
|
|
||||||
h.entries = append(h.entries, n)
|
|
||||||
}
|
|
||||||
if ix < end {
|
|
||||||
// Slide existing entries down to make room.
|
|
||||||
// This will overwrite the entry we just appended.
|
|
||||||
copy(h.entries[ix+1:], h.entries[ix:])
|
|
||||||
h.entries[ix] = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import (
|
||||||
|
|
||||||
const never = mclock.AbsTime(math.MaxInt64)
|
const never = mclock.AbsTime(math.MaxInt64)
|
||||||
|
|
||||||
|
const slowRevalidationFactor = 3
|
||||||
|
|
||||||
// tableRevalidation implements the node revalidation process.
|
// tableRevalidation implements the node revalidation process.
|
||||||
// It tracks all nodes contained in Table, and schedules sending PING to them.
|
// It tracks all nodes contained in Table, and schedules sending PING to them.
|
||||||
type tableRevalidation struct {
|
type tableRevalidation struct {
|
||||||
|
|
@ -37,9 +39,8 @@ type tableRevalidation struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type revalidationResponse struct {
|
type revalidationResponse struct {
|
||||||
n *node
|
n *tableNode
|
||||||
newRecord *enode.Node
|
newRecord *enode.Node
|
||||||
list *revalidationList
|
|
||||||
didRespond bool
|
didRespond bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,20 +50,27 @@ func (tr *tableRevalidation) init(cfg *Config) {
|
||||||
tr.fast.interval = cfg.PingInterval
|
tr.fast.interval = cfg.PingInterval
|
||||||
tr.fast.name = "fast"
|
tr.fast.name = "fast"
|
||||||
tr.slow.nextTime = never
|
tr.slow.nextTime = never
|
||||||
tr.slow.interval = cfg.PingInterval * 3
|
tr.slow.interval = cfg.PingInterval * slowRevalidationFactor
|
||||||
tr.slow.name = "slow"
|
tr.slow.name = "slow"
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeAdded is called when the table receives a new node.
|
// nodeAdded is called when the table receives a new node.
|
||||||
func (tr *tableRevalidation) nodeAdded(tab *Table, n *node) {
|
func (tr *tableRevalidation) nodeAdded(tab *Table, n *tableNode) {
|
||||||
tr.fast.push(n, tab.cfg.Clock.Now(), &tab.rand)
|
tr.fast.push(n, tab.cfg.Clock.Now(), &tab.rand)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeRemoved is called when a node was removed from the table.
|
// nodeRemoved is called when a node was removed from the table.
|
||||||
func (tr *tableRevalidation) nodeRemoved(n *node) {
|
func (tr *tableRevalidation) nodeRemoved(n *tableNode) {
|
||||||
if !tr.fast.remove(n) {
|
if n.revalList == nil {
|
||||||
tr.slow.remove(n)
|
panic(fmt.Errorf("removed node %v has nil revalList", n.ID()))
|
||||||
}
|
}
|
||||||
|
n.revalList.remove(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeEndpointChanged is called when a change in IP or port is detected.
|
||||||
|
func (tr *tableRevalidation) nodeEndpointChanged(tab *Table, n *tableNode) {
|
||||||
|
n.isValidatedLive = false
|
||||||
|
tr.moveToList(&tr.fast, n, tab.cfg.Clock.Now(), &tab.rand)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run performs node revalidation.
|
// run performs node revalidation.
|
||||||
|
|
@ -70,11 +78,11 @@ func (tr *tableRevalidation) nodeRemoved(n *node) {
|
||||||
// to schedule a timer. However, run can be called at any time.
|
// to schedule a timer. However, run can be called at any time.
|
||||||
func (tr *tableRevalidation) run(tab *Table, now mclock.AbsTime) (nextTime mclock.AbsTime) {
|
func (tr *tableRevalidation) run(tab *Table, now mclock.AbsTime) (nextTime mclock.AbsTime) {
|
||||||
if n := tr.fast.get(now, &tab.rand, tr.activeReq); n != nil {
|
if n := tr.fast.get(now, &tab.rand, tr.activeReq); n != nil {
|
||||||
tr.startRequest(tab, &tr.fast, n)
|
tr.startRequest(tab, n)
|
||||||
tr.fast.schedule(now, &tab.rand)
|
tr.fast.schedule(now, &tab.rand)
|
||||||
}
|
}
|
||||||
if n := tr.slow.get(now, &tab.rand, tr.activeReq); n != nil {
|
if n := tr.slow.get(now, &tab.rand, tr.activeReq); n != nil {
|
||||||
tr.startRequest(tab, &tr.slow, n)
|
tr.startRequest(tab, n)
|
||||||
tr.slow.schedule(now, &tab.rand)
|
tr.slow.schedule(now, &tab.rand)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,12 +90,12 @@ func (tr *tableRevalidation) run(tab *Table, now mclock.AbsTime) (nextTime mcloc
|
||||||
}
|
}
|
||||||
|
|
||||||
// startRequest spawns a revalidation request for node n.
|
// startRequest spawns a revalidation request for node n.
|
||||||
func (tr *tableRevalidation) startRequest(tab *Table, list *revalidationList, n *node) {
|
func (tr *tableRevalidation) startRequest(tab *Table, n *tableNode) {
|
||||||
if _, ok := tr.activeReq[n.ID()]; ok {
|
if _, ok := tr.activeReq[n.ID()]; ok {
|
||||||
panic(fmt.Errorf("duplicate startRequest (list %q, node %v)", list.name, n.ID()))
|
panic(fmt.Errorf("duplicate startRequest (node %v)", n.ID()))
|
||||||
}
|
}
|
||||||
tr.activeReq[n.ID()] = struct{}{}
|
tr.activeReq[n.ID()] = struct{}{}
|
||||||
resp := revalidationResponse{n: n, list: list}
|
resp := revalidationResponse{n: n}
|
||||||
|
|
||||||
// Fetch the node while holding lock.
|
// Fetch the node while holding lock.
|
||||||
tab.mutex.Lock()
|
tab.mutex.Lock()
|
||||||
|
|
@ -120,21 +128,38 @@ func (tab *Table) doRevalidate(resp revalidationResponse, node *enode.Node) {
|
||||||
|
|
||||||
// handleResponse processes the result of a revalidation request.
|
// handleResponse processes the result of a revalidation request.
|
||||||
func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationResponse) {
|
func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationResponse) {
|
||||||
now := tab.cfg.Clock.Now()
|
var (
|
||||||
n := resp.n
|
now = tab.cfg.Clock.Now()
|
||||||
b := tab.bucket(n.ID())
|
n = resp.n
|
||||||
|
b = tab.bucket(n.ID())
|
||||||
|
)
|
||||||
delete(tr.activeReq, n.ID())
|
delete(tr.activeReq, n.ID())
|
||||||
|
|
||||||
|
// If the node was removed from the table while getting checked, we need to stop
|
||||||
|
// processing here to avoid re-adding it.
|
||||||
|
if n.revalList == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store potential seeds in database.
|
||||||
|
// This is done via defer to avoid holding Table lock while writing to DB.
|
||||||
|
defer func() {
|
||||||
|
if n.isValidatedLive && n.livenessChecks > 5 {
|
||||||
|
tab.db.UpdateNode(resp.n.Node)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Remaining logic needs access to Table internals.
|
||||||
tab.mutex.Lock()
|
tab.mutex.Lock()
|
||||||
defer tab.mutex.Unlock()
|
defer tab.mutex.Unlock()
|
||||||
|
|
||||||
if !resp.didRespond {
|
if !resp.didRespond {
|
||||||
// Revalidation failed.
|
|
||||||
n.livenessChecks /= 3
|
n.livenessChecks /= 3
|
||||||
if n.livenessChecks <= 0 {
|
if n.livenessChecks <= 0 {
|
||||||
tab.deleteInBucket(b, n.ID())
|
tab.deleteInBucket(b, n.ID())
|
||||||
} else {
|
} else {
|
||||||
tr.moveToList(&tr.fast, resp.list, n, now, &tab.rand)
|
tab.log.Debug("Node revalidation failed", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", n.revalList.name)
|
||||||
|
tr.moveToList(&tr.fast, n, now, &tab.rand)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -142,50 +167,39 @@ func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationRespons
|
||||||
// The node responded.
|
// The node responded.
|
||||||
n.livenessChecks++
|
n.livenessChecks++
|
||||||
n.isValidatedLive = true
|
n.isValidatedLive = true
|
||||||
|
tab.log.Debug("Node revalidated", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", n.revalList.name)
|
||||||
var endpointChanged bool
|
var endpointChanged bool
|
||||||
if resp.newRecord != nil {
|
if resp.newRecord != nil {
|
||||||
endpointChanged = tab.bumpInBucket(b, resp.newRecord)
|
_, endpointChanged = tab.bumpInBucket(b, resp.newRecord, false)
|
||||||
if endpointChanged {
|
|
||||||
// If the node changed its advertised endpoint, the updated ENR is not served
|
|
||||||
// until it has been revalidated.
|
|
||||||
n.isValidatedLive = false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
tab.log.Debug("Revalidated node", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", resp.list.name)
|
|
||||||
|
|
||||||
// Move node over to slow queue after first validation.
|
// Node moves to slow list if it passed and hasn't changed.
|
||||||
if !endpointChanged {
|
if !endpointChanged {
|
||||||
tr.moveToList(&tr.slow, resp.list, n, now, &tab.rand)
|
tr.moveToList(&tr.slow, n, now, &tab.rand)
|
||||||
} else {
|
|
||||||
tr.moveToList(&tr.fast, resp.list, n, now, &tab.rand)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store potential seeds in database.
|
|
||||||
if n.isValidatedLive && n.livenessChecks > 5 {
|
|
||||||
tab.db.UpdateNode(resp.n.Node)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *tableRevalidation) moveToList(dest, source *revalidationList, n *node, now mclock.AbsTime, rand randomSource) {
|
// moveToList ensures n is in the 'dest' list.
|
||||||
if source == dest {
|
func (tr *tableRevalidation) moveToList(dest *revalidationList, n *tableNode, now mclock.AbsTime, rand randomSource) {
|
||||||
|
if n.revalList == dest {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !source.remove(n) {
|
if n.revalList != nil {
|
||||||
panic(fmt.Errorf("moveToList(%q -> %q): node %v not in source list", source.name, dest.name, n.ID()))
|
n.revalList.remove(n)
|
||||||
}
|
}
|
||||||
dest.push(n, now, rand)
|
dest.push(n, now, rand)
|
||||||
}
|
}
|
||||||
|
|
||||||
// revalidationList holds a list nodes and the next revalidation time.
|
// revalidationList holds a list nodes and the next revalidation time.
|
||||||
type revalidationList struct {
|
type revalidationList struct {
|
||||||
nodes []*node
|
nodes []*tableNode
|
||||||
nextTime mclock.AbsTime
|
nextTime mclock.AbsTime
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns a random node from the queue. Nodes in the 'exclude' map are not returned.
|
// get returns a random node from the queue. Nodes in the 'exclude' map are not returned.
|
||||||
func (list *revalidationList) get(now mclock.AbsTime, rand randomSource, exclude map[enode.ID]struct{}) *node {
|
func (list *revalidationList) get(now mclock.AbsTime, rand randomSource, exclude map[enode.ID]struct{}) *tableNode {
|
||||||
if now < list.nextTime || len(list.nodes) == 0 {
|
if now < list.nextTime || len(list.nodes) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -203,21 +217,28 @@ func (list *revalidationList) schedule(now mclock.AbsTime, rand randomSource) {
|
||||||
list.nextTime = now.Add(time.Duration(rand.Int63n(int64(list.interval))))
|
list.nextTime = now.Add(time.Duration(rand.Int63n(int64(list.interval))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *revalidationList) push(n *node, now mclock.AbsTime, rand randomSource) {
|
func (list *revalidationList) push(n *tableNode, now mclock.AbsTime, rand randomSource) {
|
||||||
list.nodes = append(list.nodes, n)
|
list.nodes = append(list.nodes, n)
|
||||||
if list.nextTime == never {
|
if list.nextTime == never {
|
||||||
list.schedule(now, rand)
|
list.schedule(now, rand)
|
||||||
}
|
}
|
||||||
|
n.revalList = list
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *revalidationList) remove(n *node) bool {
|
func (list *revalidationList) remove(n *tableNode) {
|
||||||
i := slices.Index(list.nodes, n)
|
i := slices.Index(list.nodes, n)
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
return false
|
panic(fmt.Errorf("node %v not found in list", n.ID()))
|
||||||
}
|
}
|
||||||
list.nodes = slices.Delete(list.nodes, i, i+1)
|
list.nodes = slices.Delete(list.nodes, i, i+1)
|
||||||
if len(list.nodes) == 0 {
|
if len(list.nodes) == 0 {
|
||||||
list.nextTime = never
|
list.nextTime = never
|
||||||
}
|
}
|
||||||
return true
|
n.revalList = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *revalidationList) contains(id enode.ID) bool {
|
||||||
|
return slices.ContainsFunc(list.nodes, func(n *tableNode) bool {
|
||||||
|
return n.ID() == id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
119
p2p/discover/table_reval_test.go
Normal file
119
p2p/discover/table_reval_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package discover
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This test checks that revalidation can handle a node disappearing while
|
||||||
|
// a request is active.
|
||||||
|
func TestRevalidation_nodeRemoved(t *testing.T) {
|
||||||
|
var (
|
||||||
|
clock mclock.Simulated
|
||||||
|
transport = newPingRecorder()
|
||||||
|
tab, db = newInactiveTestTable(transport, Config{Clock: &clock})
|
||||||
|
tr = &tab.revalidation
|
||||||
|
)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Add a node to the table.
|
||||||
|
node := nodeAtDistance(tab.self().ID(), 255, net.IP{77, 88, 99, 1})
|
||||||
|
tab.handleAddNode(addNodeOp{node: node})
|
||||||
|
|
||||||
|
// Start a revalidation request. Schedule once to get the next start time,
|
||||||
|
// then advance the clock to that point and schedule again to start.
|
||||||
|
next := tr.run(tab, clock.Now())
|
||||||
|
clock.Run(time.Duration(next + 1))
|
||||||
|
tr.run(tab, clock.Now())
|
||||||
|
if len(tr.activeReq) != 1 {
|
||||||
|
t.Fatal("revalidation request did not start:", tr.activeReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the node.
|
||||||
|
tab.deleteInBucket(tab.bucket(node.ID()), node.ID())
|
||||||
|
|
||||||
|
// Now finish the revalidation request.
|
||||||
|
var resp revalidationResponse
|
||||||
|
select {
|
||||||
|
case resp = <-tab.revalResponseCh:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatal("timed out waiting for revalidation")
|
||||||
|
}
|
||||||
|
tr.handleResponse(tab, resp)
|
||||||
|
|
||||||
|
// Ensure the node was not re-added to the table.
|
||||||
|
if tab.getNode(node.ID()) != nil {
|
||||||
|
t.Fatal("node was re-added to Table")
|
||||||
|
}
|
||||||
|
if tr.fast.contains(node.ID()) || tr.slow.contains(node.ID()) {
|
||||||
|
t.Fatal("removed node contained in revalidation list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that nodes with an updated endpoint remain in the fast revalidation list.
|
||||||
|
func TestRevalidation_endpointUpdate(t *testing.T) {
|
||||||
|
var (
|
||||||
|
clock mclock.Simulated
|
||||||
|
transport = newPingRecorder()
|
||||||
|
tab, db = newInactiveTestTable(transport, Config{Clock: &clock})
|
||||||
|
tr = &tab.revalidation
|
||||||
|
)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Add node to table.
|
||||||
|
node := nodeAtDistance(tab.self().ID(), 255, net.IP{77, 88, 99, 1})
|
||||||
|
tab.handleAddNode(addNodeOp{node: node})
|
||||||
|
|
||||||
|
// Update the record in transport, including endpoint update.
|
||||||
|
record := node.Record()
|
||||||
|
record.Set(enr.IP{100, 100, 100, 100})
|
||||||
|
record.Set(enr.UDP(9999))
|
||||||
|
nodev2 := enode.SignNull(record, node.ID())
|
||||||
|
transport.updateRecord(nodev2)
|
||||||
|
|
||||||
|
// Start a revalidation request. Schedule once to get the next start time,
|
||||||
|
// then advance the clock to that point and schedule again to start.
|
||||||
|
next := tr.run(tab, clock.Now())
|
||||||
|
clock.Run(time.Duration(next + 1))
|
||||||
|
tr.run(tab, clock.Now())
|
||||||
|
if len(tr.activeReq) != 1 {
|
||||||
|
t.Fatal("revalidation request did not start:", tr.activeReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now finish the revalidation request.
|
||||||
|
var resp revalidationResponse
|
||||||
|
select {
|
||||||
|
case resp = <-tab.revalResponseCh:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatal("timed out waiting for revalidation")
|
||||||
|
}
|
||||||
|
tr.handleResponse(tab, resp)
|
||||||
|
|
||||||
|
if tr.fast.nodes[0].ID() != node.ID() {
|
||||||
|
t.Fatal("node not contained in fast revalidation list")
|
||||||
|
}
|
||||||
|
if tr.fast.nodes[0].isValidatedLive {
|
||||||
|
t.Fatal("node is marked live after endpoint change")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -64,7 +65,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
|
||||||
|
|
||||||
// Fill up the sender's bucket.
|
// Fill up the sender's bucket.
|
||||||
replacementNodeKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
|
replacementNodeKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
|
||||||
replacementNode := wrapNode(enode.NewV4(&replacementNodeKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99))
|
replacementNode := enode.NewV4(&replacementNodeKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99)
|
||||||
last := fillBucket(tab, replacementNode.ID())
|
last := fillBucket(tab, replacementNode.ID())
|
||||||
tab.mutex.Lock()
|
tab.mutex.Lock()
|
||||||
nodeEvents := newNodeEventRecorder(128)
|
nodeEvents := newNodeEventRecorder(128)
|
||||||
|
|
@ -78,7 +79,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
|
||||||
transport.dead[replacementNode.ID()] = !newNodeIsResponding
|
transport.dead[replacementNode.ID()] = !newNodeIsResponding
|
||||||
|
|
||||||
// Add replacement node to table.
|
// Add replacement node to table.
|
||||||
tab.addFoundNode(replacementNode)
|
tab.addFoundNode(replacementNode, false)
|
||||||
|
|
||||||
t.Log("last:", last.ID())
|
t.Log("last:", last.ID())
|
||||||
t.Log("replacement:", replacementNode.ID())
|
t.Log("replacement:", replacementNode.ID())
|
||||||
|
|
@ -115,11 +116,11 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
|
||||||
if l := len(bucket.entries); l != wantSize {
|
if l := len(bucket.entries); l != wantSize {
|
||||||
t.Errorf("wrong bucket size after revalidation: got %d, want %d", l, wantSize)
|
t.Errorf("wrong bucket size after revalidation: got %d, want %d", l, wantSize)
|
||||||
}
|
}
|
||||||
if ok := contains(bucket.entries, last.ID()); ok != lastInBucketIsResponding {
|
if ok := containsID(bucket.entries, last.ID()); ok != lastInBucketIsResponding {
|
||||||
t.Errorf("revalidated node found: %t, want: %t", ok, lastInBucketIsResponding)
|
t.Errorf("revalidated node found: %t, want: %t", ok, lastInBucketIsResponding)
|
||||||
}
|
}
|
||||||
wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
|
wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
|
||||||
if ok := contains(bucket.entries, replacementNode.ID()); ok != wantNewEntry {
|
if ok := containsID(bucket.entries, replacementNode.ID()); ok != wantNewEntry {
|
||||||
t.Errorf("replacement node found: %t, want: %t", ok, wantNewEntry)
|
t.Errorf("replacement node found: %t, want: %t", ok, wantNewEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +132,7 @@ func waitForRevalidationPing(t *testing.T, transport *pingRecorder, tab *Table,
|
||||||
simclock := tab.cfg.Clock.(*mclock.Simulated)
|
simclock := tab.cfg.Clock.(*mclock.Simulated)
|
||||||
maxAttempts := tab.len() * 8
|
maxAttempts := tab.len() * 8
|
||||||
for i := 0; i < maxAttempts; i++ {
|
for i := 0; i < maxAttempts; i++ {
|
||||||
simclock.Run(tab.cfg.PingInterval)
|
simclock.Run(tab.cfg.PingInterval * slowRevalidationFactor)
|
||||||
p := transport.waitPing(2 * time.Second)
|
p := transport.waitPing(2 * time.Second)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
t.Fatal("Table did not send revalidation ping")
|
t.Fatal("Table did not send revalidation ping")
|
||||||
|
|
@ -153,7 +154,7 @@ func TestTable_IPLimit(t *testing.T) {
|
||||||
|
|
||||||
for i := 0; i < tableIPLimit+1; i++ {
|
for i := 0; i < tableIPLimit+1; i++ {
|
||||||
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
|
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
|
||||||
tab.addFoundNode(n)
|
tab.addFoundNode(n, false)
|
||||||
}
|
}
|
||||||
if tab.len() > tableIPLimit {
|
if tab.len() > tableIPLimit {
|
||||||
t.Errorf("too many nodes in table")
|
t.Errorf("too many nodes in table")
|
||||||
|
|
@ -171,7 +172,7 @@ func TestTable_BucketIPLimit(t *testing.T) {
|
||||||
d := 3
|
d := 3
|
||||||
for i := 0; i < bucketIPLimit+1; i++ {
|
for i := 0; i < bucketIPLimit+1; i++ {
|
||||||
n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
|
n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
|
||||||
tab.addFoundNode(n)
|
tab.addFoundNode(n, false)
|
||||||
}
|
}
|
||||||
if tab.len() > bucketIPLimit {
|
if tab.len() > bucketIPLimit {
|
||||||
t.Errorf("too many nodes in table")
|
t.Errorf("too many nodes in table")
|
||||||
|
|
@ -232,7 +233,7 @@ func TestTable_findnodeByID(t *testing.T) {
|
||||||
// check that the result nodes have minimum distance to target.
|
// check that the result nodes have minimum distance to target.
|
||||||
for _, b := range tab.buckets {
|
for _, b := range tab.buckets {
|
||||||
for _, n := range b.entries {
|
for _, n := range b.entries {
|
||||||
if contains(result, n.ID()) {
|
if containsID(result, n.ID()) {
|
||||||
continue // don't run the check below for nodes in result
|
continue // don't run the check below for nodes in result
|
||||||
}
|
}
|
||||||
farthestResult := result[len(result)-1].ID()
|
farthestResult := result[len(result)-1].ID()
|
||||||
|
|
@ -255,7 +256,7 @@ func TestTable_findnodeByID(t *testing.T) {
|
||||||
type closeTest struct {
|
type closeTest struct {
|
||||||
Self enode.ID
|
Self enode.ID
|
||||||
Target enode.ID
|
Target enode.ID
|
||||||
All []*node
|
All []*enode.Node
|
||||||
N int
|
N int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,14 +269,13 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||||
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
|
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
|
||||||
r := new(enr.Record)
|
r := new(enr.Record)
|
||||||
r.Set(enr.IP(genIP(rand)))
|
r.Set(enr.IP(genIP(rand)))
|
||||||
n := wrapNode(enode.SignNull(r, id))
|
n := enode.SignNull(r, id)
|
||||||
n.livenessChecks = 1
|
|
||||||
t.All = append(t.All, n)
|
t.All = append(t.All, n)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(t)
|
return reflect.ValueOf(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTable_addVerifiedNode(t *testing.T) {
|
func TestTable_addInboundNode(t *testing.T) {
|
||||||
tab, db := newTestTable(newPingRecorder(), Config{})
|
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||||
<-tab.initDone
|
<-tab.initDone
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
@ -284,31 +284,28 @@ func TestTable_addVerifiedNode(t *testing.T) {
|
||||||
// Insert two nodes.
|
// Insert two nodes.
|
||||||
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
|
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
|
||||||
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
|
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
|
||||||
tab.addFoundNode(n1)
|
tab.addFoundNode(n1, false)
|
||||||
tab.addFoundNode(n2)
|
tab.addFoundNode(n2, false)
|
||||||
bucket := tab.bucket(n1.ID())
|
checkBucketContent(t, tab, []*enode.Node{n1, n2})
|
||||||
|
|
||||||
// Verify bucket content:
|
// Add a changed version of n2. The bucket should be updated.
|
||||||
bcontent := []*node{n1, n2}
|
|
||||||
if !reflect.DeepEqual(unwrapNodes(bucket.entries), unwrapNodes(bcontent)) {
|
|
||||||
t.Fatalf("wrong bucket content: %v", bucket.entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a changed version of n2.
|
|
||||||
newrec := n2.Record()
|
newrec := n2.Record()
|
||||||
newrec.Set(enr.IP{99, 99, 99, 99})
|
newrec.Set(enr.IP{99, 99, 99, 99})
|
||||||
newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
|
n2v2 := enode.SignNull(newrec, n2.ID())
|
||||||
tab.addInboundNode(newn2)
|
tab.addInboundNode(n2v2)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
|
||||||
|
|
||||||
// Check that bucket is updated correctly.
|
// Try updating n2 without sequence number change. The update is accepted
|
||||||
newBcontent := []*node{n1, newn2}
|
// because it's inbound.
|
||||||
if !reflect.DeepEqual(unwrapNodes(bucket.entries), unwrapNodes(newBcontent)) {
|
newrec = n2.Record()
|
||||||
t.Fatalf("wrong bucket content after update: %v", bucket.entries)
|
newrec.Set(enr.IP{100, 100, 100, 100})
|
||||||
}
|
newrec.SetSeq(n2.Seq())
|
||||||
checkIPLimitInvariant(t, tab)
|
n2v3 := enode.SignNull(newrec, n2.ID())
|
||||||
|
tab.addInboundNode(n2v3)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1, n2v3})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTable_addSeenNode(t *testing.T) {
|
func TestTable_addFoundNode(t *testing.T) {
|
||||||
tab, db := newTestTable(newPingRecorder(), Config{})
|
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||||
<-tab.initDone
|
<-tab.initDone
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
@ -317,25 +314,86 @@ func TestTable_addSeenNode(t *testing.T) {
|
||||||
// Insert two nodes.
|
// Insert two nodes.
|
||||||
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
|
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
|
||||||
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
|
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
|
||||||
tab.addFoundNode(n1)
|
tab.addFoundNode(n1, false)
|
||||||
tab.addFoundNode(n2)
|
tab.addFoundNode(n2, false)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1, n2})
|
||||||
|
|
||||||
// Verify bucket content:
|
// Add a changed version of n2. The bucket should be updated.
|
||||||
bcontent := []*node{n1, n2}
|
|
||||||
if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
|
|
||||||
t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a changed version of n2.
|
|
||||||
newrec := n2.Record()
|
newrec := n2.Record()
|
||||||
newrec.Set(enr.IP{99, 99, 99, 99})
|
newrec.Set(enr.IP{99, 99, 99, 99})
|
||||||
newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
|
n2v2 := enode.SignNull(newrec, n2.ID())
|
||||||
tab.addFoundNode(newn2)
|
tab.addFoundNode(n2v2, false)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
|
||||||
|
|
||||||
// Check that bucket content is unchanged.
|
// Try updating n2 without a sequence number change.
|
||||||
if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
|
// The update should not be accepted.
|
||||||
t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
|
newrec = n2.Record()
|
||||||
|
newrec.Set(enr.IP{100, 100, 100, 100})
|
||||||
|
newrec.SetSeq(n2.Seq())
|
||||||
|
n2v3 := enode.SignNull(newrec, n2.ID())
|
||||||
|
tab.addFoundNode(n2v3, false)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that discv4 nodes can update their own endpoint via PING.
|
||||||
|
func TestTable_addInboundNodeUpdateV4Accept(t *testing.T) {
|
||||||
|
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||||
|
<-tab.initDone
|
||||||
|
defer db.Close()
|
||||||
|
defer tab.close()
|
||||||
|
|
||||||
|
// Add a v4 node.
|
||||||
|
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
|
||||||
|
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
|
||||||
|
tab.addInboundNode(n1)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1})
|
||||||
|
|
||||||
|
// Add an updated version with changed IP.
|
||||||
|
// The update will be accepted because it is inbound.
|
||||||
|
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
|
||||||
|
tab.addInboundNode(n1v2)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1v2})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that discv4 node entries will NOT be updated when a
|
||||||
|
// changed record is found.
|
||||||
|
func TestTable_addFoundNodeV4UpdateReject(t *testing.T) {
|
||||||
|
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||||
|
<-tab.initDone
|
||||||
|
defer db.Close()
|
||||||
|
defer tab.close()
|
||||||
|
|
||||||
|
// Add a v4 node.
|
||||||
|
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
|
||||||
|
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
|
||||||
|
tab.addFoundNode(n1, false)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1})
|
||||||
|
|
||||||
|
// Add an updated version with changed IP.
|
||||||
|
// The update won't be accepted because it isn't inbound.
|
||||||
|
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
|
||||||
|
tab.addFoundNode(n1v2, false)
|
||||||
|
checkBucketContent(t, tab, []*enode.Node{n1})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBucketContent(t *testing.T, tab *Table, nodes []*enode.Node) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
b := tab.bucket(nodes[0].ID())
|
||||||
|
if reflect.DeepEqual(unwrapNodes(b.entries), nodes) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
t.Log("wrong bucket content. have nodes:")
|
||||||
|
for _, n := range b.entries {
|
||||||
|
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IP())
|
||||||
|
}
|
||||||
|
t.Log("want nodes:")
|
||||||
|
for _, n := range nodes {
|
||||||
|
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IP())
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
|
||||||
|
// Also check IP limits.
|
||||||
checkIPLimitInvariant(t, tab)
|
checkIPLimitInvariant(t, tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,8 +413,8 @@ func TestTable_revalidateSyncRecord(t *testing.T) {
|
||||||
var r enr.Record
|
var r enr.Record
|
||||||
r.Set(enr.IP(net.IP{127, 0, 0, 1}))
|
r.Set(enr.IP(net.IP{127, 0, 0, 1}))
|
||||||
id := enode.ID{1}
|
id := enode.ID{1}
|
||||||
n1 := wrapNode(enode.SignNull(&r, id))
|
n1 := enode.SignNull(&r, id)
|
||||||
tab.addFoundNode(n1)
|
tab.addFoundNode(n1, false)
|
||||||
|
|
||||||
// Update the node record.
|
// Update the node record.
|
||||||
r.Set(enr.WithEntry("foo", "bar"))
|
r.Set(enr.WithEntry("foo", "bar"))
|
||||||
|
|
@ -379,7 +437,7 @@ func TestNodesPush(t *testing.T) {
|
||||||
n1 := nodeAtDistance(target, 255, intIP(1))
|
n1 := nodeAtDistance(target, 255, intIP(1))
|
||||||
n2 := nodeAtDistance(target, 254, intIP(2))
|
n2 := nodeAtDistance(target, 254, intIP(2))
|
||||||
n3 := nodeAtDistance(target, 253, intIP(3))
|
n3 := nodeAtDistance(target, 253, intIP(3))
|
||||||
perm := [][]*node{
|
perm := [][]*enode.Node{
|
||||||
{n3, n2, n1},
|
{n3, n2, n1},
|
||||||
{n3, n1, n2},
|
{n3, n1, n2},
|
||||||
{n2, n3, n1},
|
{n2, n3, n1},
|
||||||
|
|
@ -394,7 +452,7 @@ func TestNodesPush(t *testing.T) {
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
list.push(n, 3)
|
list.push(n, 3)
|
||||||
}
|
}
|
||||||
if !slicesEqual(list.entries, perm[0], nodeIDEqual) {
|
if !slices.EqualFunc(list.entries, perm[0], nodeIDEqual) {
|
||||||
t.Fatal("not equal")
|
t.Fatal("not equal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,28 +463,16 @@ func TestNodesPush(t *testing.T) {
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
list.push(n, 2)
|
list.push(n, 2)
|
||||||
}
|
}
|
||||||
if !slicesEqual(list.entries, perm[0][:2], nodeIDEqual) {
|
if !slices.EqualFunc(list.entries, perm[0][:2], nodeIDEqual) {
|
||||||
t.Fatal("not equal")
|
t.Fatal("not equal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeIDEqual(n1, n2 *node) bool {
|
func nodeIDEqual[N nodeType](n1, n2 N) bool {
|
||||||
return n1.ID() == n2.ID()
|
return n1.ID() == n2.ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
func slicesEqual[T any](s1, s2 []T, check func(e1, e2 T) bool) bool {
|
|
||||||
if len(s1) != len(s2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range s1 {
|
|
||||||
if !check(s1[i], s2[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// gen wraps quick.Value so it's easier to use.
|
// gen wraps quick.Value so it's easier to use.
|
||||||
// it generates a random value of the given value's type.
|
// it generates a random value of the given value's type.
|
||||||
func gen(typ interface{}, rand *rand.Rand) interface{} {
|
func gen(typ interface{}, rand *rand.Rand) interface{} {
|
||||||
|
|
|
||||||
|
|
@ -43,25 +43,31 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestTable(t transport, cfg Config) (*Table, *enode.DB) {
|
func newTestTable(t transport, cfg Config) (*Table, *enode.DB) {
|
||||||
db, _ := enode.OpenDB("")
|
tab, db := newInactiveTestTable(t, cfg)
|
||||||
tab, _ := newTable(t, db, cfg)
|
|
||||||
go tab.loop()
|
go tab.loop()
|
||||||
return tab, db
|
return tab, db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newInactiveTestTable creates a Table without running the main loop.
|
||||||
|
func newInactiveTestTable(t transport, cfg Config) (*Table, *enode.DB) {
|
||||||
|
db, _ := enode.OpenDB("")
|
||||||
|
tab, _ := newTable(t, db, cfg)
|
||||||
|
return tab, db
|
||||||
|
}
|
||||||
|
|
||||||
// nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld.
|
// nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld.
|
||||||
func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node {
|
func nodeAtDistance(base enode.ID, ld int, ip net.IP) *enode.Node {
|
||||||
var r enr.Record
|
var r enr.Record
|
||||||
r.Set(enr.IP(ip))
|
r.Set(enr.IP(ip))
|
||||||
r.Set(enr.UDP(30303))
|
r.Set(enr.UDP(30303))
|
||||||
return wrapNode(enode.SignNull(&r, idAtDistance(base, ld)))
|
return enode.SignNull(&r, idAtDistance(base, ld))
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld.
|
// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld.
|
||||||
func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node {
|
func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node {
|
||||||
results := make([]*enode.Node, n)
|
results := make([]*enode.Node, n)
|
||||||
for i := range results {
|
for i := range results {
|
||||||
results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i)))
|
results[i] = nodeAtDistance(base, ld, intIP(i))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
@ -99,12 +105,12 @@ func intIP(i int) net.IP {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fillBucket inserts nodes into the given bucket until it is full.
|
// fillBucket inserts nodes into the given bucket until it is full.
|
||||||
func fillBucket(tab *Table, id enode.ID) (last *node) {
|
func fillBucket(tab *Table, id enode.ID) (last *tableNode) {
|
||||||
ld := enode.LogDist(tab.self().ID(), id)
|
ld := enode.LogDist(tab.self().ID(), id)
|
||||||
b := tab.bucket(id)
|
b := tab.bucket(id)
|
||||||
for len(b.entries) < bucketSize {
|
for len(b.entries) < bucketSize {
|
||||||
node := nodeAtDistance(tab.self().ID(), ld, intIP(ld))
|
node := nodeAtDistance(tab.self().ID(), ld, intIP(ld))
|
||||||
if !tab.addFoundNode(node) {
|
if !tab.addFoundNode(node, false) {
|
||||||
panic("node not added")
|
panic("node not added")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,13 +119,9 @@ func fillBucket(tab *Table, id enode.ID) (last *node) {
|
||||||
|
|
||||||
// fillTable adds nodes the table to the end of their corresponding bucket
|
// fillTable adds nodes the table to the end of their corresponding bucket
|
||||||
// if the bucket is not full. The caller must not hold tab.mutex.
|
// if the bucket is not full. The caller must not hold tab.mutex.
|
||||||
func fillTable(tab *Table, nodes []*node, setLive bool) {
|
func fillTable(tab *Table, nodes []*enode.Node, setLive bool) {
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
if setLive {
|
tab.addFoundNode(n, setLive)
|
||||||
n.livenessChecks = 1
|
|
||||||
n.isValidatedLive = true
|
|
||||||
}
|
|
||||||
tab.addFoundNode(n)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +215,7 @@ func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||||
return t.records[n.ID()], nil
|
return t.records[n.ID()], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasDuplicates(slice []*node) bool {
|
func hasDuplicates(slice []*enode.Node) bool {
|
||||||
seen := make(map[enode.ID]bool, len(slice))
|
seen := make(map[enode.ID]bool, len(slice))
|
||||||
for i, e := range slice {
|
for i, e := range slice {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
|
|
@ -255,14 +257,14 @@ func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool {
|
||||||
return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP())
|
return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP())
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortByID(nodes []*enode.Node) {
|
func sortByID[N nodeType](nodes []N) {
|
||||||
slices.SortFunc(nodes, func(a, b *enode.Node) int {
|
slices.SortFunc(nodes, func(a, b N) int {
|
||||||
return bytes.Compare(a.ID().Bytes(), b.ID().Bytes())
|
return bytes.Compare(a.ID().Bytes(), b.ID().Bytes())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
|
func sortedByDistanceTo(distbase enode.ID, slice []*enode.Node) bool {
|
||||||
return slices.IsSortedFunc(slice, func(a, b *node) int {
|
return slices.IsSortedFunc(slice, func(a, b *enode.Node) int {
|
||||||
return enode.DistCmp(distbase, a.ID(), b.ID())
|
return enode.DistCmp(distbase, a.ID(), b.ID())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +300,7 @@ type nodeEventRecorder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordedNodeEvent struct {
|
type recordedNodeEvent struct {
|
||||||
node *node
|
node *tableNode
|
||||||
added bool
|
added bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,7 +310,7 @@ func newNodeEventRecorder(buffer int) *nodeEventRecorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (set *nodeEventRecorder) nodeAdded(b *bucket, n *node) {
|
func (set *nodeEventRecorder) nodeAdded(b *bucket, n *tableNode) {
|
||||||
select {
|
select {
|
||||||
case set.evc <- recordedNodeEvent{n, true}:
|
case set.evc <- recordedNodeEvent{n, true}:
|
||||||
default:
|
default:
|
||||||
|
|
@ -316,7 +318,7 @@ func (set *nodeEventRecorder) nodeAdded(b *bucket, n *node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (set *nodeEventRecorder) nodeRemoved(b *bucket, n *node) {
|
func (set *nodeEventRecorder) nodeRemoved(b *bucket, n *tableNode) {
|
||||||
select {
|
select {
|
||||||
case set.evc <- recordedNodeEvent{n, false}:
|
case set.evc <- recordedNodeEvent{n, false}:
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package discover
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ func TestUDPv4_Lookup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seed table with initial node.
|
// Seed table with initial node.
|
||||||
fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true)
|
fillTable(test.table, []*enode.Node{lookupTestnet.node(256, 0)}, true)
|
||||||
|
|
||||||
// Start the lookup.
|
// Start the lookup.
|
||||||
resultC := make(chan []*enode.Node, 1)
|
resultC := make(chan []*enode.Node, 1)
|
||||||
|
|
@ -70,9 +70,9 @@ func TestUDPv4_LookupIterator(t *testing.T) {
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
// Seed table with initial nodes.
|
// Seed table with initial nodes.
|
||||||
bootnodes := make([]*node, len(lookupTestnet.dists[256]))
|
bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
|
||||||
for i := range lookupTestnet.dists[256] {
|
for i := range lookupTestnet.dists[256] {
|
||||||
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
bootnodes[i] = lookupTestnet.node(256, i)
|
||||||
}
|
}
|
||||||
fillTable(test.table, bootnodes, true)
|
fillTable(test.table, bootnodes, true)
|
||||||
go serveTestnet(test, lookupTestnet)
|
go serveTestnet(test, lookupTestnet)
|
||||||
|
|
@ -105,9 +105,9 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
// Seed table with initial nodes.
|
// Seed table with initial nodes.
|
||||||
bootnodes := make([]*node, len(lookupTestnet.dists[256]))
|
bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
|
||||||
for i := range lookupTestnet.dists[256] {
|
for i := range lookupTestnet.dists[256] {
|
||||||
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
bootnodes[i] = lookupTestnet.node(256, i)
|
||||||
}
|
}
|
||||||
fillTable(test.table, bootnodes, true)
|
fillTable(test.table, bootnodes, true)
|
||||||
go serveTestnet(test, lookupTestnet)
|
go serveTestnet(test, lookupTestnet)
|
||||||
|
|
@ -136,7 +136,7 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
|
||||||
|
|
||||||
func serveTestnet(test *udpTest, testnet *preminedTestnet) {
|
func serveTestnet(test *udpTest, testnet *preminedTestnet) {
|
||||||
for done := false; !done; {
|
for done := false; !done; {
|
||||||
done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) {
|
done = test.waitPacketOut(func(p v4wire.Packet, to netip.AddrPort, hash []byte) {
|
||||||
n, key := testnet.nodeByAddr(to)
|
n, key := testnet.nodeByAddr(to)
|
||||||
switch p.(type) {
|
switch p.(type) {
|
||||||
case *v4wire.Ping:
|
case *v4wire.Ping:
|
||||||
|
|
@ -158,10 +158,10 @@ func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node
|
||||||
for _, e := range results {
|
for _, e := range results {
|
||||||
t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes())
|
t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes())
|
||||||
}
|
}
|
||||||
if hasDuplicates(wrapNodes(results)) {
|
if hasDuplicates(results) {
|
||||||
t.Errorf("result set contains duplicate entries")
|
t.Errorf("result set contains duplicate entries")
|
||||||
}
|
}
|
||||||
if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) {
|
if !sortedByDistanceTo(tn.target.id(), results) {
|
||||||
t.Errorf("result set not sorted by distance to target")
|
t.Errorf("result set not sorted by distance to target")
|
||||||
}
|
}
|
||||||
wantNodes := tn.closest(len(results))
|
wantNodes := tn.closest(len(results))
|
||||||
|
|
@ -264,9 +264,10 @@ func (tn *preminedTestnet) node(dist, index int) *enode.Node {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) {
|
func (tn *preminedTestnet) nodeByAddr(addr netip.AddrPort) (*enode.Node, *ecdsa.PrivateKey) {
|
||||||
dist := int(addr.IP[1])<<8 + int(addr.IP[2])
|
ip := addr.Addr().As4()
|
||||||
index := int(addr.IP[3])
|
dist := int(ip[1])<<8 + int(ip[2])
|
||||||
|
index := int(ip[3])
|
||||||
key := tn.dists[dist][index]
|
key := tn.dists[dist][index]
|
||||||
return tn.node(dist, index), key
|
return tn.node(dist, index), key
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +275,7 @@ func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.Pr
|
||||||
func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
|
func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
|
||||||
result := make([]v4wire.Node, len(tn.dists[dist]))
|
result := make([]v4wire.Node, len(tn.dists[dist]))
|
||||||
for i := range result {
|
for i := range result {
|
||||||
result[i] = nodeToRPC(wrapNode(tn.node(dist, i)))
|
result[i] = nodeToRPC(tn.node(dist, i))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -45,6 +46,7 @@ var (
|
||||||
errClockWarp = errors.New("reply deadline too far in the future")
|
errClockWarp = errors.New("reply deadline too far in the future")
|
||||||
errClosed = errors.New("socket closed")
|
errClosed = errors.New("socket closed")
|
||||||
errLowPort = errors.New("low port")
|
errLowPort = errors.New("low port")
|
||||||
|
errNoUDPEndpoint = errors.New("node has no UDP endpoint")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -93,7 +95,7 @@ type UDPv4 struct {
|
||||||
type replyMatcher struct {
|
type replyMatcher struct {
|
||||||
// these fields must match in the reply.
|
// these fields must match in the reply.
|
||||||
from enode.ID
|
from enode.ID
|
||||||
ip net.IP
|
ip netip.Addr
|
||||||
ptype byte
|
ptype byte
|
||||||
|
|
||||||
// time when the request must complete
|
// time when the request must complete
|
||||||
|
|
@ -119,7 +121,7 @@ type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
|
||||||
// reply is a reply packet from a certain node.
|
// reply is a reply packet from a certain node.
|
||||||
type reply struct {
|
type reply struct {
|
||||||
from enode.ID
|
from enode.ID
|
||||||
ip net.IP
|
ip netip.Addr
|
||||||
data v4wire.Packet
|
data v4wire.Packet
|
||||||
// loop indicates whether there was
|
// loop indicates whether there was
|
||||||
// a matching request by sending on this channel.
|
// a matching request by sending on this channel.
|
||||||
|
|
@ -201,9 +203,12 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
|
func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
|
||||||
n := t.Self()
|
node := t.Self()
|
||||||
a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
addr, ok := node.UDPEndpoint()
|
||||||
return v4wire.NewEndpoint(a, uint16(n.TCP()))
|
if !ok {
|
||||||
|
return v4wire.Endpoint{}
|
||||||
|
}
|
||||||
|
return v4wire.NewEndpoint(addr, uint16(node.TCP()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping sends a ping message to the given node.
|
// Ping sends a ping message to the given node.
|
||||||
|
|
@ -214,7 +219,11 @@ func (t *UDPv4) Ping(n *enode.Node) error {
|
||||||
|
|
||||||
// ping sends a ping message to the given node and waits for a reply.
|
// ping sends a ping message to the given node and waits for a reply.
|
||||||
func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
|
func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
|
||||||
rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
|
addr, ok := n.UDPEndpoint()
|
||||||
|
if !ok {
|
||||||
|
return 0, errNoUDPEndpoint
|
||||||
|
}
|
||||||
|
rm := t.sendPing(n.ID(), addr, nil)
|
||||||
if err = <-rm.errc; err == nil {
|
if err = <-rm.errc; err == nil {
|
||||||
seq = rm.reply.(*v4wire.Pong).ENRSeq
|
seq = rm.reply.(*v4wire.Pong).ENRSeq
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +232,7 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
|
||||||
|
|
||||||
// sendPing sends a ping message to the given node and invokes the callback
|
// sendPing sends a ping message to the given node and invokes the callback
|
||||||
// when the reply arrives.
|
// when the reply arrives.
|
||||||
func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
|
func (t *UDPv4) sendPing(toid enode.ID, toaddr netip.AddrPort, callback func()) *replyMatcher {
|
||||||
req := t.makePing(toaddr)
|
req := t.makePing(toaddr)
|
||||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -233,7 +242,7 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
|
||||||
}
|
}
|
||||||
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
|
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
|
||||||
// reference the ping we're about to send.
|
// reference the ping we're about to send.
|
||||||
rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
|
rm := t.pending(toid, toaddr.Addr(), v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
|
||||||
matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
|
matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
|
||||||
if matched && callback != nil {
|
if matched && callback != nil {
|
||||||
callback()
|
callback()
|
||||||
|
|
@ -241,12 +250,13 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
|
||||||
return matched, matched
|
return matched, matched
|
||||||
})
|
})
|
||||||
// Send the packet.
|
// Send the packet.
|
||||||
t.localNode.UDPContact(toaddr)
|
toUDPAddr := &net.UDPAddr{IP: toaddr.Addr().AsSlice()}
|
||||||
|
t.localNode.UDPContact(toUDPAddr)
|
||||||
t.write(toaddr, toid, req.Name(), packet)
|
t.write(toaddr, toid, req.Name(), packet)
|
||||||
return rm
|
return rm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
|
func (t *UDPv4) makePing(toaddr netip.AddrPort) *v4wire.Ping {
|
||||||
return &v4wire.Ping{
|
return &v4wire.Ping{
|
||||||
Version: 4,
|
Version: 4,
|
||||||
From: t.ourEndpoint(),
|
From: t.ourEndpoint(),
|
||||||
|
|
@ -290,35 +300,39 @@ func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
|
||||||
func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
|
func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
|
||||||
target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
|
target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
|
||||||
ekey := v4wire.Pubkey(targetKey)
|
ekey := v4wire.Pubkey(targetKey)
|
||||||
it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
|
it := newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
|
||||||
return t.findnode(n.ID(), n.addr(), ekey)
|
addr, ok := n.UDPEndpoint()
|
||||||
|
if !ok {
|
||||||
|
return nil, errNoUDPEndpoint
|
||||||
|
}
|
||||||
|
return t.findnode(n.ID(), addr, ekey)
|
||||||
})
|
})
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
// findnode sends a findnode request to the given node and waits until
|
// findnode sends a findnode request to the given node and waits until
|
||||||
// the node has sent up to k neighbors.
|
// the node has sent up to k neighbors.
|
||||||
func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
|
func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire.Pubkey) ([]*enode.Node, error) {
|
||||||
t.ensureBond(toid, toaddr)
|
t.ensureBond(toid, toAddrPort)
|
||||||
|
|
||||||
// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
|
// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
|
||||||
// active until enough nodes have been received.
|
// active until enough nodes have been received.
|
||||||
nodes := make([]*node, 0, bucketSize)
|
nodes := make([]*enode.Node, 0, bucketSize)
|
||||||
nreceived := 0
|
nreceived := 0
|
||||||
rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
rm := t.pending(toid, toAddrPort.Addr(), v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||||
reply := r.(*v4wire.Neighbors)
|
reply := r.(*v4wire.Neighbors)
|
||||||
for _, rn := range reply.Nodes {
|
for _, rn := range reply.Nodes {
|
||||||
nreceived++
|
nreceived++
|
||||||
n, err := t.nodeFromRPC(toaddr, rn)
|
n, err := t.nodeFromRPC(toAddrPort, rn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
|
t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toAddrPort, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nodes = append(nodes, n)
|
nodes = append(nodes, n)
|
||||||
}
|
}
|
||||||
return true, nreceived >= bucketSize
|
return true, nreceived >= bucketSize
|
||||||
})
|
})
|
||||||
t.send(toaddr, toid, &v4wire.Findnode{
|
t.send(toAddrPort, toid, &v4wire.Findnode{
|
||||||
Target: target,
|
Target: target,
|
||||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||||
})
|
})
|
||||||
|
|
@ -336,7 +350,7 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke
|
||||||
|
|
||||||
// RequestENR sends ENRRequest to the given node and waits for a response.
|
// RequestENR sends ENRRequest to the given node and waits for a response.
|
||||||
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||||
addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
addr, _ := n.UDPEndpoint()
|
||||||
t.ensureBond(n.ID(), addr)
|
t.ensureBond(n.ID(), addr)
|
||||||
|
|
||||||
req := &v4wire.ENRRequest{
|
req := &v4wire.ENRRequest{
|
||||||
|
|
@ -349,7 +363,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||||
|
|
||||||
// Add a matcher for the reply to the pending reply queue. Responses are matched if
|
// Add a matcher for the reply to the pending reply queue. Responses are matched if
|
||||||
// they reference the request we're about to send.
|
// they reference the request we're about to send.
|
||||||
rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
rm := t.pending(n.ID(), addr.Addr(), v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||||
matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
|
matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
|
||||||
return matched, matched
|
return matched, matched
|
||||||
})
|
})
|
||||||
|
|
@ -369,7 +383,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||||
if respN.Seq() < n.Seq() {
|
if respN.Seq() < n.Seq() {
|
||||||
return n, nil // response record is older
|
return n, nil // response record is older
|
||||||
}
|
}
|
||||||
if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil {
|
if err := netutil.CheckRelayIP(addr.Addr().AsSlice(), respN.IP()); err != nil {
|
||||||
return nil, fmt.Errorf("invalid IP in response record: %v", err)
|
return nil, fmt.Errorf("invalid IP in response record: %v", err)
|
||||||
}
|
}
|
||||||
return respN, nil
|
return respN, nil
|
||||||
|
|
@ -381,7 +395,7 @@ func (t *UDPv4) TableBuckets() [][]BucketNode {
|
||||||
|
|
||||||
// pending adds a reply matcher to the pending reply queue.
|
// pending adds a reply matcher to the pending reply queue.
|
||||||
// see the documentation of type replyMatcher for a detailed explanation.
|
// see the documentation of type replyMatcher for a detailed explanation.
|
||||||
func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher {
|
func (t *UDPv4) pending(id enode.ID, ip netip.Addr, ptype byte, callback replyMatchFunc) *replyMatcher {
|
||||||
ch := make(chan error, 1)
|
ch := make(chan error, 1)
|
||||||
p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
|
p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
|
||||||
select {
|
select {
|
||||||
|
|
@ -395,7 +409,7 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF
|
||||||
|
|
||||||
// handleReply dispatches a reply packet, invoking reply matchers. It returns
|
// handleReply dispatches a reply packet, invoking reply matchers. It returns
|
||||||
// whether any matcher considered the packet acceptable.
|
// whether any matcher considered the packet acceptable.
|
||||||
func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
|
func (t *UDPv4) handleReply(from enode.ID, fromIP netip.Addr, req v4wire.Packet) bool {
|
||||||
matched := make(chan bool, 1)
|
matched := make(chan bool, 1)
|
||||||
select {
|
select {
|
||||||
case t.gotreply <- reply{from, fromIP, req, matched}:
|
case t.gotreply <- reply{from, fromIP, req, matched}:
|
||||||
|
|
@ -461,7 +475,7 @@ func (t *UDPv4) loop() {
|
||||||
var matched bool // whether any replyMatcher considered the reply acceptable.
|
var matched bool // whether any replyMatcher considered the reply acceptable.
|
||||||
for el := plist.Front(); el != nil; el = el.Next() {
|
for el := plist.Front(); el != nil; el = el.Next() {
|
||||||
p := el.Value.(*replyMatcher)
|
p := el.Value.(*replyMatcher)
|
||||||
if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
|
if p.from == r.from && p.ptype == r.data.Kind() && p.ip == r.ip {
|
||||||
ok, requestDone := p.callback(r.data)
|
ok, requestDone := p.callback(r.data)
|
||||||
matched = matched || ok
|
matched = matched || ok
|
||||||
p.reply = r.data
|
p.reply = r.data
|
||||||
|
|
@ -500,7 +514,7 @@ func (t *UDPv4) loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
|
func (t *UDPv4) send(toaddr netip.AddrPort, toid enode.ID, req v4wire.Packet) ([]byte, error) {
|
||||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hash, err
|
return hash, err
|
||||||
|
|
@ -508,8 +522,8 @@ func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]b
|
||||||
return hash, t.write(toaddr, toid, req.Name(), packet)
|
return hash, t.write(toaddr, toid, req.Name(), packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
|
func (t *UDPv4) write(toaddr netip.AddrPort, toid enode.ID, what string, packet []byte) error {
|
||||||
_, err := t.conn.WriteToUDP(packet, toaddr)
|
_, err := t.conn.WriteToUDPAddrPort(packet, toaddr)
|
||||||
t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
|
t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -523,7 +537,7 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
|
||||||
|
|
||||||
buf := make([]byte, maxPacketSize)
|
buf := make([]byte, maxPacketSize)
|
||||||
for {
|
for {
|
||||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
nbytes, from, err := t.conn.ReadFromUDPAddrPort(buf)
|
||||||
if netutil.IsTemporaryError(err) {
|
if netutil.IsTemporaryError(err) {
|
||||||
// Ignore temporary read errors.
|
// Ignore temporary read errors.
|
||||||
t.log.Debug("Temporary UDP read error", "err", err)
|
t.log.Debug("Temporary UDP read error", "err", err)
|
||||||
|
|
@ -544,7 +558,7 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
|
func (t *UDPv4) handlePacket(from netip.AddrPort, buf []byte) error {
|
||||||
rawpacket, fromKey, hash, err := v4wire.Decode(buf)
|
rawpacket, fromKey, hash, err := v4wire.Decode(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
|
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
|
||||||
|
|
@ -563,15 +577,16 @@ func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkBond checks if the given node has a recent enough endpoint proof.
|
// checkBond checks if the given node has a recent enough endpoint proof.
|
||||||
func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
|
func (t *UDPv4) checkBond(id enode.ID, ip netip.AddrPort) bool {
|
||||||
return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
|
return time.Since(t.db.LastPongReceived(id, ip.Addr().AsSlice())) < bondExpiration
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
|
// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
|
||||||
// This ensures there is a valid endpoint proof on the remote end.
|
// This ensures there is a valid endpoint proof on the remote end.
|
||||||
func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
|
func (t *UDPv4) ensureBond(toid enode.ID, toaddr netip.AddrPort) {
|
||||||
tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration
|
ip := toaddr.Addr().AsSlice()
|
||||||
if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures {
|
tooOld := time.Since(t.db.LastPingReceived(toid, ip)) > bondExpiration
|
||||||
|
if tooOld || t.db.FindFails(toid, ip) > maxFindnodeFailures {
|
||||||
rm := t.sendPing(toid, toaddr, nil)
|
rm := t.sendPing(toid, toaddr, nil)
|
||||||
<-rm.errc
|
<-rm.errc
|
||||||
// Wait for them to ping back and process our pong.
|
// Wait for them to ping back and process our pong.
|
||||||
|
|
@ -579,11 +594,11 @@ func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
|
func (t *UDPv4) nodeFromRPC(sender netip.AddrPort, rn v4wire.Node) (*enode.Node, error) {
|
||||||
if rn.UDP <= 1024 {
|
if rn.UDP <= 1024 {
|
||||||
return nil, errLowPort
|
return nil, errLowPort
|
||||||
}
|
}
|
||||||
if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
|
if err := netutil.CheckRelayIP(sender.Addr().AsSlice(), rn.IP); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
|
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
|
||||||
|
|
@ -593,12 +608,12 @@ func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
|
n := enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))
|
||||||
err = n.ValidateComplete()
|
err = n.ValidateComplete()
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeToRPC(n *node) v4wire.Node {
|
func nodeToRPC(n *enode.Node) v4wire.Node {
|
||||||
var key ecdsa.PublicKey
|
var key ecdsa.PublicKey
|
||||||
var ekey v4wire.Pubkey
|
var ekey v4wire.Pubkey
|
||||||
if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
|
if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
|
||||||
|
|
@ -637,14 +652,14 @@ type packetHandlerV4 struct {
|
||||||
senderKey *ecdsa.PublicKey // used for ping
|
senderKey *ecdsa.PublicKey // used for ping
|
||||||
|
|
||||||
// preverify checks whether the packet is valid and should be handled at all.
|
// preverify checks whether the packet is valid and should be handled at all.
|
||||||
preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
|
preverify func(p *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error
|
||||||
// handle handles the packet.
|
// handle handles the packet.
|
||||||
handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
|
handle func(req *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PING/v4
|
// PING/v4
|
||||||
|
|
||||||
func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
func (t *UDPv4) verifyPing(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||||
req := h.Packet.(*v4wire.Ping)
|
req := h.Packet.(*v4wire.Ping)
|
||||||
|
|
||||||
if v4wire.Expired(req.Expiration) {
|
if v4wire.Expired(req.Expiration) {
|
||||||
|
|
@ -658,7 +673,7 @@ func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
|
||||||
req := h.Packet.(*v4wire.Ping)
|
req := h.Packet.(*v4wire.Ping)
|
||||||
|
|
||||||
// Reply.
|
// Reply.
|
||||||
|
|
@ -670,8 +685,9 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ping back if our last pong on file is too far in the past.
|
// Ping back if our last pong on file is too far in the past.
|
||||||
n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
|
fromIP := from.Addr().AsSlice()
|
||||||
if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
|
n := enode.NewV4(h.senderKey, fromIP, int(req.From.TCP), int(from.Port()))
|
||||||
|
if time.Since(t.db.LastPongReceived(n.ID(), fromIP)) > bondExpiration {
|
||||||
t.sendPing(fromID, from, func() {
|
t.sendPing(fromID, from, func() {
|
||||||
t.tab.addInboundNode(n)
|
t.tab.addInboundNode(n)
|
||||||
})
|
})
|
||||||
|
|
@ -680,35 +696,40 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update node database and endpoint predictor.
|
// Update node database and endpoint predictor.
|
||||||
t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now())
|
t.db.UpdateLastPingReceived(n.ID(), fromIP, time.Now())
|
||||||
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
|
fromUDPAddr := &net.UDPAddr{IP: fromIP, Port: int(from.Port())}
|
||||||
|
toUDPAddr := &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}
|
||||||
|
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PONG/v4
|
// PONG/v4
|
||||||
|
|
||||||
func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
func (t *UDPv4) verifyPong(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||||
req := h.Packet.(*v4wire.Pong)
|
req := h.Packet.(*v4wire.Pong)
|
||||||
|
|
||||||
if v4wire.Expired(req.Expiration) {
|
if v4wire.Expired(req.Expiration) {
|
||||||
return errExpired
|
return errExpired
|
||||||
}
|
}
|
||||||
if !t.handleReply(fromID, from.IP, req) {
|
if !t.handleReply(fromID, from.Addr(), req) {
|
||||||
return errUnsolicitedReply
|
return errUnsolicitedReply
|
||||||
}
|
}
|
||||||
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
|
fromIP := from.Addr().AsSlice()
|
||||||
t.db.UpdateLastPongReceived(fromID, from.IP, time.Now())
|
fromUDPAddr := &net.UDPAddr{IP: fromIP, Port: int(from.Port())}
|
||||||
|
toUDPAddr := &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}
|
||||||
|
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
|
||||||
|
t.db.UpdateLastPongReceived(fromID, fromIP, time.Now())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FINDNODE/v4
|
// FINDNODE/v4
|
||||||
|
|
||||||
func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||||
req := h.Packet.(*v4wire.Findnode)
|
req := h.Packet.(*v4wire.Findnode)
|
||||||
|
|
||||||
if v4wire.Expired(req.Expiration) {
|
if v4wire.Expired(req.Expiration) {
|
||||||
return errExpired
|
return errExpired
|
||||||
}
|
}
|
||||||
if !t.checkBond(fromID, from.IP) {
|
if !t.checkBond(fromID, from) {
|
||||||
// No endpoint proof pong exists, we don't process the packet. This prevents an
|
// No endpoint proof pong exists, we don't process the packet. This prevents an
|
||||||
// attack vector where the discovery protocol could be used to amplify traffic in a
|
// attack vector where the discovery protocol could be used to amplify traffic in a
|
||||||
// DDOS attack. A malicious actor would send a findnode request with the IP address
|
// DDOS attack. A malicious actor would send a findnode request with the IP address
|
||||||
|
|
@ -720,7 +741,7 @@ func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
func (t *UDPv4) handleFindnode(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
|
||||||
req := h.Packet.(*v4wire.Findnode)
|
req := h.Packet.(*v4wire.Findnode)
|
||||||
|
|
||||||
// Determine closest nodes.
|
// Determine closest nodes.
|
||||||
|
|
@ -732,7 +753,8 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
|
||||||
p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
|
p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
|
||||||
var sent bool
|
var sent bool
|
||||||
for _, n := range closest {
|
for _, n := range closest {
|
||||||
if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
|
fromIP := from.Addr().AsSlice()
|
||||||
|
if netutil.CheckRelayIP(fromIP, n.IP()) == nil {
|
||||||
p.Nodes = append(p.Nodes, nodeToRPC(n))
|
p.Nodes = append(p.Nodes, nodeToRPC(n))
|
||||||
}
|
}
|
||||||
if len(p.Nodes) == v4wire.MaxNeighbors {
|
if len(p.Nodes) == v4wire.MaxNeighbors {
|
||||||
|
|
@ -748,13 +770,13 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
|
||||||
|
|
||||||
// NEIGHBORS/v4
|
// NEIGHBORS/v4
|
||||||
|
|
||||||
func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||||
req := h.Packet.(*v4wire.Neighbors)
|
req := h.Packet.(*v4wire.Neighbors)
|
||||||
|
|
||||||
if v4wire.Expired(req.Expiration) {
|
if v4wire.Expired(req.Expiration) {
|
||||||
return errExpired
|
return errExpired
|
||||||
}
|
}
|
||||||
if !t.handleReply(fromID, from.IP, h.Packet) {
|
if !t.handleReply(fromID, from.Addr(), h.Packet) {
|
||||||
return errUnsolicitedReply
|
return errUnsolicitedReply
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -762,19 +784,19 @@ func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID en
|
||||||
|
|
||||||
// ENRREQUEST/v4
|
// ENRREQUEST/v4
|
||||||
|
|
||||||
func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||||
req := h.Packet.(*v4wire.ENRRequest)
|
req := h.Packet.(*v4wire.ENRRequest)
|
||||||
|
|
||||||
if v4wire.Expired(req.Expiration) {
|
if v4wire.Expired(req.Expiration) {
|
||||||
return errExpired
|
return errExpired
|
||||||
}
|
}
|
||||||
if !t.checkBond(fromID, from.IP) {
|
if !t.checkBond(fromID, from) {
|
||||||
return errUnknownNode
|
return errUnknownNode
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
|
||||||
t.send(from, fromID, &v4wire.ENRResponse{
|
t.send(from, fromID, &v4wire.ENRResponse{
|
||||||
ReplyTok: mac,
|
ReplyTok: mac,
|
||||||
Record: *t.localNode.Node().Record(),
|
Record: *t.localNode.Node().Record(),
|
||||||
|
|
@ -783,8 +805,8 @@ func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID e
|
||||||
|
|
||||||
// ENRRESPONSE/v4
|
// ENRRESPONSE/v4
|
||||||
|
|
||||||
func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||||
if !t.handleReply(fromID, from.IP, h.Packet) {
|
if !t.handleReply(fromID, from.Addr(), h.Packet) {
|
||||||
return errUnsolicitedReply
|
return errUnsolicitedReply
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -55,7 +56,7 @@ type udpTest struct {
|
||||||
udp *UDPv4
|
udp *UDPv4
|
||||||
sent [][]byte
|
sent [][]byte
|
||||||
localkey, remotekey *ecdsa.PrivateKey
|
localkey, remotekey *ecdsa.PrivateKey
|
||||||
remoteaddr *net.UDPAddr
|
remoteaddr netip.AddrPort
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUDPTest(t *testing.T) *udpTest {
|
func newUDPTest(t *testing.T) *udpTest {
|
||||||
|
|
@ -64,7 +65,7 @@ func newUDPTest(t *testing.T) *udpTest {
|
||||||
pipe: newpipe(),
|
pipe: newpipe(),
|
||||||
localkey: newkey(),
|
localkey: newkey(),
|
||||||
remotekey: newkey(),
|
remotekey: newkey(),
|
||||||
remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
|
remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"),
|
||||||
}
|
}
|
||||||
|
|
||||||
test.db, _ = enode.OpenDB("")
|
test.db, _ = enode.OpenDB("")
|
||||||
|
|
@ -92,7 +93,7 @@ func (test *udpTest) packetIn(wantError error, data v4wire.Packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handles a packet as if it had been sent to the transport by the key/endpoint.
|
// handles a packet as if it had been sent to the transport by the key/endpoint.
|
||||||
func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) {
|
func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr netip.AddrPort, data v4wire.Packet) {
|
||||||
test.t.Helper()
|
test.t.Helper()
|
||||||
|
|
||||||
enc, _, err := v4wire.Encode(key, data)
|
enc, _, err := v4wire.Encode(key, data)
|
||||||
|
|
@ -106,7 +107,7 @@ func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *
|
||||||
}
|
}
|
||||||
|
|
||||||
// waits for a packet to be sent by the transport.
|
// waits for a packet to be sent by the transport.
|
||||||
// validate should have type func(X, *net.UDPAddr, []byte), where X is a packet type.
|
// validate should have type func(X, netip.AddrPort, []byte), where X is a packet type.
|
||||||
func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
|
func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
|
||||||
test.t.Helper()
|
test.t.Helper()
|
||||||
|
|
||||||
|
|
@ -128,7 +129,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
|
||||||
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
|
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)})
|
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(hash)})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +237,7 @@ func TestUDPv4_findnodeTimeout(t *testing.T) {
|
||||||
test := newUDPTest(t)
|
test := newUDPTest(t)
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
|
toaddr := netip.AddrPortFrom(netip.MustParseAddr("1.2.3.4"), 2222)
|
||||||
toid := enode.ID{1, 2, 3, 4}
|
toid := enode.ID{1, 2, 3, 4}
|
||||||
target := v4wire.Pubkey{4, 5, 6, 7}
|
target := v4wire.Pubkey{4, 5, 6, 7}
|
||||||
result, err := test.udp.findnode(toid, toaddr, target)
|
result, err := test.udp.findnode(toid, toaddr, target)
|
||||||
|
|
@ -261,26 +262,25 @@ func TestUDPv4_findnode(t *testing.T) {
|
||||||
for i := 0; i < numCandidates; i++ {
|
for i := 0; i < numCandidates; i++ {
|
||||||
key := newkey()
|
key := newkey()
|
||||||
ip := net.IP{10, 13, 0, byte(i)}
|
ip := net.IP{10, 13, 0, byte(i)}
|
||||||
n := wrapNode(enode.NewV4(&key.PublicKey, ip, 0, 2000))
|
n := enode.NewV4(&key.PublicKey, ip, 0, 2000)
|
||||||
// Ensure half of table content isn't verified live yet.
|
// Ensure half of table content isn't verified live yet.
|
||||||
if i > numCandidates/2 {
|
if i > numCandidates/2 {
|
||||||
n.isValidatedLive = true
|
|
||||||
live[n.ID()] = true
|
live[n.ID()] = true
|
||||||
}
|
}
|
||||||
|
test.table.addFoundNode(n, live[n.ID()])
|
||||||
nodes.push(n, numCandidates)
|
nodes.push(n, numCandidates)
|
||||||
}
|
}
|
||||||
fillTable(test.table, nodes.entries, false)
|
|
||||||
|
|
||||||
// ensure there's a bond with the test node,
|
// ensure there's a bond with the test node,
|
||||||
// findnode won't be accepted otherwise.
|
// findnode won't be accepted otherwise.
|
||||||
remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID()
|
remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID()
|
||||||
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now())
|
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.Addr().AsSlice(), time.Now())
|
||||||
|
|
||||||
// check that closest neighbors are returned.
|
// check that closest neighbors are returned.
|
||||||
expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
|
expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
|
||||||
test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
|
test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
|
||||||
waitNeighbors := func(want []*node) {
|
waitNeighbors := func(want []*enode.Node) {
|
||||||
test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Neighbors, to netip.AddrPort, hash []byte) {
|
||||||
if len(p.Nodes) != len(want) {
|
if len(p.Nodes) != len(want) {
|
||||||
t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), len(want))
|
t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), len(want))
|
||||||
return
|
return
|
||||||
|
|
@ -309,10 +309,10 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
|
rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
|
||||||
test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now())
|
test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.Addr().AsSlice(), time.Now())
|
||||||
|
|
||||||
// queue a pending findnode request
|
// queue a pending findnode request
|
||||||
resultc, errc := make(chan []*node, 1), make(chan error, 1)
|
resultc, errc := make(chan []*enode.Node, 1), make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
rid := encodePubkey(&test.remotekey.PublicKey).id()
|
rid := encodePubkey(&test.remotekey.PublicKey).id()
|
||||||
ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget)
|
ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget)
|
||||||
|
|
@ -325,18 +325,18 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
|
||||||
|
|
||||||
// wait for the findnode to be sent.
|
// wait for the findnode to be sent.
|
||||||
// after it is sent, the transport is waiting for a reply
|
// after it is sent, the transport is waiting for a reply
|
||||||
test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Findnode, to netip.AddrPort, hash []byte) {
|
||||||
if p.Target != testTarget {
|
if p.Target != testTarget {
|
||||||
t.Errorf("wrong target: got %v, want %v", p.Target, testTarget)
|
t.Errorf("wrong target: got %v, want %v", p.Target, testTarget)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// send the reply as two packets.
|
// send the reply as two packets.
|
||||||
list := []*node{
|
list := []*enode.Node{
|
||||||
wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
|
enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"),
|
||||||
wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
|
enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"),
|
||||||
wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
|
enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"),
|
||||||
wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
|
enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"),
|
||||||
}
|
}
|
||||||
rpclist := make([]v4wire.Node, len(list))
|
rpclist := make([]v4wire.Node, len(list))
|
||||||
for i := range list {
|
for i := range list {
|
||||||
|
|
@ -368,8 +368,8 @@ func TestUDPv4_pingMatch(t *testing.T) {
|
||||||
crand.Read(randToken)
|
crand.Read(randToken)
|
||||||
|
|
||||||
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
||||||
test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
|
test.waitPacketOut(func(*v4wire.Pong, netip.AddrPort, []byte) {})
|
||||||
test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {})
|
test.waitPacketOut(func(*v4wire.Ping, netip.AddrPort, []byte) {})
|
||||||
test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
|
test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,10 +379,10 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
||||||
test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
|
test.waitPacketOut(func(*v4wire.Pong, netip.AddrPort, []byte) {})
|
||||||
|
|
||||||
test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) {
|
||||||
wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000}
|
wrongAddr := netip.MustParseAddrPort("33.44.1.2:30000")
|
||||||
test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{
|
test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{
|
||||||
ReplyTok: hash,
|
ReplyTok: hash,
|
||||||
To: testLocalAnnounced,
|
To: testLocalAnnounced,
|
||||||
|
|
@ -393,41 +393,36 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
|
||||||
|
|
||||||
func TestUDPv4_successfulPing(t *testing.T) {
|
func TestUDPv4_successfulPing(t *testing.T) {
|
||||||
test := newUDPTest(t)
|
test := newUDPTest(t)
|
||||||
added := make(chan *node, 1)
|
added := make(chan *tableNode, 1)
|
||||||
test.table.nodeAddedHook = func(b *bucket, n *node) { added <- n }
|
test.table.nodeAddedHook = func(b *bucket, n *tableNode) { added <- n }
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
// The remote side sends a ping packet to initiate the exchange.
|
// The remote side sends a ping packet to initiate the exchange.
|
||||||
go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
||||||
|
|
||||||
// The ping is replied to.
|
// The ping is replied to.
|
||||||
test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Pong, to netip.AddrPort, hash []byte) {
|
||||||
pinghash := test.sent[0][:32]
|
pinghash := test.sent[0][:32]
|
||||||
if !bytes.Equal(p.ReplyTok, pinghash) {
|
if !bytes.Equal(p.ReplyTok, pinghash) {
|
||||||
t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash)
|
t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash)
|
||||||
}
|
}
|
||||||
wantTo := v4wire.Endpoint{
|
// The mirrored UDP address is the UDP packet sender.
|
||||||
// The mirrored UDP address is the UDP packet sender
|
// The mirrored TCP port is the one from the ping packet.
|
||||||
IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port),
|
wantTo := v4wire.NewEndpoint(test.remoteaddr, testRemote.TCP)
|
||||||
// The mirrored TCP port is the one from the ping packet
|
|
||||||
TCP: testRemote.TCP,
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(p.To, wantTo) {
|
if !reflect.DeepEqual(p.To, wantTo) {
|
||||||
t.Errorf("got pong.To %v, want %v", p.To, wantTo)
|
t.Errorf("got pong.To %v, want %v", p.To, wantTo)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Remote is unknown, the table pings back.
|
// Remote is unknown, the table pings back.
|
||||||
test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) {
|
||||||
if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
|
wantFrom := test.udp.ourEndpoint()
|
||||||
|
wantFrom.IP = net.IP{}
|
||||||
|
if !reflect.DeepEqual(p.From, wantFrom) {
|
||||||
t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
|
t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
|
||||||
}
|
}
|
||||||
wantTo := v4wire.Endpoint{
|
|
||||||
// The mirrored UDP address is the UDP packet sender.
|
// The mirrored UDP address is the UDP packet sender.
|
||||||
IP: test.remoteaddr.IP,
|
wantTo := v4wire.NewEndpoint(test.remoteaddr, 0)
|
||||||
UDP: uint16(test.remoteaddr.Port),
|
|
||||||
TCP: 0,
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(p.To, wantTo) {
|
if !reflect.DeepEqual(p.To, wantTo) {
|
||||||
t.Errorf("got ping.To %v, want %v", p.To, wantTo)
|
t.Errorf("got ping.To %v, want %v", p.To, wantTo)
|
||||||
}
|
}
|
||||||
|
|
@ -442,11 +437,11 @@ func TestUDPv4_successfulPing(t *testing.T) {
|
||||||
if n.ID() != rid {
|
if n.ID() != rid {
|
||||||
t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid)
|
t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid)
|
||||||
}
|
}
|
||||||
if !n.IP().Equal(test.remoteaddr.IP) {
|
if !n.IP().Equal(test.remoteaddr.Addr().AsSlice()) {
|
||||||
t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP)
|
t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.Addr())
|
||||||
}
|
}
|
||||||
if n.UDP() != test.remoteaddr.Port {
|
if n.UDP() != int(test.remoteaddr.Port()) {
|
||||||
t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port)
|
t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port())
|
||||||
}
|
}
|
||||||
if n.TCP() != int(testRemote.TCP) {
|
if n.TCP() != int(testRemote.TCP) {
|
||||||
t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP)
|
t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP)
|
||||||
|
|
@ -469,12 +464,12 @@ func TestUDPv4_EIP868(t *testing.T) {
|
||||||
|
|
||||||
// Perform endpoint proof and check for sequence number in packet tail.
|
// Perform endpoint proof and check for sequence number in packet tail.
|
||||||
test.packetIn(nil, &v4wire.Ping{Expiration: futureExp})
|
test.packetIn(nil, &v4wire.Ping{Expiration: futureExp})
|
||||||
test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Pong, addr netip.AddrPort, hash []byte) {
|
||||||
if p.ENRSeq != wantNode.Seq() {
|
if p.ENRSeq != wantNode.Seq() {
|
||||||
t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq())
|
t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.Ping, addr netip.AddrPort, hash []byte) {
|
||||||
if p.ENRSeq != wantNode.Seq() {
|
if p.ENRSeq != wantNode.Seq() {
|
||||||
t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq())
|
t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq())
|
||||||
}
|
}
|
||||||
|
|
@ -483,7 +478,7 @@ func TestUDPv4_EIP868(t *testing.T) {
|
||||||
|
|
||||||
// Request should work now.
|
// Request should work now.
|
||||||
test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp})
|
test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp})
|
||||||
test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) {
|
test.waitPacketOut(func(p *v4wire.ENRResponse, addr netip.AddrPort, hash []byte) {
|
||||||
n, err := enode.New(enode.ValidSchemes, &p.Record)
|
n, err := enode.New(enode.ValidSchemes, &p.Record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("invalid record: %v", err)
|
t.Fatalf("invalid record: %v", err)
|
||||||
|
|
@ -584,7 +579,7 @@ type dgramPipe struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type dgram struct {
|
type dgram struct {
|
||||||
to net.UDPAddr
|
to netip.AddrPort
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -597,8 +592,8 @@ func newpipe() *dgramPipe {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToUDP queues a datagram.
|
// WriteToUDPAddrPort queues a datagram.
|
||||||
func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
|
func (c *dgramPipe) WriteToUDPAddrPort(b []byte, to netip.AddrPort) (n int, err error) {
|
||||||
msg := make([]byte, len(b))
|
msg := make([]byte, len(b))
|
||||||
copy(msg, b)
|
copy(msg, b)
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|
@ -606,15 +601,15 @@ func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
|
||||||
if c.closed {
|
if c.closed {
|
||||||
return 0, errors.New("closed")
|
return 0, errors.New("closed")
|
||||||
}
|
}
|
||||||
c.queue = append(c.queue, dgram{*to, b})
|
c.queue = append(c.queue, dgram{to, b})
|
||||||
c.cond.Signal()
|
c.cond.Signal()
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFromUDP just hangs until the pipe is closed.
|
// ReadFromUDPAddrPort just hangs until the pipe is closed.
|
||||||
func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
func (c *dgramPipe) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||||
<-c.closing
|
<-c.closing
|
||||||
return 0, nil, io.EOF
|
return 0, netip.AddrPort{}, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dgramPipe) Close() error {
|
func (c *dgramPipe) Close() error {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -150,14 +151,15 @@ type Endpoint struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEndpoint creates an endpoint.
|
// NewEndpoint creates an endpoint.
|
||||||
func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
|
func NewEndpoint(addr netip.AddrPort, tcpPort uint16) Endpoint {
|
||||||
ip := net.IP{}
|
var ip net.IP
|
||||||
if ip4 := addr.IP.To4(); ip4 != nil {
|
if addr.Addr().Is4() || addr.Addr().Is4In6() {
|
||||||
ip = ip4
|
ip4 := addr.Addr().As4()
|
||||||
} else if ip6 := addr.IP.To16(); ip6 != nil {
|
ip = ip4[:]
|
||||||
ip = ip6
|
} else {
|
||||||
|
ip = addr.Addr().AsSlice()
|
||||||
}
|
}
|
||||||
return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
|
return Endpoint{IP: ip, UDP: addr.Port(), TCP: tcpPort}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Packet interface {
|
type Packet interface {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package discover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRequest handles a talk request.
|
// handleRequest handles a talk request.
|
||||||
func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.TalkRequest) {
|
func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
handler, ok := t.handlers[req.Protocol]
|
handler, ok := t.handlers[req.Protocol]
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
|
@ -88,7 +89,8 @@ func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.T
|
||||||
case <-t.slots:
|
case <-t.slots:
|
||||||
go func() {
|
go func() {
|
||||||
defer func() { t.slots <- struct{}{} }()
|
defer func() { t.slots <- struct{}{} }()
|
||||||
respMessage := handler(id, addr, req.Message)
|
udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())}
|
||||||
|
respMessage := handler(id, udpAddr, req.Message)
|
||||||
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
|
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
|
||||||
t.transport.sendFromAnotherThread(id, addr, resp)
|
t.transport.sendFromAnotherThread(id, addr, resp)
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -101,14 +102,14 @@ type UDPv5 struct {
|
||||||
|
|
||||||
type sendRequest struct {
|
type sendRequest struct {
|
||||||
destID enode.ID
|
destID enode.ID
|
||||||
destAddr *net.UDPAddr
|
destAddr netip.AddrPort
|
||||||
msg v5wire.Packet
|
msg v5wire.Packet
|
||||||
}
|
}
|
||||||
|
|
||||||
// callV5 represents a remote procedure call against another node.
|
// callV5 represents a remote procedure call against another node.
|
||||||
type callV5 struct {
|
type callV5 struct {
|
||||||
id enode.ID
|
id enode.ID
|
||||||
addr *net.UDPAddr
|
addr netip.AddrPort
|
||||||
node *enode.Node // This is required to perform handshakes.
|
node *enode.Node // This is required to perform handshakes.
|
||||||
|
|
||||||
packet v5wire.Packet
|
packet v5wire.Packet
|
||||||
|
|
@ -233,7 +234,7 @@ func (t *UDPv5) AllNodes() []*enode.Node {
|
||||||
|
|
||||||
for _, b := range &t.tab.buckets {
|
for _, b := range &t.tab.buckets {
|
||||||
for _, n := range b.entries {
|
for _, n := range b.entries {
|
||||||
nodes = append(nodes, unwrapNode(n))
|
nodes = append(nodes, n.Node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nodes
|
return nodes
|
||||||
|
|
@ -266,7 +267,7 @@ func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]b
|
||||||
}
|
}
|
||||||
|
|
||||||
// TalkRequestToID sends a talk request to a node and waits for a response.
|
// TalkRequestToID sends a talk request to a node and waits for a response.
|
||||||
func (t *UDPv5) TalkRequestToID(id enode.ID, addr *net.UDPAddr, protocol string, request []byte) ([]byte, error) {
|
func (t *UDPv5) TalkRequestToID(id enode.ID, addr netip.AddrPort, protocol string, request []byte) ([]byte, error) {
|
||||||
req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
|
req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
|
||||||
resp := t.callToID(id, addr, v5wire.TalkResponseMsg, req)
|
resp := t.callToID(id, addr, v5wire.TalkResponseMsg, req)
|
||||||
defer t.callDone(resp)
|
defer t.callDone(resp)
|
||||||
|
|
@ -314,26 +315,26 @@ func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
|
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
|
||||||
return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
|
return newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
|
||||||
return t.lookupWorker(n, target)
|
return t.lookupWorker(n, target)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupWorker performs FINDNODE calls against a single node during lookup.
|
// lookupWorker performs FINDNODE calls against a single node during lookup.
|
||||||
func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
|
func (t *UDPv5) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.Node, error) {
|
||||||
var (
|
var (
|
||||||
dists = lookupDistances(target, destNode.ID())
|
dists = lookupDistances(target, destNode.ID())
|
||||||
nodes = nodesByDistance{target: target}
|
nodes = nodesByDistance{target: target}
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
var r []*enode.Node
|
var r []*enode.Node
|
||||||
r, err = t.findnode(unwrapNode(destNode), dists)
|
r, err = t.findnode(destNode, dists)
|
||||||
if errors.Is(err, errClosed) {
|
if errors.Is(err, errClosed) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, n := range r {
|
for _, n := range r {
|
||||||
if n.ID() != t.Self().ID() {
|
if n.ID() != t.Self().ID() {
|
||||||
nodes.push(wrapNode(n), findnodeResultLimit)
|
nodes.push(n, findnodeResultLimit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nodes.entries, err
|
return nodes.entries, err
|
||||||
|
|
@ -427,7 +428,7 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := netutil.CheckRelayIP(c.addr.IP, node.IP()); err != nil {
|
if err := netutil.CheckRelayIP(c.addr.Addr().AsSlice(), node.IP()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) {
|
if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) {
|
||||||
|
|
@ -452,14 +453,14 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
|
||||||
// callToNode sends the given call and sets up a handler for response packets (of message
|
// callToNode sends the given call and sets up a handler for response packets (of message
|
||||||
// type responseType). Responses are dispatched to the call's response channel.
|
// type responseType). Responses are dispatched to the call's response channel.
|
||||||
func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 {
|
func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 {
|
||||||
addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
addr, _ := n.UDPEndpoint()
|
||||||
c := &callV5{id: n.ID(), addr: addr, node: n}
|
c := &callV5{id: n.ID(), addr: addr, node: n}
|
||||||
t.initCall(c, responseType, req)
|
t.initCall(c, responseType, req)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// callToID is like callToNode, but for cases where the node record is not available.
|
// callToID is like callToNode, but for cases where the node record is not available.
|
||||||
func (t *UDPv5) callToID(id enode.ID, addr *net.UDPAddr, responseType byte, req v5wire.Packet) *callV5 {
|
func (t *UDPv5) callToID(id enode.ID, addr netip.AddrPort, responseType byte, req v5wire.Packet) *callV5 {
|
||||||
c := &callV5{id: id, addr: addr}
|
c := &callV5{id: id, addr: addr}
|
||||||
t.initCall(c, responseType, req)
|
t.initCall(c, responseType, req)
|
||||||
return c
|
return c
|
||||||
|
|
@ -619,12 +620,12 @@ func (t *UDPv5) sendCall(c *callV5) {
|
||||||
|
|
||||||
// sendResponse sends a response packet to the given node.
|
// sendResponse sends a response packet to the given node.
|
||||||
// This doesn't trigger a handshake even if no keys are available.
|
// This doesn't trigger a handshake even if no keys are available.
|
||||||
func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
|
func (t *UDPv5) sendResponse(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet) error {
|
||||||
_, err := t.send(toID, toAddr, packet, nil)
|
_, err := t.send(toID, toAddr, packet, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) {
|
func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet) {
|
||||||
select {
|
select {
|
||||||
case t.sendCh <- sendRequest{toID, toAddr, packet}:
|
case t.sendCh <- sendRequest{toID, toAddr, packet}:
|
||||||
case <-t.closeCtx.Done():
|
case <-t.closeCtx.Done():
|
||||||
|
|
@ -632,7 +633,7 @@ func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet
|
||||||
}
|
}
|
||||||
|
|
||||||
// send sends a packet to the given node.
|
// send sends a packet to the given node.
|
||||||
func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
|
func (t *UDPv5) send(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
|
||||||
addr := toAddr.String()
|
addr := toAddr.String()
|
||||||
t.logcontext = append(t.logcontext[:0], "id", toID, "addr", addr)
|
t.logcontext = append(t.logcontext[:0], "id", toID, "addr", addr)
|
||||||
t.logcontext = packet.AppendLogInfo(t.logcontext)
|
t.logcontext = packet.AppendLogInfo(t.logcontext)
|
||||||
|
|
@ -644,7 +645,7 @@ func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c
|
||||||
return nonce, err
|
return nonce, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = t.conn.WriteToUDP(enc, toAddr)
|
_, err = t.conn.WriteToUDPAddrPort(enc, toAddr)
|
||||||
t.log.Trace(">> "+packet.Name(), t.logcontext...)
|
t.log.Trace(">> "+packet.Name(), t.logcontext...)
|
||||||
return nonce, err
|
return nonce, err
|
||||||
}
|
}
|
||||||
|
|
@ -655,7 +656,7 @@ func (t *UDPv5) readLoop() {
|
||||||
|
|
||||||
buf := make([]byte, maxPacketSize)
|
buf := make([]byte, maxPacketSize)
|
||||||
for range t.readNextCh {
|
for range t.readNextCh {
|
||||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
nbytes, from, err := t.conn.ReadFromUDPAddrPort(buf)
|
||||||
if netutil.IsTemporaryError(err) {
|
if netutil.IsTemporaryError(err) {
|
||||||
// Ignore temporary read errors.
|
// Ignore temporary read errors.
|
||||||
t.log.Debug("Temporary UDP read error", "err", err)
|
t.log.Debug("Temporary UDP read error", "err", err)
|
||||||
|
|
@ -672,7 +673,7 @@ func (t *UDPv5) readLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatchReadPacket sends a packet into the dispatch loop.
|
// dispatchReadPacket sends a packet into the dispatch loop.
|
||||||
func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
|
func (t *UDPv5) dispatchReadPacket(from netip.AddrPort, content []byte) bool {
|
||||||
select {
|
select {
|
||||||
case t.packetInCh <- ReadPacket{content, from}:
|
case t.packetInCh <- ReadPacket{content, from}:
|
||||||
return true
|
return true
|
||||||
|
|
@ -682,7 +683,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePacket decodes and processes an incoming packet from the network.
|
// handlePacket decodes and processes an incoming packet from the network.
|
||||||
func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr netip.AddrPort) error {
|
||||||
addr := fromAddr.String()
|
addr := fromAddr.String()
|
||||||
fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
|
fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -699,7 +700,7 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
||||||
}
|
}
|
||||||
if fromNode != nil {
|
if fromNode != nil {
|
||||||
// Handshake succeeded, add to table.
|
// Handshake succeeded, add to table.
|
||||||
t.tab.addInboundNode(wrapNode(fromNode))
|
t.tab.addInboundNode(fromNode)
|
||||||
}
|
}
|
||||||
if packet.Kind() != v5wire.WhoareyouPacket {
|
if packet.Kind() != v5wire.WhoareyouPacket {
|
||||||
// WHOAREYOU logged separately to report errors.
|
// WHOAREYOU logged separately to report errors.
|
||||||
|
|
@ -712,13 +713,13 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleCallResponse dispatches a response packet to the call waiting for it.
|
// handleCallResponse dispatches a response packet to the call waiting for it.
|
||||||
func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
|
func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr netip.AddrPort, p v5wire.Packet) bool {
|
||||||
ac := t.activeCallByNode[fromID]
|
ac := t.activeCallByNode[fromID]
|
||||||
if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
|
if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
|
||||||
t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
|
t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !fromAddr.IP.Equal(ac.addr.IP) || fromAddr.Port != ac.addr.Port {
|
if fromAddr != ac.addr {
|
||||||
t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
|
t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -743,7 +744,7 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle processes incoming packets according to their message type.
|
// handle processes incoming packets according to their message type.
|
||||||
func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
|
func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||||
switch p := p.(type) {
|
switch p := p.(type) {
|
||||||
case *v5wire.Unknown:
|
case *v5wire.Unknown:
|
||||||
t.handleUnknown(p, fromID, fromAddr)
|
t.handleUnknown(p, fromID, fromAddr)
|
||||||
|
|
@ -753,7 +754,9 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr)
|
||||||
t.handlePing(p, fromID, fromAddr)
|
t.handlePing(p, fromID, fromAddr)
|
||||||
case *v5wire.Pong:
|
case *v5wire.Pong:
|
||||||
if t.handleCallResponse(fromID, fromAddr, p) {
|
if t.handleCallResponse(fromID, fromAddr, p) {
|
||||||
t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
|
fromUDPAddr := &net.UDPAddr{IP: fromAddr.Addr().AsSlice(), Port: int(fromAddr.Port())}
|
||||||
|
toUDPAddr := &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}
|
||||||
|
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
|
||||||
}
|
}
|
||||||
case *v5wire.Findnode:
|
case *v5wire.Findnode:
|
||||||
t.handleFindnode(p, fromID, fromAddr)
|
t.handleFindnode(p, fromID, fromAddr)
|
||||||
|
|
@ -767,7 +770,7 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUnknown initiates a handshake by responding with WHOAREYOU.
|
// handleUnknown initiates a handshake by responding with WHOAREYOU.
|
||||||
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
|
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||||
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
|
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
|
||||||
crand.Read(challenge.IDNonce[:])
|
crand.Read(challenge.IDNonce[:])
|
||||||
if n := t.getNode(fromID); n != nil {
|
if n := t.getNode(fromID); n != nil {
|
||||||
|
|
@ -783,7 +786,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleWhoareyou resends the active call as a handshake packet.
|
// handleWhoareyou resends the active call as a handshake packet.
|
||||||
func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
|
func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||||
c, err := t.matchWithCall(fromID, p.Nonce)
|
c, err := t.matchWithCall(fromID, p.Nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
|
t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
|
||||||
|
|
@ -817,32 +820,35 @@ func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePing sends a PONG response.
|
// handlePing sends a PONG response.
|
||||||
func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
|
func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||||
remoteIP := fromAddr.IP
|
var remoteIP net.IP
|
||||||
// Handle IPv4 mapped IPv6 addresses in the
|
// Handle IPv4 mapped IPv6 addresses in the event the local node is binded
|
||||||
// event the local node is binded to an
|
// to an ipv6 interface.
|
||||||
// ipv6 interface.
|
if fromAddr.Addr().Is4() || fromAddr.Addr().Is4In6() {
|
||||||
if remoteIP.To4() != nil {
|
ip4 := fromAddr.Addr().As4()
|
||||||
remoteIP = remoteIP.To4()
|
remoteIP = ip4[:]
|
||||||
|
} else {
|
||||||
|
remoteIP = fromAddr.Addr().AsSlice()
|
||||||
}
|
}
|
||||||
t.sendResponse(fromID, fromAddr, &v5wire.Pong{
|
t.sendResponse(fromID, fromAddr, &v5wire.Pong{
|
||||||
ReqID: p.ReqID,
|
ReqID: p.ReqID,
|
||||||
ToIP: remoteIP,
|
ToIP: remoteIP,
|
||||||
ToPort: uint16(fromAddr.Port),
|
ToPort: fromAddr.Port(),
|
||||||
ENRSeq: t.localNode.Node().Seq(),
|
ENRSeq: t.localNode.Node().Seq(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleFindnode returns nodes to the requester.
|
// handleFindnode returns nodes to the requester.
|
||||||
func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
|
func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||||
nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
|
nodes := t.collectTableNodes(fromAddr.Addr(), p.Distances, findnodeResultLimit)
|
||||||
for _, resp := range packNodes(p.ReqID, nodes) {
|
for _, resp := range packNodes(p.ReqID, nodes) {
|
||||||
t.sendResponse(fromID, fromAddr, resp)
|
t.sendResponse(fromID, fromAddr, resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectTableNodes creates a FINDNODE result set for the given distances.
|
// collectTableNodes creates a FINDNODE result set for the given distances.
|
||||||
func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
|
func (t *UDPv5) collectTableNodes(rip netip.Addr, distances []uint, limit int) []*enode.Node {
|
||||||
|
ripSlice := rip.AsSlice()
|
||||||
var bn []*enode.Node
|
var bn []*enode.Node
|
||||||
var nodes []*enode.Node
|
var nodes []*enode.Node
|
||||||
var processed = make(map[uint]struct{})
|
var processed = make(map[uint]struct{})
|
||||||
|
|
@ -857,7 +863,7 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en
|
||||||
for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
|
for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
|
||||||
// Apply some pre-checks to avoid sending invalid nodes.
|
// Apply some pre-checks to avoid sending invalid nodes.
|
||||||
// Note liveness is checked by appendLiveNodes.
|
// Note liveness is checked by appendLiveNodes.
|
||||||
if netutil.CheckRelayIP(rip, n.IP()) != nil {
|
if netutil.CheckRelayIP(ripSlice, n.IP()) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nodes = append(nodes, n)
|
nodes = append(nodes, n)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -103,7 +104,7 @@ func TestUDPv5_pingHandling(t *testing.T) {
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
test.packetIn(&v5wire.Ping{ReqID: []byte("foo")})
|
test.packetIn(&v5wire.Ping{ReqID: []byte("foo")})
|
||||||
test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Pong, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if !bytes.Equal(p.ReqID, []byte("foo")) {
|
if !bytes.Equal(p.ReqID, []byte("foo")) {
|
||||||
t.Error("wrong request ID in response:", p.ReqID)
|
t.Error("wrong request ID in response:", p.ReqID)
|
||||||
}
|
}
|
||||||
|
|
@ -135,16 +136,16 @@ func TestUDPv5_unknownPacket(t *testing.T) {
|
||||||
|
|
||||||
// Unknown packet from unknown node.
|
// Unknown packet from unknown node.
|
||||||
test.packetIn(&v5wire.Unknown{Nonce: nonce})
|
test.packetIn(&v5wire.Unknown{Nonce: nonce})
|
||||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
check(p, 0)
|
check(p, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Make node known.
|
// Make node known.
|
||||||
n := test.getNode(test.remotekey, test.remoteaddr).Node()
|
n := test.getNode(test.remotekey, test.remoteaddr).Node()
|
||||||
test.table.addFoundNode(wrapNode(n))
|
test.table.addFoundNode(n, false)
|
||||||
|
|
||||||
test.packetIn(&v5wire.Unknown{Nonce: nonce})
|
test.packetIn(&v5wire.Unknown{Nonce: nonce})
|
||||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
check(p, n.Seq())
|
check(p, n.Seq())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -159,9 +160,9 @@ func TestUDPv5_findnodeHandling(t *testing.T) {
|
||||||
nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16)
|
nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16)
|
||||||
nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
|
nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
|
||||||
nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
|
nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
|
||||||
fillTable(test.table, wrapNodes(nodes253), true)
|
fillTable(test.table, nodes253, true)
|
||||||
fillTable(test.table, wrapNodes(nodes249), true)
|
fillTable(test.table, nodes249, true)
|
||||||
fillTable(test.table, wrapNodes(nodes248), true)
|
fillTable(test.table, nodes248, true)
|
||||||
|
|
||||||
// Requesting with distance zero should return the node's own record.
|
// Requesting with distance zero should return the node's own record.
|
||||||
test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
|
test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
|
||||||
|
|
@ -199,7 +200,7 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Nodes, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if !bytes.Equal(p.ReqID, wantReqID) {
|
if !bytes.Equal(p.ReqID, wantReqID) {
|
||||||
test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID)
|
test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID)
|
||||||
}
|
}
|
||||||
|
|
@ -238,7 +239,7 @@ func TestUDPv5_pingCall(t *testing.T) {
|
||||||
_, err := test.udp.ping(remote)
|
_, err := test.udp.ping(remote)
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {})
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {})
|
||||||
if err := <-done; err != errTimeout {
|
if err := <-done; err != errTimeout {
|
||||||
t.Fatalf("want errTimeout, got %q", err)
|
t.Fatalf("want errTimeout, got %q", err)
|
||||||
}
|
}
|
||||||
|
|
@ -248,7 +249,7 @@ func TestUDPv5_pingCall(t *testing.T) {
|
||||||
_, err := test.udp.ping(remote)
|
_, err := test.udp.ping(remote)
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID})
|
test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID})
|
||||||
})
|
})
|
||||||
if err := <-done; err != nil {
|
if err := <-done; err != nil {
|
||||||
|
|
@ -260,8 +261,8 @@ func TestUDPv5_pingCall(t *testing.T) {
|
||||||
_, err := test.udp.ping(remote)
|
_, err := test.udp.ping(remote)
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101}
|
wrongAddr := netip.MustParseAddrPort("33.44.55.22:10101")
|
||||||
test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID})
|
test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID})
|
||||||
})
|
})
|
||||||
if err := <-done; err != errTimeout {
|
if err := <-done; err != errTimeout {
|
||||||
|
|
@ -291,7 +292,7 @@ func TestUDPv5_findnodeCall(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Serve the responses:
|
// Serve the responses:
|
||||||
test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Findnode, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if !reflect.DeepEqual(p.Distances, distances) {
|
if !reflect.DeepEqual(p.Distances, distances) {
|
||||||
t.Fatalf("wrong distances in request: %v", p.Distances)
|
t.Fatalf("wrong distances in request: %v", p.Distances)
|
||||||
}
|
}
|
||||||
|
|
@ -337,15 +338,15 @@ func TestUDPv5_callResend(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Ping answered by WHOAREYOU.
|
// Ping answered by WHOAREYOU.
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
|
||||||
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
||||||
})
|
})
|
||||||
// Ping should be re-sent.
|
// Ping should be re-sent.
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
|
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
|
||||||
})
|
})
|
||||||
// Answer the other ping.
|
// Answer the other ping.
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
|
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
|
||||||
})
|
})
|
||||||
if err := <-done; err != nil {
|
if err := <-done; err != nil {
|
||||||
|
|
@ -370,11 +371,11 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Ping answered by WHOAREYOU.
|
// Ping answered by WHOAREYOU.
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
|
||||||
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
||||||
})
|
})
|
||||||
// Ping answered by WHOAREYOU again.
|
// Ping answered by WHOAREYOU again.
|
||||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
|
||||||
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
||||||
})
|
})
|
||||||
if err := <-done; err != errTimeout {
|
if err := <-done; err != errTimeout {
|
||||||
|
|
@ -401,7 +402,7 @@ func TestUDPv5_callTimeoutReset(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Serve two responses, slowly.
|
// Serve two responses, slowly.
|
||||||
test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Findnode, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
time.Sleep(respTimeout - 50*time.Millisecond)
|
time.Sleep(respTimeout - 50*time.Millisecond)
|
||||||
test.packetIn(&v5wire.Nodes{
|
test.packetIn(&v5wire.Nodes{
|
||||||
ReqID: p.ReqID,
|
ReqID: p.ReqID,
|
||||||
|
|
@ -439,7 +440,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
|
||||||
Protocol: "test",
|
Protocol: "test",
|
||||||
Message: []byte("test request"),
|
Message: []byte("test request"),
|
||||||
})
|
})
|
||||||
test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.TalkResponse, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if !bytes.Equal(p.ReqID, []byte("foo")) {
|
if !bytes.Equal(p.ReqID, []byte("foo")) {
|
||||||
t.Error("wrong request ID in response:", p.ReqID)
|
t.Error("wrong request ID in response:", p.ReqID)
|
||||||
}
|
}
|
||||||
|
|
@ -458,7 +459,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
|
||||||
Protocol: "wrong",
|
Protocol: "wrong",
|
||||||
Message: []byte("test request"),
|
Message: []byte("test request"),
|
||||||
})
|
})
|
||||||
test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.TalkResponse, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if !bytes.Equal(p.ReqID, []byte("2")) {
|
if !bytes.Equal(p.ReqID, []byte("2")) {
|
||||||
t.Error("wrong request ID in response:", p.ReqID)
|
t.Error("wrong request ID in response:", p.ReqID)
|
||||||
}
|
}
|
||||||
|
|
@ -485,7 +486,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
|
||||||
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
|
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {})
|
test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {})
|
||||||
if err := <-done; err != errTimeout {
|
if err := <-done; err != errTimeout {
|
||||||
t.Fatalf("want errTimeout, got %q", err)
|
t.Fatalf("want errTimeout, got %q", err)
|
||||||
}
|
}
|
||||||
|
|
@ -495,7 +496,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
|
||||||
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
|
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if p.Protocol != "test" {
|
if p.Protocol != "test" {
|
||||||
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
|
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
|
||||||
}
|
}
|
||||||
|
|
@ -516,7 +517,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
|
||||||
_, err := test.udp.TalkRequestToID(remote.ID(), test.remoteaddr, "test", []byte("test request 2"))
|
_, err := test.udp.TalkRequestToID(remote.ID(), test.remoteaddr, "test", []byte("test request 2"))
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if p.Protocol != "test" {
|
if p.Protocol != "test" {
|
||||||
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
|
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
|
||||||
}
|
}
|
||||||
|
|
@ -583,13 +584,14 @@ func TestUDPv5_lookup(t *testing.T) {
|
||||||
for d, nn := range lookupTestnet.dists {
|
for d, nn := range lookupTestnet.dists {
|
||||||
for i, key := range nn {
|
for i, key := range nn {
|
||||||
n := lookupTestnet.node(d, i)
|
n := lookupTestnet.node(d, i)
|
||||||
test.getNode(key, &net.UDPAddr{IP: n.IP(), Port: n.UDP()})
|
addr, _ := n.UDPEndpoint()
|
||||||
|
test.getNode(key, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seed table with initial node.
|
// Seed table with initial node.
|
||||||
initialNode := lookupTestnet.node(256, 0)
|
initialNode := lookupTestnet.node(256, 0)
|
||||||
fillTable(test.table, []*node{wrapNode(initialNode)}, true)
|
fillTable(test.table, []*enode.Node{initialNode}, true)
|
||||||
|
|
||||||
// Start the lookup.
|
// Start the lookup.
|
||||||
resultC := make(chan []*enode.Node, 1)
|
resultC := make(chan []*enode.Node, 1)
|
||||||
|
|
@ -601,7 +603,7 @@ func TestUDPv5_lookup(t *testing.T) {
|
||||||
// Answer lookup packets.
|
// Answer lookup packets.
|
||||||
asked := make(map[enode.ID]bool)
|
asked := make(map[enode.ID]bool)
|
||||||
for done := false; !done; {
|
for done := false; !done; {
|
||||||
done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) {
|
done = test.waitPacketOut(func(p v5wire.Packet, to netip.AddrPort, _ v5wire.Nonce) {
|
||||||
recipient, key := lookupTestnet.nodeByAddr(to)
|
recipient, key := lookupTestnet.nodeByAddr(to)
|
||||||
switch p := p.(type) {
|
switch p := p.(type) {
|
||||||
case *v5wire.Ping:
|
case *v5wire.Ping:
|
||||||
|
|
@ -652,11 +654,8 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
|
||||||
test := newUDPV5Test(t)
|
test := newUDPV5Test(t)
|
||||||
defer test.close()
|
defer test.close()
|
||||||
|
|
||||||
rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5)
|
rawIP := netip.AddrFrom4([4]byte{0xFF, 0x12, 0x33, 0xE5})
|
||||||
test.remoteaddr = &net.UDPAddr{
|
test.remoteaddr = netip.AddrPortFrom(netip.AddrFrom16(rawIP.As16()), 0)
|
||||||
IP: rawIP.To16(),
|
|
||||||
Port: 0,
|
|
||||||
}
|
|
||||||
remote := test.getNode(test.remotekey, test.remoteaddr).Node()
|
remote := test.getNode(test.remotekey, test.remoteaddr).Node()
|
||||||
done := make(chan struct{}, 1)
|
done := make(chan struct{}, 1)
|
||||||
|
|
||||||
|
|
@ -665,14 +664,14 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
|
||||||
test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr)
|
test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
|
test.waitPacketOut(func(p *v5wire.Pong, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||||
if len(p.ToIP) == net.IPv6len {
|
if len(p.ToIP) == net.IPv6len {
|
||||||
t.Error("Received untruncated ip address")
|
t.Error("Received untruncated ip address")
|
||||||
}
|
}
|
||||||
if len(p.ToIP) != net.IPv4len {
|
if len(p.ToIP) != net.IPv4len {
|
||||||
t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP))
|
t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP))
|
||||||
}
|
}
|
||||||
if !p.ToIP.Equal(rawIP) {
|
if !p.ToIP.Equal(rawIP.AsSlice()) {
|
||||||
t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String())
|
t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -688,9 +687,9 @@ type udpV5Test struct {
|
||||||
db *enode.DB
|
db *enode.DB
|
||||||
udp *UDPv5
|
udp *UDPv5
|
||||||
localkey, remotekey *ecdsa.PrivateKey
|
localkey, remotekey *ecdsa.PrivateKey
|
||||||
remoteaddr *net.UDPAddr
|
remoteaddr netip.AddrPort
|
||||||
nodesByID map[enode.ID]*enode.LocalNode
|
nodesByID map[enode.ID]*enode.LocalNode
|
||||||
nodesByIP map[string]*enode.LocalNode
|
nodesByIP map[netip.Addr]*enode.LocalNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption.
|
// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption.
|
||||||
|
|
@ -750,9 +749,9 @@ func newUDPV5Test(t *testing.T) *udpV5Test {
|
||||||
pipe: newpipe(),
|
pipe: newpipe(),
|
||||||
localkey: newkey(),
|
localkey: newkey(),
|
||||||
remotekey: newkey(),
|
remotekey: newkey(),
|
||||||
remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
|
remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"),
|
||||||
nodesByID: make(map[enode.ID]*enode.LocalNode),
|
nodesByID: make(map[enode.ID]*enode.LocalNode),
|
||||||
nodesByIP: make(map[string]*enode.LocalNode),
|
nodesByIP: make(map[netip.Addr]*enode.LocalNode),
|
||||||
}
|
}
|
||||||
test.db, _ = enode.OpenDB("")
|
test.db, _ = enode.OpenDB("")
|
||||||
ln := enode.NewLocalNode(test.db, test.localkey)
|
ln := enode.NewLocalNode(test.db, test.localkey)
|
||||||
|
|
@ -777,8 +776,8 @@ func (test *udpV5Test) packetIn(packet v5wire.Packet) {
|
||||||
test.packetInFrom(test.remotekey, test.remoteaddr, packet)
|
test.packetInFrom(test.remotekey, test.remoteaddr, packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handles a packet as if it had been sent to the transport by the key/endpoint.
|
// packetInFrom handles a packet as if it had been sent to the transport by the key/endpoint.
|
||||||
func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) {
|
func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr netip.AddrPort, packet v5wire.Packet) {
|
||||||
test.t.Helper()
|
test.t.Helper()
|
||||||
|
|
||||||
ln := test.getNode(key, addr)
|
ln := test.getNode(key, addr)
|
||||||
|
|
@ -793,22 +792,22 @@ func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, pa
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNode ensures the test knows about a node at the given endpoint.
|
// getNode ensures the test knows about a node at the given endpoint.
|
||||||
func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode {
|
func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr netip.AddrPort) *enode.LocalNode {
|
||||||
id := encodePubkey(&key.PublicKey).id()
|
id := encodePubkey(&key.PublicKey).id()
|
||||||
ln := test.nodesByID[id]
|
ln := test.nodesByID[id]
|
||||||
if ln == nil {
|
if ln == nil {
|
||||||
db, _ := enode.OpenDB("")
|
db, _ := enode.OpenDB("")
|
||||||
ln = enode.NewLocalNode(db, key)
|
ln = enode.NewLocalNode(db, key)
|
||||||
ln.SetStaticIP(addr.IP)
|
ln.SetStaticIP(addr.Addr().AsSlice())
|
||||||
ln.Set(enr.UDP(addr.Port))
|
ln.Set(enr.UDP(addr.Port()))
|
||||||
test.nodesByID[id] = ln
|
test.nodesByID[id] = ln
|
||||||
}
|
}
|
||||||
test.nodesByIP[string(addr.IP)] = ln
|
test.nodesByIP[addr.Addr()] = ln
|
||||||
return ln
|
return ln
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitPacketOut waits for the next output packet and handles it using the given 'validate'
|
// waitPacketOut waits for the next output packet and handles it using the given 'validate'
|
||||||
// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is
|
// function. The function must be of type func (X, netip.AddrPort, v5wire.Nonce) where X is
|
||||||
// assignable to packetV5.
|
// assignable to packetV5.
|
||||||
func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
||||||
test.t.Helper()
|
test.t.Helper()
|
||||||
|
|
@ -824,7 +823,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
||||||
test.t.Fatalf("timed out waiting for %v", exptype)
|
test.t.Fatalf("timed out waiting for %v", exptype)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ln := test.nodesByIP[string(dgram.to.IP)]
|
ln := test.nodesByIP[dgram.to.Addr()]
|
||||||
if ln == nil {
|
if ln == nil {
|
||||||
test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to)
|
test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to)
|
||||||
return false
|
return false
|
||||||
|
|
@ -839,7 +838,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
||||||
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
|
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)})
|
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(frame.AuthTag)})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,407 +0,0 @@
|
||||||
// Copyright 2020 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package nodestate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testSetup(flagPersist []bool, fieldType []reflect.Type) (*Setup, []Flags, []Field) {
|
|
||||||
setup := &Setup{}
|
|
||||||
flags := make([]Flags, len(flagPersist))
|
|
||||||
for i, persist := range flagPersist {
|
|
||||||
if persist {
|
|
||||||
flags[i] = setup.NewPersistentFlag(fmt.Sprintf("flag-%d", i))
|
|
||||||
} else {
|
|
||||||
flags[i] = setup.NewFlag(fmt.Sprintf("flag-%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fields := make([]Field, len(fieldType))
|
|
||||||
for i, ftype := range fieldType {
|
|
||||||
switch ftype {
|
|
||||||
case reflect.TypeOf(uint64(0)):
|
|
||||||
fields[i] = setup.NewPersistentField(fmt.Sprintf("field-%d", i), ftype, uint64FieldEnc, uint64FieldDec)
|
|
||||||
case reflect.TypeOf(""):
|
|
||||||
fields[i] = setup.NewPersistentField(fmt.Sprintf("field-%d", i), ftype, stringFieldEnc, stringFieldDec)
|
|
||||||
default:
|
|
||||||
fields[i] = setup.NewField(fmt.Sprintf("field-%d", i), ftype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return setup, flags, fields
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNode(b byte) *enode.Node {
|
|
||||||
r := &enr.Record{}
|
|
||||||
r.SetSig(dummyIdentity{b}, []byte{42})
|
|
||||||
n, _ := enode.New(dummyIdentity{b}, r)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallback(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, _ := testSetup([]bool{false, false, false}, nil)
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
set0 := make(chan struct{}, 1)
|
|
||||||
set1 := make(chan struct{}, 1)
|
|
||||||
set2 := make(chan struct{}, 1)
|
|
||||||
ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { set0 <- struct{}{} })
|
|
||||||
ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) { set1 <- struct{}{} })
|
|
||||||
ns.SubscribeState(flags[2], func(n *enode.Node, oldState, newState Flags) { set2 <- struct{}{} })
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 0)
|
|
||||||
ns.SetState(testNode(1), flags[1], Flags{}, time.Second)
|
|
||||||
ns.SetState(testNode(1), flags[2], Flags{}, 2*time.Second)
|
|
||||||
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
select {
|
|
||||||
case <-set0:
|
|
||||||
case <-set1:
|
|
||||||
case <-set2:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("failed to invoke callback")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPersistentFlags(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, _ := testSetup([]bool{true, true, true, false}, nil)
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
saveNode := make(chan *nodeInfo, 5)
|
|
||||||
ns.saveNodeHook = func(node *nodeInfo) {
|
|
||||||
saveNode <- node
|
|
||||||
}
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, time.Second) // state with timeout should not be saved
|
|
||||||
ns.SetState(testNode(2), flags[1], Flags{}, 0)
|
|
||||||
ns.SetState(testNode(3), flags[2], Flags{}, 0)
|
|
||||||
ns.SetState(testNode(4), flags[3], Flags{}, 0)
|
|
||||||
ns.SetState(testNode(5), flags[0], Flags{}, 0)
|
|
||||||
ns.Persist(testNode(5))
|
|
||||||
select {
|
|
||||||
case <-saveNode:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("Timeout")
|
|
||||||
}
|
|
||||||
ns.Stop()
|
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
select {
|
|
||||||
case <-saveNode:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("Timeout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-saveNode:
|
|
||||||
t.Fatalf("Unexpected saveNode")
|
|
||||||
case <-time.After(time.Millisecond * 100):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetField(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf("")})
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
saveNode := make(chan *nodeInfo, 1)
|
|
||||||
ns.saveNodeHook = func(node *nodeInfo) {
|
|
||||||
saveNode <- node
|
|
||||||
}
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
|
|
||||||
// Set field before setting state
|
|
||||||
ns.SetField(testNode(1), fields[0], "hello world")
|
|
||||||
field := ns.GetField(testNode(1), fields[0])
|
|
||||||
if field == nil {
|
|
||||||
t.Fatalf("Field should be set before setting states")
|
|
||||||
}
|
|
||||||
ns.SetField(testNode(1), fields[0], nil)
|
|
||||||
field = ns.GetField(testNode(1), fields[0])
|
|
||||||
if field != nil {
|
|
||||||
t.Fatalf("Field should be unset")
|
|
||||||
}
|
|
||||||
// Set field after setting state
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 0)
|
|
||||||
ns.SetField(testNode(1), fields[0], "hello world")
|
|
||||||
field = ns.GetField(testNode(1), fields[0])
|
|
||||||
if field == nil {
|
|
||||||
t.Fatalf("Field should be set after setting states")
|
|
||||||
}
|
|
||||||
if err := ns.SetField(testNode(1), fields[0], 123); err == nil {
|
|
||||||
t.Fatalf("Invalid field should be rejected")
|
|
||||||
}
|
|
||||||
// Dirty node should be written back
|
|
||||||
ns.Stop()
|
|
||||||
select {
|
|
||||||
case <-saveNode:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("Timeout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetState(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, _ := testSetup([]bool{false, false, false}, nil)
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
type change struct{ old, new Flags }
|
|
||||||
set := make(chan change, 1)
|
|
||||||
ns.SubscribeState(flags[0].Or(flags[1]), func(n *enode.Node, oldState, newState Flags) {
|
|
||||||
set <- change{
|
|
||||||
old: oldState,
|
|
||||||
new: newState,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
|
|
||||||
check := func(expectOld, expectNew Flags, expectChange bool) {
|
|
||||||
if expectChange {
|
|
||||||
select {
|
|
||||||
case c := <-set:
|
|
||||||
if !c.old.Equals(expectOld) {
|
|
||||||
t.Fatalf("Old state mismatch")
|
|
||||||
}
|
|
||||||
if !c.new.Equals(expectNew) {
|
|
||||||
t.Fatalf("New state mismatch")
|
|
||||||
}
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-set:
|
|
||||||
t.Fatalf("Unexpected change")
|
|
||||||
case <-time.After(time.Millisecond * 100):
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 0)
|
|
||||||
check(Flags{}, flags[0], true)
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), flags[1], Flags{}, 0)
|
|
||||||
check(flags[0], flags[0].Or(flags[1]), true)
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), flags[2], Flags{}, 0)
|
|
||||||
check(Flags{}, Flags{}, false)
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), Flags{}, flags[0], 0)
|
|
||||||
check(flags[0].Or(flags[1]), flags[1], true)
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), Flags{}, flags[1], 0)
|
|
||||||
check(flags[1], Flags{}, true)
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), Flags{}, flags[2], 0)
|
|
||||||
check(Flags{}, Flags{}, false)
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), flags[0].Or(flags[1]), Flags{}, time.Second)
|
|
||||||
check(Flags{}, flags[0].Or(flags[1]), true)
|
|
||||||
clock.Run(time.Second)
|
|
||||||
check(flags[0].Or(flags[1]), Flags{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uint64FieldEnc(field interface{}) ([]byte, error) {
|
|
||||||
if u, ok := field.(uint64); ok {
|
|
||||||
enc, err := rlp.EncodeToBytes(&u)
|
|
||||||
return enc, err
|
|
||||||
}
|
|
||||||
return nil, errors.New("invalid field type")
|
|
||||||
}
|
|
||||||
|
|
||||||
func uint64FieldDec(enc []byte) (interface{}, error) {
|
|
||||||
var u uint64
|
|
||||||
err := rlp.DecodeBytes(enc, &u)
|
|
||||||
return u, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringFieldEnc(field interface{}) ([]byte, error) {
|
|
||||||
if s, ok := field.(string); ok {
|
|
||||||
return []byte(s), nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("invalid field type")
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringFieldDec(enc []byte) (interface{}, error) {
|
|
||||||
return string(enc), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPersistentFields(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf(uint64(0)), reflect.TypeOf("")})
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 0)
|
|
||||||
ns.SetField(testNode(1), fields[0], uint64(100))
|
|
||||||
ns.SetField(testNode(1), fields[1], "hello world")
|
|
||||||
ns.Stop()
|
|
||||||
|
|
||||||
ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
ns2.Start()
|
|
||||||
field0 := ns2.GetField(testNode(1), fields[0])
|
|
||||||
if !reflect.DeepEqual(field0, uint64(100)) {
|
|
||||||
t.Fatalf("Field changed")
|
|
||||||
}
|
|
||||||
field1 := ns2.GetField(testNode(1), fields[1])
|
|
||||||
if !reflect.DeepEqual(field1, "hello world") {
|
|
||||||
t.Fatalf("Field changed")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Version++
|
|
||||||
ns3 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
ns3.Start()
|
|
||||||
if ns3.GetField(testNode(1), fields[0]) != nil {
|
|
||||||
t.Fatalf("Old field version should have been discarded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldSub(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf(uint64(0))})
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
var (
|
|
||||||
lastState Flags
|
|
||||||
lastOldValue, lastNewValue interface{}
|
|
||||||
)
|
|
||||||
ns.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) {
|
|
||||||
lastState, lastOldValue, lastNewValue = state, oldValue, newValue
|
|
||||||
})
|
|
||||||
check := func(state Flags, oldValue, newValue interface{}) {
|
|
||||||
if !lastState.Equals(state) || lastOldValue != oldValue || lastNewValue != newValue {
|
|
||||||
t.Fatalf("Incorrect field sub callback (expected [%v %v %v], got [%v %v %v])", state, oldValue, newValue, lastState, lastOldValue, lastNewValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ns.Start()
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 0)
|
|
||||||
ns.SetField(testNode(1), fields[0], uint64(100))
|
|
||||||
check(flags[0], nil, uint64(100))
|
|
||||||
ns.Stop()
|
|
||||||
check(s.OfflineFlag(), uint64(100), nil)
|
|
||||||
|
|
||||||
ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
ns2.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) {
|
|
||||||
lastState, lastOldValue, lastNewValue = state, oldValue, newValue
|
|
||||||
})
|
|
||||||
ns2.Start()
|
|
||||||
check(s.OfflineFlag(), nil, uint64(100))
|
|
||||||
ns2.SetState(testNode(1), Flags{}, flags[0], 0)
|
|
||||||
ns2.SetField(testNode(1), fields[0], nil)
|
|
||||||
check(Flags{}, uint64(100), nil)
|
|
||||||
ns2.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDuplicatedFlags(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, _ := testSetup([]bool{true}, nil)
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
type change struct{ old, new Flags }
|
|
||||||
set := make(chan change, 1)
|
|
||||||
ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) {
|
|
||||||
set <- change{oldState, newState}
|
|
||||||
})
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
defer ns.Stop()
|
|
||||||
|
|
||||||
check := func(expectOld, expectNew Flags, expectChange bool) {
|
|
||||||
if expectChange {
|
|
||||||
select {
|
|
||||||
case c := <-set:
|
|
||||||
if !c.old.Equals(expectOld) {
|
|
||||||
t.Fatalf("Old state mismatch")
|
|
||||||
}
|
|
||||||
if !c.new.Equals(expectNew) {
|
|
||||||
t.Fatalf("New state mismatch")
|
|
||||||
}
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-set:
|
|
||||||
t.Fatalf("Unexpected change")
|
|
||||||
case <-time.After(time.Millisecond * 100):
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, time.Second)
|
|
||||||
check(Flags{}, flags[0], true)
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 2*time.Second) // extend the timeout to 2s
|
|
||||||
check(Flags{}, flags[0], false)
|
|
||||||
|
|
||||||
clock.Run(2 * time.Second)
|
|
||||||
check(flags[0], Flags{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallbackOrder(t *testing.T) {
|
|
||||||
mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
|
|
||||||
|
|
||||||
s, flags, _ := testSetup([]bool{false, false, false, false}, nil)
|
|
||||||
ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
|
|
||||||
|
|
||||||
ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) {
|
|
||||||
if newState.Equals(flags[0]) {
|
|
||||||
ns.SetStateSub(n, flags[1], Flags{}, 0)
|
|
||||||
ns.SetStateSub(n, flags[2], Flags{}, 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) {
|
|
||||||
if newState.Equals(flags[1]) {
|
|
||||||
ns.SetStateSub(n, flags[3], Flags{}, 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
lastState := Flags{}
|
|
||||||
ns.SubscribeState(MergeFlags(flags[1], flags[2], flags[3]), func(n *enode.Node, oldState, newState Flags) {
|
|
||||||
if !oldState.Equals(lastState) {
|
|
||||||
t.Fatalf("Wrong callback order")
|
|
||||||
}
|
|
||||||
lastState = newState
|
|
||||||
})
|
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
defer ns.Stop()
|
|
||||||
|
|
||||||
ns.SetState(testNode(1), flags[0], Flags{}, 0)
|
|
||||||
}
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -435,11 +436,11 @@ type sharedUDPConn struct {
|
||||||
unhandled chan discover.ReadPacket
|
unhandled chan discover.ReadPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFromUDP implements discover.UDPConn
|
// ReadFromUDPAddrPort implements discover.UDPConn
|
||||||
func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
func (s *sharedUDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||||
packet, ok := <-s.unhandled
|
packet, ok := <-s.unhandled
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, nil, errors.New("connection was closed")
|
return 0, netip.AddrPort{}, errors.New("connection was closed")
|
||||||
}
|
}
|
||||||
l := len(packet.Data)
|
l := len(packet.Data)
|
||||||
if l > len(b) {
|
if l > len(b) {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ import (
|
||||||
//
|
//
|
||||||
// - SimNode, an in-memory node in the same process
|
// - SimNode, an in-memory node in the same process
|
||||||
// - ExecNode, a child process node
|
// - ExecNode, a child process node
|
||||||
// - DockerNode, a node running in a Docker container
|
|
||||||
type Node interface {
|
type Node interface {
|
||||||
// Addr returns the node's address (e.g. an Enode URL)
|
// Addr returns the node's address (e.g. an Enode URL)
|
||||||
Addr() []byte
|
Addr() []byte
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
)
|
)
|
||||||
|
|
||||||
var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
|
var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim" or "exec")`)
|
||||||
|
|
||||||
// main() starts a simulation network which contains nodes running a simple
|
// main() starts a simulation network which contains nodes running a simple
|
||||||
// ping-pong protocol
|
// ping-pong protocol
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ func (db *Database) repairHistory() error {
|
||||||
// all of them. Fix the tests first.
|
// all of them. Fix the tests first.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
freezer, err := rawdb.NewStateFreezer(ancient, false)
|
freezer, err := rawdb.NewStateFreezer(ancient, db.readOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Failed to open state history freezer", "err", err)
|
log.Crit("Failed to open state history freezer", "err", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue