cmd/devp2p: update eth/snap protocol test suites for PoS (#28340)

Here we update the eth and snap protocol test suites with a new test chain,
created by the hivechain tool. The new test chain uses proof-of-stake. As such,
tests using PoW block propagation in the eth protocol are removed. The test suite
now connects to the node under test using the engine API in order to make it
accept transactions. 

The snap protocol test suite has been rewritten to output test descriptions and
log requests more verbosely.

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
lightclient 2023-12-20 09:23:48 -07:00 committed by GitHub
parent 8c2d455ccd
commit 577be37e0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 22764 additions and 2109 deletions

@ -108,31 +108,32 @@ Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.
The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth].
To run the eth protocol test suite against your implementation, the node needs to be initialized as such:
To run the eth protocol test suite against your implementation, the node needs to be initialized
with our test chain. The chain files are located in `./cmd/devp2p/internal/ethtest/testdata`.
1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory
2. import the `halfchain.rlp` file in the `testdata` directory
3. run geth with the following flags:
```
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5
```
1. initialize the geth node with the `genesis.json` file
2. import blocks from `chain.rlp`
3. run the client using the resulting database. For geth, use a command like the one below:
Then, run the following command, replacing `<enode>` with the enode of the geth node:
```
devp2p rlpx eth-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
```
geth \
--datadir <datadir> \
--nodiscover \
--nat=none \
--networkid 3503995874084926 \
--verbosity 5 \
--authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
Note that the tests also require access to the engine API.
The test suite can now be executed using the devp2p tool.
devp2p rlpx eth-test \
--chain internal/ethtest/testdata \
--node enode://.... \
--engineapi http://127.0.0.1:8551 \
--jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again.
#### Eth66 Test Suite
The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically.
To run the eth66 protocol test suite, initialize a geth node as described above and run the following command,
replacing `<enode>` with the enode of the geth node:
```
devp2p rlpx eth66-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
```
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup

@ -17,27 +17,118 @@
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"
"golang.org/x/exp/slices"
)
// 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 // state of head block
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]
}
// AccountsInHashOrder returns all accounts of the head state, ordered by hash of address.
func (c *Chain) AccountsInHashOrder() []state.DumpAccount {
list := make([]state.DumpAccount, len(c.state))
i := 0
for addr, acc := range c.state {
addr := addr
list[i] = acc
list[i].Address = &addr
if len(acc.AddressHash) != 32 {
panic(fmt.Errorf("missing/invalid SecureKey in dump account %v", addr))
}
i++
}
slices.SortFunc(list, func(x, y state.DumpAccount) int {
return bytes.Compare(x.AddressHash, y.AddressHash)
})
return list
}
// CodeHashes returns all bytecode hashes contained in the head state.
func (c *Chain) CodeHashes() []common.Hash {
var hashes []common.Hash
seen := make(map[common.Hash]struct{})
seen[types.EmptyCodeHash] = struct{}{}
for _, acc := range c.state {
h := common.BytesToHash(acc.CodeHash)
if _, ok := seen[h]; ok {
continue
}
hashes = append(hashes, h)
seen[h] = struct{}{}
}
slices.SortFunc(hashes, (common.Hash).Cmp)
return hashes
}
// Len returns the length of the chain.
@ -45,6 +136,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 +151,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 +164,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 +223,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 +261,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 +307,47 @@ 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)
}
state := make(map[common.Address]state.DumpAccount)
for key, acct := range dump.Accounts {
var addr common.Address
if err := addr.UnmarshalText([]byte(key)); err != nil {
return nil, fmt.Errorf("invalid address %q", key)
}
state[addr] = acct
}
return state, 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: &eth.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: &eth.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: &eth.GetBlockHeadersRequest{
Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
Amount: uint64(1),

@ -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 = &eth.StatusPacket{
ProtocolVersion: uint32(c.negotiatedProtoVersion),
NetworkID: chain.config.ChainID.Uint64(),
TD: chain.TD(),
Head: chain.blocks[chain.Len()-1].Hash(),
Genesis: chain.blocks[0].Hash(),
ForkID: chain.ForkID(),
}
}
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil {
return fmt.Errorf("write to connection failed: %v", err)
}
return nil
}

@ -0,0 +1,69 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package ethtest
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/golang-jwt/jwt/v4"
)
// EngineClient is a wrapper around engine-related data.
type EngineClient struct {
url string
jwt [32]byte
headfcu []byte
}
// NewEngineClient creates a new engine client.
func NewEngineClient(dir, url, jwt string) (*EngineClient, error) {
headfcu, err := os.ReadFile(path.Join(dir, "headfcu.json"))
if err != nil {
return nil, fmt.Errorf("failed to read headfcu: %w", err)
}
return &EngineClient{url, common.HexToHash(jwt), headfcu}, nil
}
// token returns the jwt claim token for authorization.
func (ec *EngineClient) token() string {
claims := jwt.RegisteredClaims{IssuedAt: jwt.NewNumericDate(time.Now())}
token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(ec.jwt[:])
return token
}
// sendForkchoiceUpdated sends an fcu for the head of the generated chain.
func (ec *EngineClient) sendForkchoiceUpdated() error {
var (
req, _ = http.NewRequest(http.MethodPost, ec.url, io.NopCloser(bytes.NewReader(ec.headfcu)))
header = make(http.Header)
)
// Set header
header.Set("accept", "application/json")
header.Set("content-type", "application/json")
header.Set("Authorization", fmt.Sprintf("Bearer %v", ec.token()))
req.Header = header
_, err := new(http.Client).Do(req)
return err
}

