mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-13 15:33:47 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
3f5d78ec8d
74 changed files with 23133 additions and 2300 deletions
|
|
@ -12,7 +12,6 @@ run:
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- goconst
|
|
||||||
- goimports
|
- goimports
|
||||||
- gosimple
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
|
|
@ -39,9 +38,6 @@ linters:
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gofmt:
|
gofmt:
|
||||||
simplify: true
|
simplify: true
|
||||||
goconst:
|
|
||||||
min-len: 3 # minimum length of string constant
|
|
||||||
min-occurrences: 6 # minimum number of occurrences
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
|
|
||||||
12
.travis.yml
12
.travis.yml
|
|
@ -9,18 +9,6 @@ jobs:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# This builder only tests code linters on latest version of Go
|
|
||||||
- stage: lint
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
go: 1.21.x
|
|
||||||
env:
|
|
||||||
- lint
|
|
||||||
git:
|
|
||||||
submodules: false # avoid cloning ethereum/tests
|
|
||||||
script:
|
|
||||||
- go run build/ci.go lint
|
|
||||||
|
|
||||||
# These builders create the Docker sub-images for multi-arch push and each
|
# These builders create the Docker sub-images for multi-arch push and each
|
||||||
# will attempt to push the multi-arch image if they are the last builder
|
# will attempt to push the multi-arch image if they are the last builder
|
||||||
- stage: build
|
- stage: build
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,9 @@ func NewManager(config *Config, backends ...Backend) *Manager {
|
||||||
|
|
||||||
// Close terminates the account manager's internal notification processes.
|
// Close terminates the account manager's internal notification processes.
|
||||||
func (am *Manager) Close() error {
|
func (am *Manager) Close() error {
|
||||||
|
for _, w := range am.wallets {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
errc := make(chan error)
|
errc := make(chan error)
|
||||||
am.quit <- errc
|
am.quit <- errc
|
||||||
return <-errc
|
return <-errc
|
||||||
|
|
|
||||||
|
|
@ -483,6 +483,10 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
|
||||||
w.stateLock.Lock()
|
w.stateLock.Lock()
|
||||||
defer w.stateLock.Unlock()
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
if w.device == nil {
|
||||||
|
return accounts.Account{}, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := w.paths[address]; !ok {
|
if _, ok := w.paths[address]; !ok {
|
||||||
w.accounts = append(w.accounts, account)
|
w.accounts = append(w.accounts, account)
|
||||||
w.paths[address] = make(accounts.DerivationPath, len(path))
|
w.paths[address] = make(accounts.DerivationPath, len(path))
|
||||||
|
|
|
||||||
|
|
@ -22,35 +22,36 @@ e2bc0b3e4b64111ec117295c088bde5f00eeed1567999ff77bc859d7df70078e go1.21.5.linux
|
||||||
bbe603cde7c9dee658f45164b4d06de1eff6e6e6b800100824e7c00d56a9a92f go1.21.5.windows-amd64.zip
|
bbe603cde7c9dee658f45164b4d06de1eff6e6e6b800100824e7c00d56a9a92f go1.21.5.windows-amd64.zip
|
||||||
9b7acca50e674294e43202df4fbc26d5af4d8bc3170a3342a1514f09a2dab5e9 go1.21.5.windows-arm64.zip
|
9b7acca50e674294e43202df4fbc26d5af4d8bc3170a3342a1514f09a2dab5e9 go1.21.5.windows-arm64.zip
|
||||||
|
|
||||||
# version:golangci 1.51.1
|
# version:golangci 1.55.2
|
||||||
# https://github.com/golangci/golangci-lint/releases/
|
# https://github.com/golangci/golangci-lint/releases/
|
||||||
# https://github.com/golangci/golangci-lint/releases/download/v1.51.1/
|
# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/
|
||||||
fba08acc4027f69f07cef48fbff70b8a7ecdfaa1c2aba9ad3fb31d60d9f5d4bc golangci-lint-1.51.1-darwin-amd64.tar.gz
|
632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz
|
||||||
75b8f0ff3a4e68147156be4161a49d4576f1be37a0b506473f8c482140c1e7f2 golangci-lint-1.51.1-darwin-arm64.tar.gz
|
234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz
|
||||||
e06b3459aaed356e1667580be00b05f41f3b2e29685d12cdee571c23e1edb414 golangci-lint-1.51.1-freebsd-386.tar.gz
|
2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz
|
||||||
623ce2d0fa4d35cc2e8d69fa7334227ab592380962a13b4d9cdc77cf41db2008 golangci-lint-1.51.1-freebsd-amd64.tar.gz
|
e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz
|
||||||
131365feb0584cc2736c43192fa673ca50e5b6b765456990cb379ecfb787e568 golangci-lint-1.51.1-freebsd-armv6.tar.gz
|
5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz
|
||||||
98fb627927cbb654f5bf85dcffc5f646666b2ce96ea0fed977c9fb28abd51532 golangci-lint-1.51.1-freebsd-armv7.tar.gz
|
7f21ab1008d05f32c954f99470fc86a83a059e530fe2add1d0b7d8ed4d8992a7 golangci-lint-1.55.2-freebsd-armv7.tar.gz
|
||||||
b36a99702fa762c15840261bc0fb41b4b1b16b8b19b8c0941bae98c85bb0f8b8 golangci-lint-1.51.1-linux-386.tar.gz
|
33ab06139b9219a28251f10821da94423db30285cc2af97494cbb2a281927de9 golangci-lint-1.55.2-illumos-amd64.tar.gz
|
||||||
17aeb26c76820c22efa0e1838b0ab93e90cfedef43fbfc9a2f33f27eb9e5e070 golangci-lint-1.51.1-linux-amd64.tar.gz
|
57ce6f8ce3ad6ee45d7cc3d9a047545a851c2547637834a3fcb086c7b40b1e6b golangci-lint-1.55.2-linux-386.tar.gz
|
||||||
9744bc34e7b8d82ca788b667bfb7155a39b4be9aef43bf9f10318b1372cea338 golangci-lint-1.51.1-linux-arm64.tar.gz
|
ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c golangci-lint-1.55.2-linux-amd64.tar.gz
|
||||||
0dda8dbeb2ff7455a044ec8e347f2fc6d655d2e99d281b3b95e88167031c673d golangci-lint-1.51.1-linux-armv6.tar.gz
|
8eb0cee9b1dbf0eaa49871798c7f8a5b35f2960c52d776a5f31eb7d886b92746 golangci-lint-1.55.2-linux-arm64.tar.gz
|
||||||
0512f311b11d43b8b22989d929f0fe8a2e1e5ebe497f1eb0ff73a0fc3d188fd1 golangci-lint-1.51.1-linux-armv7.tar.gz
|
3195f3e0f37d353fd5bd415cabcd4e263f5c29d3d0ffb176c26ff3d2c75eb3bb golangci-lint-1.55.2-linux-armv6.tar.gz
|
||||||
d767108dcf84a8eaa844df3454cb0f75a492f4e7102ecc2b0a3545cfe073a566 golangci-lint-1.51.1-linux-loong64.tar.gz
|
c823ee36eb1a719e171de1f2f5ca3068033dce8d9817232fd10ed71fd6650406 golangci-lint-1.55.2-linux-armv7.tar.gz
|
||||||
3bd56c54daec16585b2668e0dfabb27af2c2b38cc0fdb46923e2521e1634846b golangci-lint-1.51.1-linux-mips64.tar.gz
|
758a5d2a356dc494bd13ed4c0d4bf5a54a4dc91267ea5ecdd87b86c7ca0624e7 golangci-lint-1.55.2-linux-loong64.tar.gz
|
||||||
f72f5adfa2219e15d2414c9a2966f86e74556cf17a85c727a7fb7770a16cf814 golangci-lint-1.51.1-linux-mips64le.tar.gz
|
2c7b9abdce7cae802a67d583cd7c6dca520bff6d0e17c8535a918e2f2b437aa0 golangci-lint-1.55.2-linux-mips64.tar.gz
|
||||||
e605521dac98096d8737e1997c954f41f1d0d8275b8731f62783d410c23574b9 golangci-lint-1.51.1-linux-ppc64le.tar.gz
|
024e0a15b85352cc27271285526e16a4ab66d3e67afbbe446c9808c06cb8dbed golangci-lint-1.55.2-linux-mips64le.tar.gz
|
||||||
2f683217b814339e74d61ca700922d8407f15addd6d4c5e8b156fbab79f26a87 golangci-lint-1.51.1-linux-riscv64.tar.gz
|
6b00f89ba5506c1de1efdd9fa17c54093013a294fefd8b9b31534db626a672ee golangci-lint-1.55.2-linux-ppc64le.tar.gz
|
||||||
d98528292b65971a3594e5880530e7624597dc9806fcfccdfbe39be411713d63 golangci-lint-1.51.1-linux-s390x.tar.gz
|
0faa0d047d9bf7b703ed3ea65b6117043c93504f9ca1de25ae929d3901c73d4a golangci-lint-1.55.2-linux-riscv64.tar.gz
|
||||||
9bb2d0fe9e692ed0aea4f2537e3e6862b2f6768fe2849a84f4a6ad09da9fd971 golangci-lint-1.51.1-netbsd-386.tar.gz
|
30dec9b22e7d5bb4e9d5ccea96da20f71cd7db3c8cf30b8ddc7cb9174c4d742a golangci-lint-1.55.2-linux-s390x.tar.gz
|
||||||
34cafdcd11ae73ae88d66c33eb8449f5c976fc3e37b44774dbe9c71caa95e592 golangci-lint-1.51.1-netbsd-amd64.tar.gz
|
5a0ede48f79ad707902fdb29be8cd2abd8302dc122b65ebae3fdfc86751c7698 golangci-lint-1.55.2-netbsd-386.tar.gz
|
||||||
f8b4e1e47ac17caafe8a5f32f975a2b6a7cb14c27c0f73c1fb15c20ca91c2e03 golangci-lint-1.51.1-netbsd-armv6.tar.gz
|
95af20a2e617126dd5b08122ece7819101070e1582a961067ce8c41172f901ad golangci-lint-1.55.2-netbsd-amd64.tar.gz
|
||||||
c4f58b7e227b9fd41f0e9310dc83f4a4e7d026598e2f6e95b78761081a6d9bd2 golangci-lint-1.51.1-netbsd-armv7.tar.gz
|
94fb7dacb7527847cc95d7120904e19a2a0a81a0d50d61766c9e0251da72ab9d golangci-lint-1.55.2-netbsd-armv6.tar.gz
|
||||||
6710e2f5375dc75521c1a17980a6cbbe6ff76c2f8b852964a8af558899a97cf5 golangci-lint-1.51.1-windows-386.zip
|
ca906bce5fee9619400e4a321c56476fe4a4efb6ac4fc989d340eb5563348873 golangci-lint-1.55.2-netbsd-armv7.tar.gz
|
||||||
722d7b87b9cdda0a3835d5030b3fc5385c2eba4c107f63f6391cfb2ac35f051d golangci-lint-1.51.1-windows-amd64.zip
|
45b442f69fc8915c4500201c0247b7f3f69544dbc9165403a61f9095f2c57355 golangci-lint-1.55.2-windows-386.zip
|
||||||
eb57f9bcb56646f2e3d6ccaf02ec227815fb05077b2e0b1bf9e755805acdc2b9 golangci-lint-1.51.1-windows-arm64.zip
|
f57d434d231d43417dfa631587522f8c1991220b43c8ffadb9c7bd279508bf81 golangci-lint-1.55.2-windows-amd64.zip
|
||||||
bce02f7232723cb727755ee11f168a700a00896a25d37f87c4b173bce55596b4 golangci-lint-1.51.1-windows-armv6.zip
|
fd7dc8f4c6829ee6fafb252a4d81d2155cd35da7833665cbb25d53ce7cecd990 golangci-lint-1.55.2-windows-arm64.zip
|
||||||
cf6403f84707ce8c98664736772271bc8874f2e760c2fd0f00cf3e85963507e9 golangci-lint-1.51.1-windows-armv7.zip
|
1892c3c24f9e7ef44b02f6750c703864b6dc350129f3ec39510300007b2376f1 golangci-lint-1.55.2-windows-armv6.zip
|
||||||
|
a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-1.55.2-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!
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -366,7 +366,7 @@ func doLint(cmdline []string) {
|
||||||
|
|
||||||
linter := downloadLinter(*cachedir)
|
linter := downloadLinter(*cachedir)
|
||||||
lflags := []string{"run", "--config", ".golangci.yml"}
|
lflags := []string{"run", "--config", ".golangci.yml"}
|
||||||
build.MustRunCommand(linter, append(lflags, packages...)...)
|
build.MustRunCommandWithOutput(linter, append(lflags, packages...)...)
|
||||||
fmt.Println("You have achieved perfection.")
|
fmt.Println("You have achieved perfection.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -45,7 +44,7 @@ func main() {
|
||||||
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>)")
|
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>)")
|
||||||
netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)")
|
netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)")
|
||||||
runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode")
|
runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode")
|
||||||
verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-5)")
|
verbosity = flag.Int("verbosity", 3, "log verbosity (0-5)")
|
||||||
vmodule = flag.String("vmodule", "", "log verbosity pattern")
|
vmodule = flag.String("vmodule", "", "log verbosity pattern")
|
||||||
|
|
||||||
nodeKey *ecdsa.PrivateKey
|
nodeKey *ecdsa.PrivateKey
|
||||||
|
|
@ -54,7 +53,8 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||||
glogger.Verbosity(slog.Level(*verbosity))
|
slogVerbosity := log.FromLegacyLevel(*verbosity)
|
||||||
|
glogger.Verbosity(slogVerbosity)
|
||||||
glogger.Vmodule(*vmodule)
|
glogger.Vmodule(*vmodule)
|
||||||
log.SetDefault(log.NewLogger(glogger))
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ import (
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const legalWarning = `
|
const legalWarning = `
|
||||||
|
|
@ -493,7 +492,8 @@ func initialize(c *cli.Context) error {
|
||||||
if usecolor {
|
if usecolor {
|
||||||
output = colorable.NewColorable(logOutput)
|
output = colorable.NewColorable(logOutput)
|
||||||
}
|
}
|
||||||
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, slog.Level(c.Int(logLevelFlag.Name)), usecolor)))
|
verbosity := log.FromLegacyLevel(c.Int(logLevelFlag.Name))
|
||||||
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, verbosity, usecolor)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -704,6 +704,7 @@ func signer(c *cli.Context) error {
|
||||||
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
||||||
"light-kdf", lightKdf, "advanced", advanced)
|
"light-kdf", lightKdf, "advanced", advanced)
|
||||||
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
|
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
|
||||||
|
defer am.Close()
|
||||||
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
|
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
|
||||||
|
|
||||||
// Establish the bidirectional communication, by creating a new UI backend and registering
|
// Establish the bidirectional communication, by creating a new UI backend and registering
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ class StdIOHandler:
|
||||||
{"jsonrpc":"2.0","id":20,"method":"ui_approveTx","params":[{"transaction":{"from":"0xDEADbEeF000000000000000000000000DeaDbeEf","to":"0xDEADbEeF000000000000000000000000DeaDbeEf","gas":"0x3e8","gasPrice":"0x5","maxFeePerGas":null,"maxPriorityFeePerGas":null,"value":"0x6","nonce":"0x1","data":"0x"},"call_info":null,"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
{"jsonrpc":"2.0","id":20,"method":"ui_approveTx","params":[{"transaction":{"from":"0xDEADbEeF000000000000000000000000DeaDbeEf","to":"0xDEADbEeF000000000000000000000000DeaDbeEf","gas":"0x3e8","gasPrice":"0x5","maxFeePerGas":null,"maxPriorityFeePerGas":null,"value":"0x6","nonce":"0x1","data":"0x"},"call_info":null,"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]}
|
||||||
|
|
||||||
:param transaction: transaction info
|
:param transaction: transaction info
|
||||||
:param call_info: info abou the call, e.g. if ABI info could not be
|
:param call_info: info about the call, e.g. if ABI info could not be
|
||||||
:param meta: metadata about the request, e.g. where the call comes from
|
:param meta: metadata about the request, e.g. where the call comes from
|
||||||
:return:
|
:return:
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
|
|
|
||||||
|
|
@ -108,31 +108,32 @@ Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.
|
||||||
|
|
||||||
The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth].
|
The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth].
|
||||||
|
|
||||||
To run the eth protocol test suite against your implementation, the node needs to be initialized as such:
|
To run the eth protocol test suite against your implementation, the node needs to be initialized
|
||||||
|
with our test chain. The chain files are located in `./cmd/devp2p/internal/ethtest/testdata`.
|
||||||
|
|
||||||
1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory
|
1. initialize the geth node with the `genesis.json` file
|
||||||
2. import the `halfchain.rlp` file in the `testdata` directory
|
2. import blocks from `chain.rlp`
|
||||||
3. run geth with the following flags:
|
3. run the client using the resulting database. For geth, use a command like the one below:
|
||||||
```
|
|
||||||
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, run the following command, replacing `<enode>` with the enode of the geth node:
|
geth \
|
||||||
```
|
--datadir <datadir> \
|
||||||
devp2p rlpx eth-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
|
--nodiscover \
|
||||||
```
|
--nat=none \
|
||||||
|
--networkid 3503995874084926 \
|
||||||
|
--verbosity 5 \
|
||||||
|
--authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
|
||||||
|
|
||||||
|
Note that the tests also require access to the engine API.
|
||||||
|
The test suite can now be executed using the devp2p tool.
|
||||||
|
|
||||||
|
devp2p rlpx eth-test \
|
||||||
|
--chain internal/ethtest/testdata \
|
||||||
|
--node enode://.... \
|
||||||
|
--engineapi http://127.0.0.1:8551 \
|
||||||
|
--jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
|
||||||
|
|
||||||
Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again.
|
Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again.
|
||||||
|
|
||||||
#### Eth66 Test Suite
|
|
||||||
|
|
||||||
The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically.
|
|
||||||
To run the eth66 protocol test suite, initialize a geth node as described above and run the following command,
|
|
||||||
replacing `<enode>` with the enode of the geth node:
|
|
||||||
|
|
||||||
```
|
|
||||||
devp2p rlpx eth66-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
|
|
||||||
```
|
|
||||||
|
|
||||||
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
|
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
|
||||||
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup
|
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup
|
||||||
|
|
|
||||||
|
|
@ -17,27 +17,118 @@
|
||||||
package ethtest
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/ecdsa"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/forkid"
|
"github.com/ethereum/go-ethereum/core/forkid"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Chain is a lightweight blockchain-like store which can read a hivechain
|
||||||
|
// created chain.
|
||||||
type Chain struct {
|
type Chain struct {
|
||||||
genesis core.Genesis
|
genesis core.Genesis
|
||||||
blocks []*types.Block
|
blocks []*types.Block
|
||||||
chainConfig *params.ChainConfig
|
state map[common.Address]state.DumpAccount // state of head block
|
||||||
|
senders map[common.Address]*senderInfo
|
||||||
|
config *params.ChainConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChain takes the given chain.rlp file, and decodes and returns
|
||||||
|
// the blocks from the file.
|
||||||
|
func NewChain(dir string) (*Chain, error) {
|
||||||
|
gen, err := loadGenesis(path.Join(dir, "genesis.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gblock := gen.ToBlock()
|
||||||
|
|
||||||
|
blocks, err := blocksFromFile(path.Join(dir, "chain.rlp"), gblock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state, err := readState(path.Join(dir, "headstate.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accounts, err := readAccounts(path.Join(dir, "accounts.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Chain{
|
||||||
|
genesis: gen,
|
||||||
|
blocks: blocks,
|
||||||
|
state: state,
|
||||||
|
senders: accounts,
|
||||||
|
config: gen.Config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// senderInfo is an account record as output in the "accounts.json" file from
|
||||||
|
// hivechain.
|
||||||
|
type senderInfo struct {
|
||||||
|
Key *ecdsa.PrivateKey `json:"key"`
|
||||||
|
Nonce uint64 `json:"nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head returns the chain head.
|
||||||
|
func (c *Chain) Head() *types.Block {
|
||||||
|
return c.blocks[c.Len()-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountsInHashOrder returns all accounts of the head state, ordered by hash of address.
|
||||||
|
func (c *Chain) AccountsInHashOrder() []state.DumpAccount {
|
||||||
|
list := make([]state.DumpAccount, len(c.state))
|
||||||
|
i := 0
|
||||||
|
for addr, acc := range c.state {
|
||||||
|
addr := addr
|
||||||
|
list[i] = acc
|
||||||
|
list[i].Address = &addr
|
||||||
|
if len(acc.AddressHash) != 32 {
|
||||||
|
panic(fmt.Errorf("missing/invalid SecureKey in dump account %v", addr))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
slices.SortFunc(list, func(x, y state.DumpAccount) int {
|
||||||
|
return bytes.Compare(x.AddressHash, y.AddressHash)
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeHashes returns all bytecode hashes contained in the head state.
|
||||||
|
func (c *Chain) CodeHashes() []common.Hash {
|
||||||
|
var hashes []common.Hash
|
||||||
|
seen := make(map[common.Hash]struct{})
|
||||||
|
seen[types.EmptyCodeHash] = struct{}{}
|
||||||
|
for _, acc := range c.state {
|
||||||
|
h := common.BytesToHash(acc.CodeHash)
|
||||||
|
if _, ok := seen[h]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hashes = append(hashes, h)
|
||||||
|
seen[h] = struct{}{}
|
||||||
|
}
|
||||||
|
slices.SortFunc(hashes, (common.Hash).Cmp)
|
||||||
|
return hashes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the length of the chain.
|
// Len returns the length of the chain.
|
||||||
|
|
@ -45,6 +136,11 @@ func (c *Chain) Len() int {
|
||||||
return len(c.blocks)
|
return len(c.blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForkID gets the fork id of the chain.
|
||||||
|
func (c *Chain) ForkID() forkid.ID {
|
||||||
|
return forkid.NewID(c.config, c.blocks[0], uint64(c.Len()), c.blocks[c.Len()-1].Time())
|
||||||
|
}
|
||||||
|
|
||||||
// TD calculates the total difficulty of the chain at the
|
// TD calculates the total difficulty of the chain at the
|
||||||
// chain head.
|
// chain head.
|
||||||
func (c *Chain) TD() *big.Int {
|
func (c *Chain) TD() *big.Int {
|
||||||
|
|
@ -55,19 +151,12 @@ func (c *Chain) TD() *big.Int {
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalDifficultyAt calculates the total difficulty of the chain
|
// GetBlock returns the block at the specified number.
|
||||||
// at the given block height.
|
func (c *Chain) GetBlock(number int) *types.Block {
|
||||||
func (c *Chain) TotalDifficultyAt(height int) *big.Int {
|
return c.blocks[number]
|
||||||
sum := new(big.Int)
|
|
||||||
if height >= c.Len() {
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
for _, block := range c.blocks[:height+1] {
|
|
||||||
sum.Add(sum, block.Difficulty())
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RootAt returns the state root for the block at the given height.
|
||||||
func (c *Chain) RootAt(height int) common.Hash {
|
func (c *Chain) RootAt(height int) common.Hash {
|
||||||
if height < c.Len() {
|
if height < c.Len() {
|
||||||
return c.blocks[height].Root()
|
return c.blocks[height].Root()
|
||||||
|
|
@ -75,37 +164,56 @@ func (c *Chain) RootAt(height int) common.Hash {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkID gets the fork id of the chain.
|
// GetSender returns the address associated with account at the index in the
|
||||||
func (c *Chain) ForkID() forkid.ID {
|
// pre-funded accounts list.
|
||||||
return forkid.NewID(c.chainConfig, c.blocks[0], uint64(c.Len()), c.blocks[0].Time())
|
func (c *Chain) GetSender(idx int) (common.Address, uint64) {
|
||||||
}
|
var accounts Addresses
|
||||||
|
for addr := range c.senders {
|
||||||
// Shorten returns a copy chain of a desired height from the imported
|
accounts = append(accounts, addr)
|
||||||
func (c *Chain) Shorten(height int) *Chain {
|
|
||||||
blocks := make([]*types.Block, height)
|
|
||||||
copy(blocks, c.blocks[:height])
|
|
||||||
|
|
||||||
config := *c.chainConfig
|
|
||||||
return &Chain{
|
|
||||||
blocks: blocks,
|
|
||||||
chainConfig: &config,
|
|
||||||
}
|
}
|
||||||
|
sort.Sort(accounts)
|
||||||
|
addr := accounts[idx]
|
||||||
|
return addr, c.senders[addr].Nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head returns the chain head.
|
// IncNonce increases the specified signing account's pending nonce.
|
||||||
func (c *Chain) Head() *types.Block {
|
func (c *Chain) IncNonce(addr common.Address, amt uint64) {
|
||||||
return c.blocks[c.Len()-1]
|
if _, ok := c.senders[addr]; !ok {
|
||||||
|
panic("nonce increment for non-signer")
|
||||||
|
}
|
||||||
|
c.senders[addr].Nonce += amt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
// Balance returns the balance of an account at the head of the chain.
|
||||||
|
func (c *Chain) Balance(addr common.Address) *big.Int {
|
||||||
|
bal := new(big.Int)
|
||||||
|
if acc, ok := c.state[addr]; ok {
|
||||||
|
bal, _ = bal.SetString(acc.Balance, 10)
|
||||||
|
}
|
||||||
|
return bal
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx signs a transaction for the specified from account, so long as that
|
||||||
|
// account was in the hivechain accounts dump.
|
||||||
|
func (c *Chain) SignTx(from common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
signer := types.LatestSigner(c.config)
|
||||||
|
acc, ok := c.senders[from]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("account not available for signing: %s", from)
|
||||||
|
}
|
||||||
|
return types.SignTx(tx, signer, acc.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeaders returns the headers base on an ethGetPacketHeadersPacket.
|
||||||
|
func (c *Chain) GetHeaders(req *eth.GetBlockHeadersPacket) ([]*types.Header, error) {
|
||||||
if req.Amount < 1 {
|
if req.Amount < 1 {
|
||||||
return nil, errors.New("no block headers requested")
|
return nil, errors.New("no block headers requested")
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
headers := make([]*types.Header, req.Amount)
|
headers = make([]*types.Header, req.Amount)
|
||||||
var blockNumber uint64
|
blockNumber uint64
|
||||||
|
)
|
||||||
// range over blocks to check if our chain has the requested header
|
// Range over blocks to check if our chain has the requested header.
|
||||||
for _, block := range c.blocks {
|
for _, block := range c.blocks {
|
||||||
if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number {
|
if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number {
|
||||||
headers[0] = block.Header()
|
headers[0] = block.Header()
|
||||||
|
|
@ -115,40 +223,30 @@ func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
||||||
if headers[0] == nil {
|
if headers[0] == nil {
|
||||||
return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash)
|
return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Reverse {
|
if req.Reverse {
|
||||||
for i := 1; i < int(req.Amount); i++ {
|
for i := 1; i < int(req.Amount); i++ {
|
||||||
blockNumber -= (1 - req.Skip)
|
blockNumber -= (1 - req.Skip)
|
||||||
headers[i] = c.blocks[blockNumber].Header()
|
headers[i] = c.blocks[blockNumber].Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers, nil
|
return headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i < int(req.Amount); i++ {
|
for i := 1; i < int(req.Amount); i++ {
|
||||||
blockNumber += (1 + req.Skip)
|
blockNumber += (1 + req.Skip)
|
||||||
headers[i] = c.blocks[blockNumber].Header()
|
headers[i] = c.blocks[blockNumber].Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers, nil
|
return headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadChain takes the given chain.rlp file, and decodes and returns
|
// Shorten returns a copy chain of a desired height from the imported
|
||||||
// the blocks from the file.
|
func (c *Chain) Shorten(height int) *Chain {
|
||||||
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
blocks := make([]*types.Block, height)
|
||||||
gen, err := loadGenesis(genesis)
|
copy(blocks, c.blocks[:height])
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
gblock := gen.ToBlock()
|
|
||||||
|
|
||||||
blocks, err := blocksFromFile(chainfile, gblock)
|
config := *c.config
|
||||||
if err != nil {
|
return &Chain{
|
||||||
return nil, err
|
blocks: blocks,
|
||||||
|
config: &config,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGenesis(genesisFile string) (core.Genesis, error) {
|
func loadGenesis(genesisFile string) (core.Genesis, error) {
|
||||||
|
|
@ -163,6 +261,22 @@ func loadGenesis(genesisFile string) (core.Genesis, error) {
|
||||||
return gen, nil
|
return gen, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Addresses []common.Address
|
||||||
|
|
||||||
|
func (a Addresses) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Addresses) Less(i, j int) bool {
|
||||||
|
return bytes.Compare(a[i][:], a[j][:]) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Addresses) Swap(i, j int) {
|
||||||
|
tmp := a[i]
|
||||||
|
a[i] = a[j]
|
||||||
|
a[j] = tmp
|
||||||
|
}
|
||||||
|
|
||||||
func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) {
|
func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) {
|
||||||
// Load chain.rlp.
|
// Load chain.rlp.
|
||||||
fh, err := os.Open(chainfile)
|
fh, err := os.Open(chainfile)
|
||||||
|
|
@ -193,3 +307,47 @@ func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, erro
|
||||||
}
|
}
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readState(file string) (map[common.Address]state.DumpAccount, error) {
|
||||||
|
f, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read state: %v", err)
|
||||||
|
}
|
||||||
|
var dump state.Dump
|
||||||
|
if err := json.Unmarshal(f, &dump); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to unmarshal state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state := make(map[common.Address]state.DumpAccount)
|
||||||
|
for key, acct := range dump.Accounts {
|
||||||
|
var addr common.Address
|
||||||
|
if err := addr.UnmarshalText([]byte(key)); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid address %q", key)
|
||||||
|
}
|
||||||
|
state[addr] = acct
|
||||||
|
}
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAccounts(file string) (map[common.Address]*senderInfo, error) {
|
||||||
|
f, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read state: %v", err)
|
||||||
|
}
|
||||||
|
type account struct {
|
||||||
|
Key hexutil.Bytes `json:"key"`
|
||||||
|
}
|
||||||
|
keys := make(map[common.Address]account)
|
||||||
|
if err := json.Unmarshal(f, &keys); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to unmarshal accounts: %v", err)
|
||||||
|
}
|
||||||
|
accounts := make(map[common.Address]*senderInfo)
|
||||||
|
for addr, acc := range keys {
|
||||||
|
pk, err := crypto.HexToECDSA(common.Bytes2Hex(acc.Key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read private key for %s: %v", err, addr)
|
||||||
|
}
|
||||||
|
accounts[addr] = &senderInfo{Key: pk, Nonce: 0}
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,30 +123,26 @@ func TestEthProtocolNegotiation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestChain_GetHeaders tests whether the test suite can correctly
|
// TestChainGetHeaders tests whether the test suite can correctly
|
||||||
// respond to a GetBlockHeaders request from a node.
|
// respond to a GetBlockHeaders request from a node.
|
||||||
func TestChain_GetHeaders(t *testing.T) {
|
func TestChainGetHeaders(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
chainFile, err := filepath.Abs("./testdata/chain.rlp")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
genesisFile, err := filepath.Abs("./testdata/genesis.json")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
chain, err := loadChain(chainFile, genesisFile)
|
dir, err := filepath.Abs("./testdata")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
chain, err := NewChain(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
req GetBlockHeaders
|
req eth.GetBlockHeadersPacket
|
||||||
expected []*types.Header
|
expected []*types.Header
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
req: GetBlockHeaders{
|
req: eth.GetBlockHeadersPacket{
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||||
Origin: eth.HashOrNumber{Number: uint64(2)},
|
Origin: eth.HashOrNumber{Number: uint64(2)},
|
||||||
Amount: uint64(5),
|
Amount: uint64(5),
|
||||||
|
|
@ -163,7 +159,7 @@ func TestChain_GetHeaders(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
req: GetBlockHeaders{
|
req: eth.GetBlockHeadersPacket{
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||||
Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)},
|
Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)},
|
||||||
Amount: uint64(3),
|
Amount: uint64(3),
|
||||||
|
|
@ -178,7 +174,7 @@ func TestChain_GetHeaders(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
req: GetBlockHeaders{
|
req: eth.GetBlockHeadersPacket{
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||||
Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
|
Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
|
||||||
Amount: uint64(1),
|
Amount: uint64(1),
|
||||||
|
|
|
||||||
361
cmd/devp2p/internal/ethtest/conn.go
Normal file
361
cmd/devp2p/internal/ethtest/conn.go
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pretty = spew.ConfigState{
|
||||||
|
Indent: " ",
|
||||||
|
DisableCapacities: true,
|
||||||
|
DisablePointerAddresses: true,
|
||||||
|
SortKeys: true,
|
||||||
|
}
|
||||||
|
timeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// dial attempts to dial the given node and perform a handshake, returning the
|
||||||
|
// created Conn if successful.
|
||||||
|
func (s *Suite) dial() (*Conn, error) {
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
return s.dialAs(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialAs attempts to dial a given node and perform a handshake using the given
|
||||||
|
// private key.
|
||||||
|
func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
|
||||||
|
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())}
|
||||||
|
conn.ourKey = key
|
||||||
|
_, err = conn.Handshake(conn.ourKey)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn.caps = []p2p.Cap{
|
||||||
|
{Name: "eth", Version: 67},
|
||||||
|
{Name: "eth", Version: 68},
|
||||||
|
}
|
||||||
|
conn.ourHighestProtoVersion = 68
|
||||||
|
return &conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialSnap creates a connection with snap/1 capability.
|
||||||
|
func (s *Suite) dialSnap() (*Conn, error) {
|
||||||
|
conn, err := s.dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dial failed: %v", err)
|
||||||
|
}
|
||||||
|
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1})
|
||||||
|
conn.ourHighestSnapProtoVersion = 1
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn represents an individual connection with a peer
|
||||||
|
type Conn struct {
|
||||||
|
*rlpx.Conn
|
||||||
|
ourKey *ecdsa.PrivateKey
|
||||||
|
negotiatedProtoVersion uint
|
||||||
|
negotiatedSnapProtoVersion uint
|
||||||
|
ourHighestProtoVersion uint
|
||||||
|
ourHighestSnapProtoVersion uint
|
||||||
|
caps []p2p.Cap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a packet from the connection.
|
||||||
|
func (c *Conn) Read() (uint64, []byte, error) {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return code, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMsg attempts to read a devp2p message with a specific code.
|
||||||
|
func (c *Conn) ReadMsg(proto Proto, code uint64, msg any) error {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
got, data, err := c.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if protoOffset(proto)+code == got {
|
||||||
|
return rlp.DecodeBytes(data, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes a eth packet to the connection.
|
||||||
|
func (c *Conn) Write(proto Proto, code uint64, msg any) error {
|
||||||
|
c.SetWriteDeadline(time.Now().Add(timeout))
|
||||||
|
payload, err := rlp.EncodeToBytes(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.Conn.Write(protoOffset(proto)+code, payload)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadEth reads an Eth sub-protocol wire message.
|
||||||
|
func (c *Conn) ReadEth() (any, error) {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if code == pingMsg {
|
||||||
|
c.Write(baseProto, pongMsg, []byte{})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if getProto(code) != ethProto {
|
||||||
|
// Read until eth message.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
code -= baseProtoLen
|
||||||
|
|
||||||
|
var msg any
|
||||||
|
switch int(code) {
|
||||||
|
case eth.StatusMsg:
|
||||||
|
msg = new(eth.StatusPacket)
|
||||||
|
case eth.GetBlockHeadersMsg:
|
||||||
|
msg = new(eth.GetBlockHeadersPacket)
|
||||||
|
case eth.BlockHeadersMsg:
|
||||||
|
msg = new(eth.BlockHeadersPacket)
|
||||||
|
case eth.GetBlockBodiesMsg:
|
||||||
|
msg = new(eth.GetBlockBodiesPacket)
|
||||||
|
case eth.BlockBodiesMsg:
|
||||||
|
msg = new(eth.BlockBodiesPacket)
|
||||||
|
case eth.NewBlockMsg:
|
||||||
|
msg = new(eth.NewBlockPacket)
|
||||||
|
case eth.NewBlockHashesMsg:
|
||||||
|
msg = new(eth.NewBlockHashesPacket)
|
||||||
|
case eth.TransactionsMsg:
|
||||||
|
msg = new(eth.TransactionsPacket)
|
||||||
|
case eth.NewPooledTransactionHashesMsg:
|
||||||
|
msg = new(eth.NewPooledTransactionHashesPacket68)
|
||||||
|
case eth.GetPooledTransactionsMsg:
|
||||||
|
msg = new(eth.GetPooledTransactionsPacket)
|
||||||
|
case eth.PooledTransactionsMsg:
|
||||||
|
msg = new(eth.PooledTransactionsPacket)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unhandled eth msg code %d", code))
|
||||||
|
}
|
||||||
|
if err := rlp.DecodeBytes(data, msg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode eth msg: %v", err)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||||
|
func (c *Conn) ReadSnap() (any, error) {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if getProto(code) != snapProto {
|
||||||
|
// Read until snap message.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
code -= baseProtoLen + ethProtoLen
|
||||||
|
|
||||||
|
var msg any
|
||||||
|
switch int(code) {
|
||||||
|
case snap.GetAccountRangeMsg:
|
||||||
|
msg = new(snap.GetAccountRangePacket)
|
||||||
|
case snap.AccountRangeMsg:
|
||||||
|
msg = new(snap.AccountRangePacket)
|
||||||
|
case snap.GetStorageRangesMsg:
|
||||||
|
msg = new(snap.GetStorageRangesPacket)
|
||||||
|
case snap.StorageRangesMsg:
|
||||||
|
msg = new(snap.StorageRangesPacket)
|
||||||
|
case snap.GetByteCodesMsg:
|
||||||
|
msg = new(snap.GetByteCodesPacket)
|
||||||
|
case snap.ByteCodesMsg:
|
||||||
|
msg = new(snap.ByteCodesPacket)
|
||||||
|
case snap.GetTrieNodesMsg:
|
||||||
|
msg = new(snap.GetTrieNodesPacket)
|
||||||
|
case snap.TrieNodesMsg:
|
||||||
|
msg = new(snap.TrieNodesPacket)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unhandled snap code: %d", code))
|
||||||
|
}
|
||||||
|
if err := rlp.DecodeBytes(data, msg); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not rlp decode message: %v", err)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// peer performs both the protocol handshake and the status message
|
||||||
|
// exchange with the node in order to peer with it.
|
||||||
|
func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error {
|
||||||
|
if err := c.handshake(); err != nil {
|
||||||
|
return fmt.Errorf("handshake failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := c.statusExchange(chain, status); err != nil {
|
||||||
|
return fmt.Errorf("status exchange failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handshake performs a protocol handshake with the node.
|
||||||
|
func (c *Conn) handshake() error {
|
||||||
|
// Write hello to client.
|
||||||
|
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
|
||||||
|
ourHandshake := &protoHandshake{
|
||||||
|
Version: 5,
|
||||||
|
Caps: c.caps,
|
||||||
|
ID: pub0,
|
||||||
|
}
|
||||||
|
if err := c.Write(baseProto, handshakeMsg, ourHandshake); err != nil {
|
||||||
|
return fmt.Errorf("write to connection failed: %v", err)
|
||||||
|
}
|
||||||
|
// Read hello from client.
|
||||||
|
code, data, err := c.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("erroring reading handshake: %v", err)
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case handshakeMsg:
|
||||||
|
msg := new(protoHandshake)
|
||||||
|
if err := rlp.DecodeBytes(data, &msg); err != nil {
|
||||||
|
return fmt.Errorf("error decoding handshake msg: %v", err)
|
||||||
|
}
|
||||||
|
// Set snappy if version is at least 5.
|
||||||
|
if msg.Version >= 5 {
|
||||||
|
c.SetSnappy(true)
|
||||||
|
}
|
||||||
|
c.negotiateEthProtocol(msg.Caps)
|
||||||
|
if c.negotiatedProtoVersion == 0 {
|
||||||
|
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion)
|
||||||
|
}
|
||||||
|
// If we require snap, verify that it was negotiated.
|
||||||
|
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion {
|
||||||
|
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("bad handshake: got msg code %d", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
||||||
|
// advertised capability from peer.
|
||||||
|
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
|
||||||
|
var highestEthVersion uint
|
||||||
|
var highestSnapVersion uint
|
||||||
|
for _, capability := range caps {
|
||||||
|
switch capability.Name {
|
||||||
|
case "eth":
|
||||||
|
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion {
|
||||||
|
highestEthVersion = capability.Version
|
||||||
|
}
|
||||||
|
case "snap":
|
||||||
|
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion {
|
||||||
|
highestSnapVersion = capability.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.negotiatedProtoVersion = highestEthVersion
|
||||||
|
c.negotiatedSnapProtoVersion = highestSnapVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// statusExchange performs a `Status` message exchange with the given node.
|
||||||
|
func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error {
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
code, data, err := c.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read from connection: %w", err)
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case eth.StatusMsg + protoOffset(ethProto):
|
||||||
|
msg := new(eth.StatusPacket)
|
||||||
|
if err := rlp.DecodeBytes(data, &msg); err != nil {
|
||||||
|
return fmt.Errorf("error decoding status packet: %w", err)
|
||||||
|
}
|
||||||
|
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want {
|
||||||
|
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
|
||||||
|
want, chain.blocks[chain.Len()-1].NumberU64(), have)
|
||||||
|
}
|
||||||
|
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want {
|
||||||
|
return fmt.Errorf("wrong TD in status: have %v want %v", have, want)
|
||||||
|
}
|
||||||
|
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
|
||||||
|
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
|
||||||
|
}
|
||||||
|
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) {
|
||||||
|
return fmt.Errorf("wrong protocol version: have %v, want %v", have, want)
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
case discMsg:
|
||||||
|
var msg []p2p.DiscReason
|
||||||
|
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
||||||
|
return errors.New("invalid disconnect message")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("disconnect received: %v", pretty.Sdump(msg))
|
||||||
|
case pingMsg:
|
||||||
|
// TODO (renaynay): in the future, this should be an error
|
||||||
|
// (PINGs should not be a response upon fresh connection)
|
||||||
|
c.Write(baseProto, pongMsg, nil)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("bad status message: code %d", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// make sure eth protocol version is set for negotiation
|
||||||
|
if c.negotiatedProtoVersion == 0 {
|
||||||
|
return errors.New("eth protocol version must be set in Conn")
|
||||||
|
}
|
||||||
|
if status == nil {
|
||||||
|
// default status message
|
||||||
|
status = ð.StatusPacket{
|
||||||
|
ProtocolVersion: uint32(c.negotiatedProtoVersion),
|
||||||
|
NetworkID: chain.config.ChainID.Uint64(),
|
||||||
|
TD: chain.TD(),
|
||||||
|
Head: chain.blocks[chain.Len()-1].Hash(),
|
||||||
|
Genesis: chain.blocks[0].Hash(),
|
||||||
|
ForkID: chain.ForkID(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil {
|
||||||
|
return fmt.Errorf("write to connection failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
69
cmd/devp2p/internal/ethtest/engine.go
Normal file
69
cmd/devp2p/internal/ethtest/engine.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EngineClient is a wrapper around engine-related data.
|
||||||
|
type EngineClient struct {
|
||||||
|
url string
|
||||||
|
jwt [32]byte
|
||||||
|
headfcu []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEngineClient creates a new engine client.
|
||||||
|
func NewEngineClient(dir, url, jwt string) (*EngineClient, error) {
|
||||||
|
headfcu, err := os.ReadFile(path.Join(dir, "headfcu.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read headfcu: %w", err)
|
||||||
|
}
|
||||||
|
return &EngineClient{url, common.HexToHash(jwt), headfcu}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// token returns the jwt claim token for authorization.
|
||||||
|
func (ec *EngineClient) token() string {
|
||||||
|
claims := jwt.RegisteredClaims{IssuedAt: jwt.NewNumericDate(time.Now())}
|
||||||
|
token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(ec.jwt[:])
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendForkchoiceUpdated sends an fcu for the head of the generated chain.
|
||||||
|
func (ec *EngineClient) sendForkchoiceUpdated() error {
|
||||||
|
var (
|
||||||
|
req, _ = http.NewRequest(http.MethodPost, ec.url, io.NopCloser(bytes.NewReader(ec.headfcu)))
|
||||||
|
header = make(http.Header)
|
||||||
|
)
|
||||||
|
// Set header
|
||||||
|
header.Set("accept", "application/json")
|
||||||
|
header.Set("content-type", "application/json")
|
||||||
|
header.Set("Authorization", fmt.Sprintf("Bearer %v", ec.token()))
|
||||||
|
req.Header = header
|
||||||
|
|
||||||
|
_, err := new(http.Client).Do(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -1,650 +0,0 @@
|
||||||
// Copyright 2021 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum 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 General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package ethtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pretty = spew.ConfigState{
|
|
||||||
Indent: " ",
|
|
||||||
DisableCapacities: true,
|
|
||||||
DisablePointerAddresses: true,
|
|
||||||
SortKeys: true,
|
|
||||||
}
|
|
||||||
timeout = 20 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// dial attempts to dial the given node and perform a handshake,
|
|
||||||
// returning the created Conn if successful.
|
|
||||||
func (s *Suite) dial() (*Conn, error) {
|
|
||||||
// dial
|
|
||||||
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())}
|
|
||||||
// do encHandshake
|
|
||||||
conn.ourKey, _ = crypto.GenerateKey()
|
|
||||||
_, err = conn.Handshake(conn.ourKey)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// set default p2p capabilities
|
|
||||||
conn.caps = []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 67},
|
|
||||||
{Name: "eth", Version: 68},
|
|
||||||
}
|
|
||||||
conn.ourHighestProtoVersion = 68
|
|
||||||
return &conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dialSnap creates a connection with snap/1 capability.
|
|
||||||
func (s *Suite) dialSnap() (*Conn, error) {
|
|
||||||
conn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1})
|
|
||||||
conn.ourHighestSnapProtoVersion = 1
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// peer performs both the protocol handshake and the status message
|
|
||||||
// exchange with the node in order to peer with it.
|
|
||||||
func (c *Conn) peer(chain *Chain, status *Status) error {
|
|
||||||
if err := c.handshake(); err != nil {
|
|
||||||
return fmt.Errorf("handshake failed: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := c.statusExchange(chain, status); err != nil {
|
|
||||||
return fmt.Errorf("status exchange failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handshake performs a protocol handshake with the node.
|
|
||||||
func (c *Conn) handshake() error {
|
|
||||||
defer c.SetDeadline(time.Time{})
|
|
||||||
c.SetDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
// write hello to client
|
|
||||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
|
|
||||||
ourHandshake := &Hello{
|
|
||||||
Version: 5,
|
|
||||||
Caps: c.caps,
|
|
||||||
ID: pub0,
|
|
||||||
}
|
|
||||||
if err := c.Write(ourHandshake); err != nil {
|
|
||||||
return fmt.Errorf("write to connection failed: %v", err)
|
|
||||||
}
|
|
||||||
// read hello from client
|
|
||||||
switch msg := c.Read().(type) {
|
|
||||||
case *Hello:
|
|
||||||
// set snappy if version is at least 5
|
|
||||||
if msg.Version >= 5 {
|
|
||||||
c.SetSnappy(true)
|
|
||||||
}
|
|
||||||
c.negotiateEthProtocol(msg.Caps)
|
|
||||||
if c.negotiatedProtoVersion == 0 {
|
|
||||||
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion)
|
|
||||||
}
|
|
||||||
// If we require snap, verify that it was negotiated
|
|
||||||
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion {
|
|
||||||
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("bad handshake: %#v", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
|
||||||
// advertised capability from peer.
|
|
||||||
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
|
|
||||||
var highestEthVersion uint
|
|
||||||
var highestSnapVersion uint
|
|
||||||
for _, capability := range caps {
|
|
||||||
switch capability.Name {
|
|
||||||
case "eth":
|
|
||||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion {
|
|
||||||
highestEthVersion = capability.Version
|
|
||||||
}
|
|
||||||
case "snap":
|
|
||||||
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion {
|
|
||||||
highestSnapVersion = capability.Version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.negotiatedProtoVersion = highestEthVersion
|
|
||||||
c.negotiatedSnapProtoVersion = highestSnapVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// statusExchange performs a `Status` message exchange with the given node.
|
|
||||||
func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) {
|
|
||||||
defer c.SetDeadline(time.Time{})
|
|
||||||
c.SetDeadline(time.Now().Add(20 * time.Second))
|
|
||||||
|
|
||||||
// read status message from client
|
|
||||||
var message Message
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
switch msg := c.Read().(type) {
|
|
||||||
case *Status:
|
|
||||||
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want {
|
|
||||||
return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
|
|
||||||
want, chain.blocks[chain.Len()-1].NumberU64(), have)
|
|
||||||
}
|
|
||||||
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want {
|
|
||||||
return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want)
|
|
||||||
}
|
|
||||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
|
|
||||||
return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) {
|
|
||||||
return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
message = msg
|
|
||||||
break loop
|
|
||||||
case *Disconnect:
|
|
||||||
return nil, fmt.Errorf("disconnect received: %v", msg.Reason)
|
|
||||||
case *Ping:
|
|
||||||
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
|
||||||
// (PINGs should not be a response upon fresh connection)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// make sure eth protocol version is set for negotiation
|
|
||||||
if c.negotiatedProtoVersion == 0 {
|
|
||||||
return nil, errors.New("eth protocol version must be set in Conn")
|
|
||||||
}
|
|
||||||
if status == nil {
|
|
||||||
// default status message
|
|
||||||
status = &Status{
|
|
||||||
ProtocolVersion: uint32(c.negotiatedProtoVersion),
|
|
||||||
NetworkID: chain.chainConfig.ChainID.Uint64(),
|
|
||||||
TD: chain.TD(),
|
|
||||||
Head: chain.blocks[chain.Len()-1].Hash(),
|
|
||||||
Genesis: chain.blocks[0].Hash(),
|
|
||||||
ForkID: chain.ForkID(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := c.Write(status); err != nil {
|
|
||||||
return nil, fmt.Errorf("write to connection failed: %v", err)
|
|
||||||
}
|
|
||||||
return message, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSendAndRecvConns creates two connections, one for sending messages to the
|
|
||||||
// node, and one for receiving messages from the node.
|
|
||||||
func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) {
|
|
||||||
sendConn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
recvConn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
sendConn.Close()
|
|
||||||
return nil, nil, fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
return sendConn, recvConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readAndServe serves GetBlockHeaders requests while waiting
|
|
||||||
// on another message from the node.
|
|
||||||
func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message {
|
|
||||||
start := time.Now()
|
|
||||||
for time.Since(start) < timeout {
|
|
||||||
c.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
|
|
||||||
msg := c.Read()
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *Ping:
|
|
||||||
c.Write(&Pong{})
|
|
||||||
case *GetBlockHeaders:
|
|
||||||
headers, err := chain.GetHeaders(msg)
|
|
||||||
if err != nil {
|
|
||||||
return errorf("could not get headers for inbound header request: %v", err)
|
|
||||||
}
|
|
||||||
resp := &BlockHeaders{
|
|
||||||
RequestId: msg.ReqID(),
|
|
||||||
BlockHeadersRequest: eth.BlockHeadersRequest(headers),
|
|
||||||
}
|
|
||||||
if err := c.Write(resp); err != nil {
|
|
||||||
return errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errorf("no message received within %v", timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// headersRequest executes the given `GetBlockHeaders` request.
|
|
||||||
func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint64) ([]*types.Header, error) {
|
|
||||||
defer c.SetReadDeadline(time.Time{})
|
|
||||||
c.SetReadDeadline(time.Now().Add(20 * time.Second))
|
|
||||||
|
|
||||||
// write request
|
|
||||||
request.RequestId = reqID
|
|
||||||
if err := c.Write(request); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for response
|
|
||||||
msg := c.waitForResponse(chain, timeout, request.RequestId)
|
|
||||||
resp, ok := msg.(*BlockHeaders)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
headers := []*types.Header(resp.BlockHeadersRequest)
|
|
||||||
return headers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) {
|
|
||||||
defer c.SetReadDeadline(time.Time{})
|
|
||||||
c.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
if err := c.Write(msg); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
return c.ReadSnap(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// headersMatch returns whether the received headers match the given request
|
|
||||||
func headersMatch(expected []*types.Header, headers []*types.Header) bool {
|
|
||||||
return reflect.DeepEqual(expected, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForResponse reads from the connection until a response with the expected
|
|
||||||
// request ID is received.
|
|
||||||
func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message {
|
|
||||||
for {
|
|
||||||
msg := c.readAndServe(chain, timeout)
|
|
||||||
if msg.ReqID() == requestID {
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendNextBlock broadcasts the next block in the chain and waits
|
|
||||||
// for the node to propagate the block and import it into its chain.
|
|
||||||
func (s *Suite) sendNextBlock() error {
|
|
||||||
// set up sending and receiving connections
|
|
||||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sendConn.Close()
|
|
||||||
defer recvConn.Close()
|
|
||||||
if err = sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
// create new block announcement
|
|
||||||
nextBlock := s.fullChain.blocks[s.chain.Len()]
|
|
||||||
blockAnnouncement := &NewBlock{
|
|
||||||
Block: nextBlock,
|
|
||||||
TD: s.fullChain.TotalDifficultyAt(s.chain.Len()),
|
|
||||||
}
|
|
||||||
// send announcement and wait for node to request the header
|
|
||||||
if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil {
|
|
||||||
return fmt.Errorf("failed to announce block: %v", err)
|
|
||||||
}
|
|
||||||
// wait for client to update its chain
|
|
||||||
if err = s.waitForBlockImport(recvConn, nextBlock); err != nil {
|
|
||||||
return fmt.Errorf("failed to receive confirmation of block import: %v", err)
|
|
||||||
}
|
|
||||||
// update test suite chain
|
|
||||||
s.chain.blocks = append(s.chain.blocks, nextBlock)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testAnnounce writes a block announcement to the node and waits for the node
|
|
||||||
// to propagate it.
|
|
||||||
func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error {
|
|
||||||
if err := sendConn.Write(blockAnnouncement); err != nil {
|
|
||||||
return fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
return s.waitAnnounce(receiveConn, blockAnnouncement)
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node.
|
|
||||||
func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error {
|
|
||||||
for {
|
|
||||||
switch msg := conn.readAndServe(s.chain, timeout).(type) {
|
|
||||||
case *NewBlock:
|
|
||||||
if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) {
|
|
||||||
return fmt.Errorf("wrong header in block announcement: \nexpected %v "+
|
|
||||||
"\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header())
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) {
|
|
||||||
return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case *NewBlockHashes:
|
|
||||||
hashes := *msg
|
|
||||||
if blockAnnouncement.Block.Hash() != hashes[0].Hash {
|
|
||||||
return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
// ignore tx announcements from previous tests
|
|
||||||
case *NewPooledTransactionHashes66:
|
|
||||||
continue
|
|
||||||
case *NewPooledTransactionHashes:
|
|
||||||
continue
|
|
||||||
case *Transactions:
|
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error {
|
|
||||||
defer conn.SetReadDeadline(time.Time{})
|
|
||||||
conn.SetReadDeadline(time.Now().Add(20 * time.Second))
|
|
||||||
// create request
|
|
||||||
req := &GetBlockHeaders{
|
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
|
||||||
Origin: eth.HashOrNumber{Hash: block.Hash()},
|
|
||||||
Amount: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop until BlockHeaders response contains desired block, confirming the
|
|
||||||
// node imported the block
|
|
||||||
for {
|
|
||||||
requestID := uint64(54)
|
|
||||||
headers, err := conn.headersRequest(req, s.chain, requestID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("GetBlockHeader request failed: %v", err)
|
|
||||||
}
|
|
||||||
// if headers response is empty, node hasn't imported block yet, try again
|
|
||||||
if len(headers) == 0 {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(block.Header(), headers[0]) {
|
|
||||||
return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) oldAnnounce() error {
|
|
||||||
sendConn, receiveConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sendConn.Close()
|
|
||||||
defer receiveConn.Close()
|
|
||||||
if err := sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := receiveConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
// create old block announcement
|
|
||||||
oldBlockAnnounce := &NewBlock{
|
|
||||||
Block: s.chain.blocks[len(s.chain.blocks)/2],
|
|
||||||
TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(),
|
|
||||||
}
|
|
||||||
if err := sendConn.Write(oldBlockAnnounce); err != nil {
|
|
||||||
return fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
// wait to see if the announcement is propagated
|
|
||||||
switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) {
|
|
||||||
case *NewBlock:
|
|
||||||
block := *msg
|
|
||||||
if block.Block.Hash() == oldBlockAnnounce.Block.Hash() {
|
|
||||||
return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
case *NewBlockHashes:
|
|
||||||
hashes := *msg
|
|
||||||
for _, hash := range hashes {
|
|
||||||
if hash.Hash == oldBlockAnnounce.Block.Hash() {
|
|
||||||
return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *Error:
|
|
||||||
errMsg := *msg
|
|
||||||
// check to make sure error is timeout (propagation didn't come through == test successful)
|
|
||||||
if !strings.Contains(errMsg.String(), "timeout") {
|
|
||||||
return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) maliciousHandshakes(t *utesting.T) error {
|
|
||||||
conn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
// write hello to client
|
|
||||||
pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:]
|
|
||||||
handshakes := []*Hello{
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: largeString(2), Version: 64},
|
|
||||||
},
|
|
||||||
ID: pub0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 64},
|
|
||||||
{Name: "eth", Version: 65},
|
|
||||||
},
|
|
||||||
ID: append(pub0, byte(0)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 64},
|
|
||||||
{Name: "eth", Version: 65},
|
|
||||||
},
|
|
||||||
ID: append(pub0, pub0...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 64},
|
|
||||||
{Name: "eth", Version: 65},
|
|
||||||
},
|
|
||||||
ID: largeBuffer(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: largeString(2), Version: 64},
|
|
||||||
},
|
|
||||||
ID: largeBuffer(2),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, handshake := range handshakes {
|
|
||||||
t.Logf("Testing malicious handshake %v\n", i)
|
|
||||||
if err := conn.Write(handshake); err != nil {
|
|
||||||
return fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
// check that the peer disconnected
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) {
|
|
||||||
case *Disconnect:
|
|
||||||
case *Error:
|
|
||||||
case *Hello:
|
|
||||||
// Discard one hello as Hello's are sent concurrently
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// dial for the next round
|
|
||||||
conn, err = s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) maliciousStatus(conn *Conn) error {
|
|
||||||
if err := conn.handshake(); err != nil {
|
|
||||||
return fmt.Errorf("handshake failed: %v", err)
|
|
||||||
}
|
|
||||||
status := &Status{
|
|
||||||
ProtocolVersion: uint32(conn.negotiatedProtoVersion),
|
|
||||||
NetworkID: s.chain.chainConfig.ChainID.Uint64(),
|
|
||||||
TD: largeNumber(2),
|
|
||||||
Head: s.chain.blocks[s.chain.Len()-1].Hash(),
|
|
||||||
Genesis: s.chain.blocks[0].Hash(),
|
|
||||||
ForkID: s.chain.ForkID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// get status
|
|
||||||
msg, err := conn.statusExchange(s.chain, status)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("status exchange failed: %v", err)
|
|
||||||
}
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *Status:
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("expected status, got: %#v ", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for disconnect
|
|
||||||
switch msg := conn.readAndServe(s.chain, timeout).(type) {
|
|
||||||
case *Disconnect:
|
|
||||||
return nil
|
|
||||||
case *Error:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) hashAnnounce() error {
|
|
||||||
// create connections
|
|
||||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create connections: %v", err)
|
|
||||||
}
|
|
||||||
defer sendConn.Close()
|
|
||||||
defer recvConn.Close()
|
|
||||||
if err := sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := recvConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create NewBlockHashes announcement
|
|
||||||
type anno struct {
|
|
||||||
Hash common.Hash // Hash of one particular block being announced
|
|
||||||
Number uint64 // Number of one particular block being announced
|
|
||||||
}
|
|
||||||
nextBlock := s.fullChain.blocks[s.chain.Len()]
|
|
||||||
announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()}
|
|
||||||
newBlockHash := &NewBlockHashes{announcement}
|
|
||||||
if err := sendConn.Write(newBlockHash); err != nil {
|
|
||||||
return fmt.Errorf("failed to write to connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Announcement sent, now wait for a header request
|
|
||||||
msg := sendConn.Read()
|
|
||||||
blockHeaderReq, ok := msg.(*GetBlockHeaders)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
if blockHeaderReq.Amount != 1 {
|
|
||||||
return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount)
|
|
||||||
}
|
|
||||||
if blockHeaderReq.Origin.Hash != announcement.Hash {
|
|
||||||
return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v",
|
|
||||||
pretty.Sdump(announcement),
|
|
||||||
pretty.Sdump(blockHeaderReq))
|
|
||||||
}
|
|
||||||
err = sendConn.Write(&BlockHeaders{
|
|
||||||
RequestId: blockHeaderReq.ReqID(),
|
|
||||||
BlockHeadersRequest: eth.BlockHeadersRequest{nextBlock.Header()},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write to connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for block announcement
|
|
||||||
msg = recvConn.readAndServe(s.chain, timeout)
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *NewBlockHashes:
|
|
||||||
hashes := *msg
|
|
||||||
if len(hashes) != 1 {
|
|
||||||
return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes))
|
|
||||||
}
|
|
||||||
if nextBlock.Hash() != hashes[0].Hash {
|
|
||||||
return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(),
|
|
||||||
hashes[0].Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *NewBlock:
|
|
||||||
// node should only propagate NewBlock without having requested the body if the body is empty
|
|
||||||
nextBlockBody := nextBlock.Body()
|
|
||||||
if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 {
|
|
||||||
return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
if msg.Block.Hash() != nextBlock.Hash() {
|
|
||||||
return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v",
|
|
||||||
nextBlock.Hash(), msg.Block.Hash())
|
|
||||||
}
|
|
||||||
// check to make sure header matches header that was sent to the node
|
|
||||||
if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) {
|
|
||||||
return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
// confirm node imported block
|
|
||||||
if err := s.waitForBlockImport(recvConn, nextBlock); err != nil {
|
|
||||||
return fmt.Errorf("error waiting for node to import new block: %v", err)
|
|
||||||
}
|
|
||||||
// update the chain
|
|
||||||
s.chain.blocks = append(s.chain.blocks, nextBlock)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
// Copyright 2020 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum 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 General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package ethtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// largeNumber returns a very large big.Int.
|
|
||||||
func largeNumber(megabytes int) *big.Int {
|
|
||||||
buf := make([]byte, megabytes*1024*1024)
|
|
||||||
rand.Read(buf)
|
|
||||||
bigint := new(big.Int)
|
|
||||||
bigint.SetBytes(buf)
|
|
||||||
return bigint
|
|
||||||
}
|
|
||||||
|
|
||||||
// largeBuffer returns a very large buffer.
|
|
||||||
func largeBuffer(megabytes int) []byte {
|
|
||||||
buf := make([]byte, megabytes*1024*1024)
|
|
||||||
rand.Read(buf)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// largeString returns a very large string.
|
|
||||||
func largeString(megabytes int) string {
|
|
||||||
buf := make([]byte, megabytes*1024*1024)
|
|
||||||
rand.Read(buf)
|
|
||||||
return hexutil.Encode(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func largeBlock() *types.Block {
|
|
||||||
return types.NewBlockWithHeader(largeHeader())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a random hash
|
|
||||||
func randHash() common.Hash {
|
|
||||||
var h common.Hash
|
|
||||||
rand.Read(h[:])
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func largeHeader() *types.Header {
|
|
||||||
return &types.Header{
|
|
||||||
MixDigest: randHash(),
|
|
||||||
ReceiptHash: randHash(),
|
|
||||||
TxHash: randHash(),
|
|
||||||
Nonce: types.BlockNonce{},
|
|
||||||
Extra: []byte{},
|
|
||||||
Bloom: types.Bloom{},
|
|
||||||
GasUsed: 0,
|
|
||||||
Coinbase: common.Address{},
|
|
||||||
GasLimit: 0,
|
|
||||||
UncleHash: types.EmptyUncleHash,
|
|
||||||
Time: 1337,
|
|
||||||
ParentHash: randHash(),
|
|
||||||
Root: randHash(),
|
|
||||||
Number: largeNumber(2),
|
|
||||||
Difficulty: largeNumber(2),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
cmd/devp2p/internal/ethtest/mkchain.sh
Normal file
9
cmd/devp2p/internal/ethtest/mkchain.sh
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
hivechain generate \
|
||||||
|
--fork-interval 6 \
|
||||||
|
--tx-interval 1 \
|
||||||
|
--length 500 \
|
||||||
|
--outdir testdata \
|
||||||
|
--lastfork cancun \
|
||||||
|
--outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv
|
||||||
87
cmd/devp2p/internal/ethtest/protocol.go
Normal file
87
cmd/devp2p/internal/ethtest/protocol.go
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
package ethtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported devp2p message codes from p2p/peer.go.
|
||||||
|
const (
|
||||||
|
handshakeMsg = 0x00
|
||||||
|
discMsg = 0x01
|
||||||
|
pingMsg = 0x02
|
||||||
|
pongMsg = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported devp2p protocol lengths from p2p package.
|
||||||
|
const (
|
||||||
|
baseProtoLen = 16
|
||||||
|
ethProtoLen = 17
|
||||||
|
snapProtoLen = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported handshake structure from p2p/peer.go.
|
||||||
|
type protoHandshake struct {
|
||||||
|
Version uint64
|
||||||
|
Name string
|
||||||
|
Caps []p2p.Cap
|
||||||
|
ListenPort uint64
|
||||||
|
ID []byte
|
||||||
|
Rest []rlp.RawValue `rlp:"tail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello = protoHandshake
|
||||||
|
|
||||||
|
// Proto is an enum representing devp2p protocol types.
|
||||||
|
type Proto int
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseProto Proto = iota
|
||||||
|
ethProto
|
||||||
|
snapProto
|
||||||
|
)
|
||||||
|
|
||||||
|
// getProto returns the protocol a certain message code is associated with
|
||||||
|
// (assuming the negotiated capabilities are exactly {eth,snap})
|
||||||
|
func getProto(code uint64) Proto {
|
||||||
|
switch {
|
||||||
|
case code < baseProtoLen:
|
||||||
|
return baseProto
|
||||||
|
case code < baseProtoLen+ethProtoLen:
|
||||||
|
return ethProto
|
||||||
|
case code < baseProtoLen+ethProtoLen+snapProtoLen:
|
||||||
|
return snapProto
|
||||||
|
default:
|
||||||
|
panic("unhandled msg code beyond last protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protoOffset will return the offset at which the specified protocol's messages
|
||||||
|
// begin.
|
||||||
|
func protoOffset(proto Proto) uint64 {
|
||||||
|
switch proto {
|
||||||
|
case baseProto:
|
||||||
|
return 0
|
||||||
|
case ethProto:
|
||||||
|
return baseProtoLen
|
||||||
|
case snapProto:
|
||||||
|
return baseProtoLen + ethProtoLen
|
||||||
|
default:
|
||||||
|
panic("unhandled protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,60 +0,0 @@
|
||||||
// Copyright 2022 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum 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 General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package ethtest
|
|
||||||
|
|
||||||
import "github.com/ethereum/go-ethereum/eth/protocols/snap"
|
|
||||||
|
|
||||||
// GetAccountRange represents an account range query.
|
|
||||||
type GetAccountRange snap.GetAccountRangePacket
|
|
||||||
|
|
||||||
func (msg GetAccountRange) Code() int { return 33 }
|
|
||||||
func (msg GetAccountRange) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type AccountRange snap.AccountRangePacket
|
|
||||||
|
|
||||||
func (msg AccountRange) Code() int { return 34 }
|
|
||||||
func (msg AccountRange) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type GetStorageRanges snap.GetStorageRangesPacket
|
|
||||||
|
|
||||||
func (msg GetStorageRanges) Code() int { return 35 }
|
|
||||||
func (msg GetStorageRanges) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type StorageRanges snap.StorageRangesPacket
|
|
||||||
|
|
||||||
func (msg StorageRanges) Code() int { return 36 }
|
|
||||||
func (msg StorageRanges) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type GetByteCodes snap.GetByteCodesPacket
|
|
||||||
|
|
||||||
func (msg GetByteCodes) Code() int { return 37 }
|
|
||||||
func (msg GetByteCodes) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type ByteCodes snap.ByteCodesPacket
|
|
||||||
|
|
||||||
func (msg ByteCodes) Code() int { return 38 }
|
|
||||||
func (msg ByteCodes) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type GetTrieNodes snap.GetTrieNodesPacket
|
|
||||||
|
|
||||||
func (msg GetTrieNodes) Code() int { return 39 }
|
|
||||||
func (msg GetTrieNodes) ReqID() uint64 { return msg.ID }
|
|
||||||
|
|
||||||
type TrieNodes snap.TrieNodesPacket
|
|
||||||
|
|
||||||
func (msg TrieNodes) Code() int { return 40 }
|
|
||||||
func (msg TrieNodes) ReqID() uint64 { return msg.ID }
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -17,38 +17,53 @@
|
||||||
package ethtest
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
crand "crypto/rand"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func makeJWTSecret() (string, [32]byte, error) {
|
||||||
genesisFile = "./testdata/genesis.json"
|
var secret [32]byte
|
||||||
halfchainFile = "./testdata/halfchain.rlp"
|
if _, err := crand.Read(secret[:]); err != nil {
|
||||||
fullchainFile = "./testdata/chain.rlp"
|
return "", secret, fmt.Errorf("failed to create jwt secret: %v", err)
|
||||||
)
|
}
|
||||||
|
jwtPath := path.Join(os.TempDir(), "jwt_secret")
|
||||||
|
if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil {
|
||||||
|
return "", secret, fmt.Errorf("failed to prepare jwt secret file: %v", err)
|
||||||
|
}
|
||||||
|
return jwtPath, secret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestEthSuite(t *testing.T) {
|
func TestEthSuite(t *testing.T) {
|
||||||
t.Parallel()
|
jwtPath, secret, err := makeJWTSecret()
|
||||||
geth, err := runGeth()
|
if err != nil {
|
||||||
|
t.Fatalf("could not make jwt secret: %v", err)
|
||||||
|
}
|
||||||
|
geth, err := runGeth("./testdata", jwtPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not run geth: %v", err)
|
t.Fatalf("could not run geth: %v", err)
|
||||||
}
|
}
|
||||||
defer geth.Close()
|
defer geth.Close()
|
||||||
|
|
||||||
suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile)
|
suite, err := NewSuite(geth.Server().Self(), "./testdata", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create new test suite: %v", err)
|
t.Fatalf("could not create new test suite: %v", err)
|
||||||
}
|
}
|
||||||
for _, test := range suite.EthTests() {
|
for _, test := range suite.EthTests() {
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
|
result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
|
||||||
if result[0].Failed {
|
if result[0].Failed {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
@ -57,20 +72,23 @@ func TestEthSuite(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnapSuite(t *testing.T) {
|
func TestSnapSuite(t *testing.T) {
|
||||||
t.Parallel()
|
jwtPath, secret, err := makeJWTSecret()
|
||||||
geth, err := runGeth()
|
if err != nil {
|
||||||
|
t.Fatalf("could not make jwt secret: %v", err)
|
||||||
|
}
|
||||||
|
geth, err := runGeth("./testdata", jwtPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not run geth: %v", err)
|
t.Fatalf("could not run geth: %v", err)
|
||||||
}
|
}
|
||||||
defer geth.Close()
|
defer geth.Close()
|
||||||
|
|
||||||
suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile)
|
suite, err := NewSuite(geth.Server().Self(), "./testdata", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create new test suite: %v", err)
|
t.Fatalf("could not create new test suite: %v", err)
|
||||||
}
|
}
|
||||||
for _, test := range suite.SnapTests() {
|
for _, test := range suite.SnapTests() {
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
|
result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
|
||||||
if result[0].Failed {
|
if result[0].Failed {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
@ -79,20 +97,23 @@ func TestSnapSuite(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// runGeth creates and starts a geth node
|
// runGeth creates and starts a geth node
|
||||||
func runGeth() (*node.Node, error) {
|
func runGeth(dir string, jwtPath string) (*node.Node, error) {
|
||||||
stack, err := node.New(&node.Config{
|
stack, err := node.New(&node.Config{
|
||||||
|
AuthAddr: "127.0.0.1",
|
||||||
|
AuthPort: 0,
|
||||||
P2P: p2p.Config{
|
P2P: p2p.Config{
|
||||||
ListenAddr: "127.0.0.1:0",
|
ListenAddr: "127.0.0.1:0",
|
||||||
NoDiscovery: true,
|
NoDiscovery: true,
|
||||||
MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future
|
MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future
|
||||||
NoDial: true,
|
NoDial: true,
|
||||||
},
|
},
|
||||||
|
JWTSecret: jwtPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setupGeth(stack)
|
err = setupGeth(stack, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stack.Close()
|
stack.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -104,12 +125,11 @@ func runGeth() (*node.Node, error) {
|
||||||
return stack, nil
|
return stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupGeth(stack *node.Node) error {
|
func setupGeth(stack *node.Node, dir string) error {
|
||||||
chain, err := loadChain(halfchainFile, genesisFile)
|
chain, err := NewChain(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
backend, err := eth.New(stack, ðconfig.Config{
|
backend, err := eth.New(stack, ðconfig.Config{
|
||||||
Genesis: &chain.genesis,
|
Genesis: &chain.genesis,
|
||||||
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
|
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
|
||||||
|
|
@ -122,8 +142,9 @@ func setupGeth(stack *node.Node) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backend.SetSynced()
|
if err := catalyst.Register(stack, backend); err != nil {
|
||||||
|
return fmt.Errorf("failed to register catalyst service: %v", err)
|
||||||
|
}
|
||||||
_, err = backend.BlockChain().InsertChain(chain.blocks[1:])
|
_, err = backend.BlockChain().InsertChain(chain.blocks[1:])
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
cmd/devp2p/internal/ethtest/testdata/accounts.json
vendored
Normal file
62
cmd/devp2p/internal/ethtest/testdata/accounts.json
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"0x0c2c51a0990aee1d73c1228de158688341557508": {
|
||||||
|
"key": "0xbfcd0e032489319f4e5ca03e643b2025db624be6cf99cbfed90c4502e3754850"
|
||||||
|
},
|
||||||
|
"0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
||||||
|
"key": "0x457075f6822ac29481154792f65c5f1ec335b4fea9ca20f3fea8fa1d78a12c68"
|
||||||
|
},
|
||||||
|
"0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
|
||||||
|
"key": "0x865898edcf43206d138c93f1bbd86311f4657b057658558888aa5ac4309626a6"
|
||||||
|
},
|
||||||
|
"0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
|
||||||
|
"key": "0xee7f7875d826d7443ccc5c174e38b2c436095018774248a8074ee92d8914dcdb"
|
||||||
|
},
|
||||||
|
"0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
|
||||||
|
"key": "0x25e6ce8611cefb5cd338aeaa9292ed2139714668d123a4fb156cabb42051b5b7"
|
||||||
|
},
|
||||||
|
"0x2d389075be5be9f2246ad654ce152cf05990b209": {
|
||||||
|
"key": "0x19168cd7767604b3d19b99dc3da1302b9ccb6ee9ad61660859e07acd4a2625dd"
|
||||||
|
},
|
||||||
|
"0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
|
||||||
|
"key": "0x71aa7d299c7607dabfc3d0e5213d612b5e4a97455b596c2f642daac43fa5eeaa"
|
||||||
|
},
|
||||||
|
"0x4340ee1b812acb40a1eb561c019c327b243b92df": {
|
||||||
|
"key": "0x47f666f20e2175606355acec0ea1b37870c15e5797e962340da7ad7972a537e8"
|
||||||
|
},
|
||||||
|
"0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
|
||||||
|
"key": "0xa88293fefc623644969e2ce6919fb0dbd0fd64f640293b4bf7e1a81c97e7fc7f"
|
||||||
|
},
|
||||||
|
"0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
|
||||||
|
"key": "0x6e1e16a9c15641c73bf6e237f9293ab1d4e7c12b9adf83cfc94bcf969670f72d"
|
||||||
|
},
|
||||||
|
"0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
|
||||||
|
"key": "0x41be4e00aac79f7ffbb3455053ec05e971645440d594c047cdcc56a3c7458bd6"
|
||||||
|
},
|
||||||
|
"0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
|
||||||
|
"key": "0xc825f31cd8792851e33a290b3d749e553983111fc1f36dfbbdb45f101973f6a9"
|
||||||
|
},
|
||||||
|
"0x717f8aa2b982bee0e29f573d31df288663e1ce16": {
|
||||||
|
"key": "0x8d0faa04ae0f9bc3cd4c890aa025d5f40916f4729538b19471c0beefe11d9e19"
|
||||||
|
},
|
||||||
|
"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||||
|
"key": "0x4552dbe6ca4699322b5d923d0c9bcdd24644f5db8bf89a085b67c6c49b8a1b91"
|
||||||
|
},
|
||||||
|
"0x83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||||
|
"key": "0x34391cbbf06956bb506f45ec179cdd84df526aa364e27bbde65db9c15d866d00"
|
||||||
|
},
|
||||||
|
"0x84e75c28348fb86acea1a93a39426d7d60f4cc46": {
|
||||||
|
"key": "0xf6a8f1603b8368f3ca373292b7310c53bec7b508aecacd442554ebc1c5d0c856"
|
||||||
|
},
|
||||||
|
"0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||||
|
"key": "0x8d56bcbcf2c1b7109e1396a28d7a0234e33544ade74ea32c460ce4a443b239b1"
|
||||||
|
},
|
||||||
|
"0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
||||||
|
"key": "0xfc39d1c9ddbba176d806ebb42d7460189fe56ca163ad3eb6143bfc6beb6f6f72"
|
||||||
|
},
|
||||||
|
"0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
||||||
|
"key": "0x9ee3fd550664b246ad7cdba07162dd25530a3b1d51476dd1d85bbc29f0592684"
|
||||||
|
},
|
||||||
|
"0xeda8645ba6948855e3b3cd596bbb07596d59c603": {
|
||||||
|
"key": "0x14cdde09d1640eb8c3cda063891b0453073f57719583381ff78811efa6d4199f"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
Binary file not shown.
20
cmd/devp2p/internal/ethtest/testdata/forkenv.json
vendored
Normal file
20
cmd/devp2p/internal/ethtest/testdata/forkenv.json
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"HIVE_CANCUN_TIMESTAMP": "840",
|
||||||
|
"HIVE_CHAIN_ID": "3503995874084926",
|
||||||
|
"HIVE_FORK_ARROW_GLACIER": "60",
|
||||||
|
"HIVE_FORK_BERLIN": "48",
|
||||||
|
"HIVE_FORK_BYZANTIUM": "18",
|
||||||
|
"HIVE_FORK_CONSTANTINOPLE": "24",
|
||||||
|
"HIVE_FORK_GRAY_GLACIER": "66",
|
||||||
|
"HIVE_FORK_HOMESTEAD": "0",
|
||||||
|
"HIVE_FORK_ISTANBUL": "36",
|
||||||
|
"HIVE_FORK_LONDON": "54",
|
||||||
|
"HIVE_FORK_MUIR_GLACIER": "42",
|
||||||
|
"HIVE_FORK_PETERSBURG": "30",
|
||||||
|
"HIVE_FORK_SPURIOUS": "12",
|
||||||
|
"HIVE_FORK_TANGERINE": "6",
|
||||||
|
"HIVE_MERGE_BLOCK_ID": "72",
|
||||||
|
"HIVE_NETWORK_ID": "3503995874084926",
|
||||||
|
"HIVE_SHANGHAI_TIMESTAMP": "780",
|
||||||
|
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784"
|
||||||
|
}
|
||||||
131
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
131
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
|
|
@ -1,27 +1,112 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"chainId": 19763,
|
"chainId": 3503995874084926,
|
||||||
"homesteadBlock": 0,
|
"homesteadBlock": 0,
|
||||||
"eip150Block": 0,
|
"eip150Block": 6,
|
||||||
"eip155Block": 0,
|
"eip155Block": 12,
|
||||||
"eip158Block": 0,
|
"eip158Block": 12,
|
||||||
"byzantiumBlock": 0,
|
"byzantiumBlock": 18,
|
||||||
"terminalTotalDifficultyPassed": true,
|
"constantinopleBlock": 24,
|
||||||
"ethash": {}
|
"petersburgBlock": 30,
|
||||||
|
"istanbulBlock": 36,
|
||||||
|
"muirGlacierBlock": 42,
|
||||||
|
"berlinBlock": 48,
|
||||||
|
"londonBlock": 54,
|
||||||
|
"arrowGlacierBlock": 60,
|
||||||
|
"grayGlacierBlock": 66,
|
||||||
|
"mergeNetsplitBlock": 72,
|
||||||
|
"shanghaiTime": 780,
|
||||||
|
"cancunTime": 840,
|
||||||
|
"terminalTotalDifficulty": 9454784,
|
||||||
|
"terminalTotalDifficultyPassed": true,
|
||||||
|
"ethash": {}
|
||||||
|
},
|
||||||
|
"nonce": "0x0",
|
||||||
|
"timestamp": "0x0",
|
||||||
|
"extraData": "0x68697665636861696e",
|
||||||
|
"gasLimit": "0x23f3e20",
|
||||||
|
"difficulty": "0x20000",
|
||||||
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
|
"alloc": {
|
||||||
|
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
|
||||||
|
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
|
||||||
|
"balance": "0x2a"
|
||||||
},
|
},
|
||||||
"nonce": "0xdeadbeefdeadbeef",
|
"0c2c51a0990aee1d73c1228de158688341557508": {
|
||||||
"timestamp": "0x0",
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"gasLimit": "0x80000000",
|
|
||||||
"difficulty": "0x20000",
|
|
||||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
|
||||||
"alloc": {
|
|
||||||
"71562b71999873db5b286df957af199ec94617f7": {
|
|
||||||
"balance": "0xffffffffffffffffffffffffff"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"number": "0x0",
|
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
||||||
"gasUsed": "0x0",
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
},
|
||||||
|
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"2d389075be5be9f2246ad654ce152cf05990b209": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"4340ee1b812acb40a1eb561c019c327b243b92df": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"717f8aa2b982bee0e29f573d31df288663e1ce16": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"84e75c28348fb86acea1a93a39426d7d60f4cc46": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"8bebc8ba651aee624937e7d897853ac30c95a067": {
|
||||||
|
"storage": {
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
|
||||||
|
},
|
||||||
|
"balance": "0x1",
|
||||||
|
"nonce": "0x1"
|
||||||
|
},
|
||||||
|
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"eda8645ba6948855e3b3cd596bbb07596d59c603": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"number": "0x0",
|
||||||
|
"gasUsed": "0x0",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"baseFeePerGas": null,
|
||||||
|
"excessBlobGas": null,
|
||||||
|
"blobGasUsed": null
|
||||||
}
|
}
|
||||||
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp
vendored
Binary file not shown.
23
cmd/devp2p/internal/ethtest/testdata/headblock.json
vendored
Normal file
23
cmd/devp2p/internal/ethtest/testdata/headblock.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39",
|
||||||
|
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||||
|
"miner": "0x0000000000000000000000000000000000000000",
|
||||||
|
"stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94",
|
||||||
|
"transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48",
|
||||||
|
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
|
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"difficulty": "0x0",
|
||||||
|
"number": "0x1f4",
|
||||||
|
"gasLimit": "0x47e7c40",
|
||||||
|
"gasUsed": "0x5208",
|
||||||
|
"timestamp": "0x1388",
|
||||||
|
"extraData": "0x",
|
||||||
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"nonce": "0x0000000000000000",
|
||||||
|
"baseFeePerGas": "0x7",
|
||||||
|
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
|
"blobGasUsed": "0x0",
|
||||||
|
"excessBlobGas": "0x0",
|
||||||
|
"parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c",
|
||||||
|
"hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||||
|
}
|
||||||
13
cmd/devp2p/internal/ethtest/testdata/headfcu.json
vendored
Normal file
13
cmd/devp2p/internal/ethtest/testdata/headfcu.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "fcu500",
|
||||||
|
"method": "engine_forkchoiceUpdatedV3",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||||
|
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||||
|
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||||
|
},
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
||||||
4204
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
Normal file
4204
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
13268
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
Normal file
13268
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
3018
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
Normal file
3018
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -19,429 +19,141 @@ package ethtest
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
// sendTxs sends the given transactions to the node and
|
||||||
var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
// expects the node to accept and propagate them.
|
||||||
|
func (s *Suite) sendTxs(txs []*types.Transaction) error {
|
||||||
func (s *Suite) sendSuccessfulTxs(t *utesting.T) error {
|
// Open sending conn.
|
||||||
tests := []*types.Transaction{
|
sendConn, err := s.dial()
|
||||||
getNextTxFromChain(s),
|
|
||||||
unknownTx(s),
|
|
||||||
}
|
|
||||||
for i, tx := range tests {
|
|
||||||
if tx == nil {
|
|
||||||
return errors.New("could not find tx to send")
|
|
||||||
}
|
|
||||||
t.Logf("Testing tx propagation %d: sending tx %v %v %v\n", i, tx.Hash().String(), tx.GasPrice(), tx.Gas())
|
|
||||||
// get previous tx if exists for reference in case of old tx propagation
|
|
||||||
var prevTx *types.Transaction
|
|
||||||
if i != 0 {
|
|
||||||
prevTx = tests[i-1]
|
|
||||||
}
|
|
||||||
// write tx to connection
|
|
||||||
if err := sendSuccessfulTx(s, tx, prevTx); err != nil {
|
|
||||||
return fmt.Errorf("send successful tx test failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction) error {
|
|
||||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sendConn.Close()
|
defer sendConn.Close()
|
||||||
defer recvConn.Close()
|
|
||||||
if err = sendConn.peer(s.chain, nil); err != nil {
|
if err = sendConn.peer(s.chain, nil); err != nil {
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
return fmt.Errorf("peering failed: %v", err)
|
||||||
}
|
}
|
||||||
// Send the transaction
|
|
||||||
if err = sendConn.Write(&Transactions{tx}); err != nil {
|
|
||||||
return fmt.Errorf("failed to write to connection: %v", err)
|
|
||||||
}
|
|
||||||
// peer receiving connection to node
|
|
||||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update last nonce seen
|
// Open receiving conn.
|
||||||
nonce = tx.Nonce()
|
|
||||||
|
|
||||||
// Wait for the transaction announcement
|
|
||||||
for {
|
|
||||||
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
|
|
||||||
case *Transactions:
|
|
||||||
recTxs := *msg
|
|
||||||
// if you receive an old tx propagation, read from connection again
|
|
||||||
if len(recTxs) == 1 && prevTx != nil {
|
|
||||||
if recTxs[0] == prevTx {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, gotTx := range recTxs {
|
|
||||||
if gotTx.Hash() == tx.Hash() {
|
|
||||||
// Ok
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("missing transaction: got %v missing %v", recTxs, tx.Hash())
|
|
||||||
case *NewPooledTransactionHashes66:
|
|
||||||
txHashes := *msg
|
|
||||||
// if you receive an old tx propagation, read from connection again
|
|
||||||
if len(txHashes) == 1 && prevTx != nil {
|
|
||||||
if txHashes[0] == prevTx.Hash() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, gotHash := range txHashes {
|
|
||||||
if gotHash == tx.Hash() {
|
|
||||||
// Ok
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash())
|
|
||||||
case *NewPooledTransactionHashes:
|
|
||||||
txHashes := msg.Hashes
|
|
||||||
if len(txHashes) != len(msg.Sizes) {
|
|
||||||
return fmt.Errorf("invalid msg size lengths: hashes: %v sizes: %v", len(txHashes), len(msg.Sizes))
|
|
||||||
}
|
|
||||||
if len(txHashes) != len(msg.Types) {
|
|
||||||
return fmt.Errorf("invalid msg type lengths: hashes: %v types: %v", len(txHashes), len(msg.Types))
|
|
||||||
}
|
|
||||||
// if you receive an old tx propagation, read from connection again
|
|
||||||
if len(txHashes) == 1 && prevTx != nil {
|
|
||||||
if txHashes[0] == prevTx.Hash() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for index, gotHash := range txHashes {
|
|
||||||
if gotHash == tx.Hash() {
|
|
||||||
if msg.Sizes[index] != uint32(tx.Size()) {
|
|
||||||
return fmt.Errorf("invalid tx size: got %v want %v", msg.Sizes[index], tx.Size())
|
|
||||||
}
|
|
||||||
if msg.Types[index] != tx.Type() {
|
|
||||||
return fmt.Errorf("invalid tx type: got %v want %v", msg.Types[index], tx.Type())
|
|
||||||
}
|
|
||||||
// Ok
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash())
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) sendMaliciousTxs(t *utesting.T) error {
|
|
||||||
badTxs := []*types.Transaction{
|
|
||||||
getOldTxFromChain(s),
|
|
||||||
invalidNonceTx(s),
|
|
||||||
hugeAmount(s),
|
|
||||||
hugeGasPrice(s),
|
|
||||||
hugeData(s),
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup receiving connection before sending malicious txs
|
|
||||||
recvConn, err := s.dial()
|
recvConn, err := s.dial()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
defer recvConn.Close()
|
|
||||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tx := range badTxs {
|
|
||||||
t.Logf("Testing malicious tx propagation: %v\n", i)
|
|
||||||
if err = sendMaliciousTx(s, tx); err != nil {
|
|
||||||
return fmt.Errorf("malicious tx test failed:\ntx: %v\nerror: %v", tx, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check to make sure bad txs aren't propagated
|
|
||||||
return checkMaliciousTxPropagation(s, badTxs, recvConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendMaliciousTx(s *Suite, tx *types.Transaction) error {
|
|
||||||
conn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
if err = conn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write malicious tx
|
|
||||||
if err = conn.Write(&Transactions{tx}); err != nil {
|
|
||||||
return fmt.Errorf("failed to write to connection: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var nonce = uint64(99)
|
|
||||||
|
|
||||||
// sendMultipleSuccessfulTxs sends the given transactions to the node and
|
|
||||||
// expects the node to accept and propagate them.
|
|
||||||
func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction) error {
|
|
||||||
txMsg := Transactions(txs)
|
|
||||||
t.Logf("sending %d txs\n", len(txs))
|
|
||||||
|
|
||||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sendConn.Close()
|
|
||||||
defer recvConn.Close()
|
defer recvConn.Close()
|
||||||
if err = sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
if err = recvConn.peer(s.chain, nil); err != nil {
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
return fmt.Errorf("peering failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the transactions
|
if err = sendConn.Write(ethProto, eth.TransactionsMsg, eth.TransactionsPacket(txs)); err != nil {
|
||||||
if err = sendConn.Write(&txMsg); err != nil {
|
|
||||||
return fmt.Errorf("failed to write message to connection: %v", err)
|
return fmt.Errorf("failed to write message to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update nonce
|
var (
|
||||||
nonce = txs[len(txs)-1].Nonce()
|
got = make(map[common.Hash]bool)
|
||||||
|
end = time.Now().Add(timeout)
|
||||||
|
)
|
||||||
|
|
||||||
// Wait for the transaction announcement(s) and make sure all sent txs are being propagated.
|
// Wait for the transaction announcements, make sure all txs ar propagated.
|
||||||
// all txs should be announced within a couple announcements.
|
for time.Now().Before(end) {
|
||||||
recvHashes := make([]common.Hash, 0)
|
msg, err := recvConn.ReadEth()
|
||||||
|
if err != nil {
|
||||||
for i := 0; i < 20; i++ {
|
return fmt.Errorf("failed to read from connection: %w", err)
|
||||||
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
|
}
|
||||||
case *Transactions:
|
switch msg := msg.(type) {
|
||||||
|
case *eth.TransactionsPacket:
|
||||||
for _, tx := range *msg {
|
for _, tx := range *msg {
|
||||||
recvHashes = append(recvHashes, tx.Hash())
|
got[tx.Hash()] = true
|
||||||
|
}
|
||||||
|
case *eth.NewPooledTransactionHashesPacket68:
|
||||||
|
for _, hash := range msg.Hashes {
|
||||||
|
got[hash] = true
|
||||||
}
|
}
|
||||||
case *NewPooledTransactionHashes66:
|
|
||||||
recvHashes = append(recvHashes, *msg...)
|
|
||||||
case *NewPooledTransactionHashes:
|
|
||||||
recvHashes = append(recvHashes, msg.Hashes...)
|
|
||||||
default:
|
default:
|
||||||
if !strings.Contains(pretty.Sdump(msg), "i/o timeout") {
|
return fmt.Errorf("unexpected eth wire msg: %s", pretty.Sdump(msg))
|
||||||
return fmt.Errorf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg))
|
}
|
||||||
|
|
||||||
|
// Check if all txs received.
|
||||||
|
allReceived := func() bool {
|
||||||
|
for _, tx := range txs {
|
||||||
|
if !got[tx.Hash()] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
// break once all 2000 txs have been received
|
if allReceived() {
|
||||||
if len(recvHashes) == 2000 {
|
return nil
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(recvHashes) > 0 {
|
|
||||||
_, missingTxs := compareReceivedTxs(recvHashes, txs)
|
|
||||||
if len(missingTxs) > 0 {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
t.Logf("successfully received all %d txs", len(txs))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, missingTxs := compareReceivedTxs(recvHashes, txs)
|
|
||||||
if len(missingTxs) > 0 {
|
return fmt.Errorf("timed out waiting for txs")
|
||||||
for _, missing := range missingTxs {
|
|
||||||
t.Logf("missing tx: %v", missing.Hash())
|
|
||||||
}
|
|
||||||
return fmt.Errorf("missing %d txs", len(missingTxs))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkMaliciousTxPropagation checks whether the given malicious transactions were
|
func (s *Suite) sendInvalidTxs(txs []*types.Transaction) error {
|
||||||
// propagated by the node.
|
// Open sending conn.
|
||||||
func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) error {
|
sendConn, err := s.dial()
|
||||||
switch msg := conn.readAndServe(s.chain, time.Second*8).(type) {
|
|
||||||
case *Transactions:
|
|
||||||
// check to see if any of the failing txs were in the announcement
|
|
||||||
recvTxs := make([]common.Hash, len(*msg))
|
|
||||||
for i, recvTx := range *msg {
|
|
||||||
recvTxs[i] = recvTx.Hash()
|
|
||||||
}
|
|
||||||
badTxs, _ := compareReceivedTxs(recvTxs, txs)
|
|
||||||
if len(badTxs) > 0 {
|
|
||||||
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
|
|
||||||
}
|
|
||||||
case *NewPooledTransactionHashes66:
|
|
||||||
badTxs, _ := compareReceivedTxs(*msg, txs)
|
|
||||||
if len(badTxs) > 0 {
|
|
||||||
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
|
|
||||||
}
|
|
||||||
case *NewPooledTransactionHashes:
|
|
||||||
badTxs, _ := compareReceivedTxs(msg.Hashes, txs)
|
|
||||||
if len(badTxs) > 0 {
|
|
||||||
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
|
|
||||||
}
|
|
||||||
case *Error:
|
|
||||||
// Transaction should not be announced -> wait for timeout
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// compareReceivedTxs compares the received set of txs against the given set of txs,
|
|
||||||
// returning both the set received txs that were present within the given txs, and
|
|
||||||
// the set of txs that were missing from the set of received txs
|
|
||||||
func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (present []*types.Transaction, missing []*types.Transaction) {
|
|
||||||
// create a map of the hashes received from node
|
|
||||||
recvHashes := make(map[common.Hash]common.Hash)
|
|
||||||
for _, hash := range recvTxs {
|
|
||||||
recvHashes[hash] = hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect present txs and missing txs separately
|
|
||||||
present = make([]*types.Transaction, 0)
|
|
||||||
missing = make([]*types.Transaction, 0)
|
|
||||||
for _, tx := range txs {
|
|
||||||
if _, exists := recvHashes[tx.Hash()]; exists {
|
|
||||||
present = append(present, tx)
|
|
||||||
} else {
|
|
||||||
missing = append(missing, tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return present, missing
|
|
||||||
}
|
|
||||||
|
|
||||||
func unknownTx(s *Suite) *types.Transaction {
|
|
||||||
tx := getNextTxFromChain(s)
|
|
||||||
if tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var to common.Address
|
|
||||||
if tx.To() != nil {
|
|
||||||
to = *tx.To()
|
|
||||||
}
|
|
||||||
txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data())
|
|
||||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNextTxFromChain(s *Suite) *types.Transaction {
|
|
||||||
// Get a new transaction
|
|
||||||
for _, blocks := range s.fullChain.blocks[s.chain.Len():] {
|
|
||||||
txs := blocks.Transactions()
|
|
||||||
if txs.Len() != 0 {
|
|
||||||
return txs[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction, error) {
|
|
||||||
txHashMap := make(map[common.Hash]common.Hash, numTxs)
|
|
||||||
txs := make([]*types.Transaction, numTxs)
|
|
||||||
|
|
||||||
nextTx := getNextTxFromChain(s)
|
|
||||||
if nextTx == nil {
|
|
||||||
return nil, nil, errors.New("failed to get the next transaction")
|
|
||||||
}
|
|
||||||
gas := nextTx.Gas()
|
|
||||||
|
|
||||||
nonce = nonce + 1
|
|
||||||
// generate txs
|
|
||||||
for i := 0; i < numTxs; i++ {
|
|
||||||
tx := generateTx(s.chain.chainConfig, nonce, gas)
|
|
||||||
if tx == nil {
|
|
||||||
return nil, nil, errors.New("failed to get the next transaction")
|
|
||||||
}
|
|
||||||
txHashMap[tx.Hash()] = tx.Hash()
|
|
||||||
txs[i] = tx
|
|
||||||
nonce = nonce + 1
|
|
||||||
}
|
|
||||||
return txHashMap, txs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTx(chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction {
|
|
||||||
var to common.Address
|
|
||||||
tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{})
|
|
||||||
return signWithFaucet(chainConfig, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOldTxFromChain(s *Suite) *types.Transaction {
|
|
||||||
for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] {
|
|
||||||
txs := blocks.Transactions()
|
|
||||||
if txs.Len() != 0 {
|
|
||||||
return txs[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidNonceTx(s *Suite) *types.Transaction {
|
|
||||||
tx := getNextTxFromChain(s)
|
|
||||||
if tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var to common.Address
|
|
||||||
if tx.To() != nil {
|
|
||||||
to = *tx.To()
|
|
||||||
}
|
|
||||||
txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data())
|
|
||||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hugeAmount(s *Suite) *types.Transaction {
|
|
||||||
tx := getNextTxFromChain(s)
|
|
||||||
if tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
amount := largeNumber(2)
|
|
||||||
var to common.Address
|
|
||||||
if tx.To() != nil {
|
|
||||||
to = *tx.To()
|
|
||||||
}
|
|
||||||
txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data())
|
|
||||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hugeGasPrice(s *Suite) *types.Transaction {
|
|
||||||
tx := getNextTxFromChain(s)
|
|
||||||
if tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
gasPrice := largeNumber(2)
|
|
||||||
var to common.Address
|
|
||||||
if tx.To() != nil {
|
|
||||||
to = *tx.To()
|
|
||||||
}
|
|
||||||
txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data())
|
|
||||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hugeData(s *Suite) *types.Transaction {
|
|
||||||
tx := getNextTxFromChain(s)
|
|
||||||
if tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var to common.Address
|
|
||||||
if tx.To() != nil {
|
|
||||||
to = *tx.To()
|
|
||||||
}
|
|
||||||
txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2))
|
|
||||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
|
||||||
}
|
|
||||||
|
|
||||||
func signWithFaucet(chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction {
|
|
||||||
signer := types.LatestSigner(chainConfig)
|
|
||||||
signedTx, err := types.SignTx(tx, signer, faucetKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
|
}
|
||||||
|
defer sendConn.Close()
|
||||||
|
if err = sendConn.peer(s.chain, nil); err != nil {
|
||||||
|
return fmt.Errorf("peering failed: %v", err)
|
||||||
|
}
|
||||||
|
sendConn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
|
||||||
|
// Open receiving conn.
|
||||||
|
recvConn, err := s.dial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer recvConn.Close()
|
||||||
|
if err = recvConn.peer(s.chain, nil); err != nil {
|
||||||
|
return fmt.Errorf("peering failed: %v", err)
|
||||||
|
}
|
||||||
|
recvConn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
|
||||||
|
if err = sendConn.Write(ethProto, eth.TransactionsMsg, txs); err != nil {
|
||||||
|
return fmt.Errorf("failed to write message to connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make map of invalid txs.
|
||||||
|
invalids := make(map[common.Hash]struct{})
|
||||||
|
for _, tx := range txs {
|
||||||
|
invalids[tx.Hash()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get repsonses.
|
||||||
|
recvConn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
msg, err := recvConn.ReadEth()
|
||||||
|
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
|
// Successful if no invalid txs are propagated before timeout.
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read from connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *eth.TransactionsPacket:
|
||||||
|
for _, tx := range txs {
|
||||||
|
if _, ok := invalids[tx.Hash()]; ok {
|
||||||
|
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *eth.NewPooledTransactionHashesPacket68:
|
||||||
|
for _, hash := range msg.Hashes {
|
||||||
|
if _, ok := invalids[hash]; ok {
|
||||||
|
return fmt.Errorf("received bad tx: %s", hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected eth message: %v", pretty.Sdump(msg))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return signedTx
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
// Copyright 2020 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum 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 General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package ethtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Message interface {
|
|
||||||
Code() int
|
|
||||||
ReqID() uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Unwrap() error { return e.err }
|
|
||||||
func (e *Error) Error() string { return e.err.Error() }
|
|
||||||
func (e *Error) String() string { return e.Error() }
|
|
||||||
|
|
||||||
func (e *Error) Code() int { return -1 }
|
|
||||||
func (e *Error) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
func errorf(format string, args ...interface{}) *Error {
|
|
||||||
return &Error{fmt.Errorf(format, args...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hello is the RLP structure of the protocol handshake.
|
|
||||||
type Hello struct {
|
|
||||||
Version uint64
|
|
||||||
Name string
|
|
||||||
Caps []p2p.Cap
|
|
||||||
ListenPort uint64
|
|
||||||
ID []byte // secp256k1 public key
|
|
||||||
|
|
||||||
// Ignore additional fields (for forward compatibility).
|
|
||||||
Rest []rlp.RawValue `rlp:"tail"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg Hello) Code() int { return 0x00 }
|
|
||||||
func (msg Hello) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
// Disconnect is the RLP structure for a disconnect message.
|
|
||||||
type Disconnect struct {
|
|
||||||
Reason p2p.DiscReason
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg Disconnect) Code() int { return 0x01 }
|
|
||||||
func (msg Disconnect) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
type Ping struct{}
|
|
||||||
|
|
||||||
func (msg Ping) Code() int { return 0x02 }
|
|
||||||
func (msg Ping) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
type Pong struct{}
|
|
||||||
|
|
||||||
func (msg Pong) Code() int { return 0x03 }
|
|
||||||
func (msg Pong) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
// Status is the network packet for the status message for eth/64 and later.
|
|
||||||
type Status eth.StatusPacket
|
|
||||||
|
|
||||||
func (msg Status) Code() int { return 16 }
|
|
||||||
func (msg Status) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
// NewBlockHashes is the network packet for the block announcements.
|
|
||||||
type NewBlockHashes eth.NewBlockHashesPacket
|
|
||||||
|
|
||||||
func (msg NewBlockHashes) Code() int { return 17 }
|
|
||||||
func (msg NewBlockHashes) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
type Transactions eth.TransactionsPacket
|
|
||||||
|
|
||||||
func (msg Transactions) Code() int { return 18 }
|
|
||||||
func (msg Transactions) ReqID() uint64 { return 18 }
|
|
||||||
|
|
||||||
// GetBlockHeaders represents a block header query.
|
|
||||||
type GetBlockHeaders eth.GetBlockHeadersPacket
|
|
||||||
|
|
||||||
func (msg GetBlockHeaders) Code() int { return 19 }
|
|
||||||
func (msg GetBlockHeaders) ReqID() uint64 { return msg.RequestId }
|
|
||||||
|
|
||||||
type BlockHeaders eth.BlockHeadersPacket
|
|
||||||
|
|
||||||
func (msg BlockHeaders) Code() int { return 20 }
|
|
||||||
func (msg BlockHeaders) ReqID() uint64 { return msg.RequestId }
|
|
||||||
|
|
||||||
// GetBlockBodies represents a GetBlockBodies request
|
|
||||||
type GetBlockBodies eth.GetBlockBodiesPacket
|
|
||||||
|
|
||||||
func (msg GetBlockBodies) Code() int { return 21 }
|
|
||||||
func (msg GetBlockBodies) ReqID() uint64 { return msg.RequestId }
|
|
||||||
|
|
||||||
// BlockBodies is the network packet for block content distribution.
|
|
||||||
type BlockBodies eth.BlockBodiesPacket
|
|
||||||
|
|
||||||
func (msg BlockBodies) Code() int { return 22 }
|
|
||||||
func (msg BlockBodies) ReqID() uint64 { return msg.RequestId }
|
|
||||||
|
|
||||||
// NewBlock is the network packet for the block propagation message.
|
|
||||||
type NewBlock eth.NewBlockPacket
|
|
||||||
|
|
||||||
func (msg NewBlock) Code() int { return 23 }
|
|
||||||
func (msg NewBlock) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
// NewPooledTransactionHashes66 is the network packet for the tx hash propagation message.
|
|
||||||
type NewPooledTransactionHashes66 eth.NewPooledTransactionHashesPacket67
|
|
||||||
|
|
||||||
func (msg NewPooledTransactionHashes66) Code() int { return 24 }
|
|
||||||
func (msg NewPooledTransactionHashes66) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
// NewPooledTransactionHashes is the network packet for the tx hash propagation message.
|
|
||||||
type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket68
|
|
||||||
|
|
||||||
func (msg NewPooledTransactionHashes) Code() int { return 24 }
|
|
||||||
func (msg NewPooledTransactionHashes) ReqID() uint64 { return 0 }
|
|
||||||
|
|
||||||
type GetPooledTransactions eth.GetPooledTransactionsPacket
|
|
||||||
|
|
||||||
func (msg GetPooledTransactions) Code() int { return 25 }
|
|
||||||
func (msg GetPooledTransactions) ReqID() uint64 { return msg.RequestId }
|
|
||||||
|
|
||||||
type PooledTransactions eth.PooledTransactionsPacket
|
|
||||||
|
|
||||||
func (msg PooledTransactions) Code() int { return 26 }
|
|
||||||
func (msg PooledTransactions) ReqID() uint64 { return msg.RequestId }
|
|
||||||
|
|
||||||
// Conn represents an individual connection with a peer
|
|
||||||
type Conn struct {
|
|
||||||
*rlpx.Conn
|
|
||||||
ourKey *ecdsa.PrivateKey
|
|
||||||
negotiatedProtoVersion uint
|
|
||||||
negotiatedSnapProtoVersion uint
|
|
||||||
ourHighestProtoVersion uint
|
|
||||||
ourHighestSnapProtoVersion uint
|
|
||||||
caps []p2p.Cap
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads an eth66 packet from the connection.
|
|
||||||
func (c *Conn) Read() Message {
|
|
||||||
code, rawData, _, err := c.Conn.Read()
|
|
||||||
if err != nil {
|
|
||||||
return errorf("could not read from connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg Message
|
|
||||||
switch int(code) {
|
|
||||||
case (Hello{}).Code():
|
|
||||||
msg = new(Hello)
|
|
||||||
case (Ping{}).Code():
|
|
||||||
msg = new(Ping)
|
|
||||||
case (Pong{}).Code():
|
|
||||||
msg = new(Pong)
|
|
||||||
case (Disconnect{}).Code():
|
|
||||||
msg = new(Disconnect)
|
|
||||||
case (Status{}).Code():
|
|
||||||
msg = new(Status)
|
|
||||||
case (GetBlockHeaders{}).Code():
|
|
||||||
ethMsg := new(eth.GetBlockHeadersPacket)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return (*GetBlockHeaders)(ethMsg)
|
|
||||||
case (BlockHeaders{}).Code():
|
|
||||||
ethMsg := new(eth.BlockHeadersPacket)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return (*BlockHeaders)(ethMsg)
|
|
||||||
case (GetBlockBodies{}).Code():
|
|
||||||
ethMsg := new(eth.GetBlockBodiesPacket)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return (*GetBlockBodies)(ethMsg)
|
|
||||||
case (BlockBodies{}).Code():
|
|
||||||
ethMsg := new(eth.BlockBodiesPacket)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return (*BlockBodies)(ethMsg)
|
|
||||||
case (NewBlock{}).Code():
|
|
||||||
msg = new(NewBlock)
|
|
||||||
case (NewBlockHashes{}).Code():
|
|
||||||
msg = new(NewBlockHashes)
|
|
||||||
case (Transactions{}).Code():
|
|
||||||
msg = new(Transactions)
|
|
||||||
case (NewPooledTransactionHashes66{}).Code():
|
|
||||||
// Try decoding to eth68
|
|
||||||
ethMsg := new(NewPooledTransactionHashes)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err == nil {
|
|
||||||
return ethMsg
|
|
||||||
}
|
|
||||||
msg = new(NewPooledTransactionHashes66)
|
|
||||||
case (GetPooledTransactions{}.Code()):
|
|
||||||
ethMsg := new(eth.GetPooledTransactionsPacket)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return (*GetPooledTransactions)(ethMsg)
|
|
||||||
case (PooledTransactions{}.Code()):
|
|
||||||
ethMsg := new(eth.PooledTransactionsPacket)
|
|
||||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return (*PooledTransactions)(ethMsg)
|
|
||||||
default:
|
|
||||||
msg = errorf("invalid message code: %d", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != nil {
|
|
||||||
if err := rlp.DecodeBytes(rawData, msg); err != nil {
|
|
||||||
return errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
return errorf("invalid message: %s", string(rawData))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes a eth packet to the connection.
|
|
||||||
func (c *Conn) Write(msg Message) error {
|
|
||||||
payload, err := rlp.EncodeToBytes(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(uint64(msg.Code()), payload)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSnap reads a snap/1 response with the given id from the connection.
|
|
||||||
func (c *Conn) ReadSnap(id uint64) (Message, error) {
|
|
||||||
respId := id + 1
|
|
||||||
start := time.Now()
|
|
||||||
for respId != id && time.Since(start) < timeout {
|
|
||||||
code, rawData, _, err := c.Conn.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read from connection: %v", err)
|
|
||||||
}
|
|
||||||
var snpMsg interface{}
|
|
||||||
switch int(code) {
|
|
||||||
case (GetAccountRange{}).Code():
|
|
||||||
snpMsg = new(GetAccountRange)
|
|
||||||
case (AccountRange{}).Code():
|
|
||||||
snpMsg = new(AccountRange)
|
|
||||||
case (GetStorageRanges{}).Code():
|
|
||||||
snpMsg = new(GetStorageRanges)
|
|
||||||
case (StorageRanges{}).Code():
|
|
||||||
snpMsg = new(StorageRanges)
|
|
||||||
case (GetByteCodes{}).Code():
|
|
||||||
snpMsg = new(GetByteCodes)
|
|
||||||
case (ByteCodes{}).Code():
|
|
||||||
snpMsg = new(ByteCodes)
|
|
||||||
case (GetTrieNodes{}).Code():
|
|
||||||
snpMsg = new(GetTrieNodes)
|
|
||||||
case (TrieNodes{}).Code():
|
|
||||||
snpMsg = new(TrieNodes)
|
|
||||||
default:
|
|
||||||
//return nil, fmt.Errorf("invalid message code: %d", code)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := rlp.DecodeBytes(rawData, snpMsg); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not rlp decode message: %v", err)
|
|
||||||
}
|
|
||||||
return snpMsg.(Message), nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("request timed out")
|
|
||||||
}
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
@ -46,22 +47,30 @@ var (
|
||||||
}
|
}
|
||||||
rlpxEthTestCommand = &cli.Command{
|
rlpxEthTestCommand = &cli.Command{
|
||||||
Name: "eth-test",
|
Name: "eth-test",
|
||||||
Usage: "Runs tests against a node",
|
Usage: "Runs eth protocol tests against a node",
|
||||||
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
|
ArgsUsage: "<node>",
|
||||||
Action: rlpxEthTest,
|
Action: rlpxEthTest,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
testPatternFlag,
|
testPatternFlag,
|
||||||
testTAPFlag,
|
testTAPFlag,
|
||||||
|
testChainDirFlag,
|
||||||
|
testNodeFlag,
|
||||||
|
testNodeJWTFlag,
|
||||||
|
testNodeEngineFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rlpxSnapTestCommand = &cli.Command{
|
rlpxSnapTestCommand = &cli.Command{
|
||||||
Name: "snap-test",
|
Name: "snap-test",
|
||||||
Usage: "Runs tests against a node",
|
Usage: "Runs snap protocol tests against a node",
|
||||||
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
|
ArgsUsage: "",
|
||||||
Action: rlpxSnapTest,
|
Action: rlpxSnapTest,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
testPatternFlag,
|
testPatternFlag,
|
||||||
testTAPFlag,
|
testTAPFlag,
|
||||||
|
testChainDirFlag,
|
||||||
|
testNodeFlag,
|
||||||
|
testNodeJWTFlag,
|
||||||
|
testNodeEngineFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -103,10 +112,8 @@ func rlpxPing(ctx *cli.Context) error {
|
||||||
|
|
||||||
// rlpxEthTest runs the eth protocol test suite.
|
// rlpxEthTest runs the eth protocol test suite.
|
||||||
func rlpxEthTest(ctx *cli.Context) error {
|
func rlpxEthTest(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 3 {
|
p := cliTestParams(ctx)
|
||||||
exit("missing path to chain.rlp as command-line argument")
|
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
|
||||||
}
|
|
||||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
|
|
@ -115,12 +122,44 @@ func rlpxEthTest(ctx *cli.Context) error {
|
||||||
|
|
||||||
// rlpxSnapTest runs the snap protocol test suite.
|
// rlpxSnapTest runs the snap protocol test suite.
|
||||||
func rlpxSnapTest(ctx *cli.Context) error {
|
func rlpxSnapTest(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 3 {
|
p := cliTestParams(ctx)
|
||||||
exit("missing path to chain.rlp as command-line argument")
|
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
|
||||||
}
|
|
||||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
return runTests(ctx, suite.SnapTests())
|
return runTests(ctx, suite.SnapTests())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testParams struct {
|
||||||
|
node *enode.Node
|
||||||
|
engineAPI string
|
||||||
|
jwt string
|
||||||
|
chainDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func cliTestParams(ctx *cli.Context) *testParams {
|
||||||
|
nodeStr := ctx.String(testNodeFlag.Name)
|
||||||
|
if nodeStr == "" {
|
||||||
|
exit(fmt.Errorf("missing -%s", testNodeFlag.Name))
|
||||||
|
}
|
||||||
|
node, err := parseNode(nodeStr)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
p := testParams{
|
||||||
|
node: node,
|
||||||
|
engineAPI: ctx.String(testNodeEngineFlag.Name),
|
||||||
|
jwt: ctx.String(testNodeJWTFlag.Name),
|
||||||
|
chainDir: ctx.String(testChainDirFlag.Name),
|
||||||
|
}
|
||||||
|
if p.engineAPI == "" {
|
||||||
|
exit(fmt.Errorf("missing -%s", testNodeEngineFlag.Name))
|
||||||
|
}
|
||||||
|
if p.jwt == "" {
|
||||||
|
exit(fmt.Errorf("missing -%s", testNodeJWTFlag.Name))
|
||||||
|
}
|
||||||
|
if p.chainDir == "" {
|
||||||
|
exit(fmt.Errorf("missing -%s", testChainDirFlag.Name))
|
||||||
|
}
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
@ -27,23 +28,51 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testPatternFlag = &cli.StringFlag{
|
testPatternFlag = &cli.StringFlag{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "Pattern of test suite(s) to run",
|
Usage: "Pattern of test suite(s) to run",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
testTAPFlag = &cli.BoolFlag{
|
testTAPFlag = &cli.BoolFlag{
|
||||||
Name: "tap",
|
Name: "tap",
|
||||||
Usage: "Output TAP",
|
Usage: "Output test results in TAP format",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for eth/snap tests
|
||||||
|
testChainDirFlag = &cli.StringFlag{
|
||||||
|
Name: "chain",
|
||||||
|
Usage: "Test chain directory (required)",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
testNodeFlag = &cli.StringFlag{
|
||||||
|
Name: "node",
|
||||||
|
Usage: "Peer-to-Peer endpoint (ENR) of the test node (required)",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
testNodeJWTFlag = &cli.StringFlag{
|
||||||
|
Name: "jwtsecret",
|
||||||
|
Usage: "JWT secret for the engine API of the test node (required)",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
Value: "0x7365637265747365637265747365637265747365637265747365637265747365",
|
||||||
|
}
|
||||||
|
testNodeEngineFlag = &cli.StringFlag{
|
||||||
|
Name: "engineapi",
|
||||||
|
Usage: "Engine API endpoint of the test node (required)",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
|
||||||
// These two are specific to the discovery tests.
|
// These two are specific to the discovery tests.
|
||||||
testListen1Flag = &cli.StringFlag{
|
testListen1Flag = &cli.StringFlag{
|
||||||
Name: "listen1",
|
Name: "listen1",
|
||||||
Usage: "IP address of the first tester",
|
Usage: "IP address of the first tester",
|
||||||
Value: v4test.Listen1,
|
Value: v4test.Listen1,
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
testListen2Flag = &cli.StringFlag{
|
testListen2Flag = &cli.StringFlag{
|
||||||
Name: "listen2",
|
Name: "listen2",
|
||||||
Usage: "IP address of the second tester",
|
Usage: "IP address of the second tester",
|
||||||
Value: v4test.Listen2,
|
Value: v4test.Listen2,
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go
|
//go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go
|
||||||
|
|
@ -216,11 +214,6 @@ func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) {
|
||||||
|
|
||||||
// BuildBlock constructs a block from the given inputs.
|
// BuildBlock constructs a block from the given inputs.
|
||||||
func BuildBlock(ctx *cli.Context) error {
|
func BuildBlock(ctx *cli.Context) error {
|
||||||
// Configure the go-ethereum logger
|
|
||||||
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
|
||||||
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
|
|
||||||
log.SetDefault(log.NewLogger(glogger))
|
|
||||||
|
|
||||||
baseDir, err := createBasedir(ctx)
|
baseDir, err := createBasedir(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
|
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ type rejectedTx struct {
|
||||||
// Apply applies a set of transactions to a pre-state
|
// Apply applies a set of transactions to a pre-state
|
||||||
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
txIt txIterator, miningReward int64,
|
txIt txIterator, miningReward int64,
|
||||||
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, []byte, error) {
|
getTracerFn func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)) (*state.StateDB, *ExecutionResult, []byte, error) {
|
||||||
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
|
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
|
||||||
// required blockhashes
|
// required blockhashes
|
||||||
var hashError error
|
var hashError error
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,15 @@ import (
|
||||||
var (
|
var (
|
||||||
TraceFlag = &cli.BoolFlag{
|
TraceFlag = &cli.BoolFlag{
|
||||||
Name: "trace",
|
Name: "trace",
|
||||||
Usage: "Output full trace logs to files <txhash>.jsonl",
|
Usage: "Configures the use of the JSON opcode tracer. This tracer emits traces to files as trace-<txIndex>-<txHash>.jsonl",
|
||||||
}
|
}
|
||||||
TraceDisableMemoryFlag = &cli.BoolFlag{
|
TraceTracerFlag = &cli.StringFlag{
|
||||||
Name: "trace.nomemory",
|
Name: "trace.tracer",
|
||||||
Value: true,
|
Usage: "Configures the use of a custom tracer, e.g native or js tracers. Examples are callTracer and 4byteTracer. These tracers emit results into files as trace-<txIndex>-<txHash>.json",
|
||||||
Usage: "Disable full memory dump in traces (deprecated)",
|
}
|
||||||
|
TraceTracerConfigFlag = &cli.StringFlag{
|
||||||
|
Name: "trace.jsonconfig",
|
||||||
|
Usage: "The configurations for the custom tracer specified by --trace.tracer. If provided, must be in JSON format",
|
||||||
}
|
}
|
||||||
TraceEnableMemoryFlag = &cli.BoolFlag{
|
TraceEnableMemoryFlag = &cli.BoolFlag{
|
||||||
Name: "trace.memory",
|
Name: "trace.memory",
|
||||||
|
|
@ -43,11 +46,6 @@ var (
|
||||||
Name: "trace.nostack",
|
Name: "trace.nostack",
|
||||||
Usage: "Disable stack output in traces",
|
Usage: "Disable stack output in traces",
|
||||||
}
|
}
|
||||||
TraceDisableReturnDataFlag = &cli.BoolFlag{
|
|
||||||
Name: "trace.noreturndata",
|
|
||||||
Value: true,
|
|
||||||
Usage: "Disable return data output in traces (deprecated)",
|
|
||||||
}
|
|
||||||
TraceEnableReturnDataFlag = &cli.BoolFlag{
|
TraceEnableReturnDataFlag = &cli.BoolFlag{
|
||||||
Name: "trace.returndata",
|
Name: "trace.returndata",
|
||||||
Usage: "Enable return data output in traces",
|
Usage: "Enable return data output in traces",
|
||||||
|
|
|
||||||
81
cmd/evm/internal/t8ntool/tracewriter.go
Normal file
81
cmd/evm/internal/t8ntool/tracewriter.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package t8ntool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// traceWriter is an vm.EVMLogger which also holds an inner logger/tracer.
|
||||||
|
// When the TxEnd event happens, the inner tracer result is written to the file, and
|
||||||
|
// the file is closed.
|
||||||
|
type traceWriter struct {
|
||||||
|
inner vm.EVMLogger
|
||||||
|
f io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time interface check
|
||||||
|
var _ = vm.EVMLogger((*traceWriter)(nil))
|
||||||
|
|
||||||
|
func (t *traceWriter) CaptureTxEnd(restGas uint64) {
|
||||||
|
t.inner.CaptureTxEnd(restGas)
|
||||||
|
defer t.f.Close()
|
||||||
|
|
||||||
|
if tracer, ok := t.inner.(tracers.Tracer); ok {
|
||||||
|
result, err := tracer.GetResult()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error in tracer", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.NewEncoder(t.f).Encode(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error writing tracer output", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceWriter) CaptureTxStart(gasLimit uint64) { t.inner.CaptureTxStart(gasLimit) }
|
||||||
|
func (t *traceWriter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||||
|
t.inner.CaptureStart(env, from, to, create, input, gas, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||||
|
t.inner.CaptureEnd(output, gasUsed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceWriter) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||||
|
t.inner.CaptureEnter(typ, from, to, input, gas, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceWriter) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||||
|
t.inner.CaptureExit(output, gasUsed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceWriter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||||
|
t.inner.CaptureState(pc, op, gas, cost, scope, rData, depth, err)
|
||||||
|
}
|
||||||
|
func (t *traceWriter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||||
|
t.inner.CaptureFault(pc, op, gas, cost, scope, depth, err)
|
||||||
|
}
|
||||||
|
|
@ -28,12 +28,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/tests"
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
|
|
@ -66,11 +64,6 @@ func (r *result) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Transaction(ctx *cli.Context) error {
|
func Transaction(ctx *cli.Context) error {
|
||||||
// Configure the go-ethereum logger
|
|
||||||
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
|
||||||
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
|
|
||||||
log.SetDefault(log.NewLogger(glogger))
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||||
|
|
@ -33,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -82,62 +81,43 @@ type input struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Transition(ctx *cli.Context) error {
|
func Transition(ctx *cli.Context) error {
|
||||||
// Configure the go-ethereum logger
|
var getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil }
|
||||||
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
|
||||||
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
|
|
||||||
log.SetDefault(log.NewLogger(glogger))
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
tracer vm.EVMLogger
|
|
||||||
)
|
|
||||||
var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)
|
|
||||||
|
|
||||||
baseDir, err := createBasedir(ctx)
|
baseDir, err := createBasedir(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
|
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
|
||||||
}
|
}
|
||||||
if ctx.Bool(TraceFlag.Name) {
|
|
||||||
if ctx.IsSet(TraceDisableMemoryFlag.Name) && ctx.IsSet(TraceEnableMemoryFlag.Name) {
|
if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing
|
||||||
return NewError(ErrorConfig, fmt.Errorf("can't use both flags --%s and --%s", TraceDisableMemoryFlag.Name, TraceEnableMemoryFlag.Name))
|
|
||||||
}
|
|
||||||
if ctx.IsSet(TraceDisableReturnDataFlag.Name) && ctx.IsSet(TraceEnableReturnDataFlag.Name) {
|
|
||||||
return NewError(ErrorConfig, fmt.Errorf("can't use both flags --%s and --%s", TraceDisableReturnDataFlag.Name, TraceEnableReturnDataFlag.Name))
|
|
||||||
}
|
|
||||||
if ctx.IsSet(TraceDisableMemoryFlag.Name) {
|
|
||||||
log.Warn(fmt.Sprintf("--%s has been deprecated in favour of --%s", TraceDisableMemoryFlag.Name, TraceEnableMemoryFlag.Name))
|
|
||||||
}
|
|
||||||
if ctx.IsSet(TraceDisableReturnDataFlag.Name) {
|
|
||||||
log.Warn(fmt.Sprintf("--%s has been deprecated in favour of --%s", TraceDisableReturnDataFlag.Name, TraceEnableReturnDataFlag.Name))
|
|
||||||
}
|
|
||||||
// Configure the EVM logger
|
// Configure the EVM logger
|
||||||
logConfig := &logger.Config{
|
logConfig := &logger.Config{
|
||||||
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
|
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
|
||||||
EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name) || ctx.Bool(TraceEnableMemoryFlag.Name),
|
EnableMemory: ctx.Bool(TraceEnableMemoryFlag.Name),
|
||||||
EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name) || ctx.Bool(TraceEnableReturnDataFlag.Name),
|
EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name),
|
||||||
Debug: true,
|
Debug: true,
|
||||||
}
|
}
|
||||||
var prevFile *os.File
|
|
||||||
// This one closes the last file
|
|
||||||
defer func() {
|
|
||||||
if prevFile != nil {
|
|
||||||
prevFile.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
|
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
|
||||||
if prevFile != nil {
|
|
||||||
prevFile.Close()
|
|
||||||
}
|
|
||||||
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String())))
|
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String())))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
|
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
|
||||||
}
|
}
|
||||||
prevFile = traceFile
|
return &traceWriter{logger.NewJSONLogger(logConfig, traceFile), traceFile}, nil
|
||||||
return logger.NewJSONLogger(logConfig, traceFile), nil
|
|
||||||
}
|
}
|
||||||
} else {
|
} else if ctx.IsSet(TraceTracerFlag.Name) {
|
||||||
getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) {
|
var config json.RawMessage
|
||||||
return nil, nil
|
if ctx.IsSet(TraceTracerConfigFlag.Name) {
|
||||||
|
config = []byte(ctx.String(TraceTracerConfigFlag.Name))
|
||||||
|
}
|
||||||
|
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
|
||||||
|
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String())))
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
|
||||||
|
}
|
||||||
|
tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err))
|
||||||
|
}
|
||||||
|
return &traceWriter{tracer, traceFile}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We need to load three things: alloc, env and transactions. May be either in
|
// We need to load three things: alloc, env and transactions. May be either in
|
||||||
|
|
@ -176,9 +156,7 @@ func Transition(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
prestate.Env = *inputData.Env
|
prestate.Env = *inputData.Env
|
||||||
|
|
||||||
vmConfig := vm.Config{
|
vmConfig := vm.Config{}
|
||||||
Tracer: tracer,
|
|
||||||
}
|
|
||||||
// Construct the chainconfig
|
// Construct the chainconfig
|
||||||
var chainConfig *params.ChainConfig
|
var chainConfig *params.ChainConfig
|
||||||
if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {
|
if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
// Force-load the tracer engines to trigger registration
|
||||||
|
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
|
||||||
|
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -143,10 +147,10 @@ var stateTransitionCommand = &cli.Command{
|
||||||
Action: t8ntool.Transition,
|
Action: t8ntool.Transition,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
t8ntool.TraceFlag,
|
t8ntool.TraceFlag,
|
||||||
t8ntool.TraceDisableMemoryFlag,
|
t8ntool.TraceTracerFlag,
|
||||||
|
t8ntool.TraceTracerConfigFlag,
|
||||||
t8ntool.TraceEnableMemoryFlag,
|
t8ntool.TraceEnableMemoryFlag,
|
||||||
t8ntool.TraceDisableStackFlag,
|
t8ntool.TraceDisableStackFlag,
|
||||||
t8ntool.TraceDisableReturnDataFlag,
|
|
||||||
t8ntool.TraceEnableReturnDataFlag,
|
t8ntool.TraceEnableReturnDataFlag,
|
||||||
t8ntool.OutputBasedir,
|
t8ntool.OutputBasedir,
|
||||||
t8ntool.OutputAllocFlag,
|
t8ntool.OutputAllocFlag,
|
||||||
|
|
@ -158,7 +162,6 @@ var stateTransitionCommand = &cli.Command{
|
||||||
t8ntool.ForknameFlag,
|
t8ntool.ForknameFlag,
|
||||||
t8ntool.ChainIDFlag,
|
t8ntool.ChainIDFlag,
|
||||||
t8ntool.RewardFlag,
|
t8ntool.RewardFlag,
|
||||||
t8ntool.VerbosityFlag,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +174,6 @@ var transactionCommand = &cli.Command{
|
||||||
t8ntool.InputTxsFlag,
|
t8ntool.InputTxsFlag,
|
||||||
t8ntool.ChainIDFlag,
|
t8ntool.ChainIDFlag,
|
||||||
t8ntool.ForknameFlag,
|
t8ntool.ForknameFlag,
|
||||||
t8ntool.VerbosityFlag,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +190,6 @@ var blockBuilderCommand = &cli.Command{
|
||||||
t8ntool.InputWithdrawalsFlag,
|
t8ntool.InputWithdrawalsFlag,
|
||||||
t8ntool.InputTxsRlpFlag,
|
t8ntool.InputTxsRlpFlag,
|
||||||
t8ntool.SealCliqueFlag,
|
t8ntool.SealCliqueFlag,
|
||||||
t8ntool.VerbosityFlag,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ func runCmd(ctx *cli.Context) error {
|
||||||
initialGas = genesisConfig.GasLimit
|
initialGas = genesisConfig.GasLimit
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
genesisConfig.Config = params.AllEthashProtocolChanges
|
genesisConfig.Config = params.AllDevChainProtocolChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case *number < threshold:
|
case *number < threshold:
|
||||||
log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
|
log.Debug("Current full block not old enough to freeze", "number", *number, "hash", hash, "delay", threshold)
|
||||||
backoff = true
|
backoff = true
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -467,6 +467,20 @@ func (t *freezerTable) truncateHead(items uint64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sizeHidden returns the total data size of hidden items in the freezer table.
|
||||||
|
// This function assumes the lock is already held.
|
||||||
|
func (t *freezerTable) sizeHidden() (uint64, error) {
|
||||||
|
hidden, offset := t.itemHidden.Load(), t.itemOffset.Load()
|
||||||
|
if hidden <= offset {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
indices, err := t.getIndices(hidden-1, 1)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64(indices[1].offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
// truncateTail discards any recent data before the provided threshold number.
|
// truncateTail discards any recent data before the provided threshold number.
|
||||||
func (t *freezerTable) truncateTail(items uint64) error {
|
func (t *freezerTable) truncateTail(items uint64) error {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
|
|
@ -495,6 +509,12 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
||||||
newTail.unmarshalBinary(buffer)
|
newTail.unmarshalBinary(buffer)
|
||||||
newTailId = newTail.filenum
|
newTailId = newTail.filenum
|
||||||
}
|
}
|
||||||
|
// Save the old size for metrics tracking. This needs to be done
|
||||||
|
// before any updates to either itemHidden or itemOffset.
|
||||||
|
oldSize, err := t.sizeNolock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Update the virtual tail marker and hidden these entries in table.
|
// Update the virtual tail marker and hidden these entries in table.
|
||||||
t.itemHidden.Store(items)
|
t.itemHidden.Store(items)
|
||||||
if err := writeMetadata(t.meta, newMetadata(items)); err != nil {
|
if err := writeMetadata(t.meta, newMetadata(items)); err != nil {
|
||||||
|
|
@ -509,18 +529,12 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
||||||
if t.tailId > newTailId {
|
if t.tailId > newTailId {
|
||||||
return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId)
|
return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId)
|
||||||
}
|
}
|
||||||
// Hidden items exceed the current tail file, drop the relevant
|
|
||||||
// data files. We need to truncate, save the old size for metrics
|
|
||||||
// tracking.
|
|
||||||
oldSize, err := t.sizeNolock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Count how many items can be deleted from the file.
|
// Count how many items can be deleted from the file.
|
||||||
var (
|
var (
|
||||||
newDeleted = items
|
newDeleted = items
|
||||||
deleted = t.itemOffset.Load()
|
deleted = t.itemOffset.Load()
|
||||||
)
|
)
|
||||||
|
// Hidden items exceed the current tail file, drop the relevant data files.
|
||||||
for current := items - 1; current >= deleted; current -= 1 {
|
for current := items - 1; current >= deleted; current -= 1 {
|
||||||
if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil {
|
if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -680,6 +694,7 @@ func (t *freezerTable) releaseFilesBefore(num uint32, remove bool) {
|
||||||
func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) {
|
func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) {
|
||||||
// Apply the table-offset
|
// Apply the table-offset
|
||||||
from = from - t.itemOffset.Load()
|
from = from - t.itemOffset.Load()
|
||||||
|
|
||||||
// For reading N items, we need N+1 indices.
|
// For reading N items, we need N+1 indices.
|
||||||
buffer := make([]byte, (count+1)*indexEntrySize)
|
buffer := make([]byte, (count+1)*indexEntrySize)
|
||||||
if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil {
|
if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil {
|
||||||
|
|
@ -870,14 +885,18 @@ func (t *freezerTable) size() (uint64, error) {
|
||||||
return t.sizeNolock()
|
return t.sizeNolock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// sizeNolock returns the total data size in the freezer table without obtaining
|
// sizeNolock returns the total data size in the freezer table. This function
|
||||||
// the mutex first.
|
// assumes the lock is already held.
|
||||||
func (t *freezerTable) sizeNolock() (uint64, error) {
|
func (t *freezerTable) sizeNolock() (uint64, error) {
|
||||||
stat, err := t.index.Stat()
|
stat, err := t.index.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size())
|
hidden, err := t.sizeHidden()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) - hidden
|
||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -658,6 +658,13 @@ func TestFreezerOffset(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertTableSize(t *testing.T, f *freezerTable, size int) {
|
||||||
|
t.Helper()
|
||||||
|
if got, err := f.size(); got != uint64(size) {
|
||||||
|
t.Fatalf("expected size of %d bytes, got %d, err: %v", size, got, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTruncateTail(t *testing.T) {
|
func TestTruncateTail(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
|
rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
|
||||||
|
|
@ -692,6 +699,9 @@ func TestTruncateTail(t *testing.T) {
|
||||||
5: getChunk(20, 0xaa),
|
5: getChunk(20, 0xaa),
|
||||||
6: getChunk(20, 0x11),
|
6: getChunk(20, 0x11),
|
||||||
})
|
})
|
||||||
|
// maxFileSize*fileCount + headBytes + indexFileSize - hiddenBytes
|
||||||
|
expected := 20*7 + 48 - 0
|
||||||
|
assertTableSize(t, f, expected)
|
||||||
|
|
||||||
// truncate single element( item 0 ), deletion is only supported at file level
|
// truncate single element( item 0 ), deletion is only supported at file level
|
||||||
f.truncateTail(1)
|
f.truncateTail(1)
|
||||||
|
|
@ -707,6 +717,8 @@ func TestTruncateTail(t *testing.T) {
|
||||||
5: getChunk(20, 0xaa),
|
5: getChunk(20, 0xaa),
|
||||||
6: getChunk(20, 0x11),
|
6: getChunk(20, 0x11),
|
||||||
})
|
})
|
||||||
|
expected = 20*7 + 48 - 20
|
||||||
|
assertTableSize(t, f, expected)
|
||||||
|
|
||||||
// Reopen the table, the deletion information should be persisted as well
|
// Reopen the table, the deletion information should be persisted as well
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
@ -739,6 +751,8 @@ func TestTruncateTail(t *testing.T) {
|
||||||
5: getChunk(20, 0xaa),
|
5: getChunk(20, 0xaa),
|
||||||
6: getChunk(20, 0x11),
|
6: getChunk(20, 0x11),
|
||||||
})
|
})
|
||||||
|
expected = 20*5 + 36 - 0
|
||||||
|
assertTableSize(t, f, expected)
|
||||||
|
|
||||||
// Reopen the table, the above testing should still pass
|
// Reopen the table, the above testing should still pass
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
@ -760,6 +774,23 @@ func TestTruncateTail(t *testing.T) {
|
||||||
6: getChunk(20, 0x11),
|
6: getChunk(20, 0x11),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// truncate 3 more elements( item 2, 3, 4), the file 1 should be deleted
|
||||||
|
// file 2 should only contain item 5
|
||||||
|
f.truncateTail(5)
|
||||||
|
checkRetrieveError(t, f, map[uint64]error{
|
||||||
|
0: errOutOfBounds,
|
||||||
|
1: errOutOfBounds,
|
||||||
|
2: errOutOfBounds,
|
||||||
|
3: errOutOfBounds,
|
||||||
|
4: errOutOfBounds,
|
||||||
|
})
|
||||||
|
checkRetrieve(t, f, map[uint64][]byte{
|
||||||
|
5: getChunk(20, 0xaa),
|
||||||
|
6: getChunk(20, 0x11),
|
||||||
|
})
|
||||||
|
expected = 20*3 + 24 - 20
|
||||||
|
assertTableSize(t, f, expected)
|
||||||
|
|
||||||
// truncate all, the entire freezer should be deleted
|
// truncate all, the entire freezer should be deleted
|
||||||
f.truncateTail(7)
|
f.truncateTail(7)
|
||||||
checkRetrieveError(t, f, map[uint64]error{
|
checkRetrieveError(t, f, map[uint64]error{
|
||||||
|
|
@ -771,6 +802,8 @@ func TestTruncateTail(t *testing.T) {
|
||||||
5: errOutOfBounds,
|
5: errOutOfBounds,
|
||||||
6: errOutOfBounds,
|
6: errOutOfBounds,
|
||||||
})
|
})
|
||||||
|
expected = 12
|
||||||
|
assertTableSize(t, f, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncateHead(t *testing.T) {
|
func TestTruncateHead(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -738,7 +738,7 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset implements txpool.SubPool, allowing the blob pool's internal state to be
|
// Reset implements txpool.SubPool, allowing the blob pool's internal state to be
|
||||||
// kept in sync with the main transacion pool's internal state.
|
// kept in sync with the main transaction pool's internal state.
|
||||||
func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
||||||
waitStart := time.Now()
|
waitStart := time.Now()
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
|
|
@ -972,7 +972,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetGasTip implements txpool.SubPool, allowing the blob pool's gas requirements
|
// SetGasTip implements txpool.SubPool, allowing the blob pool's gas requirements
|
||||||
// to be kept in sync with the main transacion pool's gas requirements.
|
// to be kept in sync with the main transaction pool's gas requirements.
|
||||||
func (p *BlobPool) SetGasTip(tip *big.Int) {
|
func (p *BlobPool) SetGasTip(tip *big.Int) {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,7 @@ func TestOpenDrops(t *testing.T) {
|
||||||
verifyPoolInternals(t, pool)
|
verifyPoolInternals(t, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that transactions loaded from disk are indexed corrently.
|
// Tests that transactions loaded from disk are indexed correctly.
|
||||||
//
|
//
|
||||||
// - 1. Transactions must be groupped by sender, sorted by nonce
|
// - 1. Transactions must be groupped by sender, sorted by nonce
|
||||||
// - 2. Eviction thresholds are calculated correctly for the sequences
|
// - 2. Eviction thresholds are calculated correctly for the sequences
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ var (
|
||||||
pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil)
|
pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil)
|
||||||
|
|
||||||
// addwait/time, resetwait/time and getwait/time track the rough health of
|
// addwait/time, resetwait/time and getwait/time track the rough health of
|
||||||
// the pool and wether or not it's capable of keeping up with the load from
|
// the pool and whether or not it's capable of keeping up with the load from
|
||||||
// the network.
|
// the network.
|
||||||
addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015))
|
addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||||
addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015))
|
addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||||
|
|
|
||||||
|
|
@ -977,6 +977,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error
|
||||||
// in transactions before obtaining lock
|
// in transactions before obtaining lock
|
||||||
if err := pool.validateTxBasics(tx, local); err != nil {
|
if err := pool.validateTxBasics(tx, local); err != nil {
|
||||||
errs[i] = err
|
errs[i] = err
|
||||||
|
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
|
||||||
invalidTxMeter.Mark(1)
|
invalidTxMeter.Mark(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -671,7 +671,7 @@ func TestColdAccountAccessCost(t *testing.T) {
|
||||||
for ii, op := range tracer.StructLogs() {
|
for ii, op := range tracer.StructLogs() {
|
||||||
t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
|
t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
|
||||||
}
|
}
|
||||||
t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
|
t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -576,7 +576,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
// For non-merged networks, if there is a checkpoint available, then calculate
|
// For non-merged networks, if there is a checkpoint available, then calculate
|
||||||
// the ancientLimit through that. Otherwise calculate the ancient limit through
|
// the ancientLimit through that. Otherwise calculate the ancient limit through
|
||||||
// the advertised height of the remote peer. This most is mostly a fallback for
|
// the advertised height of the remote peer. This most is mostly a fallback for
|
||||||
// legacy networks, but should eventually be droppped. TODO(karalabe).
|
// legacy networks, but should eventually be dropped. TODO(karalabe).
|
||||||
if beaconMode {
|
if beaconMode {
|
||||||
// Beacon sync, use the latest finalized block as the ancient limit
|
// Beacon sync, use the latest finalized block as the ancient limit
|
||||||
// or a reasonable height if no finalized block is yet announced.
|
// or a reasonable height if no finalized block is yet announced.
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ func (r *resultStore) HasCompletedItems() bool {
|
||||||
// countCompleted returns the number of items ready for delivery, stopping at
|
// countCompleted returns the number of items ready for delivery, stopping at
|
||||||
// the first non-complete item.
|
// the first non-complete item.
|
||||||
//
|
//
|
||||||
// The mthod assumes (at least) rlock is held.
|
// The method assumes (at least) rlock is held.
|
||||||
func (r *resultStore) countCompleted() int {
|
func (r *resultStore) countCompleted() int {
|
||||||
// We iterate from the already known complete point, and see
|
// We iterate from the already known complete point, and see
|
||||||
// if any more has completed since last count
|
// if any more has completed since last count
|
||||||
|
|
|
||||||
|
|
@ -450,7 +450,7 @@ func testCallContract(t *testing.T, client *rpc.Client) {
|
||||||
func TestOverrideAccountMarshal(t *testing.T) {
|
func TestOverrideAccountMarshal(t *testing.T) {
|
||||||
om := map[common.Address]OverrideAccount{
|
om := map[common.Address]OverrideAccount{
|
||||||
{0x11}: {
|
{0x11}: {
|
||||||
// Zero-valued nonce is not overriddden, but simply dropped by the encoder.
|
// Zero-valued nonce is not overridden, but simply dropped by the encoder.
|
||||||
Nonce: 0,
|
Nonce: 0,
|
||||||
},
|
},
|
||||||
{0xaa}: {
|
{0xaa}: {
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -62,10 +62,10 @@ require (
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0
|
github.com/tyler-smith/go-bip39 v1.1.0
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
go.uber.org/automaxprocs v1.5.2
|
go.uber.org/automaxprocs v1.5.2
|
||||||
golang.org/x/crypto v0.15.0
|
golang.org/x/crypto v0.17.0
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
|
||||||
golang.org/x/sync v0.5.0
|
golang.org/x/sync v0.5.0
|
||||||
golang.org/x/sys v0.14.0
|
golang.org/x/sys v0.15.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
golang.org/x/tools v0.15.0
|
golang.org/x/tools v0.15.0
|
||||||
|
|
|
||||||
8
go.sum
8
go.sum
|
|
@ -614,8 +614,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
@ -774,8 +774,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.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=
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,6 @@ func Version(csdb *ChecksumDB, version string) (string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if parts[0] == version {
|
if parts[0] == version {
|
||||||
log.Printf("Found version %q", parts[1])
|
|
||||||
return parts[1], nil
|
return parts[1], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,27 @@ func MustRunCommand(cmd string, args ...string) {
|
||||||
MustRun(exec.Command(cmd, args...))
|
MustRun(exec.Command(cmd, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustRunCommandWithOutput runs the given command, and ensures that some output will be
|
||||||
|
// printed while it runs. This is useful for CI builds where the process will be stopped
|
||||||
|
// when there is no output.
|
||||||
|
func MustRunCommandWithOutput(cmd string, args ...string) {
|
||||||
|
interval := time.NewTicker(time.Minute)
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer interval.Stop()
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-interval.C:
|
||||||
|
fmt.Printf("Waiting for command %q\n", cmd)
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
MustRun(exec.Command(cmd, args...))
|
||||||
|
}
|
||||||
|
|
||||||
var warnedAboutGit bool
|
var warnedAboutGit bool
|
||||||
|
|
||||||
// RunGit runs a git subcommand and returns its output.
|
// RunGit runs a git subcommand and returns its output.
|
||||||
|
|
|
||||||
|
|
@ -137,20 +137,35 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro
|
||||||
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
||||||
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||||
}
|
}
|
||||||
// If the tx has completely specified a fee mechanism, no default is needed. This allows users
|
// If the tx has completely specified a fee mechanism, no default is needed.
|
||||||
// who are not yet synced past London to get defaults for other tx values. See
|
// This allows users who are not yet synced past London to get defaults for
|
||||||
// https://github.com/ethereum/go-ethereum/pull/23274 for more information.
|
// other tx values. See https://github.com/ethereum/go-ethereum/pull/23274
|
||||||
|
// for more information.
|
||||||
eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil
|
eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil
|
||||||
if (args.GasPrice != nil && !eip1559ParamsSet) || (args.GasPrice == nil && eip1559ParamsSet) {
|
|
||||||
// Sanity check the EIP-1559 fee parameters if present.
|
// Sanity check the EIP-1559 fee parameters if present.
|
||||||
if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
|
if args.GasPrice == nil && eip1559ParamsSet {
|
||||||
|
if args.MaxFeePerGas.ToInt().Sign() == 0 {
|
||||||
|
return errors.New("maxFeePerGas must be non-zero")
|
||||||
|
}
|
||||||
|
if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
|
||||||
return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
|
return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
|
||||||
}
|
}
|
||||||
return nil
|
return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas
|
||||||
}
|
}
|
||||||
// Now attempt to fill in default value depending on whether London is active or not.
|
// Sanity check the non-EIP-1559 fee parameters.
|
||||||
head := b.CurrentHeader()
|
head := b.CurrentHeader()
|
||||||
if b.ChainConfig().IsLondon(head.Number) {
|
isLondon := b.ChainConfig().IsLondon(head.Number)
|
||||||
|
if args.GasPrice != nil && !eip1559ParamsSet {
|
||||||
|
// Zero gas-price is not allowed after London fork
|
||||||
|
if args.GasPrice.ToInt().Sign() == 0 && isLondon {
|
||||||
|
return errors.New("gasPrice must be non-zero after london fork")
|
||||||
|
}
|
||||||
|
return nil // No need to set anything, user already set GasPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now attempt to fill in default value depending on whether London is active or not.
|
||||||
|
if isLondon {
|
||||||
// London is active, set maxPriorityFeePerGas and maxFeePerGas.
|
// London is active, set maxPriorityFeePerGas and maxFeePerGas.
|
||||||
if err := args.setLondonFeeDefaults(ctx, head, b); err != nil {
|
if err := args.setLondonFeeDefaults(ctx, head, b); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ func TestSetFeeDefaults(t *testing.T) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
b = newBackendMock()
|
b = newBackendMock()
|
||||||
|
zero = (*hexutil.Big)(big.NewInt(0))
|
||||||
fortytwo = (*hexutil.Big)(big.NewInt(42))
|
fortytwo = (*hexutil.Big)(big.NewInt(42))
|
||||||
maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
|
maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
|
||||||
al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
|
al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
|
||||||
|
|
@ -66,6 +67,13 @@ func TestSetFeeDefaults(t *testing.T) {
|
||||||
&TransactionArgs{GasPrice: fortytwo},
|
&TransactionArgs{GasPrice: fortytwo},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"legacy tx pre-London with zero price",
|
||||||
|
false,
|
||||||
|
&TransactionArgs{GasPrice: zero},
|
||||||
|
&TransactionArgs{GasPrice: zero},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"legacy tx post-London, explicit gas price",
|
"legacy tx post-London, explicit gas price",
|
||||||
true,
|
true,
|
||||||
|
|
@ -73,6 +81,13 @@ func TestSetFeeDefaults(t *testing.T) {
|
||||||
&TransactionArgs{GasPrice: fortytwo},
|
&TransactionArgs{GasPrice: fortytwo},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"legacy tx post-London with zero price",
|
||||||
|
true,
|
||||||
|
&TransactionArgs{GasPrice: zero},
|
||||||
|
nil,
|
||||||
|
errors.New("gasPrice must be non-zero after london fork"),
|
||||||
|
},
|
||||||
|
|
||||||
// Access list txs
|
// Access list txs
|
||||||
{
|
{
|
||||||
|
|
@ -161,6 +176,13 @@ func TestSetFeeDefaults(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
errors.New("maxFeePerGas (0x7) < maxPriorityFeePerGas (0x2a)"),
|
errors.New("maxFeePerGas (0x7) < maxPriorityFeePerGas (0x2a)"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"dynamic fee tx post-London, explicit gas price",
|
||||||
|
true,
|
||||||
|
&TransactionArgs{MaxFeePerGas: zero, MaxPriorityFeePerGas: zero},
|
||||||
|
nil,
|
||||||
|
errors.New("maxFeePerGas must be non-zero"),
|
||||||
|
},
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ const (
|
||||||
LoggingCategory = "LOGGING AND DEBUGGING"
|
LoggingCategory = "LOGGING AND DEBUGGING"
|
||||||
MetricsCategory = "METRICS AND STATS"
|
MetricsCategory = "METRICS AND STATS"
|
||||||
MiscCategory = "MISC"
|
MiscCategory = "MISC"
|
||||||
|
TestingCategory = "TESTING"
|
||||||
DeprecatedCategory = "ALIASED (deprecated)"
|
DeprecatedCategory = "ALIASED (deprecated)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ func MigrateGlobalFlags(ctx *cli.Context) {
|
||||||
func doMigrateFlags(ctx *cli.Context) {
|
func doMigrateFlags(ctx *cli.Context) {
|
||||||
// Figure out if there are any aliases of commands. If there are, we want
|
// Figure out if there are any aliases of commands. If there are, we want
|
||||||
// to ignore them when iterating over the flags.
|
// to ignore them when iterating over the flags.
|
||||||
var aliases = make(map[string]bool)
|
aliases := make(map[string]bool)
|
||||||
for _, fl := range ctx.Command.Flags {
|
for _, fl := range ctx.Command.Flags {
|
||||||
for _, alias := range fl.Names()[1:] {
|
for _, alias := range fl.Names()[1:] {
|
||||||
aliases[alias] = true
|
aliases[alias] = true
|
||||||
|
|
@ -239,15 +239,24 @@ func AutoEnvVars(flags []cli.Flag, prefix string) {
|
||||||
case *cli.StringFlag:
|
case *cli.StringFlag:
|
||||||
flag.EnvVars = append(flag.EnvVars, envvar)
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
|
case *cli.StringSliceFlag:
|
||||||
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
case *cli.BoolFlag:
|
case *cli.BoolFlag:
|
||||||
flag.EnvVars = append(flag.EnvVars, envvar)
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
case *cli.IntFlag:
|
case *cli.IntFlag:
|
||||||
flag.EnvVars = append(flag.EnvVars, envvar)
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
|
case *cli.Int64Flag:
|
||||||
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
case *cli.Uint64Flag:
|
case *cli.Uint64Flag:
|
||||||
flag.EnvVars = append(flag.EnvVars, envvar)
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
|
case *cli.Float64Flag:
|
||||||
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
case *cli.DurationFlag:
|
case *cli.DurationFlag:
|
||||||
flag.EnvVars = append(flag.EnvVars, envvar)
|
flag.EnvVars = append(flag.EnvVars, envvar)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1074,7 +1074,7 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
// The entire block is filled, decrease resubmit interval in case
|
// The entire block is filled, decrease resubmit interval in case
|
||||||
// of current interval is larger than the user-specified one.
|
// of current interval is larger than the user-specified one.
|
||||||
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
|
w.adjustResubmitInterval(&intervalAdjust{inc: false})
|
||||||
|
|
||||||
case errors.Is(err, errBlockInterruptedByRecommit):
|
case errors.Is(err, errBlockInterruptedByRecommit):
|
||||||
// Notify resubmit loop to increase resubmitting interval if the
|
// Notify resubmit loop to increase resubmitting interval if the
|
||||||
|
|
@ -1084,10 +1084,10 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) {
|
||||||
if ratio < 0.1 {
|
if ratio < 0.1 {
|
||||||
ratio = 0.1
|
ratio = 0.1
|
||||||
}
|
}
|
||||||
w.resubmitAdjustCh <- &intervalAdjust{
|
w.adjustResubmitInterval(&intervalAdjust{
|
||||||
ratio: ratio,
|
ratio: ratio,
|
||||||
inc: true,
|
inc: true,
|
||||||
}
|
})
|
||||||
|
|
||||||
case errors.Is(err, errBlockInterruptedByNewHead):
|
case errors.Is(err, errBlockInterruptedByNewHead):
|
||||||
// If the block building is interrupted by newhead event, discard it
|
// If the block building is interrupted by newhead event, discard it
|
||||||
|
|
@ -1169,6 +1169,15 @@ func (w *worker) isTTDReached(header *types.Header) bool {
|
||||||
return td != nil && ttd != nil && td.Cmp(ttd) >= 0
|
return td != nil && ttd != nil && td.Cmp(ttd) >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adjustResubmitInterval adjusts the resubmit interval.
|
||||||
|
func (w *worker) adjustResubmitInterval(message *intervalAdjust) {
|
||||||
|
select {
|
||||||
|
case w.resubmitAdjustCh <- message:
|
||||||
|
default:
|
||||||
|
log.Warn("the resubmitAdjustCh is full, discard the message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// copyReceipts makes a deep copy of the given receipts.
|
// copyReceipts makes a deep copy of the given receipts.
|
||||||
func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
|
func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
|
||||||
result := make([]*types.Receipt, len(receipts))
|
result := make([]*types.Receipt, len(receipts))
|
||||||
|
|
|
||||||
|
|
@ -459,6 +459,26 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendLiveNodes adds nodes at the given distance to the result slice.
|
||||||
|
func (tab *Table) appendLiveNodes(dist uint, result []*enode.Node) []*enode.Node {
|
||||||
|
if dist > 256 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if dist == 0 {
|
||||||
|
return append(result, tab.self())
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.mutex.Lock()
|
||||||
|
defer tab.mutex.Unlock()
|
||||||
|
for _, n := range tab.bucketAtDistance(int(dist)).entries {
|
||||||
|
if n.livenessChecks >= 1 {
|
||||||
|
node := n.Node // avoid handing out pointer to struct field
|
||||||
|
result = append(result, &node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// len returns the number of nodes in the table.
|
// len returns the number of nodes in the table.
|
||||||
func (tab *Table) len() (n int) {
|
func (tab *Table) len() (n int) {
|
||||||
tab.mutex.Lock()
|
tab.mutex.Lock()
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ func TestTable_findnodeByID(t *testing.T) {
|
||||||
tab, db := newTestTable(transport)
|
tab, db := newTestTable(transport)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
defer tab.close()
|
defer tab.close()
|
||||||
fillTable(tab, test.All)
|
fillTable(tab, test.All, true)
|
||||||
|
|
||||||
// check that closest(Target, N) returns nodes
|
// check that closest(Target, N) returns nodes
|
||||||
result := tab.findnodeByID(test.Target, test.N, false).entries
|
result := tab.findnodeByID(test.Target, test.N, false).entries
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,11 @@ func fillBucket(tab *Table, n *node) (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) {
|
func fillTable(tab *Table, nodes []*node, setLive bool) {
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
|
if setLive {
|
||||||
|
n.livenessChecks = 1
|
||||||
|
}
|
||||||
tab.addSeenNode(n)
|
tab.addSeenNode(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))})
|
fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true)
|
||||||
|
|
||||||
// Start the lookup.
|
// Start the lookup.
|
||||||
resultC := make(chan []*enode.Node, 1)
|
resultC := make(chan []*enode.Node, 1)
|
||||||
|
|
@ -74,7 +74,7 @@ func TestUDPv4_LookupIterator(t *testing.T) {
|
||||||
for i := range lookupTestnet.dists[256] {
|
for i := range lookupTestnet.dists[256] {
|
||||||
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
||||||
}
|
}
|
||||||
fillTable(test.table, bootnodes)
|
fillTable(test.table, bootnodes, true)
|
||||||
go serveTestnet(test, lookupTestnet)
|
go serveTestnet(test, lookupTestnet)
|
||||||
|
|
||||||
// Create the iterator and collect the nodes it yields.
|
// Create the iterator and collect the nodes it yields.
|
||||||
|
|
@ -109,7 +109,7 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
|
||||||
for i := range lookupTestnet.dists[256] {
|
for i := range lookupTestnet.dists[256] {
|
||||||
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
||||||
}
|
}
|
||||||
fillTable(test.table, bootnodes)
|
fillTable(test.table, bootnodes, true)
|
||||||
go serveTestnet(test, lookupTestnet)
|
go serveTestnet(test, lookupTestnet)
|
||||||
|
|
||||||
it := test.udp.RandomNodes()
|
it := test.udp.RandomNodes()
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,7 @@ func TestUDPv4_findnode(t *testing.T) {
|
||||||
}
|
}
|
||||||
nodes.push(n, numCandidates)
|
nodes.push(n, numCandidates)
|
||||||
}
|
}
|
||||||
fillTable(test.table, nodes.entries)
|
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.
|
||||||
|
|
|
||||||
|
|
@ -851,6 +851,7 @@ func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *ne
|
||||||
|
|
||||||
// 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 net.IP, distances []uint, limit int) []*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{})
|
||||||
for _, dist := range distances {
|
for _, dist := range distances {
|
||||||
|
|
@ -859,21 +860,11 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en
|
||||||
if seen || dist > 256 {
|
if seen || dist > 256 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the nodes.
|
|
||||||
var bn []*enode.Node
|
|
||||||
if dist == 0 {
|
|
||||||
bn = []*enode.Node{t.Self()}
|
|
||||||
} else if dist <= 256 {
|
|
||||||
t.tab.mutex.Lock()
|
|
||||||
bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries)
|
|
||||||
t.tab.mutex.Unlock()
|
|
||||||
}
|
|
||||||
processed[dist] = struct{}{}
|
processed[dist] = struct{}{}
|
||||||
|
|
||||||
// Apply some pre-checks to avoid sending invalid nodes.
|
for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
|
||||||
for _, n := range bn {
|
// Apply some pre-checks to avoid sending invalid nodes.
|
||||||
// TODO livenessChecks > 1
|
// Note liveness is checked by appendLiveNodes.
|
||||||
if netutil.CheckRelayIP(rip, n.IP()) != nil {
|
if netutil.CheckRelayIP(rip, n.IP()) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,9 +159,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))
|
fillTable(test.table, wrapNodes(nodes253), true)
|
||||||
fillTable(test.table, wrapNodes(nodes249))
|
fillTable(test.table, wrapNodes(nodes249), true)
|
||||||
fillTable(test.table, wrapNodes(nodes248))
|
fillTable(test.table, wrapNodes(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}})
|
||||||
|
|
@ -589,7 +589,7 @@ func TestUDPv5_lookup(t *testing.T) {
|
||||||
|
|
||||||
// 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)})
|
fillTable(test.table, []*node{wrapNode(initialNode)}, true)
|
||||||
|
|
||||||
// Start the lookup.
|
// Start the lookup.
|
||||||
resultC := make(chan []*enode.Node, 1)
|
resultC := make(chan []*enode.Node, 1)
|
||||||
|
|
|
||||||
|
|
@ -421,7 +421,7 @@ func BenchmarkThroughput(b *testing.B) {
|
||||||
}
|
}
|
||||||
conn2.SetSnappy(true)
|
conn2.SetSnappy(true)
|
||||||
if err := <-handshakeDone; err != nil {
|
if err := <-handshakeDone; err != nil {
|
||||||
b.Fatal("server hanshake error:", err)
|
b.Fatal("server handshake error:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read N messages.
|
// Read N messages.
|
||||||
|
|
|
||||||
|
|
@ -683,7 +683,7 @@ func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// \todo: refactor to implement shapshots
|
// \todo: refactor to implement snapshots
|
||||||
// and connect configuration methods once these are moved from
|
// and connect configuration methods once these are moved from
|
||||||
// swarm/network/simulations/connect.go
|
// swarm/network/simulations/connect.go
|
||||||
func BenchmarkMinimalService(b *testing.B) {
|
func BenchmarkMinimalService(b *testing.B) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 13 // Minor version component of the current release
|
VersionMinor = 13 // Minor version component of the current release
|
||||||
VersionPatch = 5 // Patch version component of the current release
|
VersionPatch = 8 // Patch version component of the current release
|
||||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue