cmd/devp2p/internal/ethtest: add correct chain files and improve test output (#21782)
This PR replaces the old test genesis.json and chain.rlp files in the testdata directory for the eth protocol test suite, and also adds documentation for running the eth test suite locally. It also improves the test output text and adds more timeouts. Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
5d20fbbb6f
commit
36bb7ac083
@ -81,6 +81,25 @@ 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`.
|
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 ID>` with the enode of the geth node:
|
||||||
|
```
|
||||||
|
devp2p rlpx eth-test <enode ID> cmd/devp2p/internal/ethtest/testdata/fullchain.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
|
[dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup
|
||||||
[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md
|
[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md
|
||||||
[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md
|
[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md
|
||||||
|
@ -73,7 +73,7 @@ func TestEthProtocolNegotiation(t *testing.T) {
|
|||||||
// TestChain_GetHeaders tests whether the test suite can correctly
|
// TestChain_GetHeaders tests whether the test suite can correctly
|
||||||
// respond to a GetBlockHeaders request from a node.
|
// respond to a GetBlockHeaders request from a node.
|
||||||
func TestChain_GetHeaders(t *testing.T) {
|
func TestChain_GetHeaders(t *testing.T) {
|
||||||
chainFile, err := filepath.Abs("./testdata/chain.rlp.gz")
|
chainFile, err := filepath.Abs("./testdata/fullchain.rlp.gz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ package ethtest
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
@ -27,6 +29,13 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pretty = spew.ConfigState{
|
||||||
|
Indent: " ",
|
||||||
|
DisableCapacities: true,
|
||||||
|
DisablePointerAddresses: true,
|
||||||
|
SortKeys: true,
|
||||||
|
}
|
||||||
|
|
||||||
// Suite represents a structure used to test the eth
|
// Suite represents a structure used to test the eth
|
||||||
// protocol of a node(s).
|
// protocol of a node(s).
|
||||||
type Suite struct {
|
type Suite struct {
|
||||||
@ -73,9 +82,9 @@ func (s *Suite) TestStatus(t *utesting.T) {
|
|||||||
// get status
|
// get status
|
||||||
switch msg := conn.statusExchange(t, s.chain).(type) {
|
switch msg := conn.statusExchange(t, s.chain).(type) {
|
||||||
case *Status:
|
case *Status:
|
||||||
t.Logf("%+v\n", msg)
|
t.Logf("got status message: %s", pretty.Sdump(msg))
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,16 +113,17 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
|
|||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := conn.ReadAndServe(s.chain).(type) {
|
timeout := 20 * time.Second
|
||||||
|
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
|
||||||
case *BlockHeaders:
|
case *BlockHeaders:
|
||||||
headers := msg
|
headers := msg
|
||||||
for _, header := range *headers {
|
for _, header := range *headers {
|
||||||
num := header.Number.Uint64()
|
num := header.Number.Uint64()
|
||||||
|
t.Logf("received header (%d): %s", num, pretty.Sdump(header))
|
||||||
assert.Equal(t, s.chain.blocks[int(num)].Header(), header)
|
assert.Equal(t, s.chain.blocks[int(num)].Header(), header)
|
||||||
t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,14 +143,12 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
|
|||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := conn.ReadAndServe(s.chain).(type) {
|
timeout := 20 * time.Second
|
||||||
|
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
|
||||||
case *BlockBodies:
|
case *BlockBodies:
|
||||||
bodies := msg
|
t.Logf("received %d block bodies", len(*msg))
|
||||||
for _, body := range *bodies {
|
|
||||||
t.Logf("\nBODY: %+v\n", body)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,18 +181,27 @@ func (s *Suite) TestBroadcast(t *utesting.T) {
|
|||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := receiveConn.ReadAndServe(s.chain).(type) {
|
timeout := 20 * time.Second
|
||||||
|
switch msg := receiveConn.ReadAndServe(s.chain, timeout).(type) {
|
||||||
case *NewBlock:
|
case *NewBlock:
|
||||||
assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(),
|
t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block))
|
||||||
"wrong block header in announcement")
|
assert.Equal(t,
|
||||||
assert.Equal(t, blockAnnouncement.TD, msg.TD,
|
blockAnnouncement.Block.Header(), msg.Block.Header(),
|
||||||
"wrong TD in announcement")
|
"wrong block header in announcement",
|
||||||
|
)
|
||||||
|
assert.Equal(t,
|
||||||
|
blockAnnouncement.TD, msg.TD,
|
||||||
|
"wrong TD in announcement",
|
||||||
|
)
|
||||||
case *NewBlockHashes:
|
case *NewBlockHashes:
|
||||||
hashes := *msg
|
hashes := *msg
|
||||||
assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash,
|
t.Logf("received NewBlockHashes message: %s", pretty.Sdump(hashes))
|
||||||
"wrong block hash in announcement")
|
assert.Equal(t,
|
||||||
|
blockAnnouncement.Block.Hash(), hashes[0].Hash,
|
||||||
|
"wrong block hash in announcement",
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
// update test suite chain
|
// update test suite chain
|
||||||
s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000])
|
s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000])
|
||||||
|
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz
vendored
Binary file not shown.
BIN
cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz
vendored
Normal file
BIN
cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz
vendored
Normal file
Binary file not shown.
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz
vendored
Normal file
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz
vendored
Normal file
Binary file not shown.
@ -42,10 +42,14 @@ type Error struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Unwrap() error { return e.err }
|
func (e *Error) Unwrap() error { return e.err }
|
||||||
func (e *Error) Error() string { return e.err.Error() }
|
func (e *Error) Error() string { return e.err.Error() }
|
||||||
func (e *Error) Code() int { return -1 }
|
func (e *Error) Code() int { return -1 }
|
||||||
func (e *Error) GoString() string { return e.Error() }
|
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.
|
// Hello is the RLP structure of the protocol handshake.
|
||||||
type Hello struct {
|
type Hello struct {
|
||||||
@ -174,7 +178,7 @@ type Conn struct {
|
|||||||
func (c *Conn) Read() Message {
|
func (c *Conn) Read() Message {
|
||||||
code, rawData, _, err := c.Conn.Read()
|
code, rawData, _, err := c.Conn.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Error{fmt.Errorf("could not read from connection: %v", err)}
|
return errorf("could not read from connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg Message
|
var msg Message
|
||||||
@ -202,20 +206,22 @@ func (c *Conn) Read() Message {
|
|||||||
case (NewBlockHashes{}).Code():
|
case (NewBlockHashes{}).Code():
|
||||||
msg = new(NewBlockHashes)
|
msg = new(NewBlockHashes)
|
||||||
default:
|
default:
|
||||||
return &Error{fmt.Errorf("invalid message code: %d", code)}
|
return errorf("invalid message code: %d", code)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rlp.DecodeBytes(rawData, msg); err != nil {
|
if err := rlp.DecodeBytes(rawData, msg); err != nil {
|
||||||
return &Error{fmt.Errorf("could not rlp decode message: %v", err)}
|
return errorf("could not rlp decode message: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAndServe serves GetBlockHeaders requests while waiting
|
// ReadAndServe serves GetBlockHeaders requests while waiting
|
||||||
// on another message from the node.
|
// on another message from the node.
|
||||||
func (c *Conn) ReadAndServe(chain *Chain) Message {
|
func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message {
|
||||||
for {
|
start := time.Now()
|
||||||
|
for time.Since(start) < timeout {
|
||||||
|
timeout := time.Now().Add(10 * time.Second)
|
||||||
|
c.SetReadDeadline(timeout)
|
||||||
switch msg := c.Read().(type) {
|
switch msg := c.Read().(type) {
|
||||||
case *Ping:
|
case *Ping:
|
||||||
c.Write(&Pong{})
|
c.Write(&Pong{})
|
||||||
@ -223,16 +229,17 @@ func (c *Conn) ReadAndServe(chain *Chain) Message {
|
|||||||
req := *msg
|
req := *msg
|
||||||
headers, err := chain.GetHeaders(req)
|
headers, err := chain.GetHeaders(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Error{fmt.Errorf("could not get headers for inbound header request: %v", err)}
|
return errorf("could not get headers for inbound header request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Write(headers); err != nil {
|
if err := c.Write(headers); err != nil {
|
||||||
return &Error{fmt.Errorf("could not write to connection: %v", err)}
|
return errorf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return errorf("no message received within %v", timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(msg Message) error {
|
func (c *Conn) Write(msg Message) error {
|
||||||
@ -308,7 +315,7 @@ loop:
|
|||||||
switch msg := c.Read().(type) {
|
switch msg := c.Read().(type) {
|
||||||
case *Status:
|
case *Status:
|
||||||
if msg.Head != chain.blocks[chain.Len()-1].Hash() {
|
if msg.Head != chain.blocks[chain.Len()-1].Hash() {
|
||||||
t.Fatalf("wrong head in status: %v", msg.Head)
|
t.Fatalf("wrong head block in status: %s", msg.Head.String())
|
||||||
}
|
}
|
||||||
if msg.TD.Cmp(chain.TD(chain.Len())) != 0 {
|
if msg.TD.Cmp(chain.TD(chain.Len())) != 0 {
|
||||||
t.Fatalf("wrong TD in status: %v", msg.TD)
|
t.Fatalf("wrong TD in status: %v", msg.TD)
|
||||||
@ -324,7 +331,7 @@ loop:
|
|||||||
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
||||||
// (PINGs should not be a response upon fresh connection)
|
// (PINGs should not be a response upon fresh connection)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("bad status message: %#v", msg)
|
t.Fatalf("bad status message: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// make sure eth protocol version is set for negotiation
|
// make sure eth protocol version is set for negotiation
|
||||||
@ -366,7 +373,7 @@ func (c *Conn) waitForBlock(block *types.Block) error {
|
|||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid message: %v", msg)
|
return fmt.Errorf("invalid message: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user