mirror of https://github.com/ethereum/go-ethereum
cmd/devp2p: update eth/snap protocol test suites for PoS (#28340)
Here we update the eth and snap protocol test suites with a new test chain, created by the hivechain tool. The new test chain uses proof-of-stake. As such, tests using PoW block propagation in the eth protocol are removed. The test suite now connects to the node under test using the engine API in order to make it accept transactions. The snap protocol test suite has been rewritten to output test descriptions and log requests more verbosely. --------- Co-authored-by: Felix Lange <fjl@twurst.com>pull/28043/head
parent
8c2d455ccd
commit
577be37e0e
@ -0,0 +1,361 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"reflect" |
||||
"time" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/p2p/rlpx" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
var ( |
||||
pretty = spew.ConfigState{ |
||||
Indent: " ", |
||||
DisableCapacities: true, |
||||
DisablePointerAddresses: true, |
||||
SortKeys: true, |
||||
} |
||||
timeout = 2 * time.Second |
||||
) |
||||
|
||||
// dial attempts to dial the given node and perform a handshake, returning the
|
||||
// created Conn if successful.
|
||||
func (s *Suite) dial() (*Conn, error) { |
||||
key, _ := crypto.GenerateKey() |
||||
return s.dialAs(key) |
||||
} |
||||
|
||||
// dialAs attempts to dial a given node and perform a handshake using the given
|
||||
// private key.
|
||||
func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) { |
||||
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} |
||||
conn.ourKey = key |
||||
_, err = conn.Handshake(conn.ourKey) |
||||
if err != nil { |
||||
conn.Close() |
||||
return nil, err |
||||
} |
||||
conn.caps = []p2p.Cap{ |
||||
{Name: "eth", Version: 67}, |
||||
{Name: "eth", Version: 68}, |
||||
} |
||||
conn.ourHighestProtoVersion = 68 |
||||
return &conn, nil |
||||
} |
||||
|
||||
// dialSnap creates a connection with snap/1 capability.
|
||||
func (s *Suite) dialSnap() (*Conn, error) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) |
||||
conn.ourHighestSnapProtoVersion = 1 |
||||
return conn, nil |
||||
} |
||||
|
||||
// Conn represents an individual connection with a peer
|
||||
type Conn struct { |
||||
*rlpx.Conn |
||||
ourKey *ecdsa.PrivateKey |
||||
negotiatedProtoVersion uint |
||||
negotiatedSnapProtoVersion uint |
||||
ourHighestProtoVersion uint |
||||
ourHighestSnapProtoVersion uint |
||||
caps []p2p.Cap |
||||
} |
||||
|
||||
// Read reads a packet from the connection.
|
||||
func (c *Conn) Read() (uint64, []byte, error) { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
code, data, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return 0, nil, err |
||||
} |
||||
return code, data, nil |
||||
} |
||||
|
||||
// ReadMsg attempts to read a devp2p message with a specific code.
|
||||
func (c *Conn) ReadMsg(proto Proto, code uint64, msg any) error { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
for { |
||||
got, data, err := c.Read() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if protoOffset(proto)+code == got { |
||||
return rlp.DecodeBytes(data, msg) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Write writes a eth packet to the connection.
|
||||
func (c *Conn) Write(proto Proto, code uint64, msg any) error { |
||||
c.SetWriteDeadline(time.Now().Add(timeout)) |
||||
payload, err := rlp.EncodeToBytes(msg) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = c.Conn.Write(protoOffset(proto)+code, payload) |
||||
return err |
||||
} |
||||
|
||||
// ReadEth reads an Eth sub-protocol wire message.
|
||||
func (c *Conn) ReadEth() (any, error) { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
for { |
||||
code, data, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if code == pingMsg { |
||||
c.Write(baseProto, pongMsg, []byte{}) |
||||
continue |
||||
} |
||||
if getProto(code) != ethProto { |
||||
// Read until eth message.
|
||||
continue |
||||
} |
||||
code -= baseProtoLen |
||||
|
||||
var msg any |
||||
switch int(code) { |
||||
case eth.StatusMsg: |
||||
msg = new(eth.StatusPacket) |
||||
case eth.GetBlockHeadersMsg: |
||||
msg = new(eth.GetBlockHeadersPacket) |
||||
case eth.BlockHeadersMsg: |
||||
msg = new(eth.BlockHeadersPacket) |
||||
case eth.GetBlockBodiesMsg: |
||||
msg = new(eth.GetBlockBodiesPacket) |
||||
case eth.BlockBodiesMsg: |
||||
msg = new(eth.BlockBodiesPacket) |
||||
case eth.NewBlockMsg: |
||||
msg = new(eth.NewBlockPacket) |
||||
case eth.NewBlockHashesMsg: |
||||
msg = new(eth.NewBlockHashesPacket) |
||||
case eth.TransactionsMsg: |
||||
msg = new(eth.TransactionsPacket) |
||||
case eth.NewPooledTransactionHashesMsg: |
||||
msg = new(eth.NewPooledTransactionHashesPacket68) |
||||
case eth.GetPooledTransactionsMsg: |
||||
msg = new(eth.GetPooledTransactionsPacket) |
||||
case eth.PooledTransactionsMsg: |
||||
msg = new(eth.PooledTransactionsPacket) |
||||
default: |
||||
panic(fmt.Sprintf("unhandled eth msg code %d", code)) |
||||
} |
||||
if err := rlp.DecodeBytes(data, msg); err != nil { |
||||
return nil, fmt.Errorf("unable to decode eth msg: %v", err) |
||||
} |
||||
return msg, nil |
||||
} |
||||
} |
||||
|
||||
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||
func (c *Conn) ReadSnap() (any, error) { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
for { |
||||
code, data, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if getProto(code) != snapProto { |
||||
// Read until snap message.
|
||||
continue |
||||
} |
||||
code -= baseProtoLen + ethProtoLen |
||||
|
||||
var msg any |
||||
switch int(code) { |
||||
case snap.GetAccountRangeMsg: |
||||
msg = new(snap.GetAccountRangePacket) |
||||
case snap.AccountRangeMsg: |
||||
msg = new(snap.AccountRangePacket) |
||||
case snap.GetStorageRangesMsg: |
||||
msg = new(snap.GetStorageRangesPacket) |
||||
case snap.StorageRangesMsg: |
||||
msg = new(snap.StorageRangesPacket) |
||||
case snap.GetByteCodesMsg: |
||||
msg = new(snap.GetByteCodesPacket) |
||||
case snap.ByteCodesMsg: |
||||
msg = new(snap.ByteCodesPacket) |
||||
case snap.GetTrieNodesMsg: |
||||
msg = new(snap.GetTrieNodesPacket) |
||||
case snap.TrieNodesMsg: |
||||
msg = new(snap.TrieNodesPacket) |
||||
default: |
||||
panic(fmt.Errorf("unhandled snap code: %d", code)) |
||||
} |
||||
if err := rlp.DecodeBytes(data, msg); err != nil { |
||||
return nil, fmt.Errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return msg, nil |
||||
} |
||||
} |
||||
|
||||
// peer performs both the protocol handshake and the status message
|
||||
// exchange with the node in order to peer with it.
|
||||
func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error { |
||||
if err := c.handshake(); err != nil { |
||||
return fmt.Errorf("handshake failed: %v", err) |
||||
} |
||||
if err := c.statusExchange(chain, status); err != nil { |
||||
return fmt.Errorf("status exchange failed: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// handshake performs a protocol handshake with the node.
|
||||
func (c *Conn) handshake() error { |
||||
// Write hello to client.
|
||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] |
||||
ourHandshake := &protoHandshake{ |
||||
Version: 5, |
||||
Caps: c.caps, |
||||
ID: pub0, |
||||
} |
||||
if err := c.Write(baseProto, handshakeMsg, ourHandshake); err != nil { |
||||
return fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
// Read hello from client.
|
||||
code, data, err := c.Read() |
||||
if err != nil { |
||||
return fmt.Errorf("erroring reading handshake: %v", err) |
||||
} |
||||
switch code { |
||||
case handshakeMsg: |
||||
msg := new(protoHandshake) |
||||
if err := rlp.DecodeBytes(data, &msg); err != nil { |
||||
return fmt.Errorf("error decoding handshake msg: %v", err) |
||||
} |
||||
// Set snappy if version is at least 5.
|
||||
if msg.Version >= 5 { |
||||
c.SetSnappy(true) |
||||
} |
||||
c.negotiateEthProtocol(msg.Caps) |
||||
if c.negotiatedProtoVersion == 0 { |
||||
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) |
||||
} |
||||
// If we require snap, verify that it was negotiated.
|
||||
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { |
||||
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) |
||||
} |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("bad handshake: got msg code %d", code) |
||||
} |
||||
} |
||||
|
||||
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
||||
// advertised capability from peer.
|
||||
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { |
||||
var highestEthVersion uint |
||||
var highestSnapVersion uint |
||||
for _, capability := range caps { |
||||
switch capability.Name { |
||||
case "eth": |
||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { |
||||
highestEthVersion = capability.Version |
||||
} |
||||
case "snap": |
||||
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion { |
||||
highestSnapVersion = capability.Version |
||||
} |
||||
} |
||||
} |
||||
c.negotiatedProtoVersion = highestEthVersion |
||||
c.negotiatedSnapProtoVersion = highestSnapVersion |
||||
} |
||||
|
||||
// statusExchange performs a `Status` message exchange with the given node.
|
||||
func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error { |
||||
loop: |
||||
for { |
||||
code, data, err := c.Read() |
||||
if err != nil { |
||||
return fmt.Errorf("failed to read from connection: %w", err) |
||||
} |
||||
switch code { |
||||
case eth.StatusMsg + protoOffset(ethProto): |
||||
msg := new(eth.StatusPacket) |
||||
if err := rlp.DecodeBytes(data, &msg); err != nil { |
||||
return fmt.Errorf("error decoding status packet: %w", err) |
||||
} |
||||
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { |
||||
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", |
||||
want, chain.blocks[chain.Len()-1].NumberU64(), have) |
||||
} |
||||
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { |
||||
return fmt.Errorf("wrong TD in status: have %v want %v", have, want) |
||||
} |
||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { |
||||
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) |
||||
} |
||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { |
||||
return fmt.Errorf("wrong protocol version: have %v, want %v", have, want) |
||||
} |
||||
break loop |
||||
case discMsg: |
||||
var msg []p2p.DiscReason |
||||
if rlp.DecodeBytes(data, &msg); len(msg) == 0 { |
||||
return errors.New("invalid disconnect message") |
||||
} |
||||
return fmt.Errorf("disconnect received: %v", pretty.Sdump(msg)) |
||||
case pingMsg: |
||||
// TODO (renaynay): in the future, this should be an error
|
||||
// (PINGs should not be a response upon fresh connection)
|
||||
c.Write(baseProto, pongMsg, nil) |
||||
default: |
||||
return fmt.Errorf("bad status message: code %d", code) |
||||
} |
||||
} |
||||
// make sure eth protocol version is set for negotiation
|
||||
if c.negotiatedProtoVersion == 0 { |
||||
return errors.New("eth protocol version must be set in Conn") |
||||
} |
||||
if status == nil { |
||||
// default status message
|
||||
status = ð.StatusPacket{ |
||||
ProtocolVersion: uint32(c.negotiatedProtoVersion), |
||||
NetworkID: chain.config.ChainID.Uint64(), |
||||
TD: chain.TD(), |
||||
Head: chain.blocks[chain.Len()-1].Hash(), |
||||
Genesis: chain.blocks[0].Hash(), |
||||
ForkID: chain.ForkID(), |
||||
} |
||||
} |
||||
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil { |
||||
return fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,69 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"os" |
||||
"path" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/golang-jwt/jwt/v4" |
||||
) |
||||
|
||||
// EngineClient is a wrapper around engine-related data.
|
||||
type EngineClient struct { |
||||
url string |
||||
jwt [32]byte |
||||
headfcu []byte |
||||
} |
||||
|
||||
// NewEngineClient creates a new engine client.
|
||||
func NewEngineClient(dir, url, jwt string) (*EngineClient, error) { |
||||
headfcu, err := os.ReadFile(path.Join(dir, "headfcu.json")) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to read headfcu: %w", err) |
||||
} |
||||
return &EngineClient{url, common.HexToHash(jwt), headfcu}, nil |
||||
} |
||||
|
||||
// token returns the jwt claim token for authorization.
|
||||
func (ec *EngineClient) token() string { |
||||
claims := jwt.RegisteredClaims{IssuedAt: jwt.NewNumericDate(time.Now())} |
||||
token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(ec.jwt[:]) |
||||
return token |
||||
} |
||||
|
||||
// sendForkchoiceUpdated sends an fcu for the head of the generated chain.
|
||||
func (ec *EngineClient) sendForkchoiceUpdated() error { |
||||
var ( |
||||
req, _ = http.NewRequest(http.MethodPost, ec.url, io.NopCloser(bytes.NewReader(ec.headfcu))) |
||||
header = make(http.Header) |
||||
) |
||||
// Set header
|
||||
header.Set("accept", "application/json") |
||||
header.Set("content-type", "application/json") |
||||
header.Set("Authorization", fmt.Sprintf("Bearer %v", ec.token())) |
||||
req.Header = header |
||||
|
||||
_, err := new(http.Client).Do(req) |
||||
return err |
||||
} |
@ -1,650 +0,0 @@ |
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"reflect" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/internal/utesting" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/p2p/rlpx" |
||||
) |
||||
|
||||
var ( |
||||
pretty = spew.ConfigState{ |
||||
Indent: " ", |
||||
DisableCapacities: true, |
||||
DisablePointerAddresses: true, |
||||
SortKeys: true, |
||||
} |
||||
timeout = 20 * time.Second |
||||
) |
||||
|
||||
// dial attempts to dial the given node and perform a handshake,
|
||||
// returning the created Conn if successful.
|
||||
func (s *Suite) dial() (*Conn, error) { |
||||
// dial
|
||||
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} |
||||
// do encHandshake
|
||||
conn.ourKey, _ = crypto.GenerateKey() |
||||
_, err = conn.Handshake(conn.ourKey) |
||||
if err != nil { |
||||
conn.Close() |
||||
return nil, err |
||||
} |
||||
// set default p2p capabilities
|
||||
conn.caps = []p2p.Cap{ |
||||
{Name: "eth", Version: 67}, |
||||
{Name: "eth", Version: 68}, |
||||
} |
||||
conn.ourHighestProtoVersion = 68 |
||||
return &conn, nil |
||||
} |
||||
|
||||
// dialSnap creates a connection with snap/1 capability.
|
||||
func (s *Suite) dialSnap() (*Conn, error) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) |
||||
conn.ourHighestSnapProtoVersion = 1 |
||||
return conn, nil |
||||
} |
||||
|
||||
// peer performs both the protocol handshake and the status message
|
||||
// exchange with the node in order to peer with it.
|
||||
func (c *Conn) peer(chain *Chain, status *Status) error { |
||||
if err := c.handshake(); err != nil { |
||||
return fmt.Errorf("handshake failed: %v", err) |
||||
} |
||||
if _, err := c.statusExchange(chain, status); err != nil { |
||||
return fmt.Errorf("status exchange failed: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// handshake performs a protocol handshake with the node.
|
||||
func (c *Conn) handshake() error { |
||||
defer c.SetDeadline(time.Time{}) |
||||
c.SetDeadline(time.Now().Add(10 * time.Second)) |
||||
// write hello to client
|
||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] |
||||
ourHandshake := &Hello{ |
||||
Version: 5, |
||||
Caps: c.caps, |
||||
ID: pub0, |
||||
} |
||||
if err := c.Write(ourHandshake); err != nil { |
||||
return fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
// read hello 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.negotiatedProtoVersion == 0 { |
||||
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) |
||||
} |
||||
// If we require snap, verify that it was negotiated
|
||||
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { |
||||
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) |
||||
} |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("bad handshake: %#v", msg) |
||||
} |
||||
} |
||||
|
||||
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
||||
// advertised capability from peer.
|
||||
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { |
||||
var highestEthVersion uint |
||||
var highestSnapVersion uint |
||||
for _, capability := range caps { |
||||
switch capability.Name { |
||||
case "eth": |
||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { |
||||
highestEthVersion = capability.Version |
||||
} |
||||
case "snap": |
||||
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion { |
||||
highestSnapVersion = capability.Version |
||||
} |
||||
} |
||||
} |
||||
c.negotiatedProtoVersion = highestEthVersion |
||||
c.negotiatedSnapProtoVersion = highestSnapVersion |
||||
} |
||||
|
||||
// statusExchange performs a `Status` message exchange with the given node.
|
||||
func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) { |
||||
defer c.SetDeadline(time.Time{}) |
||||
c.SetDeadline(time.Now().Add(20 * time.Second)) |
||||
|
||||
// read status message from client
|
||||
var message Message |
||||
loop: |
||||
for { |
||||
switch msg := c.Read().(type) { |
||||
case *Status: |
||||
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { |
||||
return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", |
||||
want, chain.blocks[chain.Len()-1].NumberU64(), have) |
||||
} |
||||
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { |
||||
return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want) |
||||
} |
||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { |
||||
return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) |
||||
} |
||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { |
||||
return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want) |
||||
} |
||||
message = msg |
||||
break loop |
||||
case *Disconnect: |
||||
return nil, fmt.Errorf("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: |
||||
return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
// make sure eth protocol version is set for negotiation
|
||||
if c.negotiatedProtoVersion == 0 { |
||||
return nil, errors.New("eth protocol version must be set in Conn") |
||||
} |
||||
if status == nil { |
||||
// default status message
|
||||
status = &Status{ |
||||
ProtocolVersion: uint32(c.negotiatedProtoVersion), |
||||
NetworkID: chain.chainConfig.ChainID.Uint64(), |
||||
TD: chain.TD(), |
||||
Head: chain.blocks[chain.Len()-1].Hash(), |
||||
Genesis: chain.blocks[0].Hash(), |
||||
ForkID: chain.ForkID(), |
||||
} |
||||
} |
||||
if err := c.Write(status); err != nil { |
||||
return nil, fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
return message, nil |
||||
} |
||||
|
||||
// createSendAndRecvConns creates two connections, one for sending messages to the
|
||||
// node, and one for receiving messages from the node.
|
||||
func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) { |
||||
sendConn, err := s.dial() |
||||
if err != nil { |
||||
return nil, nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
recvConn, err := s.dial() |
||||
if err != nil { |
||||
sendConn.Close() |
||||
return nil, nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
return sendConn, recvConn, nil |
||||
} |
||||
|
||||
// readAndServe serves GetBlockHeaders requests while waiting
|
||||
// on another message from the node.
|
||||
func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message { |
||||
start := time.Now() |
||||
for time.Since(start) < timeout { |
||||
c.SetReadDeadline(time.Now().Add(10 * time.Second)) |
||||
|
||||
msg := c.Read() |
||||
switch msg := msg.(type) { |
||||
case *Ping: |
||||
c.Write(&Pong{}) |
||||
case *GetBlockHeaders: |
||||
headers, err := chain.GetHeaders(msg) |
||||
if err != nil { |
||||
return errorf("could not get headers for inbound header request: %v", err) |
||||
} |
||||
resp := &BlockHeaders{ |
||||
RequestId: msg.ReqID(), |
||||
BlockHeadersRequest: eth.BlockHeadersRequest(headers), |
||||
} |
||||
if err := c.Write(resp); err != nil { |
||||
return errorf("could not write to connection: %v", err) |
||||
} |
||||
default: |
||||
return msg |
||||
} |
||||
} |
||||
return errorf("no message received within %v", timeout) |
||||
} |
||||
|
||||
// headersRequest executes the given `GetBlockHeaders` request.
|
||||
func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint64) ([]*types.Header, error) { |
||||
defer c.SetReadDeadline(time.Time{}) |
||||
c.SetReadDeadline(time.Now().Add(20 * time.Second)) |
||||
|
||||
// write request
|
||||
request.RequestId = reqID |
||||
if err := c.Write(request); err != nil { |
||||
return nil, fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
|
||||
// wait for response
|
||||
msg := c.waitForResponse(chain, timeout, request.RequestId) |
||||
resp, ok := msg.(*BlockHeaders) |
||||
if !ok { |
||||
return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg)) |
||||
} |
||||
headers := []*types.Header(resp.BlockHeadersRequest) |
||||
return headers, nil |
||||
} |
||||
|
||||
func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) { |
||||
defer c.SetReadDeadline(time.Time{}) |
||||
c.SetReadDeadline(time.Now().Add(5 * time.Second)) |
||||
if err := c.Write(msg); err != nil { |
||||
return nil, fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
return c.ReadSnap(id) |
||||
} |
||||
|
||||
// headersMatch returns whether the received headers match the given request
|
||||
func headersMatch(expected []*types.Header, headers []*types.Header) bool { |
||||
return reflect.DeepEqual(expected, headers) |
||||
} |
||||
|
||||
// waitForResponse reads from the connection until a response with the expected
|
||||
// request ID is received.
|
||||
func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { |
||||
for { |
||||
msg := c.readAndServe(chain, timeout) |
||||
if msg.ReqID() == requestID { |
||||
return msg |
||||
} |
||||
} |
||||
} |
||||
|
||||
// sendNextBlock broadcasts the next block in the chain and waits
|
||||
// for the node to propagate the block and import it into its chain.
|
||||
func (s *Suite) sendNextBlock() error { |
||||
// set up sending and receiving connections
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer sendConn.Close() |
||||
defer recvConn.Close() |
||||
if err = sendConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
if err = recvConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
// create new block announcement
|
||||
nextBlock := s.fullChain.blocks[s.chain.Len()] |
||||
blockAnnouncement := &NewBlock{ |
||||
Block: nextBlock, |
||||
TD: s.fullChain.TotalDifficultyAt(s.chain.Len()), |
||||
} |
||||
// send announcement and wait for node to request the header
|
||||
if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil { |
||||
return fmt.Errorf("failed to announce block: %v", err) |
||||
} |
||||
// wait for client to update its chain
|
||||
if err = s.waitForBlockImport(recvConn, nextBlock); err != nil { |
||||
return fmt.Errorf("failed to receive confirmation of block import: %v", err) |
||||
} |
||||
// update test suite chain
|
||||
s.chain.blocks = append(s.chain.blocks, nextBlock) |
||||
return nil |
||||
} |
||||
|
||||
// testAnnounce writes a block announcement to the node and waits for the node
|
||||
// to propagate it.
|
||||
func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error { |
||||
if err := sendConn.Write(blockAnnouncement); err != nil { |
||||
return fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
return s.waitAnnounce(receiveConn, blockAnnouncement) |
||||
} |
||||
|
||||
// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node.
|
||||
func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error { |
||||
for { |
||||
switch msg := conn.readAndServe(s.chain, timeout).(type) { |
||||
case *NewBlock: |
||||
if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) { |
||||
return fmt.Errorf("wrong header in block announcement: \nexpected %v "+ |
||||
"\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header()) |
||||
} |
||||
if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) { |
||||
return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD) |
||||
} |
||||
return nil |
||||
case *NewBlockHashes: |
||||
hashes := *msg |
||||
if blockAnnouncement.Block.Hash() != hashes[0].Hash { |
||||
return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash) |
||||
} |
||||
return nil |
||||
|
||||
// ignore tx announcements from previous tests
|
||||
case *NewPooledTransactionHashes66: |
||||
continue |
||||
case *NewPooledTransactionHashes: |
||||
continue |
||||
case *Transactions: |
||||
continue |
||||
|
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error { |
||||
defer conn.SetReadDeadline(time.Time{}) |
||||
conn.SetReadDeadline(time.Now().Add(20 * time.Second)) |
||||
// create request
|
||||
req := &GetBlockHeaders{ |
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ |
||||
Origin: eth.HashOrNumber{Hash: block.Hash()}, |
||||
Amount: 1, |
||||
}, |
||||
} |
||||
|
||||
// loop until BlockHeaders response contains desired block, confirming the
|
||||
// node imported the block
|
||||
for { |
||||
requestID := uint64(54) |
||||
headers, err := conn.headersRequest(req, s.chain, requestID) |
||||
if err != nil { |
||||
return fmt.Errorf("GetBlockHeader request failed: %v", err) |
||||
} |
||||
// if headers response is empty, node hasn't imported block yet, try again
|
||||
if len(headers) == 0 { |
||||
time.Sleep(100 * time.Millisecond) |
||||
continue |
||||
} |
||||
if !reflect.DeepEqual(block.Header(), headers[0]) { |
||||
return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0]) |
||||
} |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) oldAnnounce() error { |
||||
sendConn, receiveConn, err := s.createSendAndRecvConns() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer sendConn.Close() |
||||
defer receiveConn.Close() |
||||
if err := sendConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
if err := receiveConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
// create old block announcement
|
||||
oldBlockAnnounce := &NewBlock{ |
||||
Block: s.chain.blocks[len(s.chain.blocks)/2], |
||||
TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), |
||||
} |
||||
if err := sendConn.Write(oldBlockAnnounce); err != nil { |
||||
return fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
// wait to see if the announcement is propagated
|
||||
switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) { |
||||
case *NewBlock: |
||||
block := *msg |
||||
if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { |
||||
return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg)) |
||||
} |
||||
case *NewBlockHashes: |
||||
hashes := *msg |
||||
for _, hash := range hashes { |
||||
if hash.Hash == oldBlockAnnounce.Block.Hash() { |
||||
return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
case *Error: |
||||
errMsg := *msg |
||||
// check to make sure error is timeout (propagation didn't come through == test successful)
|
||||
if !strings.Contains(errMsg.String(), "timeout") { |
||||
return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg)) |
||||
} |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *Suite) maliciousHandshakes(t *utesting.T) error { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
return fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
|
||||
// write hello to client
|
||||
pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] |
||||
handshakes := []*Hello{ |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: largeString(2), Version: 64}, |
||||
}, |
||||
ID: pub0, |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
ID: append(pub0, byte(0)), |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
ID: append(pub0, pub0...), |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
ID: largeBuffer(2), |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: largeString(2), Version: 64}, |
||||
}, |
||||
ID: largeBuffer(2), |
||||
}, |
||||
} |
||||
for i, handshake := range handshakes { |
||||
t.Logf("Testing malicious handshake %v\n", i) |
||||
if err := conn.Write(handshake); err != nil { |
||||
return fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
// check that the peer disconnected
|
||||
for i := 0; i < 2; i++ { |
||||
switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) { |
||||
case *Disconnect: |
||||
case *Error: |
||||
case *Hello: |
||||
// Discard one hello as Hello's are sent concurrently
|
||||
continue |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
// dial for the next round
|
||||
conn, err = s.dial() |
||||
if err != nil { |
||||
return fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *Suite) maliciousStatus(conn *Conn) error { |
||||
if err := conn.handshake(); err != nil { |
||||
return fmt.Errorf("handshake failed: %v", err) |
||||
} |
||||
status := &Status{ |
||||
ProtocolVersion: uint32(conn.negotiatedProtoVersion), |
||||
NetworkID: s.chain.chainConfig.ChainID.Uint64(), |
||||
TD: largeNumber(2), |
||||
Head: s.chain.blocks[s.chain.Len()-1].Hash(), |
||||
Genesis: s.chain.blocks[0].Hash(), |
||||
ForkID: s.chain.ForkID(), |
||||
} |
||||
|
||||
// get status
|
||||
msg, err := conn.statusExchange(s.chain, status) |
||||
if err != nil { |
||||
return fmt.Errorf("status exchange failed: %v", err) |
||||
} |
||||
switch msg := msg.(type) { |
||||
case *Status: |
||||
default: |
||||
return fmt.Errorf("expected status, got: %#v ", msg) |
||||
} |
||||
|
||||
// wait for disconnect
|
||||
switch msg := conn.readAndServe(s.chain, timeout).(type) { |
||||
case *Disconnect: |
||||
return nil |
||||
case *Error: |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) hashAnnounce() error { |
||||
// create connections
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns() |
||||
if err != nil { |
||||
return fmt.Errorf("failed to create connections: %v", err) |
||||
} |
||||
defer sendConn.Close() |
||||
defer recvConn.Close() |
||||
if err := sendConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
if err := recvConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
|
||||
// create NewBlockHashes announcement
|
||||
type anno struct { |
||||
Hash common.Hash // Hash of one particular block being announced
|
||||
Number uint64 // Number of one particular block being announced
|
||||
} |
||||
nextBlock := s.fullChain.blocks[s.chain.Len()] |
||||
announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()} |
||||
newBlockHash := &NewBlockHashes{announcement} |
||||
if err := sendConn.Write(newBlockHash); err != nil { |
||||
return fmt.Errorf("failed to write to connection: %v", err) |
||||
} |
||||
|
||||
// Announcement sent, now wait for a header request
|
||||
msg := sendConn.Read() |
||||
blockHeaderReq, ok := msg.(*GetBlockHeaders) |
||||
if !ok { |
||||
return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
if blockHeaderReq.Amount != 1 { |
||||
return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) |
||||
} |
||||
if blockHeaderReq.Origin.Hash != announcement.Hash { |
||||
return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v", |
||||
pretty.Sdump(announcement), |
||||
pretty.Sdump(blockHeaderReq)) |
||||
} |
||||
err = sendConn.Write(&BlockHeaders{ |
||||
RequestId: blockHeaderReq.ReqID(), |
||||
BlockHeadersRequest: eth.BlockHeadersRequest{nextBlock.Header()}, |
||||
}) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to write to connection: %v", err) |
||||
} |
||||
|
||||
// wait for block announcement
|
||||
msg = recvConn.readAndServe(s.chain, timeout) |
||||
switch msg := msg.(type) { |
||||
case *NewBlockHashes: |
||||
hashes := *msg |
||||
if len(hashes) != 1 { |
||||
return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes)) |
||||
} |
||||
if nextBlock.Hash() != hashes[0].Hash { |
||||
return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(), |
||||
hashes[0].Hash) |
||||
} |
||||
|
||||
case *NewBlock: |
||||
// node should only propagate NewBlock without having requested the body if the body is empty
|
||||
nextBlockBody := nextBlock.Body() |
||||
if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 { |
||||
return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg)) |
||||
} |
||||
if msg.Block.Hash() != nextBlock.Hash() { |
||||
return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v", |
||||
nextBlock.Hash(), msg.Block.Hash()) |
||||
} |
||||
// check to make sure header matches header that was sent to the node
|
||||
if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) { |
||||
return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header()) |
||||
} |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
// confirm node imported block
|
||||
if err := s.waitForBlockImport(recvConn, nextBlock); err != nil { |
||||
return fmt.Errorf("error waiting for node to import new block: %v", err) |
||||
} |
||||
// update the chain
|
||||
s.chain.blocks = append(s.chain.blocks, nextBlock) |
||||
return nil |
||||
} |
@ -1,80 +0,0 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/common/hexutil" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// largeNumber returns a very large big.Int.
|
||||
func largeNumber(megabytes int) *big.Int { |
||||
buf := make([]byte, megabytes*1024*1024) |
||||
rand.Read(buf) |
||||
bigint := new(big.Int) |
||||
bigint.SetBytes(buf) |
||||
return bigint |
||||
} |
||||
|
||||
// largeBuffer returns a very large buffer.
|
||||
func largeBuffer(megabytes int) []byte { |
||||
buf := make([]byte, megabytes*1024*1024) |
||||
rand.Read(buf) |
||||
return buf |
||||
} |
||||
|
||||
// largeString returns a very large string.
|
||||
func largeString(megabytes int) string { |
||||
buf := make([]byte, megabytes*1024*1024) |
||||
rand.Read(buf) |
||||
return hexutil.Encode(buf) |
||||
} |
||||
|
||||
func largeBlock() *types.Block { |
||||
return types.NewBlockWithHeader(largeHeader()) |
||||
} |
||||
|
||||
// Returns a random hash
|
||||
func randHash() common.Hash { |
||||
var h common.Hash |
||||
rand.Read(h[:]) |
||||
return h |
||||
} |
||||
|
||||
func largeHeader() *types.Header { |
||||
return &types.Header{ |
||||
MixDigest: randHash(), |
||||
ReceiptHash: randHash(), |
||||
TxHash: randHash(), |
||||
Nonce: types.BlockNonce{}, |
||||
Extra: []byte{}, |
||||
Bloom: types.Bloom{}, |
||||
GasUsed: 0, |
||||
Coinbase: common.Address{}, |
||||
GasLimit: 0, |
||||
UncleHash: types.EmptyUncleHash, |
||||
Time: 1337, |
||||
ParentHash: randHash(), |
||||
Root: randHash(), |
||||
Number: largeNumber(2), |
||||
Difficulty: largeNumber(2), |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
#!/bin/sh |
||||
|
||||
hivechain generate \ |
||||
--fork-interval 6 \ |
||||
--tx-interval 1 \ |
||||
--length 500 \ |
||||
--outdir testdata \ |
||||
--lastfork cancun \ |
||||
--outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv |
@ -0,0 +1,87 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
// Unexported devp2p message codes from p2p/peer.go.
|
||||
const ( |
||||
handshakeMsg = 0x00 |
||||
discMsg = 0x01 |
||||
pingMsg = 0x02 |
||||
pongMsg = 0x03 |
||||
) |
||||
|
||||
// Unexported devp2p protocol lengths from p2p package.
|
||||
const ( |
||||
baseProtoLen = 16 |
||||
ethProtoLen = 17 |
||||
snapProtoLen = 8 |
||||
) |
||||
|
||||
// Unexported handshake structure from p2p/peer.go.
|
||||
type protoHandshake struct { |
||||
Version uint64 |
||||
Name string |
||||
Caps []p2p.Cap |
||||
ListenPort uint64 |
||||
ID []byte |
||||
Rest []rlp.RawValue `rlp:"tail"` |
||||
} |
||||
|
||||
type Hello = protoHandshake |
||||
|
||||
// Proto is an enum representing devp2p protocol types.
|
||||
type Proto int |
||||
|
||||
const ( |
||||
baseProto Proto = iota |
||||
ethProto |
||||
snapProto |
||||
) |
||||
|
||||
// getProto returns the protocol a certain message code is associated with
|
||||
// (assuming the negotiated capabilities are exactly {eth,snap})
|
||||
func getProto(code uint64) Proto { |
||||
switch { |
||||
case code < baseProtoLen: |
||||
return baseProto |
||||
case code < baseProtoLen+ethProtoLen: |
||||
return ethProto |
||||
case code < baseProtoLen+ethProtoLen+snapProtoLen: |
||||
return snapProto |
||||
default: |
||||
panic("unhandled msg code beyond last protocol") |
||||
} |
||||
} |
||||
|
||||
// protoOffset will return the offset at which the specified protocol's messages
|
||||
// begin.
|
||||
func protoOffset(proto Proto) uint64 { |
||||
switch proto { |
||||
case baseProto: |
||||
return 0 |
||||
case ethProto: |
||||
return baseProtoLen |
||||
case snapProto: |
||||
return baseProtoLen + ethProtoLen |
||||
default: |
||||
panic("unhandled protocol") |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@ |
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import "github.com/ethereum/go-ethereum/eth/protocols/snap" |
||||
|
||||
// GetAccountRange represents an account range query.
|
||||
type GetAccountRange snap.GetAccountRangePacket |
||||
|
||||
func (msg GetAccountRange) Code() int { return 33 } |
||||
func (msg GetAccountRange) ReqID() uint64 { return msg.ID } |
||||
|
||||
type AccountRange snap.AccountRangePacket |
||||
|
||||
func (msg AccountRange) Code() int { return 34 } |
||||
func (msg AccountRange) ReqID() uint64 { return msg.ID } |
||||
|
||||
type GetStorageRanges snap.GetStorageRangesPacket |
||||
|
||||
func (msg GetStorageRanges) Code() int { return 35 } |
||||
func (msg GetStorageRanges) ReqID() uint64 { return msg.ID } |
||||
|
||||
type StorageRanges snap.StorageRangesPacket |
||||
|
||||
func (msg StorageRanges) Code() int { return 36 } |
||||
func (msg StorageRanges) ReqID() uint64 { return msg.ID } |
||||
|
||||
type GetByteCodes snap.GetByteCodesPacket |
||||
|
||||
func (msg GetByteCodes) Code() int { return 37 } |
||||
func (msg GetByteCodes) ReqID() uint64 { return msg.ID } |
||||
|
||||
type ByteCodes snap.ByteCodesPacket |
||||
|
||||
func (msg ByteCodes) Code() int { return 38 } |
||||
func (msg ByteCodes) ReqID() uint64 { return msg.ID } |
||||
|
||||
type GetTrieNodes snap.GetTrieNodesPacket |
||||
|
||||
func (msg GetTrieNodes) Code() int { return 39 } |
||||
func (msg GetTrieNodes) ReqID() uint64 { return msg.ID } |
||||
|
||||
type TrieNodes snap.TrieNodesPacket |
||||
|
||||
func (msg TrieNodes) Code() int { return 40 } |
||||
func (msg TrieNodes) ReqID() uint64 { return msg.ID } |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@ |
||||
{ |
||||
"0x0c2c51a0990aee1d73c1228de158688341557508": { |
||||
"key": "0xbfcd0e032489319f4e5ca03e643b2025db624be6cf99cbfed90c4502e3754850" |
||||
}, |
||||
"0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { |
||||
"key": "0x457075f6822ac29481154792f65c5f1ec335b4fea9ca20f3fea8fa1d78a12c68" |
||||
}, |
||||
"0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { |
||||
"key": "0x865898edcf43206d138c93f1bbd86311f4657b057658558888aa5ac4309626a6" |
||||
}, |
||||
"0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { |
||||
"key": "0xee7f7875d826d7443ccc5c174e38b2c436095018774248a8074ee92d8914dcdb" |
||||
}, |
||||
"0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": { |
||||
"key": "0x25e6ce8611cefb5cd338aeaa9292ed2139714668d123a4fb156cabb42051b5b7" |
||||
}, |
||||
"0x2d389075be5be9f2246ad654ce152cf05990b209": { |
||||
"key": "0x19168cd7767604b3d19b99dc3da1302b9ccb6ee9ad61660859e07acd4a2625dd" |
||||
}, |
||||
"0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { |
||||
"key": "0x71aa7d299c7607dabfc3d0e5213d612b5e4a97455b596c2f642daac43fa5eeaa" |
||||
}, |
||||
"0x4340ee1b812acb40a1eb561c019c327b243b92df": { |
||||
"key": "0x47f666f20e2175606355acec0ea1b37870c15e5797e962340da7ad7972a537e8" |
||||
}, |
||||
"0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { |
||||
"key": "0xa88293fefc623644969e2ce6919fb0dbd0fd64f640293b4bf7e1a81c97e7fc7f" |
||||
}, |
||||
"0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { |
||||
"key": "0x6e1e16a9c15641c73bf6e237f9293ab1d4e7c12b9adf83cfc94bcf969670f72d" |
||||
}, |
||||
"0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": { |
||||
"key": "0x41be4e00aac79f7ffbb3455053ec05e971645440d594c047cdcc56a3c7458bd6" |
||||
}, |
||||
"0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": { |
||||
"key": "0xc825f31cd8792851e33a290b3d749e553983111fc1f36dfbbdb45f101973f6a9" |
||||
}, |
||||
"0x717f8aa2b982bee0e29f573d31df288663e1ce16": { |
||||
"key": "0x8d0faa04ae0f9bc3cd4c890aa025d5f40916f4729538b19471c0beefe11d9e19" |
||||
}, |
||||
"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { |
||||
"key": "0x4552dbe6ca4699322b5d923d0c9bcdd24644f5db8bf89a085b67c6c49b8a1b91" |
||||
}, |
||||
"0x83c7e323d189f18725ac510004fdc2941f8c4a78": { |
||||
"key": "0x34391cbbf06956bb506f45ec179cdd84df526aa364e27bbde65db9c15d866d00" |
||||
}, |
||||
"0x84e75c28348fb86acea1a93a39426d7d60f4cc46": { |
||||
"key": "0xf6a8f1603b8368f3ca373292b7310c53bec7b508aecacd442554ebc1c5d0c856" |
||||
}, |
||||
"0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": { |
||||
"key": "0x8d56bcbcf2c1b7109e1396a28d7a0234e33544ade74ea32c460ce4a443b239b1" |
||||
}, |
||||
"0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": { |
||||
"key": "0xfc39d1c9ddbba176d806ebb42d7460189fe56ca163ad3eb6143bfc6beb6f6f72" |
||||
}, |
||||
"0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { |
||||
"key": "0x9ee3fd550664b246ad7cdba07162dd25530a3b1d51476dd1d85bbc29f0592684" |
||||
}, |
||||
"0xeda8645ba6948855e3b3cd596bbb07596d59c603": { |
||||
"key": "0x14cdde09d1640eb8c3cda063891b0453073f57719583381ff78811efa6d4199f" |
||||
} |
||||
} |
Binary file not shown.
@ -0,0 +1,20 @@ |
||||
{ |
||||
"HIVE_CANCUN_TIMESTAMP": "840", |
||||
"HIVE_CHAIN_ID": "3503995874084926", |
||||
"HIVE_FORK_ARROW_GLACIER": "60", |
||||
"HIVE_FORK_BERLIN": "48", |
||||
"HIVE_FORK_BYZANTIUM": "18", |
||||
"HIVE_FORK_CONSTANTINOPLE": "24", |
||||
"HIVE_FORK_GRAY_GLACIER": "66", |
||||
"HIVE_FORK_HOMESTEAD": "0", |
||||
"HIVE_FORK_ISTANBUL": "36", |
||||
"HIVE_FORK_LONDON": "54", |
||||
"HIVE_FORK_MUIR_GLACIER": "42", |
||||
"HIVE_FORK_PETERSBURG": "30", |
||||
"HIVE_FORK_SPURIOUS": "12", |
||||
"HIVE_FORK_TANGERINE": "6", |
||||
"HIVE_MERGE_BLOCK_ID": "72", |
||||
"HIVE_NETWORK_ID": "3503995874084926", |
||||
"HIVE_SHANGHAI_TIMESTAMP": "780", |
||||
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784" |
||||
} |
@ -1,27 +1,112 @@ |
||||
{ |
||||
"config": { |
||||
"chainId": 19763, |
||||
"homesteadBlock": 0, |
||||
"eip150Block": 0, |
||||
"eip155Block": 0, |
||||
"eip158Block": 0, |
||||
"byzantiumBlock": 0, |
||||
"terminalTotalDifficultyPassed": true, |
||||
"ethash": {} |
||||
}, |
||||
"nonce": "0xdeadbeefdeadbeef", |
||||
"timestamp": "0x0", |
||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"gasLimit": "0x80000000", |
||||
"difficulty": "0x20000", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"coinbase": "0x0000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"71562b71999873db5b286df957af199ec94617f7": { |
||||
"balance": "0xffffffffffffffffffffffffff" |
||||
} |
||||
}, |
||||
"number": "0x0", |
||||
"gasUsed": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
"config": { |
||||
"chainId": 3503995874084926, |
||||
"homesteadBlock": 0, |
||||
"eip150Block": 6, |
||||
"eip155Block": 12, |
||||
"eip158Block": 12, |
||||
"byzantiumBlock": 18, |
||||
"constantinopleBlock": 24, |
||||
"petersburgBlock": 30, |
||||
"istanbulBlock": 36, |
||||
"muirGlacierBlock": 42, |
||||
"berlinBlock": 48, |
||||
"londonBlock": 54, |
||||
"arrowGlacierBlock": 60, |
||||
"grayGlacierBlock": 66, |
||||
"mergeNetsplitBlock": 72, |
||||
"shanghaiTime": 780, |
||||
"cancunTime": 840, |
||||
"terminalTotalDifficulty": 9454784, |
||||
"terminalTotalDifficultyPassed": true, |
||||
"ethash": {} |
||||
}, |
||||
"nonce": "0x0", |
||||
"timestamp": "0x0", |
||||
"extraData": "0x68697665636861696e", |
||||
"gasLimit": "0x23f3e20", |
||||
"difficulty": "0x20000", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"coinbase": "0x0000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"000f3df6d732807ef1319fb7b8bb8522d0beac02": { |
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", |
||||
"balance": "0x2a" |
||||
}, |
||||
"0c2c51a0990aee1d73c1228de158688341557508": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"2d389075be5be9f2246ad654ce152cf05990b209": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"4340ee1b812acb40a1eb561c019c327b243b92df": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"717f8aa2b982bee0e29f573d31df288663e1ce16": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"83c7e323d189f18725ac510004fdc2941f8c4a78": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"84e75c28348fb86acea1a93a39426d7d60f4cc46": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"8bebc8ba651aee624937e7d897853ac30c95a067": { |
||||
"storage": { |
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", |
||||
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002", |
||||
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003" |
||||
}, |
||||
"balance": "0x1", |
||||
"nonce": "0x1" |
||||
}, |
||||
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"eda8645ba6948855e3b3cd596bbb07596d59c603": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
} |
||||
}, |
||||
"number": "0x0", |
||||
"gasUsed": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"baseFeePerGas": null, |
||||
"excessBlobGas": null, |
||||
"blobGasUsed": null |
||||
} |
Binary file not shown.
@ -0,0 +1,23 @@ |
||||
{ |
||||
"parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39", |
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"miner": "0x0000000000000000000000000000000000000000", |
||||
"stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94", |
||||
"transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48", |
||||
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"difficulty": "0x0", |
||||
"number": "0x1f4", |
||||
"gasLimit": "0x47e7c40", |
||||
"gasUsed": "0x5208", |
||||
"timestamp": "0x1388", |
||||
"extraData": "0x", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"nonce": "0x0000000000000000", |
||||
"baseFeePerGas": "0x7", |
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"blobGasUsed": "0x0", |
||||
"excessBlobGas": "0x0", |
||||
"parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c", |
||||
"hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7" |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"id": "fcu500", |
||||
"method": "engine_forkchoiceUpdatedV3", |
||||
"params": [ |
||||
{ |
||||
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", |
||||
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", |
||||
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7" |
||||
}, |
||||
null |
||||
] |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,291 +0,0 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"errors" |
||||
"fmt" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/p2p/rlpx" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
type Message interface { |
||||
Code() int |
||||
ReqID() uint64 |
||||
} |
||||
|
||||
type Error struct { |
||||
err error |
||||
} |
||||
|
||||
func (e *Error) Unwrap() error { return e.err } |
||||
func (e *Error) Error() string { return e.err.Error() } |
||||
func (e *Error) String() string { return e.Error() } |
||||
|
||||
func (e *Error) Code() int { return -1 } |
||||
func (e *Error) ReqID() uint64 { return 0 } |
||||
|
||||
func errorf(format string, args ...interface{}) *Error { |
||||
return &Error{fmt.Errorf(format, args...)} |
||||
} |
||||
|
||||
// Hello is the RLP structure of the protocol handshake.
|
||||
type Hello struct { |
||||
Version uint64 |
||||
Name string |
||||
Caps []p2p.Cap |
||||
ListenPort uint64 |
||||
ID []byte // secp256k1 public key
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"` |
||||
} |
||||
|
||||
func (msg Hello) Code() int { return 0x00 } |
||||
func (msg Hello) ReqID() uint64 { return 0 } |
||||
|
||||
// Disconnect is the RLP structure for a disconnect message.
|
||||
type Disconnect struct { |
||||
Reason p2p.DiscReason |
||||
} |
||||
|
||||
func (msg Disconnect) Code() int { return 0x01 } |
||||
func (msg Disconnect) ReqID() uint64 { return 0 } |
||||
|
||||
type Ping struct{} |
||||
|
||||
func (msg Ping) Code() int { return 0x02 } |
||||
func (msg Ping) ReqID() uint64 { return 0 } |
||||
|
||||
type Pong struct{} |
||||
|
||||
func (msg Pong) Code() int { return 0x03 } |
||||
func (msg Pong) ReqID() uint64 { return 0 } |
||||
|
||||
// Status is the network packet for the status message for eth/64 and later.
|
||||
type Status eth.StatusPacket |
||||
|
||||
func (msg Status) Code() int { return 16 } |
||||
func (msg Status) ReqID() uint64 { return 0 } |
||||
|
||||
// NewBlockHashes is the network packet for the block announcements.
|
||||
type NewBlockHashes eth.NewBlockHashesPacket |
||||
|
||||
func (msg NewBlockHashes) Code() int { return 17 } |
||||
func (msg NewBlockHashes) ReqID() uint64 { return 0 } |
||||
|
||||
type Transactions eth.TransactionsPacket |
||||
|
||||
func (msg Transactions) Code() int { return 18 } |
||||
func (msg Transactions) ReqID() uint64 { return 18 } |
||||
|
||||
// GetBlockHeaders represents a block header query.
|
||||
type GetBlockHeaders eth.GetBlockHeadersPacket |
||||
|
||||
func (msg GetBlockHeaders) Code() int { return 19 } |
||||
func (msg GetBlockHeaders) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
type BlockHeaders eth.BlockHeadersPacket |
||||
|
||||
func (msg BlockHeaders) Code() int { return 20 } |
||||
func (msg BlockHeaders) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// GetBlockBodies represents a GetBlockBodies request
|
||||
type GetBlockBodies eth.GetBlockBodiesPacket |
||||
|
||||
func (msg GetBlockBodies) Code() int { return 21 } |
||||
func (msg GetBlockBodies) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// BlockBodies is the network packet for block content distribution.
|
||||
type BlockBodies eth.BlockBodiesPacket |
||||
|
||||
func (msg BlockBodies) Code() int { return 22 } |
||||
func (msg BlockBodies) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// NewBlock is the network packet for the block propagation message.
|
||||
type NewBlock eth.NewBlockPacket |
||||
|
||||
func (msg NewBlock) Code() int { return 23 } |
||||
func (msg NewBlock) ReqID() uint64 { return 0 } |
||||
|
||||
// NewPooledTransactionHashes66 is the network packet for the tx hash propagation message.
|
||||
type NewPooledTransactionHashes66 eth.NewPooledTransactionHashesPacket67 |
||||
|
||||
func (msg NewPooledTransactionHashes66) Code() int { return 24 } |
||||
func (msg NewPooledTransactionHashes66) ReqID() uint64 { return 0 } |
||||
|
||||
// NewPooledTransactionHashes is the network packet for the tx hash propagation message.
|
||||
type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket68 |
||||
|
||||
func (msg NewPooledTransactionHashes) Code() int { return 24 } |
||||
func (msg NewPooledTransactionHashes) ReqID() uint64 { return 0 } |
||||
|
||||
type GetPooledTransactions eth.GetPooledTransactionsPacket |
||||
|
||||
func (msg GetPooledTransactions) Code() int { return 25 } |
||||
func (msg GetPooledTransactions) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
type PooledTransactions eth.PooledTransactionsPacket |
||||
|
||||
func (msg PooledTransactions) Code() int { return 26 } |
||||
func (msg PooledTransactions) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// Conn represents an individual connection with a peer
|
||||
type Conn struct { |
||||
*rlpx.Conn |
||||
ourKey *ecdsa.PrivateKey |
||||
negotiatedProtoVersion uint |
||||
negotiatedSnapProtoVersion uint |
||||
ourHighestProtoVersion uint |
||||
ourHighestSnapProtoVersion uint |
||||
caps []p2p.Cap |
||||
} |
||||
|
||||
// Read reads an eth66 packet from the connection.
|
||||
func (c *Conn) Read() Message { |
||||
code, rawData, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return 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(): |
||||
ethMsg := new(eth.GetBlockHeadersPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*GetBlockHeaders)(ethMsg) |
||||
case (BlockHeaders{}).Code(): |
||||
ethMsg := new(eth.BlockHeadersPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*BlockHeaders)(ethMsg) |
||||
case (GetBlockBodies{}).Code(): |
||||
ethMsg := new(eth.GetBlockBodiesPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*GetBlockBodies)(ethMsg) |
||||
case (BlockBodies{}).Code(): |
||||
ethMsg := new(eth.BlockBodiesPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*BlockBodies)(ethMsg) |
||||
case (NewBlock{}).Code(): |
||||
msg = new(NewBlock) |
||||
case (NewBlockHashes{}).Code(): |
||||
msg = new(NewBlockHashes) |
||||
case (Transactions{}).Code(): |
||||
msg = new(Transactions) |
||||
case (NewPooledTransactionHashes66{}).Code(): |
||||
// Try decoding to eth68
|
||||
ethMsg := new(NewPooledTransactionHashes) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err == nil { |
||||
return ethMsg |
||||
} |
||||
msg = new(NewPooledTransactionHashes66) |
||||
case (GetPooledTransactions{}.Code()): |
||||
ethMsg := new(eth.GetPooledTransactionsPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*GetPooledTransactions)(ethMsg) |
||||
case (PooledTransactions{}.Code()): |
||||
ethMsg := new(eth.PooledTransactionsPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*PooledTransactions)(ethMsg) |
||||
default: |
||||
msg = errorf("invalid message code: %d", code) |
||||
} |
||||
|
||||
if msg != nil { |
||||
if err := rlp.DecodeBytes(rawData, msg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return msg |
||||
} |
||||
return errorf("invalid message: %s", string(rawData)) |
||||
} |
||||
|
||||
// Write writes a eth packet to the connection.
|
||||
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 |
||||
} |
||||
|
||||
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||
func (c *Conn) ReadSnap(id uint64) (Message, error) { |
||||
respId := id + 1 |
||||
start := time.Now() |
||||
for respId != id && time.Since(start) < timeout { |
||||
code, rawData, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("could not read from connection: %v", err) |
||||
} |
||||
var snpMsg interface{} |
||||
switch int(code) { |
||||
case (GetAccountRange{}).Code(): |
||||
snpMsg = new(GetAccountRange) |
||||
case (AccountRange{}).Code(): |
||||
snpMsg = new(AccountRange) |
||||
case (GetStorageRanges{}).Code(): |
||||
snpMsg = new(GetStorageRanges) |
||||
case (StorageRanges{}).Code(): |
||||
snpMsg = new(StorageRanges) |
||||
case (GetByteCodes{}).Code(): |
||||
snpMsg = new(GetByteCodes) |
||||
case (ByteCodes{}).Code(): |
||||
snpMsg = new(ByteCodes) |
||||
case (GetTrieNodes{}).Code(): |
||||
snpMsg = new(GetTrieNodes) |
||||
case (TrieNodes{}).Code(): |
||||
snpMsg = new(TrieNodes) |
||||
default: |
||||
//return nil, fmt.Errorf("invalid message code: %d", code)
|
||||
continue |
||||
} |
||||
if err := rlp.DecodeBytes(rawData, snpMsg); err != nil { |
||||
return nil, fmt.Errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return snpMsg.(Message), nil |
||||
} |
||||
return nil, errors.New("request timed out") |
||||
} |
Loading…
Reference in new issue