@ -1,650 +0,0 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package ethtest
import (
"errors"
"fmt"
"net"
"reflect"
"strings"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/rlpx"
)
var (
pretty = spew.ConfigState{
Indent: " ",
DisableCapacities: true,
DisablePointerAddresses: true,
SortKeys: true,
}
timeout = 20 * time.Second
)
// dial attempts to dial the given node and perform a handshake,
// returning the created Conn if successful.
func (s *Suite) dial() (*Conn, error) {
// dial
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
if err != nil {
return nil, err
}
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())}
// do encHandshake
conn.ourKey, _ = crypto.GenerateKey()
_, err = conn.Handshake(conn.ourKey)
if err != nil {
conn.Close()
return nil, err
}
// set default p2p capabilities
conn.caps = []p2p.Cap{
{Name: "eth", Version: 67},
{Name: "eth", Version: 68},
}
conn.ourHighestProtoVersion = 68
return &conn, nil
}
// dialSnap creates a connection with snap/1 capability.
func (s *Suite) dialSnap() (*Conn, error) {
conn, err := s.dial()
if err != nil {
return nil, fmt.Errorf("dial failed: %v", err)
}
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1})
conn.ourHighestSnapProtoVersion = 1
return conn, nil
}
// peer performs both the protocol handshake and the status message
// exchange with the node in order to peer with it.
func (c *Conn) peer(chain *Chain, status *Status) error {
if err := c.handshake(); err != nil {
return fmt.Errorf("handshake failed: %v", err)
}
if _, err := c.statusExchange(chain, status); err != nil {
return fmt.Errorf("status exchange failed: %v", err)
}
return nil
}
// handshake performs a protocol handshake with the node.
func (c *Conn) handshake() error {
defer c.SetDeadline(time.Time{})
c.SetDeadline(time.Now().Add(10 * time.Second))
// write hello to client
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
ourHandshake := &Hello{
Version: 5,
Caps: c.caps,
ID: pub0,
}
if err := c.Write(ourHandshake); err != nil {
return fmt.Errorf("write to connection failed: %v", err)
}
// read hello from client
switch msg := c.Read().(type) {
case *Hello:
// set snappy if version is at least 5
if msg.Version >= 5 {
c.SetSnappy(true)
}
c.negotiateEthProtocol(msg.Caps)
if c.negotiatedProtoVersion == 0 {
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion)
}
// If we require snap, verify that it was negotiated
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion {
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion)
}
return nil
default:
return fmt.Errorf("bad handshake: %#v", msg)
}
}
// negotiateEthProtocol sets the Conn's eth protocol version to highest
// advertised capability from peer.
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
var highestEthVersion uint
var highestSnapVersion uint
for _, capability := range caps {
switch capability.Name {
case "eth":
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion {
highestEthVersion = capability.Version
}
case "snap":
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion {
highestSnapVersion = capability.Version
}
}
}
c.negotiatedProtoVersion = highestEthVersion
c.negotiatedSnapProtoVersion = highestSnapVersion
}
// statusExchange performs a `Status` message exchange with the given node.
func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) {
defer c.SetDeadline(time.Time{})
c.SetDeadline(time.Now().Add(20 * time.Second))
// read status message from client
var message Message
loop:
for {
switch msg := c.Read().(type) {
case *Status:
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want {
return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
want, chain.blocks[chain.Len()-1].NumberU64(), have)
}
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want {
return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want)
}
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
}
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) {
return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want)
}
message = msg
break loop
case *Disconnect:
return nil, fmt.Errorf("disconnect received: %v", msg.Reason)
case *Ping:
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
// (PINGs should not be a response upon fresh connection)
default:
return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg))
}
}
// make sure eth protocol version is set for negotiation
if c.negotiatedProtoVersion == 0 {
return nil, errors.New("eth protocol version must be set in Conn")
}
if status == nil {
// default status message
status = &Status{
ProtocolVersion: uint32(c.negotiatedProtoVersion),
NetworkID: chain.chainConfig.ChainID.Uint64(),
TD: chain.TD(),
Head: chain.blocks[chain.Len()-1].Hash(),
Genesis: chain.blocks[0].Hash(),
ForkID: chain.ForkID(),
}
}
if err := c.Write(status); err != nil {
return nil, fmt.Errorf("write to connection failed: %v", err)
}
return message, nil
}
// createSendAndRecvConns creates two connections, one for sending messages to the
// node, and one for receiving messages from the node.
func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) {
sendConn, err := s.dial()
if err != nil {
return nil, nil, fmt.Errorf("dial failed: %v", err)
}
recvConn, err := s.dial()
if err != nil {
sendConn.Close()
return nil, nil, fmt.Errorf("dial failed: %v", err)
}
return sendConn, recvConn, nil
}
// readAndServe serves GetBlockHeaders requests while waiting
// on another message from the node.
func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message {
start := time.Now()
for time.Since(start) < timeout {
c.SetReadDeadline(time.Now().Add(10 * time.Second))
msg := c.Read()
switch msg := msg.(type) {
case *Ping:
c.Write(&Pong{})
case *GetBlockHeaders:
headers, err := chain.GetHeaders(msg)
if err != nil {
return errorf("could not get headers for inbound header request: %v", err)
}
resp := &BlockHeaders{
RequestId: msg.ReqID(),
BlockHeadersRequest: eth.BlockHeadersRequest(headers),
}
if err := c.Write(resp); err != nil {
return errorf("could not write to connection: %v", err)
}
default:
return msg
}
}
return errorf("no message received within %v", timeout)
}
// headersRequest executes the given `GetBlockHeaders` request.
func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint64) ([]*types.Header, error) {
defer c.SetReadDeadline(time.Time{})
c.SetReadDeadline(time.Now().Add(20 * time.Second))
// write request
request.RequestId = reqID
if err := c.Write(request); err != nil {
return nil, fmt.Errorf("could not write to connection: %v", err)
}
// wait for response
msg := c.waitForResponse(chain, timeout, request.RequestId)
resp, ok := msg.(*BlockHeaders)
if !ok {
return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg))
}
headers := []*types.Header(resp.BlockHeadersRequest)
return headers, nil
}
func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) {
defer c.SetReadDeadline(time.Time{})
c.SetReadDeadline(time.Now().Add(5 * time.Second))
if err := c.Write(msg); err != nil {
return nil, fmt.Errorf("could not write to connection: %v", err)
}
return c.ReadSnap(id)
}
// headersMatch returns whether the received headers match the given request
func headersMatch(expected []*types.Header, headers []*types.Header) bool {
return reflect.DeepEqual(expected, headers)
}
// waitForResponse reads from the connection until a response with the expected
// request ID is received.
func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message {
for {
msg := c.readAndServe(chain, timeout)
if msg.ReqID() == requestID {
return msg
}
}
}
// sendNextBlock broadcasts the next block in the chain and waits
// for the node to propagate the block and import it into its chain.
func (s *Suite) sendNextBlock() error {
// set up sending and receiving connections
sendConn, recvConn, err := s.createSendAndRecvConns()
if err != nil {
return err
}
defer sendConn.Close()
defer recvConn.Close()
if err = sendConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
if err = recvConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
// create new block announcement
nextBlock := s.fullChain.blocks[s.chain.Len()]
blockAnnouncement := &NewBlock{
Block: nextBlock,
TD: s.fullChain.TotalDifficultyAt(s.chain.Len()),
}
// send announcement and wait for node to request the header
if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil {
return fmt.Errorf("failed to announce block: %v", err)
}
// wait for client to update its chain
if err = s.waitForBlockImport(recvConn, nextBlock); err != nil {
return fmt.Errorf("failed to receive confirmation of block import: %v", err)
}
// update test suite chain
s.chain.blocks = append(s.chain.blocks, nextBlock)
return nil
}
// testAnnounce writes a block announcement to the node and waits for the node
// to propagate it.
func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error {
if err := sendConn.Write(blockAnnouncement); err != nil {
return fmt.Errorf("could not write to connection: %v", err)
}
return s.waitAnnounce(receiveConn, blockAnnouncement)
}
// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node.
func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error {
for {
switch msg := conn.readAndServe(s.chain, timeout).(type) {
case *NewBlock:
if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) {
return fmt.Errorf("wrong header in block announcement: \nexpected %v "+
"\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header())
}
if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) {
return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD)
}
return nil
case *NewBlockHashes:
hashes := *msg
if blockAnnouncement.Block.Hash() != hashes[0].Hash {
return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash)
}
return nil
// ignore tx announcements from previous tests
case *NewPooledTransactionHashes66:
continue
case *NewPooledTransactionHashes:
continue
case *Transactions:
continue
default:
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
}
}
}
func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error {
defer conn.SetReadDeadline(time.Time{})
conn.SetReadDeadline(time.Now().Add(20 * time.Second))
// create request
req := &GetBlockHeaders{
GetBlockHeadersRequest: &eth.GetBlockHeadersRequest{
Origin: eth.HashOrNumber{Hash: block.Hash()},
Amount: 1,
},
}
// loop until BlockHeaders response contains desired block, confirming the
// node imported the block
for {
requestID := uint64(54)
headers, err := conn.headersRequest(req, s.chain, requestID)
if err != nil {
return fmt.Errorf("GetBlockHeader request failed: %v", err)
}
// if headers response is empty, node hasn't imported block yet, try again
if len(headers) == 0 {
time.Sleep(100 * time.Millisecond)
continue
}
if !reflect.DeepEqual(block.Header(), headers[0]) {
return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0])
}
return nil
}
}
func (s *Suite) oldAnnounce() error {
sendConn, receiveConn, err := s.createSendAndRecvConns()
if err != nil {
return err
}
defer sendConn.Close()
defer receiveConn.Close()
if err := sendConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
if err := receiveConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
// create old block announcement
oldBlockAnnounce := &NewBlock{
Block: s.chain.blocks[len(s.chain.blocks)/2],
TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(),
}
if err := sendConn.Write(oldBlockAnnounce); err != nil {
return fmt.Errorf("could not write to connection: %v", err)
}
// wait to see if the announcement is propagated
switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) {
case *NewBlock:
block := *msg
if block.Block.Hash() == oldBlockAnnounce.Block.Hash() {
return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg))
}
case *NewBlockHashes:
hashes := *msg
for _, hash := range hashes {
if hash.Hash == oldBlockAnnounce.Block.Hash() {
return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg))
}
}
case *Error:
errMsg := *msg
// check to make sure error is timeout (propagation didn't come through == test successful)
if !strings.Contains(errMsg.String(), "timeout") {
return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg))
}
default:
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
}
return nil
}
func (s *Suite) maliciousHandshakes(t *utesting.T) error {
conn, err := s.dial()
if err != nil {
return fmt.Errorf("dial failed: %v", err)
}
defer conn.Close()
// write hello to client
pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:]
handshakes := []*Hello{
{
Version: 5,
Caps: []p2p.Cap{
{Name: largeString(2), Version: 64},
},
ID: pub0,
},
{
Version: 5,
Caps: []p2p.Cap{
{Name: "eth", Version: 64},
{Name: "eth", Version: 65},
},
ID: append(pub0, byte(0)),
},
{
Version: 5,
Caps: []p2p.Cap{
{Name: "eth", Version: 64},
{Name: "eth", Version: 65},
},
ID: append(pub0, pub0...),
},
{
Version: 5,
Caps: []p2p.Cap{
{Name: "eth", Version: 64},
{Name: "eth", Version: 65},
},
ID: largeBuffer(2),
},
{
Version: 5,
Caps: []p2p.Cap{
{Name: largeString(2), Version: 64},
},
ID: largeBuffer(2),
},
}
for i, handshake := range handshakes {
t.Logf("Testing malicious handshake %v\n", i)
if err := conn.Write(handshake); err != nil {
return fmt.Errorf("could not write to connection: %v", err)
}
// check that the peer disconnected
for i := 0; i < 2; i++ {
switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) {
case *Disconnect:
case *Error:
case *Hello:
// Discard one hello as Hello's are sent concurrently
continue
default:
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
}
}
// dial for the next round
conn, err = s.dial()
if err != nil {
return fmt.Errorf("dial failed: %v", err)
}
}
return nil
}
func (s *Suite) maliciousStatus(conn *Conn) error {
if err := conn.handshake(); err != nil {
return fmt.Errorf("handshake failed: %v", err)
}
status := &Status{
ProtocolVersion: uint32(conn.negotiatedProtoVersion),
NetworkID: s.chain.chainConfig.ChainID.Uint64(),
TD: largeNumber(2),
Head: s.chain.blocks[s.chain.Len()-1].Hash(),
Genesis: s.chain.blocks[0].Hash(),
ForkID: s.chain.ForkID(),
}
// get status
msg, err := conn.statusExchange(s.chain, status)
if err != nil {
return fmt.Errorf("status exchange failed: %v", err)
}
switch msg := msg.(type) {
case *Status:
default:
return fmt.Errorf("expected status, got: %#v ", msg)
}
// wait for disconnect
switch msg := conn.readAndServe(s.chain, timeout).(type) {
case *Disconnect:
return nil
case *Error:
return nil
default:
return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg))
}
}
func (s *Suite) hashAnnounce() error {
// create connections
sendConn, recvConn, err := s.createSendAndRecvConns()
if err != nil {
return fmt.Errorf("failed to create connections: %v", err)
}
defer sendConn.Close()
defer recvConn.Close()
if err := sendConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
if err := recvConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
// create NewBlockHashes announcement
type anno struct {
Hash common.Hash // Hash of one particular block being announced
Number uint64 // Number of one particular block being announced
}
nextBlock := s.fullChain.blocks[s.chain.Len()]
announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()}
newBlockHash := &NewBlockHashes{announcement}
if err := sendConn.Write(newBlockHash); err != nil {
return fmt.Errorf("failed to write to connection: %v", err)
}
// Announcement sent, now wait for a header request
msg := sendConn.Read()
blockHeaderReq, ok := msg.(*GetBlockHeaders)
if !ok {
return fmt.Errorf("unexpected %s", pretty.Sdump(msg))
}
if blockHeaderReq.Amount != 1 {
return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount)
}
if blockHeaderReq.Origin.Hash != announcement.Hash {
return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v",
pretty.Sdump(announcement),
pretty.Sdump(blockHeaderReq))
}
err = sendConn.Write(&BlockHeaders{
RequestId: blockHeaderReq.ReqID(),
BlockHeadersRequest: eth.BlockHeadersRequest{nextBlock.Header()},
})
if err != nil {
return fmt.Errorf("failed to write to connection: %v", err)
}
// wait for block announcement
msg = recvConn.readAndServe(s.chain, timeout)
switch msg := msg.(type) {
case *NewBlockHashes:
hashes := *msg
if len(hashes) != 1 {
return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes))
}
if nextBlock.Hash() != hashes[0].Hash {
return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(),
hashes[0].Hash)
}
case *NewBlock:
// node should only propagate NewBlock without having requested the body if the body is empty
nextBlockBody := nextBlock.Body()
if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 {
return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg))
}
if msg.Block.Hash() != nextBlock.Hash() {
return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v",
nextBlock.Hash(), msg.Block.Hash())
}
// check to make sure header matches header that was sent to the node
if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) {
return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header())
}
default:
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
}
// confirm node imported block
if err := s.waitForBlockImport(recvConn, nextBlock); err != nil {
return fmt.Errorf("error waiting for node to import new block: %v", err)
}
// update the chain
s.chain.blocks = append(s.chain.blocks, nextBlock)
return nil
}

@ -1,80 +0,0 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package ethtest
import (
"crypto/rand"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
// largeNumber returns a very large big.Int.
func largeNumber(megabytes int) *big.Int {
buf := make([]byte, megabytes*1024*1024)
rand.Read(buf)
bigint := new(big.Int)
bigint.SetBytes(buf)
return bigint
}
// largeBuffer returns a very large buffer.
func largeBuffer(megabytes int) []byte {
buf := make([]byte, megabytes*1024*1024)
rand.Read(buf)
return buf
}
// largeString returns a very large string.
func largeString(megabytes int) string {
buf := make([]byte, megabytes*1024*1024)
rand.Read(buf)
return hexutil.Encode(buf)
}
func largeBlock() *types.Block {
return types.NewBlockWithHeader(largeHeader())
}
// Returns a random hash
func randHash() common.Hash {
var h common.Hash
rand.Read(h[:])
return h
}
func largeHeader() *types.Header {
return &types.Header{
MixDigest: randHash(),
ReceiptHash: randHash(),
TxHash: randHash(),
Nonce: types.BlockNonce{},
Extra: []byte{},
Bloom: types.Bloom{},
GasUsed: 0,
Coinbase: common.Address{},
GasLimit: 0,
UncleHash: types.EmptyUncleHash,
Time: 1337,
ParentHash: randHash(),
Root: randHash(),
Number: largeNumber(2),
Difficulty: largeNumber(2),
}
}

@ -0,0 +1,9 @@
#!/bin/sh
hivechain generate \
--fork-interval 6 \
--tx-interval 1 \
--length 500 \
--outdir testdata \
--lastfork cancun \
--outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv

@ -0,0 +1,87 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package ethtest
import (
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
)
// Unexported devp2p message codes from p2p/peer.go.
const (
handshakeMsg = 0x00
discMsg = 0x01
pingMsg = 0x02
pongMsg = 0x03
)
// Unexported devp2p protocol lengths from p2p package.
const (
baseProtoLen = 16
ethProtoLen = 17
snapProtoLen = 8
)
// Unexported handshake structure from p2p/peer.go.
type protoHandshake struct {
Version uint64
Name string
Caps []p2p.Cap
ListenPort uint64
ID []byte
Rest []rlp.RawValue `rlp:"tail"`
}
type Hello = protoHandshake
// Proto is an enum representing devp2p protocol types.
type Proto int
const (
baseProto Proto = iota
ethProto
snapProto
)
// getProto returns the protocol a certain message code is associated with
// (assuming the negotiated capabilities are exactly {eth,snap})
func getProto(code uint64) Proto {
switch {
case code < baseProtoLen:
return baseProto
case code < baseProtoLen+ethProtoLen:
return ethProto
case code < baseProtoLen+ethProtoLen+snapProtoLen:
return snapProto
default:
panic("unhandled msg code beyond last protocol")
}
}
// protoOffset will return the offset at which the specified protocol's messages
// begin.
func protoOffset(proto Proto) uint64 {
switch proto {
case baseProto:
return 0
case ethProto:
return baseProtoLen
case snapProto:
return baseProtoLen + ethProtoLen
default:
panic("unhandled protocol")
}
}

File diff suppressed because it is too large Load Diff

@ -1,60 +0,0 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package ethtest
import "github.com/ethereum/go-ethereum/eth/protocols/snap"
// GetAccountRange represents an account range query.
type GetAccountRange snap.GetAccountRangePacket
func (msg GetAccountRange) Code() int { return 33 }
func (msg GetAccountRange) ReqID() uint64 { return msg.ID }
type AccountRange snap.AccountRangePacket
func (msg AccountRange) Code() int { return 34 }
func (msg AccountRange) ReqID() uint64 { return msg.ID }
type GetStorageRanges snap.GetStorageRangesPacket
func (msg GetStorageRanges) Code() int { return 35 }
func (msg GetStorageRanges) ReqID() uint64 { return msg.ID }
type StorageRanges snap.StorageRangesPacket
func (msg StorageRanges) Code() int { return 36 }
func (msg StorageRanges) ReqID() uint64 { return msg.ID }
type GetByteCodes snap.GetByteCodesPacket
func (msg GetByteCodes) Code() int { return 37 }
func (msg GetByteCodes) ReqID() uint64 { return msg.ID }
type ByteCodes snap.ByteCodesPacket
func (msg ByteCodes) Code() int { return 38 }
func (msg ByteCodes) ReqID() uint64 { return msg.ID }
type GetTrieNodes snap.GetTrieNodesPacket
func (msg GetTrieNodes) Code() int { return 39 }
func (msg GetTrieNodes) ReqID() uint64 { return msg.ID }
type TrieNodes snap.TrieNodesPacket
func (msg TrieNodes) Code() int { return 40 }
func (msg TrieNodes) ReqID() uint64 { return msg.ID }

File diff suppressed because it is too large Load Diff

@ -17,38 +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/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)
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)
}
for _, test := range suite.EthTests() {
t.Run(test.Name, func(t *testing.T) {
result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
if result[0].Failed {
t.Fatal()
}
@ -57,20 +72,23 @@ 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", jwtPath)
if err != nil {
t.Fatalf("could not run geth: %v", err)
}
defer geth.Close()
suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile)
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)
}
for _, test := range suite.SnapTests() {
t.Run(test.Name, func(t *testing.T) {
result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
if result[0].Failed {
t.Fatal()
}
@ -79,20 +97,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: 0,
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 +125,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, &ethconfig.Config{
Genesis: &chain.genesis,
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
@ -122,8 +142,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
}

@ -0,0 +1,62 @@
{
"0x0c2c51a0990aee1d73c1228de158688341557508": {
"key": "0xbfcd0e032489319f4e5ca03e643b2025db624be6cf99cbfed90c4502e3754850"
},
"0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
"key": "0x457075f6822ac29481154792f65c5f1ec335b4fea9ca20f3fea8fa1d78a12c68"
},
"0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
"key": "0x865898edcf43206d138c93f1bbd86311f4657b057658558888aa5ac4309626a6"
},
"0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
"key": "0xee7f7875d826d7443ccc5c174e38b2c436095018774248a8074ee92d8914dcdb"
},
"0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
"key": "0x25e6ce8611cefb5cd338aeaa9292ed2139714668d123a4fb156cabb42051b5b7"
},
"0x2d389075be5be9f2246ad654ce152cf05990b209": {
"key": "0x19168cd7767604b3d19b99dc3da1302b9ccb6ee9ad61660859e07acd4a2625dd"
},
"0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
"key": "0x71aa7d299c7607dabfc3d0e5213d612b5e4a97455b596c2f642daac43fa5eeaa"
},
"0x4340ee1b812acb40a1eb561c019c327b243b92df": {
"key": "0x47f666f20e2175606355acec0ea1b37870c15e5797e962340da7ad7972a537e8"
},
"0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
"key": "0xa88293fefc623644969e2ce6919fb0dbd0fd64f640293b4bf7e1a81c97e7fc7f"
},
"0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
"key": "0x6e1e16a9c15641c73bf6e237f9293ab1d4e7c12b9adf83cfc94bcf969670f72d"
},
"0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
"key": "0x41be4e00aac79f7ffbb3455053ec05e971645440d594c047cdcc56a3c7458bd6"
},
"0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
"key": "0xc825f31cd8792851e33a290b3d749e553983111fc1f36dfbbdb45f101973f6a9"
},
"0x717f8aa2b982bee0e29f573d31df288663e1ce16": {
"key": "0x8d0faa04ae0f9bc3cd4c890aa025d5f40916f4729538b19471c0beefe11d9e19"
},
"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
"key": "0x4552dbe6ca4699322b5d923d0c9bcdd24644f5db8bf89a085b67c6c49b8a1b91"
},
"0x83c7e323d189f18725ac510004fdc2941f8c4a78": {
"key": "0x34391cbbf06956bb506f45ec179cdd84df526aa364e27bbde65db9c15d866d00"
},
"0x84e75c28348fb86acea1a93a39426d7d60f4cc46": {
"key": "0xf6a8f1603b8368f3ca373292b7310c53bec7b508aecacd442554ebc1c5d0c856"
},
"0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": {
"key": "0x8d56bcbcf2c1b7109e1396a28d7a0234e33544ade74ea32c460ce4a443b239b1"
},
"0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": {
"key": "0xfc39d1c9ddbba176d806ebb42d7460189fe56ca163ad3eb6143bfc6beb6f6f72"
},
"0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
"key": "0x9ee3fd550664b246ad7cdba07162dd25530a3b1d51476dd1d85bbc29f0592684"
},
"0xeda8645ba6948855e3b3cd596bbb07596d59c603": {
"key": "0x14cdde09d1640eb8c3cda063891b0453073f57719583381ff78811efa6d4199f"
}
}

