mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
cmd/devp2p: update ethtest suite for pos with new hivechain generator
This commit is contained in:
parent
0f74aad641
commit
8abcae516a
27 changed files with 21145 additions and 1862 deletions
|
|
@ -17,27 +17,81 @@
|
|||
package ethtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"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/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Chain is a lightweight blockchain-like store which can read a hivechain
|
||||
// created chain.
|
||||
type Chain struct {
|
||||
genesis core.Genesis
|
||||
blocks []*types.Block
|
||||
chainConfig *params.ChainConfig
|
||||
genesis core.Genesis
|
||||
blocks []*types.Block
|
||||
state map[common.Address]state.DumpAccount
|
||||
senders map[common.Address]*senderInfo
|
||||
config *params.ChainConfig
|
||||
}
|
||||
|
||||
// NewChain takes the given chain.rlp file, and decodes and returns
|
||||
// the blocks from the file.
|
||||
func NewChain(dir string) (*Chain, error) {
|
||||
gen, err := loadGenesis(path.Join(dir, "genesis.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gblock := gen.ToBlock()
|
||||
|
||||
blocks, err := blocksFromFile(path.Join(dir, "chain.rlp"), gblock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state, err := readState(path.Join(dir, "headstate.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts, err := readAccounts(path.Join(dir, "accounts.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Chain{
|
||||
genesis: gen,
|
||||
blocks: blocks,
|
||||
state: state,
|
||||
senders: accounts,
|
||||
config: gen.Config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// senderInfo is an account record as output in the "accounts.json" file from
|
||||
// hivechain.
|
||||
type senderInfo struct {
|
||||
Key *ecdsa.PrivateKey `json:"key"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
}
|
||||
|
||||
// Head returns the chain head.
|
||||
func (c *Chain) Head() *types.Block {
|
||||
return c.blocks[c.Len()-1]
|
||||
}
|
||||
|
||||
// Len returns the length of the chain.
|
||||
|
|
@ -45,6 +99,11 @@ func (c *Chain) Len() int {
|
|||
return len(c.blocks)
|
||||
}
|
||||
|
||||
// ForkID gets the fork id of the chain.
|
||||
func (c *Chain) ForkID() forkid.ID {
|
||||
return forkid.NewID(c.config, c.blocks[0], uint64(c.Len()), c.blocks[c.Len()-1].Time())
|
||||
}
|
||||
|
||||
// TD calculates the total difficulty of the chain at the
|
||||
// chain head.
|
||||
func (c *Chain) TD() *big.Int {
|
||||
|
|
@ -55,19 +114,12 @@ func (c *Chain) TD() *big.Int {
|
|||
return sum
|
||||
}
|
||||
|
||||
// TotalDifficultyAt calculates the total difficulty of the chain
|
||||
// at the given block height.
|
||||
func (c *Chain) TotalDifficultyAt(height int) *big.Int {
|
||||
sum := new(big.Int)
|
||||
if height >= c.Len() {
|
||||
return sum
|
||||
}
|
||||
for _, block := range c.blocks[:height+1] {
|
||||
sum.Add(sum, block.Difficulty())
|
||||
}
|
||||
return sum
|
||||
// GetBlock returns the block at the specified number.
|
||||
func (c *Chain) GetBlock(number int) *types.Block {
|
||||
return c.blocks[number]
|
||||
}
|
||||
|
||||
// RootAt returns the state root for the block at the given height.
|
||||
func (c *Chain) RootAt(height int) common.Hash {
|
||||
if height < c.Len() {
|
||||
return c.blocks[height].Root()
|
||||
|
|
@ -75,37 +127,56 @@ func (c *Chain) RootAt(height int) common.Hash {
|
|||
return common.Hash{}
|
||||
}
|
||||
|
||||
// ForkID gets the fork id of the chain.
|
||||
func (c *Chain) ForkID() forkid.ID {
|
||||
return forkid.NewID(c.chainConfig, c.blocks[0], uint64(c.Len()), c.blocks[0].Time())
|
||||
}
|
||||
|
||||
// Shorten returns a copy chain of a desired height from the imported
|
||||
func (c *Chain) Shorten(height int) *Chain {
|
||||
blocks := make([]*types.Block, height)
|
||||
copy(blocks, c.blocks[:height])
|
||||
|
||||
config := *c.chainConfig
|
||||
return &Chain{
|
||||
blocks: blocks,
|
||||
chainConfig: &config,
|
||||
// GetSender returns the address associated with account at the index in the
|
||||
// pre-funded accounts list.
|
||||
func (c *Chain) GetSender(idx int) (common.Address, uint64) {
|
||||
var accounts Addresses
|
||||
for addr := range c.senders {
|
||||
accounts = append(accounts, addr)
|
||||
}
|
||||
sort.Sort(accounts)
|
||||
addr := accounts[idx]
|
||||
return addr, c.senders[addr].Nonce
|
||||
}
|
||||
|
||||
// Head returns the chain head.
|
||||
func (c *Chain) Head() *types.Block {
|
||||
return c.blocks[c.Len()-1]
|
||||
// IncNonce increases the specified signing account's pending nonce.
|
||||
func (c *Chain) IncNonce(addr common.Address, amt uint64) {
|
||||
if _, ok := c.senders[addr]; !ok {
|
||||
panic("nonce increment for non-signer")
|
||||
}
|
||||
c.senders[addr].Nonce += amt
|
||||
}
|
||||
|
||||
func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
||||
// Balance returns the balance of an account at the head of the chain.
|
||||
func (c *Chain) Balance(addr common.Address) *big.Int {
|
||||
bal := new(big.Int)
|
||||
if acc, ok := c.state[addr]; ok {
|
||||
bal, _ = bal.SetString(acc.Balance, 10)
|
||||
}
|
||||
return bal
|
||||
}
|
||||
|
||||
// SignTx signs a transaction for the specified from account, so long as that
|
||||
// account was in the hivechain accounts dump.
|
||||
func (c *Chain) SignTx(from common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||
signer := types.LatestSigner(c.config)
|
||||
acc, ok := c.senders[from]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("account not available for signing: %s", from)
|
||||
}
|
||||
return types.SignTx(tx, signer, acc.Key)
|
||||
}
|
||||
|
||||
// GetHeaders returns the headers base on an ethGetPacketHeadersPacket.
|
||||
func (c *Chain) GetHeaders(req *eth.GetBlockHeadersPacket) ([]*types.Header, error) {
|
||||
if req.Amount < 1 {
|
||||
return nil, errors.New("no block headers requested")
|
||||
}
|
||||
|
||||
headers := make([]*types.Header, req.Amount)
|
||||
var blockNumber uint64
|
||||
|
||||
// range over blocks to check if our chain has the requested header
|
||||
var (
|
||||
headers = make([]*types.Header, req.Amount)
|
||||
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()
|
||||
|
|
@ -115,40 +186,30 @@ func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
|||
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
|
||||
// the blocks from the file.
|
||||
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
||||
gen, err := loadGenesis(genesis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gblock := gen.ToBlock()
|
||||
// Shorten returns a copy chain of a desired height from the imported
|
||||
func (c *Chain) Shorten(height int) *Chain {
|
||||
blocks := make([]*types.Block, height)
|
||||
copy(blocks, c.blocks[:height])
|
||||
|
||||
blocks, err := blocksFromFile(chainfile, gblock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
config := *c.config
|
||||
return &Chain{
|
||||
blocks: blocks,
|
||||
config: &config,
|
||||
}
|
||||
|
||||
c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func loadGenesis(genesisFile string) (core.Genesis, error) {
|
||||
|
|
@ -163,6 +224,22 @@ func loadGenesis(genesisFile string) (core.Genesis, error) {
|
|||
return gen, nil
|
||||
}
|
||||
|
||||
type Addresses []common.Address
|
||||
|
||||
func (a Addresses) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a Addresses) Less(i, j int) bool {
|
||||
return bytes.Compare(a[i][:], a[j][:]) < 0
|
||||
}
|
||||
|
||||
func (a Addresses) Swap(i, j int) {
|
||||
tmp := a[i]
|
||||
a[i] = a[j]
|
||||
a[j] = tmp
|
||||
}
|
||||
|
||||
func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) {
|
||||
// Load chain.rlp.
|
||||
fh, err := os.Open(chainfile)
|
||||
|
|
@ -193,3 +270,38 @@ func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, erro
|
|||
}
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
func readState(file string) (map[common.Address]state.DumpAccount, error) {
|
||||
f, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read state: %v", err)
|
||||
}
|
||||
var dump state.Dump
|
||||
if err := json.Unmarshal(f, &dump); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal state: %v", err)
|
||||
}
|
||||
return dump.Accounts, nil
|
||||
}
|
||||
|
||||
func readAccounts(file string) (map[common.Address]*senderInfo, error) {
|
||||
f, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read state: %v", err)
|
||||
}
|
||||
type account struct {
|
||||
Key hexutil.Bytes `json:"key"`
|
||||
}
|
||||
keys := make(map[common.Address]account)
|
||||
if err := json.Unmarshal(f, &keys); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal accounts: %v", err)
|
||||
}
|
||||
accounts := make(map[common.Address]*senderInfo)
|
||||
for addr, acc := range keys {
|
||||
pk, err := crypto.HexToECDSA(common.Bytes2Hex(acc.Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read private key for %s: %v", err, addr)
|
||||
}
|
||||
accounts[addr] = &senderInfo{Key: pk, Nonce: 0}
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,30 +123,26 @@ func TestEthProtocolNegotiation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestChain_GetHeaders tests whether the test suite can correctly
|
||||
// TestChainGetHeaders tests whether the test suite can correctly
|
||||
// respond to a GetBlockHeaders request from a node.
|
||||
func TestChain_GetHeaders(t *testing.T) {
|
||||
func TestChainGetHeaders(t *testing.T) {
|
||||
t.Parallel()
|
||||
chainFile, err := filepath.Abs("./testdata/chain.rlp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
genesisFile, err := filepath.Abs("./testdata/genesis.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
chain, err := loadChain(chainFile, genesisFile)
|
||||
dir, err := filepath.Abs("./testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chain, err := NewChain(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
req GetBlockHeaders
|
||||
req eth.GetBlockHeadersPacket
|
||||
expected []*types.Header
|
||||
}{
|
||||
{
|
||||
req: GetBlockHeaders{
|
||||
req: eth.GetBlockHeadersPacket{
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{Number: uint64(2)},
|
||||
Amount: uint64(5),
|
||||
|
|
@ -163,7 +159,7 @@ func TestChain_GetHeaders(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
req: GetBlockHeaders{
|
||||
req: eth.GetBlockHeadersPacket{
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)},
|
||||
Amount: uint64(3),
|
||||
|
|
@ -178,7 +174,7 @@ func TestChain_GetHeaders(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
req: GetBlockHeaders{
|
||||
req: eth.GetBlockHeadersPacket{
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
|
||||
Amount: uint64(1),
|
||||
|
|
|
|||
361
cmd/devp2p/internal/ethtest/conn.go
Normal file
361
cmd/devp2p/internal/ethtest/conn.go
Normal file
|
|
@ -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
|
||||
}
|
||||
69
cmd/devp2p/internal/ethtest/engine.go
Normal file
69
cmd/devp2p/internal/ethtest/engine.go
Normal file
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
87
cmd/devp2p/internal/ethtest/protocol.go
Normal file
87
cmd/devp2p/internal/ethtest/protocol.go
Normal file
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,13 @@ import (
|
|||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func (c *Conn) snapRequest(code uint64, msg any) (any, error) {
|
||||
if err := c.Write(snapProto, code, msg); err != nil {
|
||||
return nil, fmt.Errorf("could not write to connection: %v", err)
|
||||
}
|
||||
return c.ReadSnap()
|
||||
}
|
||||
|
||||
func (s *Suite) TestSnapStatus(t *utesting.T) {
|
||||
conn, err := s.dialSnap()
|
||||
if err != nil {
|
||||
|
|
@ -479,22 +486,20 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error {
|
|||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// write request
|
||||
req := &GetAccountRange{
|
||||
req := &snap.GetAccountRangePacket{
|
||||
ID: uint64(rand.Int63()),
|
||||
Root: tc.root,
|
||||
Origin: tc.origin,
|
||||
Limit: tc.limit,
|
||||
Bytes: tc.nBytes,
|
||||
}
|
||||
resp, err := conn.snapRequest(req, req.ID, s.chain)
|
||||
msg, err := conn.snapRequest(snap.GetAccountRangeMsg, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("account range request failed: %v", err)
|
||||
}
|
||||
var res *snap.AccountRangePacket
|
||||
if r, ok := resp.(*AccountRange); !ok {
|
||||
return fmt.Errorf("account range response wrong: %T %v", resp, resp)
|
||||
} else {
|
||||
res = (*snap.AccountRangePacket)(r)
|
||||
res, ok := msg.(*snap.AccountRangePacket)
|
||||
if !ok {
|
||||
return fmt.Errorf("account range response wrong: %T %v", msg, msg)
|
||||
}
|
||||
if exp, got := tc.expAccounts, len(res.Accounts); exp != got {
|
||||
return fmt.Errorf("expected %d accounts, got %d", exp, got)
|
||||
|
|
@ -550,7 +555,7 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error {
|
|||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// write request
|
||||
req := &GetStorageRanges{
|
||||
req := &snap.GetStorageRangesPacket{
|
||||
ID: uint64(rand.Int63()),
|
||||
Root: tc.root,
|
||||
Accounts: tc.accounts,
|
||||
|
|
@ -558,15 +563,13 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error {
|
|||
Limit: tc.limit,
|
||||
Bytes: tc.nBytes,
|
||||
}
|
||||
resp, err := conn.snapRequest(req, req.ID, s.chain)
|
||||
msg, err := conn.snapRequest(snap.GetStorageRangesMsg, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("account range request failed: %v", err)
|
||||
}
|
||||
var res *snap.StorageRangesPacket
|
||||
if r, ok := resp.(*StorageRanges); !ok {
|
||||
return fmt.Errorf("account range response wrong: %T %v", resp, resp)
|
||||
} else {
|
||||
res = (*snap.StorageRangesPacket)(r)
|
||||
res, ok := msg.(*snap.StorageRangesPacket)
|
||||
if !ok {
|
||||
return fmt.Errorf("account range response wrong: %T %v", msg, msg)
|
||||
}
|
||||
gotSlots := 0
|
||||
// Ensure the ranges are monotonically increasing
|
||||
|
|
@ -594,24 +597,22 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error {
|
|||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// write request
|
||||
req := &GetByteCodes{
|
||||
req := &snap.GetByteCodesPacket{
|
||||
ID: uint64(rand.Int63()),
|
||||
Hashes: tc.hashes,
|
||||
Bytes: tc.nBytes,
|
||||
}
|
||||
resp, err := conn.snapRequest(req, req.ID, s.chain)
|
||||
msg, err := conn.snapRequest(snap.GetByteCodesMsg, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getBytecodes request failed: %v", err)
|
||||
}
|
||||
var res *snap.ByteCodesPacket
|
||||
if r, ok := resp.(*ByteCodes); !ok {
|
||||
return fmt.Errorf("bytecodes response wrong: %T %v", resp, resp)
|
||||
} else {
|
||||
res = (*snap.ByteCodesPacket)(r)
|
||||
res, ok := msg.(*snap.ByteCodesPacket)
|
||||
if !ok {
|
||||
return fmt.Errorf("bytecodes response wrong: %T %v", msg, msg)
|
||||
}
|
||||
if exp, got := tc.expHashes, len(res.Codes); exp != got {
|
||||
for i, c := range res.Codes {
|
||||
fmt.Printf("%d. %#x\n", i, c)
|
||||
t.Logf("%d. %#x\n", i, c)
|
||||
}
|
||||
return fmt.Errorf("expected %d bytecodes, got %d", exp, got)
|
||||
}
|
||||
|
|
@ -655,24 +656,22 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error {
|
|||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// write request
|
||||
req := &GetTrieNodes{
|
||||
req := &snap.GetTrieNodesPacket{
|
||||
ID: uint64(rand.Int63()),
|
||||
Root: tc.root,
|
||||
Paths: tc.paths,
|
||||
Bytes: tc.nBytes,
|
||||
}
|
||||
resp, err := conn.snapRequest(req, req.ID, s.chain)
|
||||
msg, err := conn.snapRequest(snap.GetTrieNodesMsg, req)
|
||||
if err != nil {
|
||||
if tc.expReject {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("trienodes request failed: %v", err)
|
||||
}
|
||||
var res *snap.TrieNodesPacket
|
||||
if r, ok := resp.(*TrieNodes); !ok {
|
||||
return fmt.Errorf("trienodes response wrong: %T %v", resp, resp)
|
||||
} else {
|
||||
res = (*snap.TrieNodesPacket)(r)
|
||||
res, ok := msg.(*snap.TrieNodesPacket)
|
||||
if !ok {
|
||||
return fmt.Errorf("trienodes response wrong: %T %v", msg, msg)
|
||||
}
|
||||
|
||||
// Check the correctness
|
||||
|
|
@ -690,7 +689,7 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error {
|
|||
hasher.Write(trienode)
|
||||
hasher.Read(hash)
|
||||
if got, want := hash, tc.expHashes[i]; !bytes.Equal(got, want[:]) {
|
||||
fmt.Printf("hash %d wrong, got %#x, want %#x\n", i, got, want)
|
||||
t.Logf("hash %d wrong, got %#x, want %#x\n", i, got, want)
|
||||
err = fmt.Errorf("hash %d wrong, got %#x, want %#x", i, got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -17,35 +17,44 @@
|
|||
package ethtest
|
||||
|
||||
import (
|
||||
"time"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"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/enode"
|
||||
)
|
||||
|
||||
// Suite represents a structure used to test a node's conformance
|
||||
// to the eth protocol.
|
||||
type Suite struct {
|
||||
Dest *enode.Node
|
||||
|
||||
chain *Chain
|
||||
fullChain *Chain
|
||||
Dest *enode.Node
|
||||
chain *Chain
|
||||
engine *EngineClient
|
||||
}
|
||||
|
||||
// NewSuite creates and returns a new eth-test suite that can
|
||||
// be used to test the given node against the given blockchain
|
||||
// data.
|
||||
func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) {
|
||||
chain, err := loadChain(chainfile, genesisfile)
|
||||
func NewSuite(dest *enode.Node, chainDir, engineURL, jwt string) (*Suite, error) {
|
||||
chain, err := NewChain(chainDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engine, err := NewEngineClient(chainDir, engineURL, jwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Suite{
|
||||
Dest: dest,
|
||||
chain: chain.Shorten(1000),
|
||||
fullChain: chain,
|
||||
Dest: dest,
|
||||
chain: chain,
|
||||
engine: engine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -60,17 +69,12 @@ func (s *Suite) EthTests() []utesting.Test {
|
|||
{Name: "TestZeroRequestID", Fn: s.TestZeroRequestID},
|
||||
// get block bodies
|
||||
{Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies},
|
||||
// broadcast
|
||||
{Name: "TestBroadcast", Fn: s.TestBroadcast},
|
||||
{Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce},
|
||||
{Name: "TestOldAnnounce", Fn: s.TestOldAnnounce},
|
||||
{Name: "TestBlockHashAnnounce", Fn: s.TestBlockHashAnnounce},
|
||||
// malicious handshakes + status
|
||||
// // malicious handshakes + status
|
||||
{Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake},
|
||||
{Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus},
|
||||
// test transactions
|
||||
{Name: "TestTransaction", Fn: s.TestTransaction},
|
||||
{Name: "TestMaliciousTx", Fn: s.TestMaliciousTx},
|
||||
{Name: "TestInvalidTxs", Fn: s.TestInvalidTxs},
|
||||
{Name: "TestLargeTxRequest", Fn: s.TestLargeTxRequest},
|
||||
{Name: "TestNewPooledTxs", Fn: s.TestNewPooledTxs},
|
||||
}
|
||||
|
|
@ -86,8 +90,8 @@ func (s *Suite) SnapTests() []utesting.Test {
|
|||
}
|
||||
}
|
||||
|
||||
// TestStatus attempts to connect to the given node and exchange
|
||||
// a status message with it on the eth protocol.
|
||||
// TestStatus attempts to connect to the given node and exchange a status
|
||||
// message with it on the eth protocol.
|
||||
func (s *Suite) TestStatus(t *utesting.T) {
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
|
|
@ -99,8 +103,13 @@ func (s *Suite) TestStatus(t *utesting.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestGetBlockHeaders tests whether the given node can respond to
|
||||
// an eth `GetBlockHeaders` request and that the response is accurate.
|
||||
// headersMatch returns whether the received headers match the given request
|
||||
func headersMatch(expected []*types.Header, headers []*types.Header) bool {
|
||||
return reflect.DeepEqual(expected, headers)
|
||||
}
|
||||
|
||||
// TestGetBlockHeaders tests whether the given node can respond to an eth
|
||||
// `GetBlockHeaders` request and that the response is accurate.
|
||||
func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
|
|
@ -110,8 +119,9 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
|
|||
if err = conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// write request
|
||||
req := &GetBlockHeaders{
|
||||
// Send headers request.
|
||||
req := ð.GetBlockHeadersPacket{
|
||||
RequestId: 33,
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{Hash: s.chain.blocks[1].Hash()},
|
||||
Amount: 2,
|
||||
|
|
@ -119,25 +129,31 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
|
|||
Reverse: false,
|
||||
},
|
||||
}
|
||||
headers, err := conn.headersRequest(req, s.chain, 33)
|
||||
if err != nil {
|
||||
t.Fatalf("could not get block headers: %v", err)
|
||||
// Read headers response.
|
||||
if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req); err != nil {
|
||||
t.Fatalf("could not write to connection: %v", err)
|
||||
}
|
||||
// check for correct headers
|
||||
headers := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers); err != nil {
|
||||
t.Fatalf("error reading msg: %v", err)
|
||||
}
|
||||
if got, want := headers.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id")
|
||||
}
|
||||
// Check for correct headers.
|
||||
expected, err := s.chain.GetHeaders(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get headers for given request: %v", err)
|
||||
}
|
||||
if !headersMatch(expected, headers) {
|
||||
if !headersMatch(expected, headers.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSimultaneousRequests sends two simultaneous `GetBlockHeader` requests from
|
||||
// the same connection with different request IDs and checks to make sure the node
|
||||
// responds with the correct headers per request.
|
||||
// TestSimultaneousRequests sends two simultaneous `GetBlockHeader` requests
|
||||
// from the same connection with different request IDs and checks to make sure
|
||||
// the node responds with the correct headers per request.
|
||||
func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
|
||||
// create a connection
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
|
|
@ -147,8 +163,8 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
|
|||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
|
||||
// create two requests
|
||||
req1 := &GetBlockHeaders{
|
||||
// Create two different requests.
|
||||
req1 := ð.GetBlockHeadersPacket{
|
||||
RequestId: uint64(111),
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{
|
||||
|
|
@ -159,7 +175,7 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
|
|||
Reverse: false,
|
||||
},
|
||||
}
|
||||
req2 := &GetBlockHeaders{
|
||||
req2 := ð.GetBlockHeadersPacket{
|
||||
RequestId: uint64(222),
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{
|
||||
|
|
@ -171,46 +187,45 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
|
|||
},
|
||||
}
|
||||
|
||||
// write the first request
|
||||
if err := conn.Write(req1); err != nil {
|
||||
// Send both requests.
|
||||
if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req1); err != nil {
|
||||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
// write the second request
|
||||
if err := conn.Write(req2); err != nil {
|
||||
if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req2); err != nil {
|
||||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
|
||||
// wait for responses
|
||||
msg := conn.waitForResponse(s.chain, timeout, req1.RequestId)
|
||||
headers1, ok := msg.(*BlockHeaders)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg))
|
||||
// Wait for responses.
|
||||
headers1 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil {
|
||||
t.Fatalf("error reading block headers msg: %v", err)
|
||||
}
|
||||
msg = conn.waitForResponse(s.chain, timeout, req2.RequestId)
|
||||
headers2, ok := msg.(*BlockHeaders)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg))
|
||||
if got, want := headers1.RequestId, req1.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
headers2 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil {
|
||||
t.Fatalf("error reading block headers msg: %v", err)
|
||||
}
|
||||
if got, want := headers2.RequestId, req2.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// check received headers for accuracy
|
||||
expected1, err := s.chain.GetHeaders(req1)
|
||||
if err != nil {
|
||||
// Check received headers for accuracy.
|
||||
if expected, err := s.chain.GetHeaders(req1); err != nil {
|
||||
t.Fatalf("failed to get expected headers for request 1: %v", err)
|
||||
} else if !headersMatch(expected, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1)
|
||||
}
|
||||
expected2, err := s.chain.GetHeaders(req2)
|
||||
if err != nil {
|
||||
if expected, err := s.chain.GetHeaders(req2); err != nil {
|
||||
t.Fatalf("failed to get expected headers for request 2: %v", err)
|
||||
}
|
||||
if !headersMatch(expected1, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1)
|
||||
}
|
||||
if !headersMatch(expected2, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2)
|
||||
} else if !headersMatch(expected, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSameRequestID sends two requests with the same request ID to a
|
||||
// single node.
|
||||
// TestSameRequestID sends two requests with the same request ID to a single
|
||||
// node.
|
||||
func (s *Suite) TestSameRequestID(t *utesting.T) {
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
|
|
@ -220,9 +235,10 @@ func (s *Suite) TestSameRequestID(t *utesting.T) {
|
|||
if err := conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// create requests
|
||||
|
||||
// Create two different requests with the same ID.
|
||||
reqID := uint64(1234)
|
||||
request1 := &GetBlockHeaders{
|
||||
request1 := ð.GetBlockHeadersPacket{
|
||||
RequestId: reqID,
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{
|
||||
|
|
@ -231,7 +247,7 @@ func (s *Suite) TestSameRequestID(t *utesting.T) {
|
|||
Amount: 2,
|
||||
},
|
||||
}
|
||||
request2 := &GetBlockHeaders{
|
||||
request2 := ð.GetBlockHeadersPacket{
|
||||
RequestId: reqID,
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{
|
||||
|
|
@ -241,40 +257,40 @@ func (s *Suite) TestSameRequestID(t *utesting.T) {
|
|||
},
|
||||
}
|
||||
|
||||
// write the requests
|
||||
if err = conn.Write(request1); err != nil {
|
||||
// Send the requests.
|
||||
if err = conn.Write(ethProto, eth.GetBlockHeadersMsg, request1); err != nil {
|
||||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
if err = conn.Write(request2); err != nil {
|
||||
if err = conn.Write(ethProto, eth.GetBlockHeadersMsg, request2); err != nil {
|
||||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
|
||||
// wait for responses
|
||||
msg := conn.waitForResponse(s.chain, timeout, reqID)
|
||||
headers1, ok := msg.(*BlockHeaders)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg))
|
||||
// Wait for the responses.
|
||||
headers1 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
msg = conn.waitForResponse(s.chain, timeout, reqID)
|
||||
headers2, ok := msg.(*BlockHeaders)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg))
|
||||
if got, want := headers1.RequestId, request1.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id: got %d, want %d", got, want)
|
||||
}
|
||||
headers2 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
if got, want := headers2.RequestId, request2.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id: got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// check if headers match
|
||||
expected1, err := s.chain.GetHeaders(request1)
|
||||
if err != nil {
|
||||
// Check if headers match.
|
||||
if expected, err := s.chain.GetHeaders(request1); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
} else if !headersMatch(expected, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1)
|
||||
}
|
||||
expected2, err := s.chain.GetHeaders(request2)
|
||||
if err != nil {
|
||||
if expected, err := s.chain.GetHeaders(request2); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
}
|
||||
if !headersMatch(expected1, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1)
|
||||
}
|
||||
if !headersMatch(expected2, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2)
|
||||
} else if !headersMatch(expected, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,27 +305,32 @@ func (s *Suite) TestZeroRequestID(t *utesting.T) {
|
|||
if err := conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
req := &GetBlockHeaders{
|
||||
req := ð.GetBlockHeadersPacket{
|
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||
Origin: eth.HashOrNumber{Number: 0},
|
||||
Amount: 2,
|
||||
},
|
||||
}
|
||||
headers, err := conn.headersRequest(req, s.chain, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get block headers: %v", err)
|
||||
// Read headers response.
|
||||
if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req); err != nil {
|
||||
t.Fatalf("could not write to connection: %v", err)
|
||||
}
|
||||
expected, err := s.chain.GetHeaders(req)
|
||||
if err != nil {
|
||||
headers := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers); err != nil {
|
||||
t.Fatalf("error reading msg: %v", err)
|
||||
}
|
||||
if got, want := headers.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id")
|
||||
}
|
||||
if expected, err := s.chain.GetHeaders(req); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
}
|
||||
if !headersMatch(expected, headers) {
|
||||
} else if !headersMatch(expected, headers.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBlockBodies tests whether the given node can respond to
|
||||
// a `GetBlockBodies` request and that the response is accurate.
|
||||
// TestGetBlockBodies tests whether the given node can respond to a
|
||||
// `GetBlockBodies` request and that the response is accurate.
|
||||
func (s *Suite) TestGetBlockBodies(t *utesting.T) {
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
|
|
@ -319,104 +340,110 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
|
|||
if err := conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// create block bodies request
|
||||
req := &GetBlockBodies{
|
||||
RequestId: uint64(55),
|
||||
// Create block bodies request.
|
||||
req := ð.GetBlockBodiesPacket{
|
||||
RequestId: 55,
|
||||
GetBlockBodiesRequest: eth.GetBlockBodiesRequest{
|
||||
s.chain.blocks[54].Hash(),
|
||||
s.chain.blocks[75].Hash(),
|
||||
},
|
||||
}
|
||||
if err := conn.Write(req); err != nil {
|
||||
if err := conn.Write(ethProto, eth.GetBlockBodiesMsg, req); err != nil {
|
||||
t.Fatalf("could not write to connection: %v", err)
|
||||
}
|
||||
// wait for block bodies response
|
||||
msg := conn.waitForResponse(s.chain, timeout, req.RequestId)
|
||||
resp, ok := msg.(*BlockBodies)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||
// Wait for response.
|
||||
resp := new(eth.BlockBodiesPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockBodiesMsg, &resp); err != nil {
|
||||
t.Fatalf("error reading block bodies msg: %v", err)
|
||||
}
|
||||
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in respond", got, want)
|
||||
}
|
||||
bodies := resp.BlockBodiesResponse
|
||||
t.Logf("received %d block bodies", len(bodies))
|
||||
if len(bodies) != len(req.GetBlockBodiesRequest) {
|
||||
t.Fatalf("wrong bodies in response: expected %d bodies, "+
|
||||
"got %d", len(req.GetBlockBodiesRequest), len(bodies))
|
||||
t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetBlockBodiesRequest), len(bodies))
|
||||
}
|
||||
}
|
||||
|
||||
// TestBroadcast tests whether a block announcement is correctly
|
||||
// propagated to the node's peers.
|
||||
func (s *Suite) TestBroadcast(t *utesting.T) {
|
||||
if err := s.sendNextBlock(); err != nil {
|
||||
t.Fatalf("block broadcast failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLargeAnnounce tests the announcement mechanism with a large block.
|
||||
func (s *Suite) TestLargeAnnounce(t *utesting.T) {
|
||||
nextBlock := len(s.chain.blocks)
|
||||
blocks := []*NewBlock{
|
||||
{
|
||||
Block: largeBlock(),
|
||||
TD: s.fullChain.TotalDifficultyAt(nextBlock),
|
||||
},
|
||||
{
|
||||
Block: s.fullChain.blocks[nextBlock],
|
||||
TD: largeNumber(2),
|
||||
},
|
||||
{
|
||||
Block: largeBlock(),
|
||||
TD: largeNumber(2),
|
||||
},
|
||||
}
|
||||
|
||||
for i, blockAnnouncement := range blocks[0:3] {
|
||||
t.Logf("Testing malicious announcement: %v\n", i)
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
if err := conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
if err := conn.Write(blockAnnouncement); err != nil {
|
||||
t.Fatalf("could not write to connection: %v", err)
|
||||
}
|
||||
// Invalid announcement, check that peer disconnected
|
||||
switch msg := conn.readAndServe(s.chain, 8*time.Second).(type) {
|
||||
case *Disconnect:
|
||||
case *Error:
|
||||
break
|
||||
default:
|
||||
t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg))
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
// Test the last block as a valid block
|
||||
if err := s.sendNextBlock(); err != nil {
|
||||
t.Fatalf("failed to broadcast next block: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOldAnnounce tests the announcement mechanism with an old block.
|
||||
func (s *Suite) TestOldAnnounce(t *utesting.T) {
|
||||
if err := s.oldAnnounce(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHashAnnounce sends a new block hash announcement and expects
|
||||
// the node to perform a `GetBlockHeaders` request.
|
||||
func (s *Suite) TestBlockHashAnnounce(t *utesting.T) {
|
||||
if err := s.hashAnnounce(); err != nil {
|
||||
t.Fatalf("block hash announcement failed: %v", err)
|
||||
}
|
||||
// randBuf makes a random buffer size kilobytes large.
|
||||
func randBuf(size int) []byte {
|
||||
buf := make([]byte, size*1024)
|
||||
rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
// TestMaliciousHandshake tries to send malicious data during the handshake.
|
||||
func (s *Suite) TestMaliciousHandshake(t *utesting.T) {
|
||||
if err := s.maliciousHandshakes(t); err != nil {
|
||||
t.Fatal(err)
|
||||
key, _ := crypto.GenerateKey()
|
||||
|
||||
// Write hello to client.
|
||||
var (
|
||||
pub0 = crypto.FromECDSAPub(&key.PublicKey)[1:]
|
||||
version = eth.ProtocolVersions[0]
|
||||
)
|
||||
handshakes := []*protoHandshake{
|
||||
{
|
||||
Version: 5,
|
||||
Caps: []p2p.Cap{
|
||||
{Name: string(randBuf(2)), Version: version},
|
||||
},
|
||||
ID: pub0,
|
||||
},
|
||||
{
|
||||
Version: 5,
|
||||
Caps: []p2p.Cap{
|
||||
{Name: "eth", Version: version},
|
||||
},
|
||||
ID: append(pub0, byte(0)),
|
||||
},
|
||||
{
|
||||
Version: 5,
|
||||
Caps: []p2p.Cap{
|
||||
{Name: "eth", Version: version},
|
||||
},
|
||||
ID: append(pub0, pub0...),
|
||||
},
|
||||
{
|
||||
Version: 5,
|
||||
Caps: []p2p.Cap{
|
||||
{Name: "eth", Version: version},
|
||||
},
|
||||
ID: randBuf(2),
|
||||
},
|
||||
{
|
||||
Version: 5,
|
||||
Caps: []p2p.Cap{
|
||||
{Name: string(randBuf(2)), Version: version},
|
||||
},
|
||||
ID: randBuf(2),
|
||||
},
|
||||
}
|
||||
for _, handshake := range handshakes {
|
||||
conn, err := s.dialAs(key)
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.Write(ethProto, handshakeMsg, handshake); err != nil {
|
||||
t.Fatalf("could not write to connection: %v", err)
|
||||
}
|
||||
// Check that the peer disconnected
|
||||
for i := 0; i < 2; i++ {
|
||||
code, _, err := conn.Read()
|
||||
if err != nil {
|
||||
// Client may have disconnected without sending disconnect msg.
|
||||
continue
|
||||
}
|
||||
switch code {
|
||||
case discMsg:
|
||||
case handshakeMsg:
|
||||
// Discard one hello as Hello's are sent concurrently
|
||||
continue
|
||||
default:
|
||||
t.Fatalf("unexpected msg: code %d", code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -427,46 +454,184 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) {
|
|||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := s.maliciousStatus(conn); err != nil {
|
||||
t.Fatal(err)
|
||||
if err := conn.handshake(); err != nil {
|
||||
t.Fatalf("handshake failed: %v", err)
|
||||
}
|
||||
// Create status with large total difficulty.
|
||||
status := ð.StatusPacket{
|
||||
ProtocolVersion: uint32(conn.negotiatedProtoVersion),
|
||||
NetworkID: s.chain.config.ChainID.Uint64(),
|
||||
TD: new(big.Int).SetBytes(randBuf(2048)),
|
||||
Head: s.chain.Head().Hash(),
|
||||
Genesis: s.chain.GetBlock(0).Hash(),
|
||||
ForkID: s.chain.ForkID(),
|
||||
}
|
||||
if err := conn.statusExchange(s.chain, status); err != nil {
|
||||
t.Fatalf("status exchange failed: %v", err)
|
||||
}
|
||||
// Wait for disconnect.
|
||||
code, _, err := conn.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
switch code {
|
||||
case discMsg:
|
||||
break
|
||||
default:
|
||||
t.Fatalf("expected disconnect, got: %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransaction sends a valid transaction to the node and
|
||||
// checks if the transaction gets propagated.
|
||||
// TestTransaction sends a valid transaction to the node and checks if the
|
||||
// transaction gets propagated.
|
||||
func (s *Suite) TestTransaction(t *utesting.T) {
|
||||
if err := s.sendSuccessfulTxs(t); err != nil {
|
||||
// Nudge client out of syncing mode to accept pending txs.
|
||||
if err := s.engine.sendForkchoiceUpdated(); err != nil {
|
||||
t.Fatalf("failed to send next block: %v", err)
|
||||
}
|
||||
from, nonce := s.chain.GetSender(0)
|
||||
inner := &types.DynamicFeeTx{
|
||||
ChainID: s.chain.config.ChainID,
|
||||
Nonce: nonce,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 30000,
|
||||
To: &common.Address{0xaa},
|
||||
Value: common.Big1,
|
||||
}
|
||||
tx, err := s.chain.SignTx(from, types.NewTx(inner))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign tx: %v", err)
|
||||
}
|
||||
if err := s.sendTxs([]*types.Transaction{tx}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.chain.IncNonce(from, 1)
|
||||
}
|
||||
|
||||
// TestMaliciousTx sends several invalid transactions and tests whether
|
||||
// TestInvalidTxs sends several invalid transactions and tests whether
|
||||
// the node will propagate them.
|
||||
func (s *Suite) TestMaliciousTx(t *utesting.T) {
|
||||
if err := s.sendMaliciousTxs(t); err != nil {
|
||||
t.Fatal(err)
|
||||
func (s *Suite) TestInvalidTxs(t *utesting.T) {
|
||||
// Nudge client out of syncing mode to accept pending txs.
|
||||
if err := s.engine.sendForkchoiceUpdated(); err != nil {
|
||||
t.Fatalf("failed to send next block: %v", err)
|
||||
}
|
||||
|
||||
from, nonce := s.chain.GetSender(0)
|
||||
inner := &types.DynamicFeeTx{
|
||||
ChainID: s.chain.config.ChainID,
|
||||
Nonce: nonce,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 30000,
|
||||
To: &common.Address{0xaa},
|
||||
}
|
||||
tx, err := s.chain.SignTx(from, types.NewTx(inner))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign tx: %v", err)
|
||||
}
|
||||
if err := s.sendTxs([]*types.Transaction{tx}); err != nil {
|
||||
t.Fatalf("failed to send txs: %v", err)
|
||||
}
|
||||
s.chain.IncNonce(from, 1)
|
||||
|
||||
inners := []*types.DynamicFeeTx{
|
||||
// Nonce already used
|
||||
{
|
||||
ChainID: s.chain.config.ChainID,
|
||||
Nonce: nonce - 1,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 100000,
|
||||
},
|
||||
// Value exceeds balance
|
||||
{
|
||||
Nonce: nonce,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 100000,
|
||||
Value: s.chain.Balance(from),
|
||||
},
|
||||
// Gas limit too low
|
||||
{
|
||||
Nonce: nonce,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 1337,
|
||||
},
|
||||
// Code size too large
|
||||
{
|
||||
Nonce: nonce,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Data: randBuf(50),
|
||||
Gas: 1_000_000,
|
||||
},
|
||||
// Data too large
|
||||
{
|
||||
Nonce: nonce,
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
To: &common.Address{0xaa},
|
||||
Data: randBuf(128),
|
||||
Gas: 5_000_000,
|
||||
},
|
||||
}
|
||||
|
||||
var txs []*types.Transaction
|
||||
for _, inner := range inners {
|
||||
tx, err := s.chain.SignTx(from, types.NewTx(inner))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign tx: %v", err)
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
if err := s.sendInvalidTxs(txs); err != nil {
|
||||
t.Fatalf("failed to send invalid txs: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLargeTxRequest tests whether a node can fulfill a large GetPooledTransactions
|
||||
// request.
|
||||
func (s *Suite) TestLargeTxRequest(t *utesting.T) {
|
||||
// send the next block to ensure the node is no longer syncing and
|
||||
// is able to accept txs
|
||||
if err := s.sendNextBlock(); err != nil {
|
||||
// Nudge client out of syncing mode to accept pending txs.
|
||||
if err := s.engine.sendForkchoiceUpdated(); err != nil {
|
||||
t.Fatalf("failed to send next block: %v", err)
|
||||
}
|
||||
// send 2000 transactions to the node
|
||||
hashMap, txs, err := generateTxs(s, 2000)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate transactions: %v", err)
|
||||
|
||||
// Generate many transactions to seed target with.
|
||||
var (
|
||||
from, nonce = s.chain.GetSender(1)
|
||||
count = 2000
|
||||
txs []*types.Transaction
|
||||
hashes []common.Hash
|
||||
set = make(map[common.Hash]struct{})
|
||||
)
|
||||
for i := 0; i < count; i++ {
|
||||
inner := &types.DynamicFeeTx{
|
||||
ChainID: s.chain.config.ChainID,
|
||||
Nonce: nonce + uint64(i),
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 75000,
|
||||
}
|
||||
tx, err := s.chain.SignTx(from, types.NewTx(inner))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign tx: err")
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
set[tx.Hash()] = struct{}{}
|
||||
hashes = append(hashes, tx.Hash())
|
||||
}
|
||||
if err = sendMultipleSuccessfulTxs(t, s, txs); err != nil {
|
||||
t.Fatalf("failed to send multiple txs: %v", err)
|
||||
s.chain.IncNonce(from, uint64(count))
|
||||
|
||||
// Send txs.
|
||||
if err := s.sendTxs(txs); err != nil {
|
||||
t.Fatalf("failed to send txs: %v", err)
|
||||
}
|
||||
// set up connection to receive to ensure node is peered with the receiving connection
|
||||
// before tx request is sent
|
||||
|
||||
// Set up receive connection to ensure node is peered with the receiving
|
||||
// connection before tx request is sent.
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
|
|
@ -475,55 +640,62 @@ func (s *Suite) TestLargeTxRequest(t *utesting.T) {
|
|||
if err = conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
// create and send pooled tx request
|
||||
hashes := make([]common.Hash, 0)
|
||||
for _, hash := range hashMap {
|
||||
hashes = append(hashes, hash)
|
||||
}
|
||||
getTxReq := &GetPooledTransactions{
|
||||
// Create and send pooled tx request.
|
||||
req := ð.GetPooledTransactionsPacket{
|
||||
RequestId: 1234,
|
||||
GetPooledTransactionsRequest: hashes,
|
||||
}
|
||||
if err = conn.Write(getTxReq); err != nil {
|
||||
if err = conn.Write(ethProto, eth.GetPooledTransactionsMsg, req); err != nil {
|
||||
t.Fatalf("could not write to conn: %v", err)
|
||||
}
|
||||
// check that all received transactions match those that were sent to node
|
||||
switch msg := conn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) {
|
||||
case *PooledTransactions:
|
||||
for _, gotTx := range msg.PooledTransactionsResponse {
|
||||
if _, exists := hashMap[gotTx.Hash()]; !exists {
|
||||
t.Fatalf("unexpected tx received: %v", gotTx.Hash())
|
||||
}
|
||||
// Check that all received transactions match those that were sent to node.
|
||||
msg := new(eth.PooledTransactionsPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.PooledTransactionsMsg, &msg); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
if got, want := msg.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
for _, got := range msg.PooledTransactionsResponse {
|
||||
if _, exists := set[got.Hash()]; !exists {
|
||||
t.Fatalf("unexpected tx received: %v", got.Hash())
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg))
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewPooledTxs tests whether a node will do a GetPooledTransactions
|
||||
// request upon receiving a NewPooledTransactionHashes announcement.
|
||||
// TestNewPooledTxs tests whether a node will do a GetPooledTransactions request
|
||||
// upon receiving a NewPooledTransactionHashes announcement.
|
||||
func (s *Suite) TestNewPooledTxs(t *utesting.T) {
|
||||
// send the next block to ensure the node is no longer syncing and
|
||||
// is able to accept txs
|
||||
if err := s.sendNextBlock(); err != nil {
|
||||
// Nudge client out of syncing mode to accept pending txs.
|
||||
if err := s.engine.sendForkchoiceUpdated(); err != nil {
|
||||
t.Fatalf("failed to send next block: %v", err)
|
||||
}
|
||||
|
||||
// generate 50 txs
|
||||
_, txs, err := generateTxs(s, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate transactions: %v", err)
|
||||
}
|
||||
hashes := make([]common.Hash, len(txs))
|
||||
types := make([]byte, len(txs))
|
||||
sizes := make([]uint32, len(txs))
|
||||
for i, tx := range txs {
|
||||
var (
|
||||
count = 50
|
||||
from, nonce = s.chain.GetSender(1)
|
||||
hashes = make([]common.Hash, count)
|
||||
txTypes = make([]byte, count)
|
||||
sizes = make([]uint32, count)
|
||||
)
|
||||
for i := 0; i < count; i++ {
|
||||
inner := &types.DynamicFeeTx{
|
||||
ChainID: s.chain.config.ChainID,
|
||||
Nonce: nonce + uint64(i),
|
||||
GasTipCap: common.Big1,
|
||||
GasFeeCap: s.chain.Head().BaseFee(),
|
||||
Gas: 75000,
|
||||
}
|
||||
tx, err := s.chain.SignTx(from, types.NewTx(inner))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign tx: err")
|
||||
}
|
||||
hashes[i] = tx.Hash()
|
||||
types[i] = tx.Type()
|
||||
txTypes[i] = tx.Type()
|
||||
sizes[i] = uint32(tx.Size())
|
||||
}
|
||||
s.chain.IncNonce(from, uint64(count))
|
||||
|
||||
// send announcement
|
||||
// Connect to peer.
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
|
|
@ -533,37 +705,28 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) {
|
|||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
|
||||
var ann Message = NewPooledTransactionHashes{Types: types, Sizes: sizes, Hashes: hashes}
|
||||
if conn.negotiatedProtoVersion < eth.ETH68 {
|
||||
ann = NewPooledTransactionHashes66(hashes)
|
||||
}
|
||||
err = conn.Write(ann)
|
||||
// Send announcement.
|
||||
ann := eth.NewPooledTransactionHashesPacket68{Types: txTypes, Sizes: sizes, Hashes: hashes}
|
||||
err = conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
|
||||
// wait for GetPooledTxs request
|
||||
// Wait for GetPooledTxs request.
|
||||
for {
|
||||
msg := conn.readAndServe(s.chain, timeout)
|
||||
msg, err := conn.ReadEth()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read eth msg: %v", err)
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *GetPooledTransactions:
|
||||
case *eth.GetPooledTransactionsPacket:
|
||||
if len(msg.GetPooledTransactionsRequest) != len(hashes) {
|
||||
t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsRequest))
|
||||
}
|
||||
return
|
||||
|
||||
// ignore propagated txs from previous tests
|
||||
case *NewPooledTransactionHashes66:
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
continue
|
||||
case *NewPooledTransactionHashes:
|
||||
continue
|
||||
case *Transactions:
|
||||
continue
|
||||
|
||||
// ignore block announcements from previous tests
|
||||
case *NewBlockHashes:
|
||||
continue
|
||||
case *NewBlock:
|
||||
case *eth.TransactionsPacket:
|
||||
continue
|
||||
default:
|
||||
t.Fatalf("unexpected %s", pretty.Sdump(msg))
|
||||
|
|
|
|||
|
|
@ -17,32 +17,53 @@
|
|||
package ethtest
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
var (
|
||||
genesisFile = "./testdata/genesis.json"
|
||||
halfchainFile = "./testdata/halfchain.rlp"
|
||||
fullchainFile = "./testdata/chain.rlp"
|
||||
)
|
||||
func makeJWTSecret() (string, [32]byte, error) {
|
||||
var secret [32]byte
|
||||
if _, err := crand.Read(secret[:]); err != nil {
|
||||
return "", secret, fmt.Errorf("failed to create jwt secret: %v", err)
|
||||
}
|
||||
jwtPath := path.Join(os.TempDir(), "jwt_secret")
|
||||
if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil {
|
||||
return "", secret, fmt.Errorf("failed to prepare jwt secret file: %v", err)
|
||||
}
|
||||
return jwtPath, secret, nil
|
||||
}
|
||||
|
||||
func TestEthSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
geth, err := runGeth()
|
||||
|
||||
jwtPath, secret, err := makeJWTSecret()
|
||||
if err != nil {
|
||||
t.Fatalf("could not make jwt secret: %v", err)
|
||||
}
|
||||
geth, err := runGeth("./testdata", jwtPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not run geth: %v", err)
|
||||
}
|
||||
defer geth.Close()
|
||||
|
||||
suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile)
|
||||
// TODO: remove this
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
|
||||
suite, err := NewSuite(geth.Server().Self(), "./testdata", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:]))
|
||||
if err != nil {
|
||||
t.Fatalf("could not create new test suite: %v", err)
|
||||
}
|
||||
|
|
@ -58,13 +79,21 @@ func TestEthSuite(t *testing.T) {
|
|||
|
||||
func TestSnapSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
geth, err := runGeth()
|
||||
|
||||
jwtPath, secret, err := makeJWTSecret()
|
||||
if err != nil {
|
||||
t.Fatalf("could not make jwt secret: %v", err)
|
||||
}
|
||||
geth, err := runGeth("./testdata/snap", jwtPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not run geth: %v", err)
|
||||
}
|
||||
defer geth.Close()
|
||||
|
||||
suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile)
|
||||
// TODO: remove this
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
|
||||
suite, err := NewSuite(geth.Server().Self(), "./testdata/snap", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:]))
|
||||
if err != nil {
|
||||
t.Fatalf("could not create new test suite: %v", err)
|
||||
}
|
||||
|
|
@ -79,20 +108,23 @@ func TestSnapSuite(t *testing.T) {
|
|||
}
|
||||
|
||||
// runGeth creates and starts a geth node
|
||||
func runGeth() (*node.Node, error) {
|
||||
func runGeth(dir string, jwtPath string) (*node.Node, error) {
|
||||
stack, err := node.New(&node.Config{
|
||||
AuthAddr: "127.0.0.1",
|
||||
AuthPort: 18551,
|
||||
P2P: p2p.Config{
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
NoDiscovery: true,
|
||||
MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future
|
||||
NoDial: true,
|
||||
},
|
||||
JWTSecret: jwtPath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setupGeth(stack)
|
||||
err = setupGeth(stack, dir)
|
||||
if err != nil {
|
||||
stack.Close()
|
||||
return nil, err
|
||||
|
|
@ -104,12 +136,11 @@ func runGeth() (*node.Node, error) {
|
|||
return stack, nil
|
||||
}
|
||||
|
||||
func setupGeth(stack *node.Node) error {
|
||||
chain, err := loadChain(halfchainFile, genesisFile)
|
||||
func setupGeth(stack *node.Node, dir string) error {
|
||||
chain, err := NewChain(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backend, err := eth.New(stack, ðconfig.Config{
|
||||
Genesis: &chain.genesis,
|
||||
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
|
||||
|
|
@ -122,8 +153,9 @@ func setupGeth(stack *node.Node) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backend.SetSynced()
|
||||
|
||||
if err := catalyst.Register(stack, backend); err != nil {
|
||||
return fmt.Errorf("failed to register catalyst service: %v", err)
|
||||
}
|
||||
_, err = backend.BlockChain().InsertChain(chain.blocks[1:])
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
62
cmd/devp2p/internal/ethtest/testdata/accounts.json
vendored
Normal file
62
cmd/devp2p/internal/ethtest/testdata/accounts.json
vendored
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
Binary file not shown.
120
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
120
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
|
|
@ -1,27 +1,99 @@
|
|||
{
|
||||
"config": {
|
||||
"chainId": 19763,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"terminalTotalDifficultyPassed": true,
|
||||
"ethash": {}
|
||||
"config": {
|
||||
"chainId": 35039958740849263516136087381459012528369084397061947147216452157383146382873,
|
||||
"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": {
|
||||
"0c2c51a0990aee1d73c1228de158688341557508": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"nonce": "0xdeadbeefdeadbeef",
|
||||
"timestamp": "0x0",
|
||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"gasLimit": "0x80000000",
|
||||
"difficulty": "0x20000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"71562b71999873db5b286df957af199ec94617f7": {
|
||||
"balance": "0xffffffffffffffffffffffffff"
|
||||
}
|
||||
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
"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"
|
||||
},
|
||||
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"eda8645ba6948855e3b3cd596bbb07596d59c603": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
}
|
||||
},
|
||||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"baseFeePerGas": null,
|
||||
"excessBlobGas": null,
|
||||
"blobGasUsed": null
|
||||
}
|
||||
23
cmd/devp2p/internal/ethtest/testdata/headblock.json
vendored
Normal file
23
cmd/devp2p/internal/ethtest/testdata/headblock.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"parentHash": "0xc27fe0e3f88cd8b83701989a46266e18eb199baf742d351536613b0848438578",
|
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"miner": "0x0000000000000000000000000000000000000000",
|
||||
"stateRoot": "0x8126c508b9368662bc602ad6ef75b26975ea314f7296ff54db5af4b971c537f8",
|
||||
"transactionsRoot": "0xce0b40c6a6c1b6133193c48aee99f16da7583e851fc889bb6eb8b22d27ddf7d3",
|
||||
"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": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"hash": "0xbdddd6c13b6345c06012c2fab056ccd257409b71c794c9945fae994b8022dda8"
|
||||
}
|
||||
13
cmd/devp2p/internal/ethtest/testdata/headfcu.json
vendored
Normal file
13
cmd/devp2p/internal/ethtest/testdata/headfcu.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "fcu500",
|
||||
"method": "engine_forkchoiceUpdatedV3",
|
||||
"params": [
|
||||
{
|
||||
"headBlockHash": "0xbdddd6c13b6345c06012c2fab056ccd257409b71c794c9945fae994b8022dda8",
|
||||
"safeBlockHash": "0xbdddd6c13b6345c06012c2fab056ccd257409b71c794c9945fae994b8022dda8",
|
||||
"finalizedBlockHash": "0xbdddd6c13b6345c06012c2fab056ccd257409b71c794c9945fae994b8022dda8"
|
||||
},
|
||||
null
|
||||
]
|
||||
}
|
||||
3348
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
Normal file
3348
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
13268
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
Normal file
13268
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
cmd/devp2p/internal/ethtest/testdata/snap/accounts.json
vendored
Normal file
1
cmd/devp2p/internal/ethtest/testdata/snap/accounts.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
27
cmd/devp2p/internal/ethtest/testdata/snap/genesis.json
vendored
Normal file
27
cmd/devp2p/internal/ethtest/testdata/snap/genesis.json
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
0
cmd/devp2p/internal/ethtest/testdata/snap/headfcu.json
vendored
Normal file
0
cmd/devp2p/internal/ethtest/testdata/snap/headfcu.json
vendored
Normal file
1
cmd/devp2p/internal/ethtest/testdata/snap/headstate.json
vendored
Normal file
1
cmd/devp2p/internal/ethtest/testdata/snap/headstate.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
3018
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
Normal file
3018
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -19,429 +19,141 @@ package ethtest
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/params"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
)
|
||||
|
||||
// var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
||||
var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
func (s *Suite) sendSuccessfulTxs(t *utesting.T) error {
|
||||
tests := []*types.Transaction{
|
||||
getNextTxFromChain(s),
|
||||
unknownTx(s),
|
||||
}
|
||||
for i, tx := range tests {
|
||||
if tx == nil {
|
||||
return errors.New("could not find tx to send")
|
||||
}
|
||||
t.Logf("Testing tx propagation %d: sending tx %v %v %v\n", i, tx.Hash().String(), tx.GasPrice(), tx.Gas())
|
||||
// get previous tx if exists for reference in case of old tx propagation
|
||||
var prevTx *types.Transaction
|
||||
if i != 0 {
|
||||
prevTx = tests[i-1]
|
||||
}
|
||||
// write tx to connection
|
||||
if err := sendSuccessfulTx(s, tx, prevTx); err != nil {
|
||||
return fmt.Errorf("send successful tx test failed: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction) error {
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
||||
// sendTxs sends the given transactions to the node and
|
||||
// expects the node to accept and propagate them.
|
||||
func (s *Suite) sendTxs(txs []*types.Transaction) error {
|
||||
// Open sending conn.
|
||||
sendConn, err := s.dial()
|
||||
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)
|
||||
}
|
||||
// Send the transaction
|
||||
if err = sendConn.Write(&Transactions{tx}); err != nil {
|
||||
return fmt.Errorf("failed to write to connection: %v", err)
|
||||
}
|
||||
// peer receiving connection to node
|
||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
||||
return fmt.Errorf("peering failed: %v", err)
|
||||
}
|
||||
|
||||
// update last nonce seen
|
||||
nonce = tx.Nonce()
|
||||
|
||||
// Wait for the transaction announcement
|
||||
for {
|
||||
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
|
||||
case *Transactions:
|
||||
recTxs := *msg
|
||||
// if you receive an old tx propagation, read from connection again
|
||||
if len(recTxs) == 1 && prevTx != nil {
|
||||
if recTxs[0] == prevTx {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, gotTx := range recTxs {
|
||||
if gotTx.Hash() == tx.Hash() {
|
||||
// Ok
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("missing transaction: got %v missing %v", recTxs, tx.Hash())
|
||||
case *NewPooledTransactionHashes66:
|
||||
txHashes := *msg
|
||||
// if you receive an old tx propagation, read from connection again
|
||||
if len(txHashes) == 1 && prevTx != nil {
|
||||
if txHashes[0] == prevTx.Hash() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, gotHash := range txHashes {
|
||||
if gotHash == tx.Hash() {
|
||||
// Ok
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash())
|
||||
case *NewPooledTransactionHashes:
|
||||
txHashes := msg.Hashes
|
||||
if len(txHashes) != len(msg.Sizes) {
|
||||
return fmt.Errorf("invalid msg size lengths: hashes: %v sizes: %v", len(txHashes), len(msg.Sizes))
|
||||
}
|
||||
if len(txHashes) != len(msg.Types) {
|
||||
return fmt.Errorf("invalid msg type lengths: hashes: %v types: %v", len(txHashes), len(msg.Types))
|
||||
}
|
||||
// if you receive an old tx propagation, read from connection again
|
||||
if len(txHashes) == 1 && prevTx != nil {
|
||||
if txHashes[0] == prevTx.Hash() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for index, gotHash := range txHashes {
|
||||
if gotHash == tx.Hash() {
|
||||
if msg.Sizes[index] != uint32(tx.Size()) {
|
||||
return fmt.Errorf("invalid tx size: got %v want %v", msg.Sizes[index], tx.Size())
|
||||
}
|
||||
if msg.Types[index] != tx.Type() {
|
||||
return fmt.Errorf("invalid tx type: got %v want %v", msg.Types[index], tx.Type())
|
||||
}
|
||||
// Ok
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash())
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) sendMaliciousTxs(t *utesting.T) error {
|
||||
badTxs := []*types.Transaction{
|
||||
getOldTxFromChain(s),
|
||||
invalidNonceTx(s),
|
||||
hugeAmount(s),
|
||||
hugeGasPrice(s),
|
||||
hugeData(s),
|
||||
}
|
||||
|
||||
// setup receiving connection before sending malicious txs
|
||||
// Open receiving conn.
|
||||
recvConn, err := s.dial()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial failed: %v", err)
|
||||
}
|
||||
defer recvConn.Close()
|
||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
||||
return fmt.Errorf("peering failed: %v", err)
|
||||
}
|
||||
|
||||
for i, tx := range badTxs {
|
||||
t.Logf("Testing malicious tx propagation: %v\n", i)
|
||||
if err = sendMaliciousTx(s, tx); err != nil {
|
||||
return fmt.Errorf("malicious tx test failed:\ntx: %v\nerror: %v", tx, err)
|
||||
}
|
||||
}
|
||||
// check to make sure bad txs aren't propagated
|
||||
return checkMaliciousTxPropagation(s, badTxs, recvConn)
|
||||
}
|
||||
|
||||
func sendMaliciousTx(s *Suite, tx *types.Transaction) error {
|
||||
conn, err := s.dial()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err = conn.peer(s.chain, nil); err != nil {
|
||||
return fmt.Errorf("peering failed: %v", err)
|
||||
}
|
||||
|
||||
// write malicious tx
|
||||
if err = conn.Write(&Transactions{tx}); err != nil {
|
||||
return fmt.Errorf("failed to write to connection: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var nonce = uint64(99)
|
||||
|
||||
// sendMultipleSuccessfulTxs sends the given transactions to the node and
|
||||
// expects the node to accept and propagate them.
|
||||
func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction) error {
|
||||
txMsg := Transactions(txs)
|
||||
t.Logf("sending %d txs\n", len(txs))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Send the transactions
|
||||
if err = sendConn.Write(&txMsg); err != nil {
|
||||
if err = sendConn.Write(ethProto, eth.TransactionsMsg, eth.TransactionsPacket(txs)); err != nil {
|
||||
return fmt.Errorf("failed to write message to connection: %v", err)
|
||||
}
|
||||
|
||||
// update nonce
|
||||
nonce = txs[len(txs)-1].Nonce()
|
||||
var (
|
||||
got = make(map[common.Hash]bool)
|
||||
end = time.Now().Add(timeout)
|
||||
)
|
||||
|
||||
// Wait for the transaction announcement(s) and make sure all sent txs are being propagated.
|
||||
// all txs should be announced within a couple announcements.
|
||||
recvHashes := make([]common.Hash, 0)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
|
||||
case *Transactions:
|
||||
// Wait for the transaction announcements, make sure all txs ar propagated.
|
||||
for time.Now().Before(end) {
|
||||
msg, err := recvConn.ReadEth()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read from connection: %w", err)
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *eth.TransactionsPacket:
|
||||
for _, tx := range *msg {
|
||||
recvHashes = append(recvHashes, tx.Hash())
|
||||
got[tx.Hash()] = true
|
||||
}
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
for _, hash := range msg.Hashes {
|
||||
got[hash] = true
|
||||
}
|
||||
case *NewPooledTransactionHashes66:
|
||||
recvHashes = append(recvHashes, *msg...)
|
||||
case *NewPooledTransactionHashes:
|
||||
recvHashes = append(recvHashes, msg.Hashes...)
|
||||
default:
|
||||
if !strings.Contains(pretty.Sdump(msg), "i/o timeout") {
|
||||
return fmt.Errorf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg))
|
||||
return fmt.Errorf("unexpected eth wire msg: %s", pretty.Sdump(msg))
|
||||
}
|
||||
|
||||
// Check if all txs received.
|
||||
allReceived := func() bool {
|
||||
for _, tx := range txs {
|
||||
if !got[tx.Hash()] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// break once all 2000 txs have been received
|
||||
if len(recvHashes) == 2000 {
|
||||
break
|
||||
}
|
||||
if len(recvHashes) > 0 {
|
||||
_, missingTxs := compareReceivedTxs(recvHashes, txs)
|
||||
if len(missingTxs) > 0 {
|
||||
continue
|
||||
} else {
|
||||
t.Logf("successfully received all %d txs", len(txs))
|
||||
return nil
|
||||
}
|
||||
if allReceived() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
_, missingTxs := compareReceivedTxs(recvHashes, txs)
|
||||
if len(missingTxs) > 0 {
|
||||
for _, missing := range missingTxs {
|
||||
t.Logf("missing tx: %v", missing.Hash())
|
||||
}
|
||||
return fmt.Errorf("missing %d txs", len(missingTxs))
|
||||
}
|
||||
return nil
|
||||
|
||||
return fmt.Errorf("timed out waiting for txs")
|
||||
}
|
||||
|
||||
// checkMaliciousTxPropagation checks whether the given malicious transactions were
|
||||
// propagated by the node.
|
||||
func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) error {
|
||||
switch msg := conn.readAndServe(s.chain, time.Second*8).(type) {
|
||||
case *Transactions:
|
||||
// check to see if any of the failing txs were in the announcement
|
||||
recvTxs := make([]common.Hash, len(*msg))
|
||||
for i, recvTx := range *msg {
|
||||
recvTxs[i] = recvTx.Hash()
|
||||
}
|
||||
badTxs, _ := compareReceivedTxs(recvTxs, txs)
|
||||
if len(badTxs) > 0 {
|
||||
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
|
||||
}
|
||||
case *NewPooledTransactionHashes66:
|
||||
badTxs, _ := compareReceivedTxs(*msg, txs)
|
||||
if len(badTxs) > 0 {
|
||||
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
|
||||
}
|
||||
case *NewPooledTransactionHashes:
|
||||
badTxs, _ := compareReceivedTxs(msg.Hashes, txs)
|
||||
if len(badTxs) > 0 {
|
||||
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
|
||||
}
|
||||
case *Error:
|
||||
// Transaction should not be announced -> wait for timeout
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compareReceivedTxs compares the received set of txs against the given set of txs,
|
||||
// returning both the set received txs that were present within the given txs, and
|
||||
// the set of txs that were missing from the set of received txs
|
||||
func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (present []*types.Transaction, missing []*types.Transaction) {
|
||||
// create a map of the hashes received from node
|
||||
recvHashes := make(map[common.Hash]common.Hash)
|
||||
for _, hash := range recvTxs {
|
||||
recvHashes[hash] = hash
|
||||
}
|
||||
|
||||
// collect present txs and missing txs separately
|
||||
present = make([]*types.Transaction, 0)
|
||||
missing = make([]*types.Transaction, 0)
|
||||
for _, tx := range txs {
|
||||
if _, exists := recvHashes[tx.Hash()]; exists {
|
||||
present = append(present, tx)
|
||||
} else {
|
||||
missing = append(missing, tx)
|
||||
}
|
||||
}
|
||||
return present, missing
|
||||
}
|
||||
|
||||
func unknownTx(s *Suite) *types.Transaction {
|
||||
tx := getNextTxFromChain(s)
|
||||
if tx == nil {
|
||||
return nil
|
||||
}
|
||||
var to common.Address
|
||||
if tx.To() != nil {
|
||||
to = *tx.To()
|
||||
}
|
||||
txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data())
|
||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
||||
}
|
||||
|
||||
func getNextTxFromChain(s *Suite) *types.Transaction {
|
||||
// Get a new transaction
|
||||
for _, blocks := range s.fullChain.blocks[s.chain.Len():] {
|
||||
txs := blocks.Transactions()
|
||||
if txs.Len() != 0 {
|
||||
return txs[0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction, error) {
|
||||
txHashMap := make(map[common.Hash]common.Hash, numTxs)
|
||||
txs := make([]*types.Transaction, numTxs)
|
||||
|
||||
nextTx := getNextTxFromChain(s)
|
||||
if nextTx == nil {
|
||||
return nil, nil, errors.New("failed to get the next transaction")
|
||||
}
|
||||
gas := nextTx.Gas()
|
||||
|
||||
nonce = nonce + 1
|
||||
// generate txs
|
||||
for i := 0; i < numTxs; i++ {
|
||||
tx := generateTx(s.chain.chainConfig, nonce, gas)
|
||||
if tx == nil {
|
||||
return nil, nil, errors.New("failed to get the next transaction")
|
||||
}
|
||||
txHashMap[tx.Hash()] = tx.Hash()
|
||||
txs[i] = tx
|
||||
nonce = nonce + 1
|
||||
}
|
||||
return txHashMap, txs, nil
|
||||
}
|
||||
|
||||
func generateTx(chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction {
|
||||
var to common.Address
|
||||
tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{})
|
||||
return signWithFaucet(chainConfig, tx)
|
||||
}
|
||||
|
||||
func getOldTxFromChain(s *Suite) *types.Transaction {
|
||||
for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] {
|
||||
txs := blocks.Transactions()
|
||||
if txs.Len() != 0 {
|
||||
return txs[0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func invalidNonceTx(s *Suite) *types.Transaction {
|
||||
tx := getNextTxFromChain(s)
|
||||
if tx == nil {
|
||||
return nil
|
||||
}
|
||||
var to common.Address
|
||||
if tx.To() != nil {
|
||||
to = *tx.To()
|
||||
}
|
||||
txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data())
|
||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
||||
}
|
||||
|
||||
func hugeAmount(s *Suite) *types.Transaction {
|
||||
tx := getNextTxFromChain(s)
|
||||
if tx == nil {
|
||||
return nil
|
||||
}
|
||||
amount := largeNumber(2)
|
||||
var to common.Address
|
||||
if tx.To() != nil {
|
||||
to = *tx.To()
|
||||
}
|
||||
txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data())
|
||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
||||
}
|
||||
|
||||
func hugeGasPrice(s *Suite) *types.Transaction {
|
||||
tx := getNextTxFromChain(s)
|
||||
if tx == nil {
|
||||
return nil
|
||||
}
|
||||
gasPrice := largeNumber(2)
|
||||
var to common.Address
|
||||
if tx.To() != nil {
|
||||
to = *tx.To()
|
||||
}
|
||||
txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data())
|
||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
||||
}
|
||||
|
||||
func hugeData(s *Suite) *types.Transaction {
|
||||
tx := getNextTxFromChain(s)
|
||||
if tx == nil {
|
||||
return nil
|
||||
}
|
||||
var to common.Address
|
||||
if tx.To() != nil {
|
||||
to = *tx.To()
|
||||
}
|
||||
txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2))
|
||||
return signWithFaucet(s.chain.chainConfig, txNew)
|
||||
}
|
||||
|
||||
func signWithFaucet(chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction {
|
||||
signer := types.LatestSigner(chainConfig)
|
||||
signedTx, err := types.SignTx(tx, signer, faucetKey)
|
||||
func (s *Suite) sendInvalidTxs(txs []*types.Transaction) error {
|
||||
// Open sending conn.
|
||||
sendConn, err := s.dial()
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
defer sendConn.Close()
|
||||
if err = sendConn.peer(s.chain, nil); err != nil {
|
||||
return fmt.Errorf("peering failed: %v", err)
|
||||
}
|
||||
sendConn.SetDeadline(time.Now().Add(timeout))
|
||||
|
||||
// Open receiving conn.
|
||||
recvConn, err := s.dial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer recvConn.Close()
|
||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
||||
return fmt.Errorf("peering failed: %v", err)
|
||||
}
|
||||
recvConn.SetDeadline(time.Now().Add(timeout))
|
||||
|
||||
if err = sendConn.Write(ethProto, eth.TransactionsMsg, txs); err != nil {
|
||||
return fmt.Errorf("failed to write message to connection: %w", err)
|
||||
}
|
||||
|
||||
// Make map of invalid txs.
|
||||
invalids := make(map[common.Hash]struct{})
|
||||
for _, tx := range txs {
|
||||
invalids[tx.Hash()] = struct{}{}
|
||||
}
|
||||
|
||||
// Get repsonses.
|
||||
recvConn.SetReadDeadline(time.Now().Add(timeout))
|
||||
for {
|
||||
msg, err := recvConn.ReadEth()
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
// Successful if no invalid txs are propagated before timeout.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to read from connection: %w", err)
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *eth.TransactionsPacket:
|
||||
for _, tx := range txs {
|
||||
if _, ok := invalids[tx.Hash()]; ok {
|
||||
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
||||
}
|
||||
}
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
for _, hash := range msg.Hashes {
|
||||
if _, ok := invalids[hash]; ok {
|
||||
return fmt.Errorf("received bad tx: %s", hash)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected eth message: %v", pretty.Sdump(msg))
|
||||
}
|
||||
}
|
||||
return signedTx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ func rlpxEthTest(ctx *cli.Context) error {
|
|||
if ctx.NArg() < 3 {
|
||||
exit("missing path to chain.rlp as command-line argument")
|
||||
}
|
||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
|
||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2), ctx.Args().Get(3))
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ func rlpxSnapTest(ctx *cli.Context) error {
|
|||
if ctx.NArg() < 3 {
|
||||
exit("missing path to chain.rlp as command-line argument")
|
||||
}
|
||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
|
||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2), ctx.Args().Get(3))
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue