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:
@ -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])
|
||||
|
Reference in New Issue
Block a user