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:
rene 2020-11-04 17:36:56 +01:00 committed by GitHub
parent 5d20fbbb6f
commit 36bb7ac083
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 35 deletions

View File

@ -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

View File

@ -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)
} }

View File

@ -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])

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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))
} }
} }
} }