cmd/devp2p/internal/ethtest: improve eth test suite (#21615)

This fixes issues with the protocol handshake and status exchange
and adds support for responding to GetBlockHeaders requests.
This commit is contained in:
rene
2020-10-07 17:22:44 +02:00
committed by GitHub
parent e43d827a19
commit 716864deba
6 changed files with 487 additions and 147 deletions

View File

@ -1,19 +1,29 @@
// 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"
"net"
"reflect"
"time"
"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/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/rlpx"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
)
@ -26,137 +36,6 @@ type Suite struct {
fullChain *Chain
}
type Conn struct {
*rlpx.Conn
ourKey *ecdsa.PrivateKey
}
func (c *Conn) Read() Message {
code, rawData, _, err := c.Conn.Read()
if err != nil {
return &Error{fmt.Errorf("could not read from connection: %v", err)}
}
var msg Message
switch int(code) {
case (Hello{}).Code():
msg = new(Hello)
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)
default:
return &Error{fmt.Errorf("invalid message code: %d", code)}
}
if err := rlp.DecodeBytes(rawData, msg); err != nil {
return &Error{fmt.Errorf("could not rlp decode message: %v", err)}
}
return msg
}
func (c *Conn) Write(msg Message) error {
payload, err := rlp.EncodeToBytes(msg)
if err != nil {
return err
}
_, err = c.Conn.Write(uint64(msg.Code()), payload)
return err
}
// handshake checks to make sure a `HELLO` is received.
func (c *Conn) handshake(t *utesting.T) Message {
// write protoHandshake to client
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
ourHandshake := &Hello{
Version: 5,
Caps: []p2p.Cap{{Name: "eth", Version: 64}, {Name: "eth", Version: 65}},
ID: pub0,
}
if err := c.Write(ourHandshake); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// read protoHandshake from client
switch msg := c.Read().(type) {
case *Hello:
return msg
default:
t.Fatalf("bad handshake: %v", msg)
return nil
}
}
// statusExchange performs a `Status` message exchange with the given
// node.
func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message {
// read status message from client
var message Message
switch msg := c.Read().(type) {
case *Status:
if msg.Head != chain.blocks[chain.Len()-1].Hash() {
t.Fatalf("wrong head in status: %v", msg.Head)
}
if msg.TD.Cmp(chain.TD(chain.Len())) != 0 {
t.Fatalf("wrong TD in status: %v", msg.TD)
}
if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) {
t.Fatalf("wrong fork ID in status: %v", msg.ForkID)
}
message = msg
default:
t.Fatalf("bad status message: %v", msg)
}
// write status message to client
status := Status{
ProtocolVersion: 64,
NetworkID: 1,
TD: chain.TD(chain.Len()),
Head: chain.blocks[chain.Len()-1].Hash(),
Genesis: chain.blocks[0].Hash(),
ForkID: chain.ForkID(),
}
if err := c.Write(status); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
return message
}
// waitForBlock waits for confirmation from the client that it has
// imported the given block.
func (c *Conn) waitForBlock(block *types.Block) error {
for {
req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1}
if err := c.Write(req); err != nil {
return err
}
switch msg := c.Read().(type) {
case *BlockHeaders:
if len(*msg) > 0 {
return nil
}
time.Sleep(100 * time.Millisecond)
default:
return fmt.Errorf("invalid message: %v", msg)
}
}
}
// NewSuite creates and returns a new eth-test suite that can
// be used to test the given node against the given blockchain
// data.
@ -196,7 +75,7 @@ func (s *Suite) TestStatus(t *utesting.T) {
case *Status:
t.Logf("%+v\n", msg)
default:
t.Fatalf("error: %v", msg)
t.Fatalf("unexpected: %#v", msg)
}
}
@ -225,7 +104,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err)
}
switch msg := conn.Read().(type) {
switch msg := conn.ReadAndServe(s.chain).(type) {
case *BlockHeaders:
headers := msg
for _, header := range *headers {
@ -234,7 +113,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header)
}
default:
t.Fatalf("error: %v", msg)
t.Fatalf("unexpected: %#v", msg)
}
}
@ -254,14 +133,14 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err)
}
switch msg := conn.Read().(type) {
switch msg := conn.ReadAndServe(s.chain).(type) {
case *BlockBodies:
bodies := msg
for _, body := range *bodies {
t.Logf("\nBODY: %+v\n", body)
}
default:
t.Fatalf("error: %v", msg)
t.Fatalf("unexpected: %#v", msg)
}
}
@ -294,7 +173,7 @@ func (s *Suite) TestBroadcast(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err)
}
switch msg := receiveConn.Read().(type) {
switch msg := receiveConn.ReadAndServe(s.chain).(type) {
case *NewBlock:
assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(),
"wrong block header in announcement")
@ -305,7 +184,7 @@ func (s *Suite) TestBroadcast(t *utesting.T) {
assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash,
"wrong block hash in announcement")
default:
t.Fatal(msg)
t.Fatalf("unexpected: %#v", msg)
}
// update test suite chain
s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000])