Binary file not shown.

@ -0,0 +1,20 @@
{
"HIVE_CANCUN_TIMESTAMP": "840",
"HIVE_CHAIN_ID": "3503995874084926",
"HIVE_FORK_ARROW_GLACIER": "60",
"HIVE_FORK_BERLIN": "48",
"HIVE_FORK_BYZANTIUM": "18",
"HIVE_FORK_CONSTANTINOPLE": "24",
"HIVE_FORK_GRAY_GLACIER": "66",
"HIVE_FORK_HOMESTEAD": "0",
"HIVE_FORK_ISTANBUL": "36",
"HIVE_FORK_LONDON": "54",
"HIVE_FORK_MUIR_GLACIER": "42",
"HIVE_FORK_PETERSBURG": "30",
"HIVE_FORK_SPURIOUS": "12",
"HIVE_FORK_TANGERINE": "6",
"HIVE_MERGE_BLOCK_ID": "72",
"HIVE_NETWORK_ID": "3503995874084926",
"HIVE_SHANGHAI_TIMESTAMP": "780",
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784"
}

@ -1,27 +1,112 @@
{
"config": {
"chainId": 19763,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"terminalTotalDifficultyPassed": true,
"ethash": {}
"config": {
"chainId": 3503995874084926,
"homesteadBlock": 0,
"eip150Block": 6,
"eip155Block": 12,
"eip158Block": 12,
"byzantiumBlock": 18,
"constantinopleBlock": 24,
"petersburgBlock": 30,
"istanbulBlock": 36,
"muirGlacierBlock": 42,
"berlinBlock": 48,
"londonBlock": 54,
"arrowGlacierBlock": 60,
"grayGlacierBlock": 66,
"mergeNetsplitBlock": 72,
"shanghaiTime": 780,
"cancunTime": 840,
"terminalTotalDifficulty": 9454784,
"terminalTotalDifficultyPassed": true,
"ethash": {}
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0x68697665636861696e",
"gasLimit": "0x23f3e20",
"difficulty": "0x20000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
"balance": "0x2a"
},
"nonce": "0xdeadbeefdeadbeef",
"timestamp": "0x0",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x80000000",
"difficulty": "0x20000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"71562b71999873db5b286df957af199ec94617f7": {
"balance": "0xffffffffffffffffffffffffff"
}
"0c2c51a0990aee1d73c1228de158688341557508": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"2d389075be5be9f2246ad654ce152cf05990b209": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"4340ee1b812acb40a1eb561c019c327b243b92df": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"717f8aa2b982bee0e29f573d31df288663e1ce16": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"84e75c28348fb86acea1a93a39426d7d60f4cc46": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"8bebc8ba651aee624937e7d897853ac30c95a067": {
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
},
"balance": "0x1",
"nonce": "0x1"
},
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
},
"eda8645ba6948855e3b3cd596bbb07596d59c603": {
"balance": "0xc097ce7bc90715b34b9f1000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": null,
"excessBlobGas": null,
"blobGasUsed": null
}

