mirror of https://github.com/ethereum/go-ethereum
commit
f097096227
@ -0,0 +1,17 @@ |
||||
--- |
||||
name: Request a feature |
||||
about: Report a missing feature - e.g. as a step before submitting a PR |
||||
title: '' |
||||
labels: 'type:feature' |
||||
assignees: '' |
||||
--- |
||||
|
||||
# Rationale |
||||
|
||||
Why should this feature exist? |
||||
What are the use-cases? |
||||
|
||||
# Implementation |
||||
|
||||
Do you have ideas regarding the implementation of this feature? |
||||
Are you willing to implement this feature? |
@ -0,0 +1,9 @@ |
||||
--- |
||||
name: Ask a question |
||||
about: Something is unclear |
||||
title: '' |
||||
labels: 'type:docs' |
||||
assignees: '' |
||||
--- |
||||
|
||||
This should only be used in very rare cases e.g. if you are not 100% sure if something is a bug or asking a question that leads to improving the documentation. For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. |
@ -1,3 +1,4 @@ |
||||
[submodule "tests"] |
||||
path = tests/testdata |
||||
url = https://github.com/ethereum/tests |
||||
shallow = true |
||||
|
@ -0,0 +1,82 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
) |
||||
|
||||
var ( |
||||
errBadBool = errors.New("abi: improperly encoded boolean value") |
||||
) |
||||
|
||||
// formatSliceString formats the reflection kind with the given slice size
|
||||
// and returns a formatted string representation.
|
||||
func formatSliceString(kind reflect.Kind, sliceSize int) string { |
||||
if sliceSize == -1 { |
||||
return fmt.Sprintf("[]%v", kind) |
||||
} |
||||
return fmt.Sprintf("[%d]%v", sliceSize, kind) |
||||
} |
||||
|
||||
// sliceTypeCheck checks that the given slice can by assigned to the reflection
|
||||
// type in t.
|
||||
func sliceTypeCheck(t Type, val reflect.Value) error { |
||||
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { |
||||
return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) |
||||
} |
||||
|
||||
if t.T == ArrayTy && val.Len() != t.Size { |
||||
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) |
||||
} |
||||
|
||||
if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { |
||||
if val.Len() > 0 { |
||||
return sliceTypeCheck(*t.Elem, val.Index(0)) |
||||
} |
||||
} |
||||
|
||||
if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { |
||||
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// typeCheck checks that the given reflection value can be assigned to the reflection
|
||||
// type in t.
|
||||
func typeCheck(t Type, value reflect.Value) error { |
||||
if t.T == SliceTy || t.T == ArrayTy { |
||||
return sliceTypeCheck(t, value) |
||||
} |
||||
|
||||
// Check base type validity. Element types will be checked later on.
|
||||
if t.GetType().Kind() != value.Kind() { |
||||
return typeErr(t.GetType().Kind(), value.Kind()) |
||||
} else if t.T == FixedBytesTy && t.Size != value.Len() { |
||||
return typeErr(t.GetType(), value.Type()) |
||||
} else { |
||||
return nil |
||||
} |
||||
|
||||
} |
||||
|
||||
// typeErr returns a formatted type casting error.
|
||||
func typeErr(expected, got interface{}) error { |
||||
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) |
||||
} |
@ -1,41 +1,57 @@ |
||||
os: Visual Studio 2015 |
||||
|
||||
# Clone directly into GOPATH. |
||||
clone_folder: C:\gopath\src\github.com\ethereum\go-ethereum |
||||
clone_depth: 5 |
||||
version: "{branch}.{build}" |
||||
|
||||
image: |
||||
- Ubuntu |
||||
- Visual Studio 2019 |
||||
|
||||
environment: |
||||
global: |
||||
GO111MODULE: on |
||||
GOPATH: C:\gopath |
||||
CC: gcc.exe |
||||
matrix: |
||||
- GETH_ARCH: amd64 |
||||
MSYS2_ARCH: x86_64 |
||||
MSYS2_BITS: 64 |
||||
MSYSTEM: MINGW64 |
||||
PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% |
||||
GETH_MINGW: 'C:\msys64\mingw64' |
||||
- GETH_ARCH: 386 |
||||
MSYS2_ARCH: i686 |
||||
MSYS2_BITS: 32 |
||||
MSYSTEM: MINGW32 |
||||
PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% |
||||
GETH_MINGW: 'C:\msys64\mingw32' |
||||
|
||||
install: |
||||
- git submodule update --init |
||||
- rmdir C:\go /s /q |
||||
- appveyor DownloadFile https://dl.google.com/go/go1.15.windows-%GETH_ARCH%.zip |
||||
- 7z x go1.15.windows-%GETH_ARCH%.zip -y -oC:\ > NUL |
||||
- git submodule update --init --depth 1 |
||||
- go version |
||||
- gcc --version |
||||
|
||||
build_script: |
||||
- go run build\ci.go install |
||||
for: |
||||
# Linux has its own script without -arch and -cc. |
||||
# The linux builder also runs lint. |
||||
- matrix: |
||||
only: |
||||
- image: Ubuntu |
||||
build_script: |
||||
- go run build/ci.go lint |
||||
- go run build/ci.go install -dlgo |
||||
test_script: |
||||
- go run build/ci.go test -dlgo -coverage |
||||
|
||||
after_build: |
||||
- go run build\ci.go archive -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds |
||||
- go run build\ci.go nsis -signer WINDOWS_SIGNING_KEY -upload gethstore/builds |
||||
# linux/386 is disabled. |
||||
- matrix: |
||||
exclude: |
||||
- image: Ubuntu |
||||
GETH_ARCH: 386 |
||||
|
||||
test_script: |
||||
- set CGO_ENABLED=1 |
||||
- go run build\ci.go test -coverage |
||||
# Windows builds for amd64 + 386. |
||||
- matrix: |
||||
only: |
||||
- image: Visual Studio 2019 |
||||
environment: |
||||
# We use gcc from MSYS2 because it is the most recent compiler version available on |
||||
# AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is |
||||
# contained in PATH. |
||||
GETH_CC: '%GETH_MINGW%\bin\gcc.exe' |
||||
PATH: '%GETH_MINGW%\bin;C:\Program Files (x86)\NSIS\;%PATH%' |
||||
build_script: |
||||
- 'echo %GETH_ARCH%' |
||||
- 'echo %GETH_CC%' |
||||
- '%GETH_CC% --version' |
||||
- go run build/ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC% |
||||
after_build: |
||||
# Upload builds. Note that ci.go makes this a no-op PR builds. |
||||
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds |
||||
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds |
||||
test_script: |
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -coverage |
||||
|
@ -1,20 +1,37 @@ |
||||
# This file contains sha256 checksums of optional build dependencies. |
||||
|
||||
69438f7ed4f532154ffaf878f3dfd83747e7a00b70b3556eddabf7aaee28ac3a go1.15.src.tar.gz |
||||
2255eb3e4e824dd7d5fcdc2e7f84534371c186312e546fb1086a34c17752f431 go1.17.2.src.tar.gz |
||||
7914497a302a132a465d33f5ee044ce05568bacdb390ab805cb75a3435a23f94 go1.17.2.darwin-amd64.tar.gz |
||||
ce8771bd3edfb5b28104084b56bbb532eeb47fbb7769c3e664c6223712c30904 go1.17.2.darwin-arm64.tar.gz |
||||
8cea5b8d1f8e8cbb58069bfed58954c71c5b1aca2f3c857765dae83bf724d0d7 go1.17.2.freebsd-386.tar.gz |
||||
c96e57218fb03e74d683ad63b1684d44c89d5e5b994f36102b33dce21b58499a go1.17.2.freebsd-amd64.tar.gz |
||||
8617f2e40d51076983502894181ae639d1d8101bfbc4d7463a2b442f239f5596 go1.17.2.linux-386.tar.gz |
||||
f242a9db6a0ad1846de7b6d94d507915d14062660616a61ef7c808a76e4f1676 go1.17.2.linux-amd64.tar.gz |
||||
a5a43c9cdabdb9f371d56951b14290eba8ce2f9b0db48fb5fc657943984fd4fc go1.17.2.linux-arm64.tar.gz |
||||
04d16105008230a9763005be05606f7eb1c683a3dbf0fbfed4034b23889cb7f2 go1.17.2.linux-armv6l.tar.gz |
||||
12e2dc7e0ffeebe77083f267ef6705fec1621cdf2ed6489b3af04a13597ed68d go1.17.2.linux-ppc64le.tar.gz |
||||
c4b2349a8d11350ca038b8c57f3cc58dc0b31284bcbed4f7fca39aeed28b4a51 go1.17.2.linux-s390x.tar.gz |
||||
8a85257a351996fdf045fe95ed5fdd6917dd48636d562dd11dedf193005a53e0 go1.17.2.windows-386.zip |
||||
fa6da0b829a66f5fab7e4e312fd6aa1b2d8f045c7ecee83b3d00f6fe5306759a go1.17.2.windows-amd64.zip |
||||
00575c85dc7a129ba892685a456b27a3f3670f71c8bfde1c5ad151f771d55df7 go1.17.2.windows-arm64.zip |
||||
|
||||
d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz |
||||
bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip |
||||
bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint-1.27.0-windows-amd64.zip |
||||
0e2a57d6ba709440d3ed018ef1037465fa010ed02595829092860e5cf863042e golangci-lint-1.27.0-freebsd-386.tar.gz |
||||
90205fc42ab5ed0096413e790d88ac9b4ed60f4c47e576d13dc0660f7ed4b013 golangci-lint-1.27.0-linux-arm64.tar.gz |
||||
8d345e4e88520e21c113d81978e89ad77fc5b13bfdf20e5bca86b83fc4261272 golangci-lint-1.27.0-linux-amd64.tar.gz |
||||
cc619634a77f18dc73df2a0725be13116d64328dc35131ca1737a850d6f76a59 golangci-lint-1.27.0-freebsd-armv7.tar.gz |
||||
fe683583cfc9eeec83e498c0d6159d87b5e1919dbe4b6c3b3913089642906069 golangci-lint-1.27.0-linux-s390x.tar.gz |
||||
058f5579bee75bdaacbaf75b75e1369f7ad877fd8b3b145aed17a17545de913e golangci-lint-1.27.0-freebsd-armv6.tar.gz |
||||
38e1e3dadbe3f56ab62b4de82ee0b88e8fad966d8dfd740a26ef94c2edef9818 golangci-lint-1.27.0-linux-armv6.tar.gz |
||||
071b34af5516f4e1ddcaea6011e18208f4f043e1af8ba21eeccad4585cb3d095 golangci-lint.exe-1.27.0-windows-386.zip |
||||
071b34af5516f4e1ddcaea6011e18208f4f043e1af8ba21eeccad4585cb3d095 golangci-lint-1.27.0-windows-386.zip |
||||
5f37e2b33914ecddb7cad38186ef4ec61d88172fc04f930fa0267c91151ff306 golangci-lint-1.27.0-linux-386.tar.gz |
||||
4d94cfb51fdebeb205f1d5a349ac2b683c30591c5150708073c1c329e15965f0 golangci-lint-1.27.0-freebsd-amd64.tar.gz |
||||
52572ba8ff07d5169c2365d3de3fec26dc55a97522094d13d1596199580fa281 golangci-lint-1.27.0-linux-ppc64le.tar.gz |
||||
3fb1a1683a29c6c0a8cd76135f62b606fbdd538d5a7aeab94af1af70ffdc2fd4 golangci-lint-1.27.0-darwin-amd64.tar.gz |
||||
d4bd25b9814eeaa2134197dd2c7671bb791eae786d42010d9d788af20dee4bfa golangci-lint-1.42.0-darwin-amd64.tar.gz |
||||
e56859c04a2ad5390c6a497b1acb1cc9329ecb1010260c6faae9b5a4c35b35ea golangci-lint-1.42.0-darwin-arm64.tar.gz |
||||
14d912a3fa856830339472fc4dc341933adf15f37bdb7130bbbfcf960ecf4809 golangci-lint-1.42.0-freebsd-386.tar.gz |
||||
337257fccc9baeb5ee1cd7e70c153e9d9f59d3afde46d631659500048afbdf80 golangci-lint-1.42.0-freebsd-amd64.tar.gz |
||||
6debcc266b629359fdd8eef4f4abb05a621604079d27016265afb5b4593b0eff golangci-lint-1.42.0-freebsd-armv6.tar.gz |
||||
878f0e190169db2ce9dde8cefbd99adc4fe28b90b68686bbfcfcc2085e6d693e golangci-lint-1.42.0-freebsd-armv7.tar.gz |
||||
42c78e31faf62b225363eff1b1d2aa74f9dbcb75686c8914aa3e90d6af65cece golangci-lint-1.42.0-linux-386.tar.gz |
||||
6937f62f8e2329e94822dc11c10b871ace5557ae1fcc4ee2f9980cd6aecbc159 golangci-lint-1.42.0-linux-amd64.tar.gz |
||||
2cf8d23d96cd854a537b355dab2962b960b88a06b615232599f066afd233f246 golangci-lint-1.42.0-linux-arm64.tar.gz |
||||
08b003d1ed61367473886defc957af5301066e62338e5d96a319c34dadc4c1d1 golangci-lint-1.42.0-linux-armv6.tar.gz |
||||
c7c00ec4845e806a1f32685f5b150219e180bd6d6a9d584be8d27f0c41d7a1bf golangci-lint-1.42.0-linux-armv7.tar.gz |
||||
3650fcf29eb3d8ee326d77791a896b15259eb2d5bf77437dc72e7efe5af6bd40 golangci-lint-1.42.0-linux-mips64.tar.gz |
||||
f51ae003fdbca4fef78ba73e2eb736a939c8eaa178cd452234213b489da5a420 golangci-lint-1.42.0-linux-mips64le.tar.gz |
||||
1b0bb7b8b22cc4ea7da44fd5ad5faaf6111d0677e01cc6f961b62a96537de2c6 golangci-lint-1.42.0-linux-ppc64le.tar.gz |
||||
8cb56927eb75e572450efbe0ff0f9cf3f56dc9faa81d9e8d30d6559fc1d06e6d golangci-lint-1.42.0-linux-riscv64.tar.gz |
||||
5ac41cd31825a176b21505a371a7b307cd9cdf17df0f35bbb3bf1466f9356ccc golangci-lint-1.42.0-linux-s390x.tar.gz |
||||
e1cebd2af621ac4b64c20937df92c3819264f2174c92f51e196db1e64ae097e0 golangci-lint-1.42.0-windows-386.zip |
||||
7e70fcde8e87a17cae0455df07d257ebc86669f3968d568e12727fa24bbe9883 golangci-lint-1.42.0-windows-amd64.zip |
||||
59da7ce1bda432616bfc28ae663e52c3675adee8d9bf5959fafd657c159576ab golangci-lint-1.42.0-windows-armv6.zip |
||||
65f62dda937bfcede0326ac77abe947ce1548931e6e13298ca036cb31f224db5 golangci-lint-1.42.0-windows-armv7.zip |
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"method": "account_signTransaction", |
||||
"params": [ |
||||
{ |
||||
"from": "0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"to": "0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"gas": "0x333", |
||||
"maxFeePerGas": "0x123", |
||||
"nonce": "0x0", |
||||
"value": "0x10", |
||||
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" |
||||
} |
||||
], |
||||
"id": 67 |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"method": "account_signTransaction", |
||||
"params": [ |
||||
{ |
||||
"from": "0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"to": "0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"gas": "0x333", |
||||
"maxPriorityFeePerGas": "0x123", |
||||
"nonce": "0x0", |
||||
"value": "0x10", |
||||
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" |
||||
} |
||||
], |
||||
"id": 67 |
||||
} |
@ -0,0 +1,17 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"method": "account_signTransaction", |
||||
"params": [ |
||||
{ |
||||
"from": "0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"to": "0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"gas": "0x333", |
||||
"maxPriorityFeePerGas": "0x123", |
||||
"maxFeePerGas": "0x123", |
||||
"nonce": "0x0", |
||||
"value": "0x10", |
||||
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" |
||||
} |
||||
], |
||||
"id": 67 |
||||
} |
@ -0,0 +1,17 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"method": "account_signTransaction", |
||||
"params": [ |
||||
{ |
||||
"from":"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", |
||||
"to":"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", |
||||
"gas": "0x333", |
||||
"gasPrice": "0x123", |
||||
"nonce": "0x0", |
||||
"value": "0x10", |
||||
"data": |
||||
"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" |
||||
} |
||||
], |
||||
"id": 67 |
||||
} |
@ -0,0 +1,17 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"method": "account_signTransaction", |
||||
"params": [ |
||||
{ |
||||
"from":"0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"to":"0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192", |
||||
"gas": "0x333", |
||||
"gasPrice": "0x123", |
||||
"nonce": "0x0", |
||||
"value": "0x10", |
||||
"data": |
||||
"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" |
||||
} |
||||
], |
||||
"id": 67 |
||||
} |
@ -0,0 +1,140 @@ |
||||
# The devp2p command |
||||
|
||||
The devp2p command line tool is a utility for low-level peer-to-peer debugging and |
||||
protocol development purposes. It can do many things. |
||||
|
||||
### ENR Decoding |
||||
|
||||
Use `devp2p enrdump <base64>` to verify and display an Ethereum Node Record. |
||||
|
||||
### Node Key Management |
||||
|
||||
The `devp2p key ...` command family deals with node key files. |
||||
|
||||
Run `devp2p key generate mynode.key` to create a new node key in the `mynode.key` file. |
||||
|
||||
Run `devp2p key to-enode mynode.key -ip 127.0.0.1 -tcp 30303` to create an enode:// URL |
||||
corresponding to the given node key and address information. |
||||
|
||||
### Maintaining DNS Discovery Node Lists |
||||
|
||||
The devp2p command can create and publish DNS discovery node lists. |
||||
|
||||
Run `devp2p dns sign <directory>` to update the signature of a DNS discovery tree. |
||||
|
||||
Run `devp2p dns sync <enrtree-URL>` to download a complete DNS discovery tree. |
||||
|
||||
Run `devp2p dns to-cloudflare <directory>` to publish a tree to CloudFlare DNS. |
||||
|
||||
Run `devp2p dns to-route53 <directory>` to publish a tree to Amazon Route53. |
||||
|
||||
You can find more information about these commands in the [DNS Discovery Setup Guide][dns-tutorial]. |
||||
|
||||
### Node Set Utilities |
||||
|
||||
There are several commands for working with JSON node set files. These files are generated |
||||
by the discovery crawlers and DNS client commands. Node sets also used as the input of the |
||||
DNS deployer commands. |
||||
|
||||
Run `devp2p nodeset info <nodes.json>` to display statistics of a node set. |
||||
|
||||
Run `devp2p nodeset filter <nodes.json> <filter flags...>` to write a new, filtered node |
||||
set to standard output. The following filters are supported: |
||||
|
||||
- `-limit <N>` limits the output set to N entries, taking the top N nodes by score |
||||
- `-ip <CIDR>` filters nodes by IP subnet |
||||
- `-min-age <duration>` filters nodes by 'first seen' time |
||||
- `-eth-network <mainnet/rinkeby/goerli/ropsten>` filters nodes by "eth" ENR entry |
||||
- `-les-server` filters nodes by LES server support |
||||
- `-snap` filters nodes by snap protocol support |
||||
|
||||
For example, given a node set in `nodes.json`, you could create a filtered set containing |
||||
up to 20 eth mainnet nodes which also support snap sync using this command: |
||||
|
||||
devp2p nodeset filter nodes.json -eth-network mainnet -snap -limit 20 |
||||
|
||||
### Discovery v4 Utilities |
||||
|
||||
The `devp2p discv4 ...` command family deals with the [Node Discovery v4][discv4] |
||||
protocol. |
||||
|
||||
Run `devp2p discv4 ping <enode/ENR>` to ping a node. |
||||
|
||||
Run `devp2p discv4 resolve <enode/ENR>` to find the most recent node record of a node in |
||||
the DHT. |
||||
|
||||
Run `devp2p discv4 crawl <nodes.json path>` to create or update a JSON node set. |
||||
|
||||
### Discovery v5 Utilities |
||||
|
||||
The `devp2p discv5 ...` command family deals with the [Node Discovery v5][discv5] |
||||
protocol. This protocol is currently under active development. |
||||
|
||||
Run `devp2p discv5 ping <ENR>` to ping a node. |
||||
|
||||
Run `devp2p discv5 resolve <ENR>` to find the most recent node record of a node in |
||||
the discv5 DHT. |
||||
|
||||
Run `devp2p discv5 listen` to run a Discovery v5 node. |
||||
|
||||
Run `devp2p discv5 crawl <nodes.json path>` to create or update a JSON node set containing |
||||
discv5 nodes. |
||||
|
||||
### Discovery Test Suites |
||||
|
||||
The devp2p command also contains interactive test suites for Discovery v4 and Discovery |
||||
v5. |
||||
|
||||
To run these tests against your implementation, you need to set up a networking |
||||
environment where two separate UDP listening addresses are available on the same machine. |
||||
The two listening addresses must also be routed such that they are able to reach the node |
||||
you want to test. |
||||
|
||||
For example, if you want to run the test on your local host, and the node under test is |
||||
also on the local host, you need to assign two IP addresses (or a larger range) to your |
||||
loopback interface. On macOS, this can be done by executing the following command: |
||||
|
||||
sudo ifconfig lo0 add 127.0.0.2 |
||||
|
||||
You can now run either test suite as follows: Start the node under test first, ensuring |
||||
that it won't talk to the Internet (i.e. disable bootstrapping). An easy way to prevent |
||||
unintended connections to the global DHT is listening on `127.0.0.1`. |
||||
|
||||
Now get the ENR of your node and store it in the `NODE` environment variable. |
||||
|
||||
Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`. |
||||
|
||||
### Eth Protocol Test Suite |
||||
|
||||
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: |
||||
|
||||
1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory |
||||
2. import the `halfchain.rlp` file in the `testdata` directory |
||||
3. run geth with the following flags: |
||||
``` |
||||
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5 |
||||
``` |
||||
|
||||
Then, run the following command, replacing `<enode>` with the enode of the geth node: |
||||
``` |
||||
devp2p rlpx eth-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json |
||||
``` |
||||
|
||||
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 |
||||
[dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup |
||||
[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md |
||||
[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md |
@ -0,0 +1,188 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"compress/gzip" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"math/big" |
||||
"os" |
||||
"strings" |
||||
|
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/core/forkid" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
type Chain struct { |
||||
genesis core.Genesis |
||||
blocks []*types.Block |
||||
chainConfig *params.ChainConfig |
||||
} |
||||
|
||||
// Len returns the length of the chain.
|
||||
func (c *Chain) Len() int { |
||||
return len(c.blocks) |
||||
} |
||||
|
||||
// TD calculates the total difficulty of the chain at the
|
||||
// chain head.
|
||||
func (c *Chain) TD() *big.Int { |
||||
sum := big.NewInt(0) |
||||
for _, block := range c.blocks[:c.Len()] { |
||||
sum.Add(sum, block.Difficulty()) |
||||
} |
||||
return sum |
||||
} |
||||
|
||||
// TotalDifficultyAt calculates the total difficulty of the chain
|
||||
// at the given block height.
|
||||
func (c *Chain) TotalDifficultyAt(height int) *big.Int { |
||||
sum := big.NewInt(0) |
||||
if height >= c.Len() { |
||||
return sum |
||||
} |
||||
for _, block := range c.blocks[:height+1] { |
||||
sum.Add(sum, block.Difficulty()) |
||||
} |
||||
return sum |
||||
} |
||||
|
||||
// ForkID gets the fork id of the chain.
|
||||
func (c *Chain) ForkID() forkid.ID { |
||||
return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len())) |
||||
} |
||||
|
||||
// Shorten returns a copy chain of a desired height from the imported
|
||||
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, |
||||
} |
||||
} |
||||
|
||||
// Head returns the chain head.
|
||||
func (c *Chain) Head() *types.Block { |
||||
return c.blocks[c.Len()-1] |
||||
} |
||||
|
||||
func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) { |
||||
if req.Amount < 1 { |
||||
return nil, fmt.Errorf("no block headers requested") |
||||
} |
||||
|
||||
headers := make(BlockHeaders, req.Amount) |
||||
var blockNumber uint64 |
||||
|
||||
// range over blocks to check if our chain has the requested header
|
||||
for _, block := range c.blocks { |
||||
if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number { |
||||
headers[0] = block.Header() |
||||
blockNumber = block.Number().Uint64() |
||||
} |
||||
} |
||||
if headers[0] == nil { |
||||
return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash) |
||||
} |
||||
|
||||
if req.Reverse { |
||||
for i := 1; i < int(req.Amount); i++ { |
||||
blockNumber -= (1 - req.Skip) |
||||
headers[i] = c.blocks[blockNumber].Header() |
||||
|
||||
} |
||||
|
||||
return headers, nil |
||||
} |
||||
|
||||
for i := 1; i < int(req.Amount); i++ { |
||||
blockNumber += (1 + req.Skip) |
||||
headers[i] = c.blocks[blockNumber].Header() |
||||
} |
||||
|
||||
return headers, nil |
||||
} |
||||
|
||||
// loadChain takes the given chain.rlp file, and decodes and returns
|
||||
// the blocks from the file.
|
||||
func loadChain(chainfile string, genesis string) (*Chain, error) { |
||||
gen, err := loadGenesis(genesis) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
gblock := gen.ToBlock(nil) |
||||
|
||||
blocks, err := blocksFromFile(chainfile, gblock) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config} |
||||
return c, nil |
||||
} |
||||
|
||||
func loadGenesis(genesisFile string) (core.Genesis, error) { |
||||
chainConfig, err := ioutil.ReadFile(genesisFile) |
||||
if err != nil { |
||||
return core.Genesis{}, err |
||||
} |
||||
var gen core.Genesis |
||||
if err := json.Unmarshal(chainConfig, &gen); err != nil { |
||||
return core.Genesis{}, err |
||||
} |
||||
return gen, nil |
||||
} |
||||
|
||||
func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) { |
||||
// Load chain.rlp.
|
||||
fh, err := os.Open(chainfile) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer fh.Close() |
||||
var reader io.Reader = fh |
||||
if strings.HasSuffix(chainfile, ".gz") { |
||||
if reader, err = gzip.NewReader(reader); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
stream := rlp.NewStream(reader, 0) |
||||
var blocks = make([]*types.Block, 1) |
||||
blocks[0] = gblock |
||||
for i := 0; ; i++ { |
||||
var b types.Block |
||||
if err := stream.Decode(&b); err == io.EOF { |
||||
break |
||||
} else if err != nil { |
||||
return nil, fmt.Errorf("at block index %d: %v", i, err) |
||||
} |
||||
if b.NumberU64() != uint64(i+1) { |
||||
return nil, fmt.Errorf("block at index %d has wrong number %d", i, b.NumberU64()) |
||||
} |
||||
blocks = append(blocks, &b) |
||||
} |
||||
return blocks, nil |
||||
} |
@ -0,0 +1,201 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"path/filepath" |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
// TestEthProtocolNegotiation tests whether the test suite
|
||||
// can negotiate the highest eth protocol in a status message exchange
|
||||
func TestEthProtocolNegotiation(t *testing.T) { |
||||
var tests = []struct { |
||||
conn *Conn |
||||
caps []p2p.Cap |
||||
expected uint32 |
||||
}{ |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 65, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 63}, |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
expected: uint32(65), |
||||
}, |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 65, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 63}, |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
expected: uint32(65), |
||||
}, |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 65, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 63}, |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
expected: uint32(65), |
||||
}, |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 64, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 63}, |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
expected: 64, |
||||
}, |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 65, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 0}, |
||||
{Name: "eth", Version: 89}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
expected: uint32(65), |
||||
}, |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 64, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 63}, |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "wrongProto", Version: 65}, |
||||
}, |
||||
expected: uint32(64), |
||||
}, |
||||
{ |
||||
conn: &Conn{ |
||||
ourHighestProtoVersion: 65, |
||||
}, |
||||
caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 63}, |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "wrongProto", Version: 65}, |
||||
}, |
||||
expected: uint32(64), |
||||
}, |
||||
} |
||||
|
||||
for i, tt := range tests { |
||||
t.Run(strconv.Itoa(i), func(t *testing.T) { |
||||
tt.conn.negotiateEthProtocol(tt.caps) |
||||
assert.Equal(t, tt.expected, uint32(tt.conn.negotiatedProtoVersion)) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// TestChain_GetHeaders tests whether the test suite can correctly
|
||||
// respond to a GetBlockHeaders request from a node.
|
||||
func TestChain_GetHeaders(t *testing.T) { |
||||
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) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
var tests = []struct { |
||||
req GetBlockHeaders |
||||
expected BlockHeaders |
||||
}{ |
||||
{ |
||||
req: GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Number: uint64(2), |
||||
}, |
||||
Amount: uint64(5), |
||||
Skip: 1, |
||||
Reverse: false, |
||||
}, |
||||
expected: BlockHeaders{ |
||||
chain.blocks[2].Header(), |
||||
chain.blocks[4].Header(), |
||||
chain.blocks[6].Header(), |
||||
chain.blocks[8].Header(), |
||||
chain.blocks[10].Header(), |
||||
}, |
||||
}, |
||||
{ |
||||
req: GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Number: uint64(chain.Len() - 1), |
||||
}, |
||||
Amount: uint64(3), |
||||
Skip: 0, |
||||
Reverse: true, |
||||
}, |
||||
expected: BlockHeaders{ |
||||
chain.blocks[chain.Len()-1].Header(), |
||||
chain.blocks[chain.Len()-2].Header(), |
||||
chain.blocks[chain.Len()-3].Header(), |
||||
}, |
||||
}, |
||||
{ |
||||
req: GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Hash: chain.Head().Hash(), |
||||
}, |
||||
Amount: uint64(1), |
||||
Skip: 0, |
||||
Reverse: false, |
||||
}, |
||||
expected: BlockHeaders{ |
||||
chain.Head().Header(), |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for i, tt := range tests { |
||||
t.Run(strconv.Itoa(i), func(t *testing.T) { |
||||
headers, err := chain.GetHeaders(tt.req) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
assert.Equal(t, headers, tt.expected) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,757 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"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 |
||||
) |
||||
|
||||
// Is_66 checks if the node supports the eth66 protocol version,
|
||||
// and if not, exists the test suite
|
||||
func (s *Suite) Is_66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
if err := conn.handshake(); err != nil { |
||||
t.Fatalf("handshake failed: %v", err) |
||||
} |
||||
if conn.negotiatedProtoVersion < 66 { |
||||
t.Fail() |
||||
} |
||||
} |
||||
|
||||
// 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: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
} |
||||
conn.ourHighestProtoVersion = 65 |
||||
return &conn, nil |
||||
} |
||||
|
||||
// dial66 attempts to dial the given node and perform a handshake,
|
||||
// returning the created Conn with additional eth66 capabilities if
|
||||
// successful
|
||||
func (s *Suite) dial66() (*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: "eth", Version: 66}) |
||||
conn.ourHighestProtoVersion = 66 |
||||
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 protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) |
||||
} |
||||
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 |
||||
for _, capability := range caps { |
||||
if capability.Name != "eth" { |
||||
continue |
||||
} |
||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { |
||||
highestEthVersion = capability.Version |
||||
} |
||||
} |
||||
c.negotiatedProtoVersion = highestEthVersion |
||||
} |
||||
|
||||
// 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, fmt.Errorf("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(isEth66 bool) (*Conn, *Conn, error) { |
||||
var ( |
||||
sendConn *Conn |
||||
recvConn *Conn |
||||
err error |
||||
) |
||||
if isEth66 { |
||||
sendConn, err = s.dial66() |
||||
if err != nil { |
||||
return nil, nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
recvConn, err = s.dial66() |
||||
if err != nil { |
||||
sendConn.Close() |
||||
return nil, nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
} else { |
||||
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 |
||||
} |
||||
|
||||
func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message { |
||||
if c.negotiatedProtoVersion == 66 { |
||||
_, msg := c.readAndServe66(chain, timeout) |
||||
return msg |
||||
} |
||||
return c.readAndServe65(chain, timeout) |
||||
} |
||||
|
||||
// readAndServe serves GetBlockHeaders requests while waiting
|
||||
// on another message from the node.
|
||||
func (c *Conn) readAndServe65(chain *Chain, timeout time.Duration) Message { |
||||
start := time.Now() |
||||
for time.Since(start) < timeout { |
||||
c.SetReadDeadline(time.Now().Add(5 * time.Second)) |
||||
switch msg := c.Read().(type) { |
||||
case *Ping: |
||||
c.Write(&Pong{}) |
||||
case *GetBlockHeaders: |
||||
req := *msg |
||||
headers, err := chain.GetHeaders(req) |
||||
if err != nil { |
||||
return errorf("could not get headers for inbound header request: %v", err) |
||||
} |
||||
if err := c.Write(headers); err != nil { |
||||
return errorf("could not write to connection: %v", err) |
||||
} |
||||
default: |
||||
return msg |
||||
} |
||||
} |
||||
return errorf("no message received within %v", timeout) |
||||
} |
||||
|
||||
// readAndServe66 serves eth66 GetBlockHeaders requests while waiting
|
||||
// on another message from the node.
|
||||
func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { |
||||
start := time.Now() |
||||
for time.Since(start) < timeout { |
||||
c.SetReadDeadline(time.Now().Add(10 * time.Second)) |
||||
|
||||
reqID, msg := c.Read66() |
||||
|
||||
switch msg := msg.(type) { |
||||
case *Ping: |
||||
c.Write(&Pong{}) |
||||
case GetBlockHeaders: |
||||
headers, err := chain.GetHeaders(msg) |
||||
if err != nil { |
||||
return 0, errorf("could not get headers for inbound header request: %v", err) |
||||
} |
||||
resp := ð.BlockHeadersPacket66{ |
||||
RequestId: reqID, |
||||
BlockHeadersPacket: eth.BlockHeadersPacket(headers), |
||||
} |
||||
if err := c.Write66(resp, BlockHeaders{}.Code()); err != nil { |
||||
return 0, errorf("could not write to connection: %v", err) |
||||
} |
||||
default: |
||||
return reqID, msg |
||||
} |
||||
} |
||||
return 0, errorf("no message received within %v", timeout) |
||||
} |
||||
|
||||
// headersRequest executes the given `GetBlockHeaders` request.
|
||||
func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, isEth66 bool, reqID uint64) (BlockHeaders, error) { |
||||
defer c.SetReadDeadline(time.Time{}) |
||||
c.SetReadDeadline(time.Now().Add(20 * time.Second)) |
||||
// if on eth66 connection, perform eth66 GetBlockHeaders request
|
||||
if isEth66 { |
||||
return getBlockHeaders66(chain, c, request, reqID) |
||||
} |
||||
if err := c.Write(request); err != nil { |
||||
return nil, err |
||||
} |
||||
switch msg := c.readAndServe(chain, timeout).(type) { |
||||
case *BlockHeaders: |
||||
return *msg, nil |
||||
default: |
||||
return nil, fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
|
||||
// getBlockHeaders66 executes the given `GetBlockHeaders` request over the eth66 protocol.
|
||||
func getBlockHeaders66(chain *Chain, conn *Conn, request *GetBlockHeaders, id uint64) (BlockHeaders, error) { |
||||
// write request
|
||||
packet := eth.GetBlockHeadersPacket(*request) |
||||
req := ð.GetBlockHeadersPacket66{ |
||||
RequestId: id, |
||||
GetBlockHeadersPacket: &packet, |
||||
} |
||||
if err := conn.Write66(req, GetBlockHeaders{}.Code()); err != nil { |
||||
return nil, fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
// wait for response
|
||||
msg := conn.waitForResponse(chain, timeout, req.RequestId) |
||||
headers, ok := msg.(BlockHeaders) |
||||
if !ok { |
||||
return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg)) |
||||
} |
||||
return headers, nil |
||||
} |
||||
|
||||
// headersMatch returns whether the received headers match the given request
|
||||
func headersMatch(expected BlockHeaders, headers BlockHeaders) 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 { |
||||
id, msg := c.readAndServe66(chain, timeout) |
||||
if id == 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(isEth66 bool) error { |
||||
// set up sending and receiving connections
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns(isEth66) |
||||
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, isEth66); 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 |
||||
case *NewPooledTransactionHashes: |
||||
// ignore tx announcements from previous tests
|
||||
continue |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block, isEth66 bool) error { |
||||
defer conn.SetReadDeadline(time.Time{}) |
||||
conn.SetReadDeadline(time.Now().Add(20 * time.Second)) |
||||
// create request
|
||||
req := &GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Hash: block.Hash(), |
||||
}, |
||||
Amount: 1, |
||||
} |
||||
// loop until BlockHeaders response contains desired block, confirming the
|
||||
// node imported the block
|
||||
for { |
||||
var ( |
||||
headers BlockHeaders |
||||
err error |
||||
) |
||||
if isEth66 { |
||||
requestID := uint64(54) |
||||
headers, err = conn.headersRequest(req, s.chain, eth66, requestID) |
||||
} else { |
||||
headers, err = conn.headersRequest(req, s.chain, eth65, 0) |
||||
} |
||||
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(isEth66 bool) error { |
||||
sendConn, receiveConn, err := s.createSendAndRecvConns(isEth66) |
||||
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, isEth66 bool) error { |
||||
var ( |
||||
conn *Conn |
||||
err error |
||||
) |
||||
if isEth66 { |
||||
conn, err = s.dial66() |
||||
if err != nil { |
||||
return fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
} else { |
||||
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
|
||||
if isEth66 { |
||||
conn, err = s.dial66() |
||||
if err != nil { |
||||
return fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
} else { |
||||
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(isEth66 bool) error { |
||||
// create connections
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns(isEth66) |
||||
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
|
||||
var ( |
||||
id uint64 |
||||
msg Message |
||||
blockHeaderReq GetBlockHeaders |
||||
) |
||||
if isEth66 { |
||||
id, msg = sendConn.Read66() |
||||
switch msg := msg.(type) { |
||||
case GetBlockHeaders: |
||||
blockHeaderReq = msg |
||||
default: |
||||
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)) |
||||
} |
||||
if err := sendConn.Write66(ð.BlockHeadersPacket66{ |
||||
RequestId: id, |
||||
BlockHeadersPacket: eth.BlockHeadersPacket{ |
||||
nextBlock.Header(), |
||||
}, |
||||
}, BlockHeaders{}.Code()); err != nil { |
||||
return fmt.Errorf("failed to write to connection: %v", err) |
||||
} |
||||
} else { |
||||
msg = sendConn.Read() |
||||
switch msg := msg.(type) { |
||||
case *GetBlockHeaders: |
||||
blockHeaderReq = *msg |
||||
default: |
||||
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)) |
||||
} |
||||
if err := sendConn.Write(&BlockHeaders{nextBlock.Header()}); 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, isEth66); 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 |
||||
} |
@ -0,0 +1,80 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package 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), |
||||
} |
||||
} |
@ -0,0 +1,783 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/internal/utesting" |
||||
"github.com/ethereum/go-ethereum/p2p/enode" |
||||
) |
||||
|
||||
// Suite represents a structure used to test a node's conformance
|
||||
// to the eth protocol.
|
||||
type Suite struct { |
||||
Dest *enode.Node |
||||
|
||||
chain *Chain |
||||
fullChain *Chain |
||||
} |
||||
|
||||
// NewSuite creates and returns a new eth-test suite that can
|
||||
// be used to test the given node against the given blockchain
|
||||
// data.
|
||||
func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) { |
||||
chain, err := loadChain(chainfile, genesisfile) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Suite{ |
||||
Dest: dest, |
||||
chain: chain.Shorten(1000), |
||||
fullChain: chain, |
||||
}, nil |
||||
} |
||||
|
||||
func (s *Suite) AllEthTests() []utesting.Test { |
||||
return []utesting.Test{ |
||||
// status
|
||||
{Name: "TestStatus65", Fn: s.TestStatus65}, |
||||
{Name: "TestStatus66", Fn: s.TestStatus66}, |
||||
// get block headers
|
||||
{Name: "TestGetBlockHeaders65", Fn: s.TestGetBlockHeaders65}, |
||||
{Name: "TestGetBlockHeaders66", Fn: s.TestGetBlockHeaders66}, |
||||
{Name: "TestSimultaneousRequests66", Fn: s.TestSimultaneousRequests66}, |
||||
{Name: "TestSameRequestID66", Fn: s.TestSameRequestID66}, |
||||
{Name: "TestZeroRequestID66", Fn: s.TestZeroRequestID66}, |
||||
// get block bodies
|
||||
{Name: "TestGetBlockBodies65", Fn: s.TestGetBlockBodies65}, |
||||
{Name: "TestGetBlockBodies66", Fn: s.TestGetBlockBodies66}, |
||||
// broadcast
|
||||
{Name: "TestBroadcast65", Fn: s.TestBroadcast65}, |
||||
{Name: "TestBroadcast66", Fn: s.TestBroadcast66}, |
||||
{Name: "TestLargeAnnounce65", Fn: s.TestLargeAnnounce65}, |
||||
{Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66}, |
||||
{Name: "TestOldAnnounce65", Fn: s.TestOldAnnounce65}, |
||||
{Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66}, |
||||
{Name: "TestBlockHashAnnounce65", Fn: s.TestBlockHashAnnounce65}, |
||||
{Name: "TestBlockHashAnnounce66", Fn: s.TestBlockHashAnnounce66}, |
||||
// malicious handshakes + status
|
||||
{Name: "TestMaliciousHandshake65", Fn: s.TestMaliciousHandshake65}, |
||||
{Name: "TestMaliciousStatus65", Fn: s.TestMaliciousStatus65}, |
||||
{Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66}, |
||||
{Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66}, |
||||
// test transactions
|
||||
{Name: "TestTransaction65", Fn: s.TestTransaction65}, |
||||
{Name: "TestTransaction66", Fn: s.TestTransaction66}, |
||||
{Name: "TestMaliciousTx65", Fn: s.TestMaliciousTx65}, |
||||
{Name: "TestMaliciousTx66", Fn: s.TestMaliciousTx66}, |
||||
{Name: "TestLargeTxRequest66", Fn: s.TestLargeTxRequest66}, |
||||
{Name: "TestNewPooledTxs66", Fn: s.TestNewPooledTxs66}, |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) EthTests() []utesting.Test { |
||||
return []utesting.Test{ |
||||
{Name: "TestStatus65", Fn: s.TestStatus65}, |
||||
{Name: "TestGetBlockHeaders65", Fn: s.TestGetBlockHeaders65}, |
||||
{Name: "TestGetBlockBodies65", Fn: s.TestGetBlockBodies65}, |
||||
{Name: "TestBroadcast65", Fn: s.TestBroadcast65}, |
||||
{Name: "TestLargeAnnounce65", Fn: s.TestLargeAnnounce65}, |
||||
{Name: "TestOldAnnounce65", Fn: s.TestOldAnnounce65}, |
||||
{Name: "TestBlockHashAnnounce65", Fn: s.TestBlockHashAnnounce65}, |
||||
{Name: "TestMaliciousHandshake65", Fn: s.TestMaliciousHandshake65}, |
||||
{Name: "TestMaliciousStatus65", Fn: s.TestMaliciousStatus65}, |
||||
{Name: "TestTransaction65", Fn: s.TestTransaction65}, |
||||
{Name: "TestMaliciousTx65", Fn: s.TestMaliciousTx65}, |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) Eth66Tests() []utesting.Test { |
||||
return []utesting.Test{ |
||||
// only proceed with eth66 test suite if node supports eth 66 protocol
|
||||
{Name: "TestStatus66", Fn: s.TestStatus66}, |
||||
{Name: "TestGetBlockHeaders66", Fn: s.TestGetBlockHeaders66}, |
||||
{Name: "TestSimultaneousRequests66", Fn: s.TestSimultaneousRequests66}, |
||||
{Name: "TestSameRequestID66", Fn: s.TestSameRequestID66}, |
||||
{Name: "TestZeroRequestID66", Fn: s.TestZeroRequestID66}, |
||||
{Name: "TestGetBlockBodies66", Fn: s.TestGetBlockBodies66}, |
||||
{Name: "TestBroadcast66", Fn: s.TestBroadcast66}, |
||||
{Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66}, |
||||
{Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66}, |
||||
{Name: "TestBlockHashAnnounce66", Fn: s.TestBlockHashAnnounce66}, |
||||
{Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66}, |
||||
{Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66}, |
||||
{Name: "TestTransaction66", Fn: s.TestTransaction66}, |
||||
{Name: "TestMaliciousTx66", Fn: s.TestMaliciousTx66}, |
||||
{Name: "TestLargeTxRequest66", Fn: s.TestLargeTxRequest66}, |
||||
{Name: "TestNewPooledTxs66", Fn: s.TestNewPooledTxs66}, |
||||
} |
||||
} |
||||
|
||||
var ( |
||||
eth66 = true // indicates whether suite should negotiate eth66 connection
|
||||
eth65 = false // indicates whether suite should negotiate eth65 connection or below.
|
||||
) |
||||
|
||||
// TestStatus65 attempts to connect to the given node and exchange
|
||||
// a status message with it.
|
||||
func (s *Suite) TestStatus65(t *utesting.T) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestStatus66 attempts to connect to the given node and exchange
|
||||
// a status message with it on the eth66 protocol.
|
||||
func (s *Suite) TestStatus66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestGetBlockHeaders65 tests whether the given node can respond to
|
||||
// a `GetBlockHeaders` request accurately.
|
||||
func (s *Suite) TestGetBlockHeaders65(t *utesting.T) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("handshake(s) failed: %v", err) |
||||
} |
||||
// write request
|
||||
req := &GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Hash: s.chain.blocks[1].Hash(), |
||||
}, |
||||
Amount: 2, |
||||
Skip: 1, |
||||
Reverse: false, |
||||
} |
||||
headers, err := conn.headersRequest(req, s.chain, eth65, 0) |
||||
if err != nil { |
||||
t.Fatalf("GetBlockHeaders request failed: %v", err) |
||||
} |
||||
// check for correct headers
|
||||
expected, err := s.chain.GetHeaders(*req) |
||||
if err != nil { |
||||
t.Fatalf("failed to get headers for given request: %v", err) |
||||
} |
||||
if !headersMatch(expected, headers) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) |
||||
} |
||||
} |
||||
|
||||
// TestGetBlockHeaders66 tests whether the given node can respond to
|
||||
// an eth66 `GetBlockHeaders` request and that the response is accurate.
|
||||
func (s *Suite) TestGetBlockHeaders66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err = conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
// write request
|
||||
req := &GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Hash: s.chain.blocks[1].Hash(), |
||||
}, |
||||
Amount: 2, |
||||
Skip: 1, |
||||
Reverse: false, |
||||
} |
||||
headers, err := conn.headersRequest(req, s.chain, eth66, 33) |
||||
if err != nil { |
||||
t.Fatalf("could not get block headers: %v", err) |
||||
} |
||||
// check for correct headers
|
||||
expected, err := s.chain.GetHeaders(*req) |
||||
if err != nil { |
||||
t.Fatalf("failed to get headers for given request: %v", err) |
||||
} |
||||
if !headersMatch(expected, headers) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) |
||||
} |
||||
} |
||||
|
||||
// TestSimultaneousRequests66 sends two simultaneous `GetBlockHeader` requests from
|
||||
// the same connection with different request IDs and checks to make sure the node
|
||||
// responds with the correct headers per request.
|
||||
func (s *Suite) TestSimultaneousRequests66(t *utesting.T) { |
||||
// create a connection
|
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
// create two requests
|
||||
req1 := ð.GetBlockHeadersPacket66{ |
||||
RequestId: uint64(111), |
||||
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ |
||||
Origin: eth.HashOrNumber{ |
||||
Hash: s.chain.blocks[1].Hash(), |
||||
}, |
||||
Amount: 2, |
||||
Skip: 1, |
||||
Reverse: false, |
||||
}, |
||||
} |
||||
req2 := ð.GetBlockHeadersPacket66{ |
||||
RequestId: uint64(222), |
||||
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ |
||||
Origin: eth.HashOrNumber{ |
||||
Hash: s.chain.blocks[1].Hash(), |
||||
}, |
||||
Amount: 4, |
||||
Skip: 1, |
||||
Reverse: false, |
||||
}, |
||||
} |
||||
// write the first request
|
||||
if err := conn.Write66(req1, GetBlockHeaders{}.Code()); err != nil { |
||||
t.Fatalf("failed to write to connection: %v", err) |
||||
} |
||||
// write the second request
|
||||
if err := conn.Write66(req2, GetBlockHeaders{}.Code()); err != nil { |
||||
t.Fatalf("failed to write to connection: %v", err) |
||||
} |
||||
// wait for responses
|
||||
msg := conn.waitForResponse(s.chain, timeout, req1.RequestId) |
||||
headers1, ok := msg.(BlockHeaders) |
||||
if !ok { |
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
msg = conn.waitForResponse(s.chain, timeout, req2.RequestId) |
||||
headers2, ok := msg.(BlockHeaders) |
||||
if !ok { |
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
// check received headers for accuracy
|
||||
expected1, err := s.chain.GetHeaders(GetBlockHeaders(*req1.GetBlockHeadersPacket)) |
||||
if err != nil { |
||||
t.Fatalf("failed to get expected headers for request 1: %v", err) |
||||
} |
||||
expected2, err := s.chain.GetHeaders(GetBlockHeaders(*req2.GetBlockHeadersPacket)) |
||||
if err != nil { |
||||
t.Fatalf("failed to get expected headers for request 2: %v", err) |
||||
} |
||||
if !headersMatch(expected1, headers1) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) |
||||
} |
||||
if !headersMatch(expected2, headers2) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) |
||||
} |
||||
} |
||||
|
||||
// TestSameRequestID66 sends two requests with the same request ID to a
|
||||
// single node.
|
||||
func (s *Suite) TestSameRequestID66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
// create requests
|
||||
reqID := uint64(1234) |
||||
request1 := ð.GetBlockHeadersPacket66{ |
||||
RequestId: reqID, |
||||
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ |
||||
Origin: eth.HashOrNumber{ |
||||
Number: 1, |
||||
}, |
||||
Amount: 2, |
||||
}, |
||||
} |
||||
request2 := ð.GetBlockHeadersPacket66{ |
||||
RequestId: reqID, |
||||
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ |
||||
Origin: eth.HashOrNumber{ |
||||
Number: 33, |
||||
}, |
||||
Amount: 2, |
||||
}, |
||||
} |
||||
// write the requests
|
||||
if err = conn.Write66(request1, GetBlockHeaders{}.Code()); err != nil { |
||||
t.Fatalf("failed to write to connection: %v", err) |
||||
} |
||||
if err = conn.Write66(request2, GetBlockHeaders{}.Code()); err != nil { |
||||
t.Fatalf("failed to write to connection: %v", err) |
||||
} |
||||
// wait for responses
|
||||
msg := conn.waitForResponse(s.chain, timeout, reqID) |
||||
headers1, ok := msg.(BlockHeaders) |
||||
if !ok { |
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
msg = conn.waitForResponse(s.chain, timeout, reqID) |
||||
headers2, ok := msg.(BlockHeaders) |
||||
if !ok { |
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
// check if headers match
|
||||
expected1, err := s.chain.GetHeaders(GetBlockHeaders(*request1.GetBlockHeadersPacket)) |
||||
if err != nil { |
||||
t.Fatalf("failed to get expected block headers: %v", err) |
||||
} |
||||
expected2, err := s.chain.GetHeaders(GetBlockHeaders(*request2.GetBlockHeadersPacket)) |
||||
if err != nil { |
||||
t.Fatalf("failed to get expected block headers: %v", err) |
||||
} |
||||
if !headersMatch(expected1, headers1) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) |
||||
} |
||||
if !headersMatch(expected2, headers2) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) |
||||
} |
||||
} |
||||
|
||||
// TestZeroRequestID_66 checks that a message with a request ID of zero is still handled
|
||||
// by the node.
|
||||
func (s *Suite) TestZeroRequestID66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
req := &GetBlockHeaders{ |
||||
Origin: eth.HashOrNumber{ |
||||
Number: 0, |
||||
}, |
||||
Amount: 2, |
||||
} |
||||
headers, err := conn.headersRequest(req, s.chain, eth66, 0) |
||||
if err != nil { |
||||
t.Fatalf("failed to get block headers: %v", err) |
||||
} |
||||
expected, err := s.chain.GetHeaders(*req) |
||||
if err != nil { |
||||
t.Fatalf("failed to get expected block headers: %v", err) |
||||
} |
||||
if !headersMatch(expected, headers) { |
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) |
||||
} |
||||
} |
||||
|
||||
// TestGetBlockBodies65 tests whether the given node can respond to
|
||||
// a `GetBlockBodies` request and that the response is accurate.
|
||||
func (s *Suite) TestGetBlockBodies65(t *utesting.T) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
// create block bodies request
|
||||
req := &GetBlockBodies{ |
||||
s.chain.blocks[54].Hash(), |
||||
s.chain.blocks[75].Hash(), |
||||
} |
||||
if err := conn.Write(req); err != nil { |
||||
t.Fatalf("could not write to connection: %v", err) |
||||
} |
||||
// wait for response
|
||||
switch msg := conn.readAndServe(s.chain, timeout).(type) { |
||||
case *BlockBodies: |
||||
t.Logf("received %d block bodies", len(*msg)) |
||||
if len(*msg) != len(*req) { |
||||
t.Fatalf("wrong bodies in response: expected %d bodies, "+ |
||||
"got %d", len(*req), len(*msg)) |
||||
} |
||||
default: |
||||
t.Fatalf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
|
||||
// TestGetBlockBodies66 tests whether the given node can respond to
|
||||
// a `GetBlockBodies` request and that the response is accurate over
|
||||
// the eth66 protocol.
|
||||
func (s *Suite) TestGetBlockBodies66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
// create block bodies request
|
||||
req := ð.GetBlockBodiesPacket66{ |
||||
RequestId: uint64(55), |
||||
GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ |
||||
s.chain.blocks[54].Hash(), |
||||
s.chain.blocks[75].Hash(), |
||||
}, |
||||
} |
||||
if err := conn.Write66(req, GetBlockBodies{}.Code()); err != nil { |
||||
t.Fatalf("could not write to connection: %v", err) |
||||
} |
||||
// wait for block bodies response
|
||||
msg := conn.waitForResponse(s.chain, timeout, req.RequestId) |
||||
blockBodies, ok := msg.(BlockBodies) |
||||
if !ok { |
||||
t.Fatalf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
t.Logf("received %d block bodies", len(blockBodies)) |
||||
if len(blockBodies) != len(req.GetBlockBodiesPacket) { |
||||
t.Fatalf("wrong bodies in response: expected %d bodies, "+ |
||||
"got %d", len(req.GetBlockBodiesPacket), len(blockBodies)) |
||||
} |
||||
} |
||||
|
||||
// TestBroadcast65 tests whether a block announcement is correctly
|
||||
// propagated to the given node's peer(s).
|
||||
func (s *Suite) TestBroadcast65(t *utesting.T) { |
||||
if err := s.sendNextBlock(eth65); err != nil { |
||||
t.Fatalf("block broadcast failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestBroadcast66 tests whether a block announcement is correctly
|
||||
// propagated to the given node's peer(s) on the eth66 protocol.
|
||||
func (s *Suite) TestBroadcast66(t *utesting.T) { |
||||
if err := s.sendNextBlock(eth66); err != nil { |
||||
t.Fatalf("block broadcast failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestLargeAnnounce65 tests the announcement mechanism with a large block.
|
||||
func (s *Suite) TestLargeAnnounce65(t *utesting.T) { |
||||
nextBlock := len(s.chain.blocks) |
||||
blocks := []*NewBlock{ |
||||
{ |
||||
Block: largeBlock(), |
||||
TD: s.fullChain.TotalDifficultyAt(nextBlock), |
||||
}, |
||||
{ |
||||
Block: s.fullChain.blocks[nextBlock], |
||||
TD: largeNumber(2), |
||||
}, |
||||
{ |
||||
Block: largeBlock(), |
||||
TD: largeNumber(2), |
||||
}, |
||||
} |
||||
|
||||
for i, blockAnnouncement := range blocks { |
||||
t.Logf("Testing malicious announcement: %v\n", i) |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
if err = conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
if err = conn.Write(blockAnnouncement); err != nil { |
||||
t.Fatalf("could not write to connection: %v", err) |
||||
} |
||||
// Invalid announcement, check that peer disconnected
|
||||
switch msg := conn.readAndServe(s.chain, time.Second*8).(type) { |
||||
case *Disconnect: |
||||
case *Error: |
||||
break |
||||
default: |
||||
t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) |
||||
} |
||||
conn.Close() |
||||
} |
||||
// Test the last block as a valid block
|
||||
if err := s.sendNextBlock(eth65); err != nil { |
||||
t.Fatalf("failed to broadcast next block: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestLargeAnnounce66 tests the announcement mechanism with a large
|
||||
// block over the eth66 protocol.
|
||||
func (s *Suite) TestLargeAnnounce66(t *utesting.T) { |
||||
nextBlock := len(s.chain.blocks) |
||||
blocks := []*NewBlock{ |
||||
{ |
||||
Block: largeBlock(), |
||||
TD: s.fullChain.TotalDifficultyAt(nextBlock), |
||||
}, |
||||
{ |
||||
Block: s.fullChain.blocks[nextBlock], |
||||
TD: largeNumber(2), |
||||
}, |
||||
{ |
||||
Block: largeBlock(), |
||||
TD: largeNumber(2), |
||||
}, |
||||
} |
||||
|
||||
for i, blockAnnouncement := range blocks[0:3] { |
||||
t.Logf("Testing malicious announcement: %v\n", i) |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
if err := conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
if err := conn.Write(blockAnnouncement); err != nil { |
||||
t.Fatalf("could not write to connection: %v", err) |
||||
} |
||||
// Invalid announcement, check that peer disconnected
|
||||
switch msg := conn.readAndServe(s.chain, time.Second*8).(type) { |
||||
case *Disconnect: |
||||
case *Error: |
||||
break |
||||
default: |
||||
t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) |
||||
} |
||||
conn.Close() |
||||
} |
||||
// Test the last block as a valid block
|
||||
if err := s.sendNextBlock(eth66); err != nil { |
||||
t.Fatalf("failed to broadcast next block: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestOldAnnounce65 tests the announcement mechanism with an old block.
|
||||
func (s *Suite) TestOldAnnounce65(t *utesting.T) { |
||||
if err := s.oldAnnounce(eth65); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestOldAnnounce66 tests the announcement mechanism with an old block,
|
||||
// over the eth66 protocol.
|
||||
func (s *Suite) TestOldAnnounce66(t *utesting.T) { |
||||
if err := s.oldAnnounce(eth66); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestBlockHashAnnounce65 sends a new block hash announcement and expects
|
||||
// the node to perform a `GetBlockHeaders` request.
|
||||
func (s *Suite) TestBlockHashAnnounce65(t *utesting.T) { |
||||
if err := s.hashAnnounce(eth65); err != nil { |
||||
t.Fatalf("block hash announcement failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestBlockHashAnnounce66 sends a new block hash announcement and expects
|
||||
// the node to perform a `GetBlockHeaders` request.
|
||||
func (s *Suite) TestBlockHashAnnounce66(t *utesting.T) { |
||||
if err := s.hashAnnounce(eth66); err != nil { |
||||
t.Fatalf("block hash announcement failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestMaliciousHandshake65 tries to send malicious data during the handshake.
|
||||
func (s *Suite) TestMaliciousHandshake65(t *utesting.T) { |
||||
if err := s.maliciousHandshakes(t, eth65); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestMaliciousHandshake66 tries to send malicious data during the handshake.
|
||||
func (s *Suite) TestMaliciousHandshake66(t *utesting.T) { |
||||
if err := s.maliciousHandshakes(t, eth66); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestMaliciousStatus65 sends a status package with a large total difficulty.
|
||||
func (s *Suite) TestMaliciousStatus65(t *utesting.T) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
|
||||
if err := s.maliciousStatus(conn); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestMaliciousStatus66 sends a status package with a large total
|
||||
// difficulty over the eth66 protocol.
|
||||
func (s *Suite) TestMaliciousStatus66(t *utesting.T) { |
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
|
||||
if err := s.maliciousStatus(conn); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestTransaction65 sends a valid transaction to the node and
|
||||
// checks if the transaction gets propagated.
|
||||
func (s *Suite) TestTransaction65(t *utesting.T) { |
||||
if err := s.sendSuccessfulTxs(t, eth65); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestTransaction66 sends a valid transaction to the node and
|
||||
// checks if the transaction gets propagated.
|
||||
func (s *Suite) TestTransaction66(t *utesting.T) { |
||||
if err := s.sendSuccessfulTxs(t, eth66); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestMaliciousTx65 sends several invalid transactions and tests whether
|
||||
// the node will propagate them.
|
||||
func (s *Suite) TestMaliciousTx65(t *utesting.T) { |
||||
if err := s.sendMaliciousTxs(t, eth65); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestMaliciousTx66 sends several invalid transactions and tests whether
|
||||
// the node will propagate them.
|
||||
func (s *Suite) TestMaliciousTx66(t *utesting.T) { |
||||
if err := s.sendMaliciousTxs(t, eth66); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
// TestLargeTxRequest66 tests whether a node can fulfill a large GetPooledTransactions
|
||||
// request.
|
||||
func (s *Suite) TestLargeTxRequest66(t *utesting.T) { |
||||
// send the next block to ensure the node is no longer syncing and
|
||||
// is able to accept txs
|
||||
if err := s.sendNextBlock(eth66); err != nil { |
||||
t.Fatalf("failed to send next block: %v", err) |
||||
} |
||||
// send 2000 transactions to the node
|
||||
hashMap, txs, err := generateTxs(s, 2000) |
||||
if err != nil { |
||||
t.Fatalf("failed to generate transactions: %v", err) |
||||
} |
||||
if err = sendMultipleSuccessfulTxs(t, s, txs); err != nil { |
||||
t.Fatalf("failed to send multiple txs: %v", err) |
||||
} |
||||
// set up connection to receive to ensure node is peered with the receiving connection
|
||||
// before tx request is sent
|
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err = conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
// create and send pooled tx request
|
||||
hashes := make([]common.Hash, 0) |
||||
for _, hash := range hashMap { |
||||
hashes = append(hashes, hash) |
||||
} |
||||
getTxReq := ð.GetPooledTransactionsPacket66{ |
||||
RequestId: 1234, |
||||
GetPooledTransactionsPacket: hashes, |
||||
} |
||||
if err = conn.Write66(getTxReq, GetPooledTransactions{}.Code()); err != nil { |
||||
t.Fatalf("could not write to conn: %v", err) |
||||
} |
||||
// check that all received transactions match those that were sent to node
|
||||
switch msg := conn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) { |
||||
case PooledTransactions: |
||||
for _, gotTx := range msg { |
||||
if _, exists := hashMap[gotTx.Hash()]; !exists { |
||||
t.Fatalf("unexpected tx received: %v", gotTx.Hash()) |
||||
} |
||||
} |
||||
default: |
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
|
||||
// TestNewPooledTxs_66 tests whether a node will do a GetPooledTransactions
|
||||
// request upon receiving a NewPooledTransactionHashes announcement.
|
||||
func (s *Suite) TestNewPooledTxs66(t *utesting.T) { |
||||
// send the next block to ensure the node is no longer syncing and
|
||||
// is able to accept txs
|
||||
if err := s.sendNextBlock(eth66); err != nil { |
||||
t.Fatalf("failed to send next block: %v", err) |
||||
} |
||||
|
||||
// generate 50 txs
|
||||
hashMap, _, err := generateTxs(s, 50) |
||||
if err != nil { |
||||
t.Fatalf("failed to generate transactions: %v", err) |
||||
} |
||||
|
||||
// create new pooled tx hashes announcement
|
||||
hashes := make([]common.Hash, 0) |
||||
for _, hash := range hashMap { |
||||
hashes = append(hashes, hash) |
||||
} |
||||
announce := NewPooledTransactionHashes(hashes) |
||||
|
||||
// send announcement
|
||||
conn, err := s.dial66() |
||||
if err != nil { |
||||
t.Fatalf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
if err = conn.peer(s.chain, nil); err != nil { |
||||
t.Fatalf("peering failed: %v", err) |
||||
} |
||||
if err = conn.Write(announce); err != nil { |
||||
t.Fatalf("failed to write to connection: %v", err) |
||||
} |
||||
|
||||
// wait for GetPooledTxs request
|
||||
for { |
||||
_, msg := conn.readAndServe66(s.chain, timeout) |
||||
switch msg := msg.(type) { |
||||
case GetPooledTransactions: |
||||
if len(msg) != len(hashes) { |
||||
t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg)) |
||||
} |
||||
return |
||||
// ignore propagated txs from previous tests
|
||||
case *NewPooledTransactionHashes: |
||||
continue |
||||
// ignore block announcements from previous tests
|
||||
case *NewBlockHashes: |
||||
continue |
||||
case *NewBlock: |
||||
continue |
||||
default: |
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,107 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"os" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/eth" |
||||
"github.com/ethereum/go-ethereum/eth/ethconfig" |
||||
"github.com/ethereum/go-ethereum/internal/utesting" |
||||
"github.com/ethereum/go-ethereum/node" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
) |
||||
|
||||
var ( |
||||
genesisFile = "./testdata/genesis.json" |
||||
halfchainFile = "./testdata/halfchain.rlp" |
||||
fullchainFile = "./testdata/chain.rlp" |
||||
) |
||||
|
||||
func TestEthSuite(t *testing.T) { |
||||
geth, err := runGeth() |
||||
if err != nil { |
||||
t.Fatalf("could not run geth: %v", err) |
||||
} |
||||
defer geth.Close() |
||||
|
||||
suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile) |
||||
if err != nil { |
||||
t.Fatalf("could not create new test suite: %v", err) |
||||
} |
||||
for _, test := range suite.Eth66Tests() { |
||||
t.Run(test.Name, func(t *testing.T) { |
||||
result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) |
||||
if result[0].Failed { |
||||
t.Fatal() |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// runGeth creates and starts a geth node
|
||||
func runGeth() (*node.Node, error) { |
||||
stack, err := node.New(&node.Config{ |
||||
P2P: p2p.Config{ |
||||
ListenAddr: "127.0.0.1:0", |
||||
NoDiscovery: true, |
||||
MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future
|
||||
NoDial: true, |
||||
}, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
err = setupGeth(stack) |
||||
if err != nil { |
||||
stack.Close() |
||||
return nil, err |
||||
} |
||||
if err = stack.Start(); err != nil { |
||||
stack.Close() |
||||
return nil, err |
||||
} |
||||
return stack, nil |
||||
} |
||||
|
||||
func setupGeth(stack *node.Node) error { |
||||
chain, err := loadChain(halfchainFile, genesisFile) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
backend, err := eth.New(stack, ðconfig.Config{ |
||||
Genesis: &chain.genesis, |
||||
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
|
||||
DatabaseCache: 10, |
||||
TrieCleanCache: 10, |
||||
TrieCleanCacheJournal: "", |
||||
TrieCleanCacheRejournal: 60 * time.Minute, |
||||
TrieDirtyCache: 16, |
||||
TrieTimeout: 60 * time.Minute, |
||||
SnapshotCache: 10, |
||||
}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
_, err = backend.BlockChain().InsertChain(chain.blocks[1:]) |
||||
return err |
||||
} |
Binary file not shown.
@ -0,0 +1,26 @@ |
||||
{ |
||||
"config": { |
||||
"chainId": 19763, |
||||
"homesteadBlock": 0, |
||||
"eip150Block": 0, |
||||
"eip155Block": 0, |
||||
"eip158Block": 0, |
||||
"byzantiumBlock": 0, |
||||
"ethash": {} |
||||
}, |
||||
"nonce": "0xdeadbeefdeadbeef", |
||||
"timestamp": "0x0", |
||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"gasLimit": "0x80000000", |
||||
"difficulty": "0x20000", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"coinbase": "0x0000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"71562b71999873db5b286df957af199ec94617f7": { |
||||
"balance": "0xffffffffffffffffffffffffff" |
||||
} |
||||
}, |
||||
"number": "0x0", |
||||
"gasUsed": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
Binary file not shown.
@ -0,0 +1,419 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/internal/utesting" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
//var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
||||
var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
||||
|
||||
func (s *Suite) sendSuccessfulTxs(t *utesting.T, isEth66 bool) error { |
||||
tests := []*types.Transaction{ |
||||
getNextTxFromChain(s), |
||||
unknownTx(s), |
||||
} |
||||
for i, tx := range tests { |
||||
if tx == nil { |
||||
return fmt.Errorf("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, isEth66); err != nil { |
||||
return fmt.Errorf("send successful tx test failed: %v", err) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction, isEth66 bool) error { |
||||
sendConn, recvConn, err := s.createSendAndRecvConns(isEth66) |
||||
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) |
||||
} |
||||
// 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
|
||||
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 *NewPooledTransactionHashes: |
||||
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()) |
||||
default: |
||||
return fmt.Errorf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) sendMaliciousTxs(t *utesting.T, isEth66 bool) error { |
||||
badTxs := []*types.Transaction{ |
||||
getOldTxFromChain(s), |
||||
invalidNonceTx(s), |
||||
hugeAmount(s), |
||||
hugeGasPrice(s), |
||||
hugeData(s), |
||||
} |
||||
// setup receiving connection before sending malicious txs
|
||||
var ( |
||||
recvConn *Conn |
||||
err error |
||||
) |
||||
if isEth66 { |
||||
recvConn, err = s.dial66() |
||||
} else { |
||||
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, isEth66); 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, isEth66 bool) error { |
||||
// setup connection
|
||||
var ( |
||||
conn *Conn |
||||
err error |
||||
) |
||||
if isEth66 { |
||||
conn, err = s.dial66() |
||||
} else { |
||||
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(true) |
||||
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) |
||||
} |
||||
// Send the transactions
|
||||
if err = sendConn.Write(&txMsg); err != nil { |
||||
return fmt.Errorf("failed to write message to connection: %v", err) |
||||
} |
||||
// update nonce
|
||||
nonce = txs[len(txs)-1].Nonce() |
||||
// Wait for the transaction announcement(s) and make sure all sent txs are being propagated
|
||||
recvHashes := make([]common.Hash, 0) |
||||
// all txs should be announced within 3 announcements
|
||||
for i := 0; i < 3; i++ { |
||||
switch msg := recvConn.readAndServe(s.chain, timeout).(type) { |
||||
case *Transactions: |
||||
for _, tx := range *msg { |
||||
recvHashes = append(recvHashes, tx.Hash()) |
||||
} |
||||
case *NewPooledTransactionHashes: |
||||
recvHashes = append(recvHashes, *msg...) |
||||
default: |
||||
if !strings.Contains(pretty.Sdump(msg), "i/o timeout") { |
||||
return fmt.Errorf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
// break once all 2000 txs have been received
|
||||
if len(recvHashes) == 2000 { |
||||
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 { |
||||
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
|
||||
// propagated by the node.
|
||||
func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) error { |
||||
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 *NewPooledTransactionHashes: |
||||
badTxs, _ := compareReceivedTxs(*msg, 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, fmt.Errorf("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, fmt.Errorf("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 { |
||||
return nil |
||||
} |
||||
return signedTx |
||||
} |
@ -0,0 +1,283 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"fmt" |
||||
|
||||
"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 |
||||
} |
||||
|
||||
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) Code() int { return -1 } |
||||
func (e *Error) String() string { return e.Error() } |
||||
|
||||
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 (h Hello) Code() int { return 0x00 } |
||||
|
||||
// Disconnect is the RLP structure for a disconnect message.
|
||||
type Disconnect struct { |
||||
Reason p2p.DiscReason |
||||
} |
||||
|
||||
func (d Disconnect) Code() int { return 0x01 } |
||||
|
||||
type Ping struct{} |
||||
|
||||
func (p Ping) Code() int { return 0x02 } |
||||
|
||||
type Pong struct{} |
||||
|
||||
func (p Pong) Code() int { return 0x03 } |
||||
|
||||
// Status is the network packet for the status message for eth/64 and later.
|
||||
type Status eth.StatusPacket |
||||
|
||||
func (s Status) Code() int { return 16 } |
||||
|
||||
// NewBlockHashes is the network packet for the block announcements.
|
||||
type NewBlockHashes eth.NewBlockHashesPacket |
||||
|
||||
func (nbh NewBlockHashes) Code() int { return 17 } |
||||
|
||||
type Transactions eth.TransactionsPacket |
||||
|
||||
func (t Transactions) Code() int { return 18 } |
||||
|
||||
// GetBlockHeaders represents a block header query.
|
||||
type GetBlockHeaders eth.GetBlockHeadersPacket |
||||
|
||||
func (g GetBlockHeaders) Code() int { return 19 } |
||||
|
||||
type BlockHeaders eth.BlockHeadersPacket |
||||
|
||||
func (bh BlockHeaders) Code() int { return 20 } |
||||
|
||||
// GetBlockBodies represents a GetBlockBodies request
|
||||
type GetBlockBodies eth.GetBlockBodiesPacket |
||||
|
||||
func (gbb GetBlockBodies) Code() int { return 21 } |
||||
|
||||
// BlockBodies is the network packet for block content distribution.
|
||||
type BlockBodies eth.BlockBodiesPacket |
||||
|
||||
func (bb BlockBodies) Code() int { return 22 } |
||||
|
||||
// NewBlock is the network packet for the block propagation message.
|
||||
type NewBlock eth.NewBlockPacket |
||||
|
||||
func (nb NewBlock) Code() int { return 23 } |
||||
|
||||
// NewPooledTransactionHashes is the network packet for the tx hash propagation message.
|
||||
type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket |
||||
|
||||
func (nb NewPooledTransactionHashes) Code() int { return 24 } |
||||
|
||||
type GetPooledTransactions eth.GetPooledTransactionsPacket |
||||
|
||||
func (gpt GetPooledTransactions) Code() int { return 25 } |
||||
|
||||
type PooledTransactions eth.PooledTransactionsPacket |
||||
|
||||
func (pt PooledTransactions) Code() int { return 26 } |
||||
|
||||
// Conn represents an individual connection with a peer
|
||||
type Conn struct { |
||||
*rlpx.Conn |
||||
ourKey *ecdsa.PrivateKey |
||||
negotiatedProtoVersion uint |
||||
ourHighestProtoVersion uint |
||||
caps []p2p.Cap |
||||
} |
||||
|
||||
// Read reads an eth 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(): |
||||
msg = new(GetBlockHeaders) |
||||
case (BlockHeaders{}).Code(): |
||||
msg = new(BlockHeaders) |
||||
case (GetBlockBodies{}).Code(): |
||||
msg = new(GetBlockBodies) |
||||
case (BlockBodies{}).Code(): |
||||
msg = new(BlockBodies) |
||||
case (NewBlock{}).Code(): |
||||
msg = new(NewBlock) |
||||
case (NewBlockHashes{}).Code(): |
||||
msg = new(NewBlockHashes) |
||||
case (Transactions{}).Code(): |
||||
msg = new(Transactions) |
||||
case (NewPooledTransactionHashes{}).Code(): |
||||
msg = new(NewPooledTransactionHashes) |
||||
case (GetPooledTransactions{}.Code()): |
||||
msg = new(GetPooledTransactions) |
||||
case (PooledTransactions{}.Code()): |
||||
msg = new(PooledTransactions) |
||||
default: |
||||
return errorf("invalid message code: %d", code) |
||||
} |
||||
// if message is devp2p, decode here
|
||||
if err := rlp.DecodeBytes(rawData, msg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return msg |
||||
} |
||||
|
||||
// Read66 reads an eth66 packet from the connection.
|
||||
func (c *Conn) Read66() (uint64, Message) { |
||||
code, rawData, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return 0, 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.GetBlockHeadersPacket66) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket) |
||||
case (BlockHeaders{}).Code(): |
||||
ethMsg := new(eth.BlockHeadersPacket66) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket) |
||||
case (GetBlockBodies{}).Code(): |
||||
ethMsg := new(eth.GetBlockBodiesPacket66) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket) |
||||
case (BlockBodies{}).Code(): |
||||
ethMsg := new(eth.BlockBodiesPacket66) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket) |
||||
case (NewBlock{}).Code(): |
||||
msg = new(NewBlock) |
||||
case (NewBlockHashes{}).Code(): |
||||
msg = new(NewBlockHashes) |
||||
case (Transactions{}).Code(): |
||||
msg = new(Transactions) |
||||
case (NewPooledTransactionHashes{}).Code(): |
||||
msg = new(NewPooledTransactionHashes) |
||||
case (GetPooledTransactions{}.Code()): |
||||
ethMsg := new(eth.GetPooledTransactionsPacket66) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return ethMsg.RequestId, GetPooledTransactions(ethMsg.GetPooledTransactionsPacket) |
||||
case (PooledTransactions{}.Code()): |
||||
ethMsg := new(eth.PooledTransactionsPacket66) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return ethMsg.RequestId, PooledTransactions(ethMsg.PooledTransactionsPacket) |
||||
default: |
||||
msg = errorf("invalid message code: %d", code) |
||||
} |
||||
|
||||
if msg != nil { |
||||
if err := rlp.DecodeBytes(rawData, msg); err != nil { |
||||
return 0, errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return 0, msg |
||||
} |
||||
return 0, errorf("invalid message: %s", string(rawData)) |
||||
} |
||||
|
||||
// Write writes a eth packet to the connection.
|
||||
func (c *Conn) Write(msg Message) error { |
||||
// check if message is eth protocol message
|
||||
var ( |
||||
payload []byte |
||||
err error |
||||
) |
||||
payload, err = rlp.EncodeToBytes(msg) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = c.Conn.Write(uint64(msg.Code()), payload) |
||||
return err |
||||
} |
||||
|
||||
// Write66 writes an eth66 packet to the connection.
|
||||
func (c *Conn) Write66(req eth.Packet, code int) error { |
||||
payload, err := rlp.EncodeToBytes(req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = c.Conn.Write(uint64(code), payload) |
||||
return err |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue