mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 16:59:26 +00:00
cmd/devp2p/internal/ethtest: add snap/2 (EIP-8189) tests
This commit is contained in:
parent
184bde8ca0
commit
4fd4120450
6 changed files with 483 additions and 2 deletions
|
|
@ -84,6 +84,19 @@ func (s *Suite) dialSnap() (*Conn, error) {
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
// dialSnap2 creates a connection advertising snap/2 as the only snap capability.
|
||||
// This is used by the snap/2 (EIP-8189) test suite to force the peer to
|
||||
// negotiate snap/2 rather than falling back to snap/1.
|
||||
func (s *Suite) dialSnap2() (*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: 2})
|
||||
conn.ourHighestSnapProtoVersion = 2
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Conn represents an individual connection with a peer
|
||||
type Conn struct {
|
||||
*rlpx.Conn
|
||||
|
|
@ -183,7 +196,10 @@ func (c *Conn) ReadEth() (any, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||
// ReadSnap reads a snap protocol response from the connection. It decodes
|
||||
// the full message catalog of both snap/1 and snap/2. The caller is
|
||||
// expected to only receive codes that were actually valid on the
|
||||
// negotiated protocol version.
|
||||
func (c *Conn) ReadSnap() (any, error) {
|
||||
c.SetReadDeadline(time.Now().Add(timeout))
|
||||
for {
|
||||
|
|
@ -215,6 +231,10 @@ func (c *Conn) ReadSnap() (any, error) {
|
|||
msg = new(snap.GetTrieNodesPacket)
|
||||
case snap.TrieNodesMsg:
|
||||
msg = new(snap.TrieNodesPacket)
|
||||
case snap.GetAccessListsMsg:
|
||||
msg = new(snap.GetAccessListsPacket)
|
||||
case snap.AccessListsMsg:
|
||||
msg = new(snap.AccessListsPacket)
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled snap code: %d", code))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ const (
|
|||
const (
|
||||
baseProtoLen = 16
|
||||
ethProtoLen = 18
|
||||
snapProtoLen = 8
|
||||
// snapProtoLen accommodates snap/2 (EIP-8189) which extends snap/1 with two
|
||||
// additional message codes (GetBlockAccessLists=0x08, BlockAccessLists=0x09).
|
||||
// Using 10 is safe for snap/1 connections because the extra codes are simply
|
||||
// never used on that protocol version.
|
||||
snapProtoLen = 10
|
||||
)
|
||||
|
||||
// Unexported handshake structure from p2p/peer.go.
|
||||
|
|
|
|||
397
cmd/devp2p/internal/ethtest/snap2.go
Normal file
397
cmd/devp2p/internal/ethtest/snap2.go
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
// Copyright 2026 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"
|
||||
"math/rand"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Snap/2 (EIP-8189) replaces trie node healing with BAL-based state catch-up.
|
||||
// It keeps 0x00..0x05 (AccountRange/StorageRanges/ByteCodes) unchanged, removes
|
||||
// GetTrieNodes (0x06) / TrieNodes (0x07), and adds GetBlockAccessLists (0x08) /
|
||||
// BlockAccessLists (0x09).
|
||||
//
|
||||
// The tests in this file focus on the wire behavior that is new or changed in
|
||||
// snap/2. Tests for the unchanged messages are already covered by the snap/1
|
||||
// suite in snap.go; the harness reuses the same code paths because those
|
||||
// message formats are identical across versions.
|
||||
|
||||
// TestSnap2Status performs an RLPx+eth+snap/2 handshake against the node,
|
||||
// verifying that the node advertises and negotiates snap/2.
|
||||
func (s *Suite) TestSnap2Status(t *utesting.T) {
|
||||
t.Log(`This test performs a snap/2 (EIP-8189) handshake. The peer is expected to
|
||||
advertise snap/2 as a p2p capability and accept the connection.`)
|
||||
|
||||
conn, err := s.dialSnap2()
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
if conn.negotiatedSnapProtoVersion != 2 {
|
||||
t.Fatalf("unexpected negotiated snap version: got %d, want 2", conn.negotiatedSnapProtoVersion)
|
||||
}
|
||||
}
|
||||
|
||||
type accessListsTest struct {
|
||||
nBytes uint64
|
||||
hashes []common.Hash
|
||||
|
||||
// minEntries/maxEntries bound the number of entries the response list
|
||||
// MUST contain. Per EIP-8189 the server may truncate from the tail when
|
||||
// the byte soft limit is reached, but MUST preserve request order.
|
||||
minEntries int
|
||||
maxEntries int
|
||||
|
||||
// expectAllEmpty is set for requests where every entry in the response is
|
||||
// expected to be the RLP empty string (e.g. random/unknown hashes, or
|
||||
// pre-Amsterdam blocks for which BALs are not available).
|
||||
expectAllEmpty bool
|
||||
|
||||
// mustBeEmptyAt lists positions (zero-based indices into hashes) that MUST
|
||||
// be returned as the RLP empty string. Used to assert that unknown hashes
|
||||
// in mixed requests do not receive fabricated BAL data.
|
||||
mustBeEmptyAt []int
|
||||
|
||||
desc string
|
||||
}
|
||||
|
||||
// TestSnap2GetBlockAccessLists exercises various forms of GetBlockAccessLists
|
||||
// requests defined in EIP-8189. Per the spec:
|
||||
//
|
||||
// - Nodes MUST always respond.
|
||||
// - Unavailable BALs are returned as the RLP empty string (0x80) at the
|
||||
// matching position.
|
||||
// - The server MAY return fewer entries than requested (respecting the byte
|
||||
// soft limit or QoS limits), truncating from the tail.
|
||||
// - Returned entries MUST preserve request order.
|
||||
// - When a BAL is returned, its keccak256(rlp.encode(bal)) MUST match the
|
||||
// block-access-list-hash field of the corresponding block header.
|
||||
func (s *Suite) TestSnap2GetBlockAccessLists(t *utesting.T) {
|
||||
var (
|
||||
head = s.chain.Head()
|
||||
headHash = head.Hash()
|
||||
preHash = s.chain.blocks[s.chain.Len()-2].Hash()
|
||||
unknown = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
||||
)
|
||||
|
||||
// Collect a window of recent canonical block hashes. Limit to at most 16
|
||||
// entries to keep the request small and well under any reasonable limit.
|
||||
var recent []common.Hash
|
||||
start := s.chain.Len() - 16
|
||||
if start < 1 {
|
||||
start = 1
|
||||
}
|
||||
for i := start; i < s.chain.Len(); i++ {
|
||||
recent = append(recent, s.chain.blocks[i].Hash())
|
||||
}
|
||||
|
||||
tests := []accessListsTest{
|
||||
{
|
||||
desc: `An empty request. The server must respond with an empty list and must
|
||||
not disconnect.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: nil,
|
||||
minEntries: 0,
|
||||
maxEntries: 0,
|
||||
expectAllEmpty: true,
|
||||
},
|
||||
{
|
||||
desc: `A request for a single random/unknown block hash. Per the spec the
|
||||
server must respond and include an RLP empty string (0x80) at that position.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: []common.Hash{unknown},
|
||||
minEntries: 1,
|
||||
maxEntries: 1,
|
||||
expectAllEmpty: true,
|
||||
},
|
||||
{
|
||||
desc: `A request for multiple random/unknown block hashes. The server must
|
||||
preserve request order and return an RLP empty string for each position.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: []common.Hash{
|
||||
unknown,
|
||||
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
|
||||
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
minEntries: 3,
|
||||
maxEntries: 3,
|
||||
expectAllEmpty: true,
|
||||
},
|
||||
{
|
||||
desc: `A request for the chain head. The server must respond. If the node is
|
||||
post-Amsterdam and has the BAL for this block, the returned BAL must hash to
|
||||
the block-access-list-hash in the header. Otherwise an empty entry is valid.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: []common.Hash{headHash},
|
||||
minEntries: 1,
|
||||
maxEntries: 1,
|
||||
},
|
||||
{
|
||||
desc: `A request for the chain head and its parent. The server must return
|
||||
exactly two entries, in request order.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: []common.Hash{headHash, preHash},
|
||||
minEntries: 2,
|
||||
maxEntries: 2,
|
||||
},
|
||||
{
|
||||
desc: `A mixed request with known and unknown hashes. The server must
|
||||
return entries in request order, with the RLP empty string at positions
|
||||
corresponding to unknown hashes.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: []common.Hash{headHash, unknown, preHash, unknown},
|
||||
// We expect exactly 4 entries — mixed responses are small and well
|
||||
// under the byte limit, so truncation is not expected.
|
||||
minEntries: 4,
|
||||
maxEntries: 4,
|
||||
// Positions 1 and 3 are unknown hashes and MUST be empty.
|
||||
mustBeEmptyAt: []int{1, 3},
|
||||
},
|
||||
{
|
||||
desc: `A request spanning the most recent canonical window. Implementations
|
||||
may serve or drop individual entries, but the entries that are returned must
|
||||
preserve request order.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: recent,
|
||||
minEntries: 0,
|
||||
maxEntries: len(recent),
|
||||
},
|
||||
{
|
||||
desc: `A request with a very small byte soft limit. The server must return
|
||||
at least zero entries and no more than the requested number, truncating from
|
||||
the tail. It must not disconnect.`,
|
||||
nBytes: 1,
|
||||
hashes: recent,
|
||||
minEntries: 0,
|
||||
maxEntries: len(recent),
|
||||
},
|
||||
{
|
||||
desc: `A request with a zero byte soft limit. The server must still respond
|
||||
(possibly with an empty list) and must not disconnect.`,
|
||||
nBytes: 0,
|
||||
hashes: recent,
|
||||
minEntries: 0,
|
||||
maxEntries: len(recent),
|
||||
},
|
||||
{
|
||||
desc: `A request containing the same hash repeated. The server must treat
|
||||
each position independently and preserve request order.`,
|
||||
nBytes: softResponseLimitSnap,
|
||||
hashes: []common.Hash{headHash, headHash, headHash},
|
||||
minEntries: 3,
|
||||
maxEntries: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
if i > 0 {
|
||||
t.Log("\n")
|
||||
}
|
||||
t.Logf("-- Test %d", i)
|
||||
t.Log(tc.desc)
|
||||
t.Log(" request:")
|
||||
t.Logf(" hashes: %d", len(tc.hashes))
|
||||
t.Logf(" responseBytes: %d", tc.nBytes)
|
||||
if err := s.snapGetAccessLists(t, &tc); err != nil {
|
||||
t.Errorf("test %d failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSnap2TrieNodesRemoved verifies that snap/2 no longer serves the
|
||||
// GetTrieNodes message (0x06). Per EIP-8189, snap/2 removes GetTrieNodes and
|
||||
// TrieNodes entirely. A server that negotiated snap/2 must not treat these
|
||||
// codes as valid snap messages and should disconnect the peer that sends them.
|
||||
func (s *Suite) TestSnap2TrieNodesRemoved(t *utesting.T) {
|
||||
t.Log(`This test verifies that sending a GetTrieNodes message over a snap/2
|
||||
connection causes the peer to reject the request. Per EIP-8189, GetTrieNodes
|
||||
is removed in snap/2.`)
|
||||
|
||||
conn, err := s.dialSnap2()
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := conn.peer(s.chain, nil); err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
}
|
||||
|
||||
// Build a syntactically valid GetTrieNodes request to the head state root.
|
||||
paths, err := rlp.EncodeToRawList([]snap.TrieNodePathSet{{[]byte{0}}})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode paths: %v", err)
|
||||
}
|
||||
req := &snap.GetTrieNodesPacket{
|
||||
ID: uint64(rand.Int63()),
|
||||
Root: s.chain.Head().Root(),
|
||||
Paths: paths,
|
||||
Bytes: 5000,
|
||||
}
|
||||
if err := conn.Write(snapProto, snap.GetTrieNodesMsg, req); err != nil {
|
||||
t.Fatalf("failed to write GetTrieNodes: %v", err)
|
||||
}
|
||||
|
||||
// We expect either a disconnect or a read error/timeout. We must NOT
|
||||
// receive a valid TrieNodes response. Loop a few times to consume any
|
||||
// incidental messages the peer might send (e.g. block updates) before
|
||||
// deciding.
|
||||
for i := 0; i < 5; i++ {
|
||||
msg, err := conn.ReadSnap()
|
||||
if err != nil {
|
||||
// Disconnect or read error — the peer rejected the request.
|
||||
return
|
||||
}
|
||||
if _, ok := msg.(*snap.TrieNodesPacket); ok {
|
||||
t.Fatal("peer responded with TrieNodes over snap/2; GetTrieNodes must be unsupported")
|
||||
}
|
||||
}
|
||||
t.Fatal("peer did not reject GetTrieNodes over snap/2 within the observation window")
|
||||
}
|
||||
|
||||
// softResponseLimitSnap mirrors the recommended 2 MiB soft limit for
|
||||
// BlockAccessLists responses from EIP-8189 §"Response Size Limit".
|
||||
const softResponseLimitSnap = 2 * 1024 * 1024
|
||||
|
||||
// snapGetAccessLists sends a GetBlockAccessLists request, validates the
|
||||
// response structure against EIP-8189, and verifies BAL content against the
|
||||
// block-access-list-hash field of the corresponding block header (when the
|
||||
// block is known and a BAL was returned).
|
||||
func (s *Suite) snapGetAccessLists(t *utesting.T, tc *accessListsTest) error {
|
||||
conn, err := s.dialSnap2()
|
||||
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)
|
||||
}
|
||||
|
||||
req := &snap.GetAccessListsPacket{
|
||||
ID: uint64(rand.Int63()),
|
||||
Hashes: tc.hashes,
|
||||
Bytes: tc.nBytes,
|
||||
}
|
||||
msg, err := conn.snapRequest(snap.GetAccessListsMsg, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("access list request failed: %v", err)
|
||||
}
|
||||
res, ok := msg.(*snap.AccessListsPacket)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected response type: %T", msg)
|
||||
}
|
||||
if res.ID != req.ID {
|
||||
return fmt.Errorf("request id mismatch: got %d, want %d", res.ID, req.ID)
|
||||
}
|
||||
|
||||
// Check list length bounds.
|
||||
got := res.AccessLists.Len()
|
||||
if got < tc.minEntries || got > tc.maxEntries {
|
||||
return fmt.Errorf("response has %d entries, want between %d and %d", got, tc.minEntries, tc.maxEntries)
|
||||
}
|
||||
|
||||
// Build a map of request-index -> block so we can verify BAL hashes.
|
||||
blocks := make(map[int]*types.Block)
|
||||
for i, h := range tc.hashes {
|
||||
for _, b := range s.chain.blocks {
|
||||
if b.Hash() == h {
|
||||
blocks[i] = b
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate the response, validating each entry positionally.
|
||||
var (
|
||||
idx int
|
||||
it = res.AccessLists.ContentIterator()
|
||||
)
|
||||
// Build a set of positions that MUST be empty (for per-position checks).
|
||||
mustEmpty := make(map[int]struct{}, len(tc.mustBeEmptyAt))
|
||||
for _, p := range tc.mustBeEmptyAt {
|
||||
mustEmpty[p] = struct{}{}
|
||||
}
|
||||
|
||||
for it.Next() {
|
||||
raw := it.Value()
|
||||
|
||||
// Empty entry: per spec, indicates BAL is unavailable for that block.
|
||||
if bytes.Equal(raw, rlp.EmptyString) {
|
||||
if !tc.expectAllEmpty && blocks[idx] != nil && blocks[idx].Header().BlockAccessListHash != nil {
|
||||
// Not a failure — the server is allowed to legitimately not
|
||||
// have the BAL. But we log it so the test output is diagnosable.
|
||||
t.Logf(" entry %d: server returned empty for known post-Amsterdam block %x", idx, tc.hashes[idx])
|
||||
}
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
|
||||
// Non-empty entry. If the requester asked for a block we do not know
|
||||
// about, receiving data here is a protocol violation.
|
||||
if tc.expectAllEmpty {
|
||||
return fmt.Errorf("entry %d: expected empty entry, got %d bytes of BAL data", idx, len(raw))
|
||||
}
|
||||
if _, required := mustEmpty[idx]; required {
|
||||
return fmt.Errorf("entry %d: position must be empty (unknown hash), got %d bytes of BAL data", idx, len(raw))
|
||||
}
|
||||
|
||||
// Per EIP-8189: compute keccak256(rlp.encode(bal)) against the raw
|
||||
// bytes actually received on the wire, and compare to the header
|
||||
// commitment. Hashing raw bytes (rather than re-encoding after a
|
||||
// decode round-trip) catches peers that send non-canonical BAL
|
||||
// encodings.
|
||||
block, known := blocks[idx]
|
||||
if known && block.Header().BlockAccessListHash != nil {
|
||||
have := crypto.Keccak256Hash(raw)
|
||||
want := *block.Header().BlockAccessListHash
|
||||
if have != want {
|
||||
return fmt.Errorf("entry %d: BAL hash mismatch: have %x, want %x", idx, have, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Also decode and validate the BAL's internal structure: ordering of
|
||||
// accounts/slots/changes, code-size limits, etc. This catches
|
||||
// malformed responses even when we can't compare to a header hash
|
||||
// (e.g. requested hash is for a block we don't know locally).
|
||||
var accessList bal.BlockAccessList
|
||||
if err := rlp.DecodeBytes(raw, &accessList); err != nil {
|
||||
return fmt.Errorf("entry %d: invalid BAL RLP: %v", idx, err)
|
||||
}
|
||||
if err := accessList.Validate(); err != nil {
|
||||
return fmt.Errorf("entry %d: BAL failed validation: %v", idx, err)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
// Sanity: iterator consumed exactly the reported number of entries.
|
||||
if idx != got {
|
||||
return fmt.Errorf("iterator visited %d entries, expected %d", idx, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -106,6 +106,16 @@ func (s *Suite) SnapTests() []utesting.Test {
|
|||
}
|
||||
}
|
||||
|
||||
// Snap2Tests returns the list of tests for the snap/2 protocol (EIP-8189).
|
||||
// These tests require the peer to advertise and negotiate snap/2.
|
||||
func (s *Suite) Snap2Tests() []utesting.Test {
|
||||
return []utesting.Test{
|
||||
{Name: "Status", Fn: s.TestSnap2Status},
|
||||
{Name: "GetBlockAccessLists", Fn: s.TestSnap2GetBlockAccessLists},
|
||||
{Name: "TrieNodesRemoved", Fn: s.TestSnap2TrieNodesRemoved},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestStatus(t *utesting.T) {
|
||||
t.Log(`This test is just a sanity check. It performs an eth protocol handshake.`)
|
||||
conn, err := s.dialAndPeer(nil)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,31 @@ func TestSnapSuite(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSnap2Suite(t *testing.T) {
|
||||
jwtPath, secret, err := makeJWTSecret(t)
|
||||
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(), "./testdata", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:]))
|
||||
if err != nil {
|
||||
t.Fatalf("could not create new test suite: %v", err)
|
||||
}
|
||||
for _, test := range suite.Snap2Tests() {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
|
||||
if result[0].Failed {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// runGeth creates and starts a geth node
|
||||
func runGeth(dir string, jwtPath string) (*node.Node, error) {
|
||||
stack, err := node.New(&node.Config{
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ var (
|
|||
rlpxPingCommand,
|
||||
rlpxEthTestCommand,
|
||||
rlpxSnapTestCommand,
|
||||
rlpxSnap2TestCommand,
|
||||
},
|
||||
}
|
||||
rlpxPingCommand = &cli.Command{
|
||||
|
|
@ -99,6 +100,20 @@ var (
|
|||
testNodeEngineFlag,
|
||||
},
|
||||
}
|
||||
rlpxSnap2TestCommand = &cli.Command{
|
||||
Name: "snap2-test",
|
||||
Usage: "Runs snap/2 (EIP-8189) protocol tests against a node",
|
||||
ArgsUsage: "",
|
||||
Action: rlpxSnap2Test,
|
||||
Flags: []cli.Flag{
|
||||
testPatternFlag,
|
||||
testTAPFlag,
|
||||
testChainDirFlag,
|
||||
testNodeFlag,
|
||||
testNodeJWTFlag,
|
||||
testNodeEngineFlag,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func rlpxPing(ctx *cli.Context) error {
|
||||
|
|
@ -164,6 +179,16 @@ func rlpxSnapTest(ctx *cli.Context) error {
|
|||
return runTests(ctx, suite.SnapTests())
|
||||
}
|
||||
|
||||
// rlpxSnap2Test runs the snap/2 (EIP-8189) protocol test suite.
|
||||
func rlpxSnap2Test(ctx *cli.Context) error {
|
||||
p := cliTestParams(ctx)
|
||||
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
return runTests(ctx, suite.Snap2Tests())
|
||||
}
|
||||
|
||||
type testParams struct {
|
||||
node *enode.Node
|
||||
engineAPI string
|
||||
|
|
|
|||
Loading…
Reference in a new issue