@ -0,0 +1,23 @@
{
"parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"miner": "0x0000000000000000000000000000000000000000",
"stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94",
"transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48",
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x0",
"number": "0x1f4",
"gasLimit": "0x47e7c40",
"gasUsed": "0x5208",
"timestamp": "0x1388",
"extraData": "0x",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"baseFeePerGas": "0x7",
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"blobGasUsed": "0x0",
"excessBlobGas": "0x0",
"parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c",
"hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
}

@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": "fcu500",
"method": "engine_forkchoiceUpdatedV3",
"params": [
{
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
},
null
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -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")
}

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/rlpx"
"github.com/ethereum/go-ethereum/rlp"
"github.com/urfave/cli/v2"
@ -46,22 +47,30 @@ var (
}
rlpxEthTestCommand = &cli.Command{
Name: "eth-test",
Usage: "Runs tests against a node",
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
Usage: "Runs eth protocol tests against a node",
ArgsUsage: "<node>",
Action: rlpxEthTest,
Flags: []cli.Flag{
testPatternFlag,
testTAPFlag,
testChainDirFlag,
testNodeFlag,
testNodeJWTFlag,
testNodeEngineFlag,
},
}
rlpxSnapTestCommand = &cli.Command{
Name: "snap-test",
Usage: "Runs tests against a node",
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
Usage: "Runs snap protocol tests against a node",
ArgsUsage: "",
Action: rlpxSnapTest,
Flags: []cli.Flag{
testPatternFlag,
testTAPFlag,
testChainDirFlag,
testNodeFlag,
testNodeJWTFlag,
testNodeEngineFlag,
},
}
)
@ -103,10 +112,8 @@ func rlpxPing(ctx *cli.Context) error {
// rlpxEthTest runs the eth protocol test suite.
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))
p := cliTestParams(ctx)
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
if err != nil {
exit(err)
}
@ -115,12 +122,44 @@ func rlpxEthTest(ctx *cli.Context) error {
// rlpxSnapTest runs the snap protocol test suite.
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))
p := cliTestParams(ctx)
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
if err != nil {
exit(err)
}
return runTests(ctx, suite.SnapTests())
}
type testParams struct {
node *enode.Node
engineAPI string
jwt string
chainDir string
}
func cliTestParams(ctx *cli.Context) *testParams {
nodeStr := ctx.String(testNodeFlag.Name)
if nodeStr == "" {
exit(fmt.Errorf("missing -%s", testNodeFlag.Name))
}
node, err := parseNode(nodeStr)
if err != nil {
exit(err)
}
p := testParams{
node: node,
engineAPI: ctx.String(testNodeEngineFlag.Name),
jwt: ctx.String(testNodeJWTFlag.Name),
chainDir: ctx.String(testChainDirFlag.Name),
}
if p.engineAPI == "" {
exit(fmt.Errorf("missing -%s", testNodeEngineFlag.Name))
}
if p.jwt == "" {
exit(fmt.Errorf("missing -%s", testNodeJWTFlag.Name))
}
if p.chainDir == "" {
exit(fmt.Errorf("missing -%s", testChainDirFlag.Name))
}
return &p
}

