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:
parent
e43d827a19
commit
716864deba
@ -1,3 +1,19 @@
|
|||||||
|
// 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
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -68,6 +84,43 @@ func (c *Chain) Head() *types.Block {
|
|||||||
return c.blocks[c.Len()-1]
|
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
|
// loadChain takes the given chain.rlp file, and decodes and returns
|
||||||
// the blocks from the file.
|
// the blocks from the file.
|
||||||
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
||||||
|
150
cmd/devp2p/internal/ethtest/chain_test.go
Normal file
150
cmd/devp2p/internal/ethtest/chain_test.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// 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/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{},
|
||||||
|
caps: []p2p.Cap{
|
||||||
|
{Name: "eth", Version: 63},
|
||||||
|
{Name: "eth", Version: 64},
|
||||||
|
{Name: "eth", Version: 65},
|
||||||
|
},
|
||||||
|
expected: uint32(65),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conn: &Conn{},
|
||||||
|
caps: []p2p.Cap{
|
||||||
|
{Name: "eth", Version: 0},
|
||||||
|
{Name: "eth", Version: 89},
|
||||||
|
{Name: "eth", Version: 65},
|
||||||
|
},
|
||||||
|
expected: uint32(65),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conn: &Conn{},
|
||||||
|
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.ethProtocolVersion))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.gz")
|
||||||
|
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: 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: 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: 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"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"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,137 +36,6 @@ type Suite struct {
|
|||||||
fullChain *Chain
|
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
|
// NewSuite creates and returns a new eth-test suite that can
|
||||||
// be used to test the given node against the given blockchain
|
// be used to test the given node against the given blockchain
|
||||||
// data.
|
// data.
|
||||||
@ -196,7 +75,7 @@ func (s *Suite) TestStatus(t *utesting.T) {
|
|||||||
case *Status:
|
case *Status:
|
||||||
t.Logf("%+v\n", msg)
|
t.Logf("%+v\n", msg)
|
||||||
default:
|
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)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := conn.Read().(type) {
|
switch msg := conn.ReadAndServe(s.chain).(type) {
|
||||||
case *BlockHeaders:
|
case *BlockHeaders:
|
||||||
headers := msg
|
headers := msg
|
||||||
for _, header := range *headers {
|
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)
|
t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header)
|
||||||
}
|
}
|
||||||
default:
|
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)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := conn.Read().(type) {
|
switch msg := conn.ReadAndServe(s.chain).(type) {
|
||||||
case *BlockBodies:
|
case *BlockBodies:
|
||||||
bodies := msg
|
bodies := msg
|
||||||
for _, body := range *bodies {
|
for _, body := range *bodies {
|
||||||
t.Logf("\nBODY: %+v\n", body)
|
t.Logf("\nBODY: %+v\n", body)
|
||||||
}
|
}
|
||||||
default:
|
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)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := receiveConn.Read().(type) {
|
switch msg := receiveConn.ReadAndServe(s.chain).(type) {
|
||||||
case *NewBlock:
|
case *NewBlock:
|
||||||
assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(),
|
assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(),
|
||||||
"wrong block header in announcement")
|
"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,
|
assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash,
|
||||||
"wrong block hash in announcement")
|
"wrong block hash in announcement")
|
||||||
default:
|
default:
|
||||||
t.Fatal(msg)
|
t.Fatalf("unexpected: %#v", 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
Executable file
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz
vendored
Executable file
Binary file not shown.
26
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
Normal file
26
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"chainId": 1,
|
||||||
|
"homesteadBlock": 0,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip155Block": 0,
|
||||||
|
"eip158Block": 0,
|
||||||
|
"byzantiumBlock": 0,
|
||||||
|
"ethash": {}
|
||||||
|
},
|
||||||
|
"nonce": "0xdeadbeefdeadbeef",
|
||||||
|
"timestamp": "0x0",
|
||||||
|
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"gasLimit": "0x8000000",
|
||||||
|
"difficulty": "0x10",
|
||||||
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
|
"alloc": {
|
||||||
|
"71562b71999873db5b286df957af199ec94617f7": {
|
||||||
|
"balance": "0xf4240"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"number": "0x0",
|
||||||
|
"gasUsed": "0x0",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
}
|
@ -1,14 +1,36 @@
|
|||||||
|
// 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
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/forkid"
|
"github.com/ethereum/go-ethereum/core/forkid"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"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"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,9 +42,10 @@ 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() }
|
||||||
|
|
||||||
// Hello is the RLP structure of the protocol handshake.
|
// Hello is the RLP structure of the protocol handshake.
|
||||||
type Hello struct {
|
type Hello struct {
|
||||||
@ -45,6 +68,14 @@ type Disconnect struct {
|
|||||||
|
|
||||||
func (d Disconnect) Code() int { return 0x01 }
|
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.
|
// Status is the network packet for the status message for eth/64 and later.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
ProtocolVersion uint32
|
ProtocolVersion uint32
|
||||||
@ -132,3 +163,204 @@ func (gbb GetBlockBodies) Code() int { return 21 }
|
|||||||
type BlockBodies []*types.Body
|
type BlockBodies []*types.Body
|
||||||
|
|
||||||
func (bb BlockBodies) Code() int { return 22 }
|
func (bb BlockBodies) Code() int { return 22 }
|
||||||
|
|
||||||
|
// Conn represents an individual connection with a peer
|
||||||
|
type Conn struct {
|
||||||
|
*rlpx.Conn
|
||||||
|
ourKey *ecdsa.PrivateKey
|
||||||
|
ethProtocolVersion uint
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAndServe serves GetBlockHeaders requests while waiting
|
||||||
|
// on another message from the node.
|
||||||
|
func (c *Conn) ReadAndServe(chain *Chain) Message {
|
||||||
|
for {
|
||||||
|
switch msg := c.Read().(type) {
|
||||||
|
case *Ping:
|
||||||
|
c.Write(&Pong{})
|
||||||
|
case *GetBlockHeaders:
|
||||||
|
req := *msg
|
||||||
|
headers, err := chain.GetHeaders(req)
|
||||||
|
if err != nil {
|
||||||
|
return &Error{fmt.Errorf("could not get headers for inbound header request: %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Write(headers); err != nil {
|
||||||
|
return &Error{fmt.Errorf("could not write to connection: %v", err)}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
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:
|
||||||
|
// set snappy if version is at least 5
|
||||||
|
if msg.Version >= 5 {
|
||||||
|
c.SetSnappy(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.negotiateEthProtocol(msg.Caps)
|
||||||
|
if c.ethProtocolVersion == 0 {
|
||||||
|
t.Fatalf("unexpected eth protocol version")
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
default:
|
||||||
|
t.Fatalf("bad handshake: %#v", msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 <= 65 {
|
||||||
|
highestEthVersion = capability.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.ethProtocolVersion = highestEthVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
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
|
||||||
|
break loop
|
||||||
|
case *Disconnect:
|
||||||
|
t.Fatalf("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:
|
||||||
|
t.Fatalf("bad status message: %#v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// make sure eth protocol version is set for negotiation
|
||||||
|
if c.ethProtocolVersion == 0 {
|
||||||
|
t.Fatalf("eth protocol version must be set in Conn")
|
||||||
|
}
|
||||||
|
// write status message to client
|
||||||
|
status := Status{
|
||||||
|
ProtocolVersion: uint32(c.ethProtocolVersion),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user