@ -20,6 +20,7 @@ import (
"os"
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
@ -27,23 +28,51 @@ import (
var (
testPatternFlag = &cli.StringFlag{
Name: "run",
Usage: "Pattern of test suite(s) to run",
Name: "run",
Usage: "Pattern of test suite(s) to run",
Category: flags.TestingCategory,
}
testTAPFlag = &cli.BoolFlag{
Name: "tap",
Usage: "Output TAP",
Name: "tap",
Usage: "Output test results in TAP format",
Category: flags.TestingCategory,
}
// for eth/snap tests
testChainDirFlag = &cli.StringFlag{
Name: "chain",
Usage: "Test chain directory (required)",
Category: flags.TestingCategory,
}
testNodeFlag = &cli.StringFlag{
Name: "node",
Usage: "Peer-to-Peer endpoint (ENR) of the test node (required)",
Category: flags.TestingCategory,
}
testNodeJWTFlag = &cli.StringFlag{
Name: "jwtsecret",
Usage: "JWT secret for the engine API of the test node (required)",
Category: flags.TestingCategory,
Value: "0x7365637265747365637265747365637265747365637265747365637265747365",
}
testNodeEngineFlag = &cli.StringFlag{
Name: "engineapi",
Usage: "Engine API endpoint of the test node (required)",
Category: flags.TestingCategory,
}
// These two are specific to the discovery tests.
testListen1Flag = &cli.StringFlag{
Name: "listen1",
Usage: "IP address of the first tester",
Value: v4test.Listen1,
Name: "listen1",
Usage: "IP address of the first tester",
Value: v4test.Listen1,
Category: flags.TestingCategory,
}
testListen2Flag = &cli.StringFlag{
Name: "listen2",
Usage: "IP address of the second tester",
Value: v4test.Listen2,
Name: "listen2",
Usage: "IP address of the second tester",
Value: v4test.Listen2,
Category: flags.TestingCategory,
}
)

@ -131,7 +131,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
continue
case *number < threshold:
log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
log.Debug("Current full block not old enough to freeze", "number", *number, "hash", hash, "delay", threshold)
backoff = true
continue

@ -977,6 +977,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error
// in transactions before obtaining lock
if err := pool.validateTxBasics(tx, local); err != nil {
errs[i] = err
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
invalidTxMeter.Mark(1)
continue
}

@ -35,6 +35,7 @@ const (
LoggingCategory = "LOGGING AND DEBUGGING"
MetricsCategory = "METRICS AND STATS"
MiscCategory = "MISC"
TestingCategory = "TESTING"
DeprecatedCategory = "ALIASED (deprecated)"
)