forked from cerc-io/plugeth
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:
parent
8c2d455ccd
commit
577be37e0e
@ -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].
|
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
|
1. initialize the geth node with the `genesis.json` file
|
||||||
2. import the `halfchain.rlp` file in the `testdata` directory
|
2. import blocks from `chain.rlp`
|
||||||
3. run geth with the following flags:
|
3. run the client using the resulting database. For geth, use a command like the one below:
|
||||||
```
|
|
||||||
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, run the following command, replacing `<enode>` with the enode of the geth node:
|
geth \
|
||||||
```
|
--datadir <datadir> \
|
||||||
devp2p rlpx eth-test <enode> cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
|
--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.
|
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
|
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
|
||||||
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup
|
[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup
|
||||||
|
@ -17,27 +17,118 @@
|
|||||||
package ethtest
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/ecdsa"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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"
|
||||||
"github.com/ethereum/go-ethereum/core/forkid"
|
"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/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/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"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 {
|
type Chain struct {
|
||||||
genesis core.Genesis
|
genesis core.Genesis
|
||||||
blocks []*types.Block
|
blocks []*types.Block
|
||||||
chainConfig *params.ChainConfig
|
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.
|
// Len returns the length of the chain.
|
||||||
@ -45,6 +136,11 @@ func (c *Chain) Len() int {
|
|||||||
return len(c.blocks)
|
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
|
// TD calculates the total difficulty of the chain at the
|
||||||
// chain head.
|
// chain head.
|
||||||
func (c *Chain) TD() *big.Int {
|
func (c *Chain) TD() *big.Int {
|
||||||
@ -55,19 +151,12 @@ func (c *Chain) TD() *big.Int {
|
|||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalDifficultyAt calculates the total difficulty of the chain
|
// GetBlock returns the block at the specified number.
|
||||||
// at the given block height.
|
func (c *Chain) GetBlock(number int) *types.Block {
|
||||||
func (c *Chain) TotalDifficultyAt(height int) *big.Int {
|
return c.blocks[number]
|
||||||
sum := new(big.Int)
|
|
||||||
if height >= c.Len() {
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
for _, block := range c.blocks[:height+1] {
|
|
||||||
sum.Add(sum, block.Difficulty())
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RootAt returns the state root for the block at the given height.
|
||||||
func (c *Chain) RootAt(height int) common.Hash {
|
func (c *Chain) RootAt(height int) common.Hash {
|
||||||
if height < c.Len() {
|
if height < c.Len() {
|
||||||
return c.blocks[height].Root()
|
return c.blocks[height].Root()
|
||||||
@ -75,37 +164,56 @@ func (c *Chain) RootAt(height int) common.Hash {
|
|||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkID gets the fork id of the chain.
|
// GetSender returns the address associated with account at the index in the
|
||||||
func (c *Chain) ForkID() forkid.ID {
|
// pre-funded accounts list.
|
||||||
return forkid.NewID(c.chainConfig, c.blocks[0], uint64(c.Len()), c.blocks[0].Time())
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shorten returns a copy chain of a desired height from the imported
|
// IncNonce increases the specified signing account's pending nonce.
|
||||||
func (c *Chain) Shorten(height int) *Chain {
|
func (c *Chain) IncNonce(addr common.Address, amt uint64) {
|
||||||
blocks := make([]*types.Block, height)
|
if _, ok := c.senders[addr]; !ok {
|
||||||
copy(blocks, c.blocks[:height])
|
panic("nonce increment for non-signer")
|
||||||
|
|
||||||
config := *c.chainConfig
|
|
||||||
return &Chain{
|
|
||||||
blocks: blocks,
|
|
||||||
chainConfig: &config,
|
|
||||||
}
|
}
|
||||||
|
c.senders[addr].Nonce += amt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head returns the chain head.
|
// Balance returns the balance of an account at the head of the chain.
|
||||||
func (c *Chain) Head() *types.Block {
|
func (c *Chain) Balance(addr common.Address) *big.Int {
|
||||||
return c.blocks[c.Len()-1]
|
bal := new(big.Int)
|
||||||
|
if acc, ok := c.state[addr]; ok {
|
||||||
|
bal, _ = bal.SetString(acc.Balance, 10)
|
||||||
|
}
|
||||||
|
return bal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
// 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 {
|
if req.Amount < 1 {
|
||||||
return nil, errors.New("no block headers requested")
|
return nil, errors.New("no block headers requested")
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
headers := make([]*types.Header, req.Amount)
|
headers = make([]*types.Header, req.Amount)
|
||||||
var blockNumber uint64
|
blockNumber uint64
|
||||||
|
)
|
||||||
// range over blocks to check if our chain has the requested header
|
// Range over blocks to check if our chain has the requested header.
|
||||||
for _, block := range c.blocks {
|
for _, block := range c.blocks {
|
||||||
if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number {
|
if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number {
|
||||||
headers[0] = block.Header()
|
headers[0] = block.Header()
|
||||||
@ -115,40 +223,30 @@ func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
|||||||
if headers[0] == nil {
|
if headers[0] == nil {
|
||||||
return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash)
|
return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Reverse {
|
if req.Reverse {
|
||||||
for i := 1; i < int(req.Amount); i++ {
|
for i := 1; i < int(req.Amount); i++ {
|
||||||
blockNumber -= (1 - req.Skip)
|
blockNumber -= (1 - req.Skip)
|
||||||
headers[i] = c.blocks[blockNumber].Header()
|
headers[i] = c.blocks[blockNumber].Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers, nil
|
return headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i < int(req.Amount); i++ {
|
for i := 1; i < int(req.Amount); i++ {
|
||||||
blockNumber += (1 + req.Skip)
|
blockNumber += (1 + req.Skip)
|
||||||
headers[i] = c.blocks[blockNumber].Header()
|
headers[i] = c.blocks[blockNumber].Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers, nil
|
return headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadChain takes the given chain.rlp file, and decodes and returns
|
// Shorten returns a copy chain of a desired height from the imported
|
||||||
// the blocks from the file.
|
func (c *Chain) Shorten(height int) *Chain {
|
||||||
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
blocks := make([]*types.Block, height)
|
||||||
gen, err := loadGenesis(genesis)
|
copy(blocks, c.blocks[:height])
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
gblock := gen.ToBlock()
|
|
||||||
|
|
||||||
blocks, err := blocksFromFile(chainfile, gblock)
|
config := *c.config
|
||||||
if err != nil {
|
return &Chain{
|
||||||
return nil, err
|
blocks: blocks,
|
||||||
|
config: &config,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGenesis(genesisFile string) (core.Genesis, error) {
|
func loadGenesis(genesisFile string) (core.Genesis, error) {
|
||||||
@ -163,6 +261,22 @@ func loadGenesis(genesisFile string) (core.Genesis, error) {
|
|||||||
return gen, nil
|
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) {
|
func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) {
|
||||||
// Load chain.rlp.
|
// Load chain.rlp.
|
||||||
fh, err := os.Open(chainfile)
|
fh, err := os.Open(chainfile)
|
||||||
@ -193,3 +307,47 @@ func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, erro
|
|||||||
}
|
}
|
||||||
return blocks, nil
|
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.
|
// respond to a GetBlockHeaders request from a node.
|
||||||
func TestChain_GetHeaders(t *testing.T) {
|
func TestChainGetHeaders(t *testing.T) {
|
||||||
t.Parallel()
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
req GetBlockHeaders
|
req eth.GetBlockHeadersPacket
|
||||||
expected []*types.Header
|
expected []*types.Header
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
req: GetBlockHeaders{
|
req: eth.GetBlockHeadersPacket{
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||||
Origin: eth.HashOrNumber{Number: uint64(2)},
|
Origin: eth.HashOrNumber{Number: uint64(2)},
|
||||||
Amount: uint64(5),
|
Amount: uint64(5),
|
||||||
@ -163,7 +159,7 @@ func TestChain_GetHeaders(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
req: GetBlockHeaders{
|
req: eth.GetBlockHeadersPacket{
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||||
Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)},
|
Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)},
|
||||||
Amount: uint64(3),
|
Amount: uint64(3),
|
||||||
@ -178,7 +174,7 @@ func TestChain_GetHeaders(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
req: GetBlockHeaders{
|
req: eth.GetBlockHeadersPacket{
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
||||||
Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
|
Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
|
||||||
Amount: uint64(1),
|
Amount: uint64(1),
|
||||||
|
361
cmd/devp2p/internal/ethtest/conn.go
Normal file
361
cmd/devp2p/internal/ethtest/conn.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pretty = spew.ConfigState{
|
||||||
|
Indent: " ",
|
||||||
|
DisableCapacities: true,
|
||||||
|
DisablePointerAddresses: true,
|
||||||
|
SortKeys: true,
|
||||||
|
}
|
||||||
|
timeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// dial attempts to dial the given node and perform a handshake, returning the
|
||||||
|
// created Conn if successful.
|
||||||
|
func (s *Suite) dial() (*Conn, error) {
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
return s.dialAs(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialAs attempts to dial a given node and perform a handshake using the given
|
||||||
|
// private key.
|
||||||
|
func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
|
||||||
|
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())}
|
||||||
|
conn.ourKey = key
|
||||||
|
_, err = conn.Handshake(conn.ourKey)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn.caps = []p2p.Cap{
|
||||||
|
{Name: "eth", Version: 67},
|
||||||
|
{Name: "eth", Version: 68},
|
||||||
|
}
|
||||||
|
conn.ourHighestProtoVersion = 68
|
||||||
|
return &conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialSnap creates a connection with snap/1 capability.
|
||||||
|
func (s *Suite) dialSnap() (*Conn, error) {
|
||||||
|
conn, err := s.dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dial failed: %v", err)
|
||||||
|
}
|
||||||
|
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1})
|
||||||
|
conn.ourHighestSnapProtoVersion = 1
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn represents an individual connection with a peer
|
||||||
|
type Conn struct {
|
||||||
|
*rlpx.Conn
|
||||||
|
ourKey *ecdsa.PrivateKey
|
||||||
|
negotiatedProtoVersion uint
|
||||||
|
negotiatedSnapProtoVersion uint
|
||||||
|
ourHighestProtoVersion uint
|
||||||
|
ourHighestSnapProtoVersion uint
|
||||||
|
caps []p2p.Cap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a packet from the connection.
|
||||||
|
func (c *Conn) Read() (uint64, []byte, error) {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return code, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMsg attempts to read a devp2p message with a specific code.
|
||||||
|
func (c *Conn) ReadMsg(proto Proto, code uint64, msg any) error {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
got, data, err := c.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if protoOffset(proto)+code == got {
|
||||||
|
return rlp.DecodeBytes(data, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes a eth packet to the connection.
|
||||||
|
func (c *Conn) Write(proto Proto, code uint64, msg any) error {
|
||||||
|
c.SetWriteDeadline(time.Now().Add(timeout))
|
||||||
|
payload, err := rlp.EncodeToBytes(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.Conn.Write(protoOffset(proto)+code, payload)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadEth reads an Eth sub-protocol wire message.
|
||||||
|
func (c *Conn) ReadEth() (any, error) {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if code == pingMsg {
|
||||||
|
c.Write(baseProto, pongMsg, []byte{})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if getProto(code) != ethProto {
|
||||||
|
// Read until eth message.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
code -= baseProtoLen
|
||||||
|
|
||||||
|
var msg any
|
||||||
|
switch int(code) {
|
||||||
|
case eth.StatusMsg:
|
||||||
|
msg = new(eth.StatusPacket)
|
||||||
|
case eth.GetBlockHeadersMsg:
|
||||||
|
msg = new(eth.GetBlockHeadersPacket)
|
||||||
|
case eth.BlockHeadersMsg:
|
||||||
|
msg = new(eth.BlockHeadersPacket)
|
||||||
|
case eth.GetBlockBodiesMsg:
|
||||||
|
msg = new(eth.GetBlockBodiesPacket)
|
||||||
|
case eth.BlockBodiesMsg:
|
||||||
|
msg = new(eth.BlockBodiesPacket)
|
||||||
|
case eth.NewBlockMsg:
|
||||||
|
msg = new(eth.NewBlockPacket)
|
||||||
|
case eth.NewBlockHashesMsg:
|
||||||
|
msg = new(eth.NewBlockHashesPacket)
|
||||||
|
case eth.TransactionsMsg:
|
||||||
|
msg = new(eth.TransactionsPacket)
|
||||||
|
case eth.NewPooledTransactionHashesMsg:
|
||||||
|
msg = new(eth.NewPooledTransactionHashesPacket68)
|
||||||
|
case eth.GetPooledTransactionsMsg:
|
||||||
|
msg = new(eth.GetPooledTransactionsPacket)
|
||||||
|
case eth.PooledTransactionsMsg:
|
||||||
|
msg = new(eth.PooledTransactionsPacket)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unhandled eth msg code %d", code))
|
||||||
|
}
|
||||||
|
if err := rlp.DecodeBytes(data, msg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode eth msg: %v", err)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||||
|
func (c *Conn) ReadSnap() (any, error) {
|
||||||
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
for {
|
||||||
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if getProto(code) != snapProto {
|
||||||
|
// Read until snap message.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
code -= baseProtoLen + ethProtoLen
|
||||||
|
|
||||||
|
var msg any
|
||||||
|
switch int(code) {
|
||||||
|
case snap.GetAccountRangeMsg:
|
||||||
|
msg = new(snap.GetAccountRangePacket)
|
||||||
|
case snap.AccountRangeMsg:
|
||||||
|
msg = new(snap.AccountRangePacket)
|
||||||
|
case snap.GetStorageRangesMsg:
|
||||||
|
msg = new(snap.GetStorageRangesPacket)
|
||||||
|
case snap.StorageRangesMsg:
|
||||||
|
msg = new(snap.StorageRangesPacket)
|
||||||
|
case snap.GetByteCodesMsg:
|
||||||
|
msg = new(snap.GetByteCodesPacket)
|
||||||
|
case snap.ByteCodesMsg:
|
||||||
|
msg = new(snap.ByteCodesPacket)
|
||||||
|
case snap.GetTrieNodesMsg:
|
||||||
|
msg = new(snap.GetTrieNodesPacket)
|
||||||
|
case snap.TrieNodesMsg:
|
||||||
|
msg = new(snap.TrieNodesPacket)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unhandled snap code: %d", code))
|
||||||
|
}
|
||||||
|
if err := rlp.DecodeBytes(data, msg); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not rlp decode message: %v", err)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// peer performs both the protocol handshake and the status message
|
||||||
|
// exchange with the node in order to peer with it.
|
||||||
|
func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error {
|
||||||
|
if err := c.handshake(); err != nil {
|
||||||
|
return fmt.Errorf("handshake failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := c.statusExchange(chain, status); err != nil {
|
||||||
|
return fmt.Errorf("status exchange failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handshake performs a protocol handshake with the node.
|
||||||
|
func (c *Conn) handshake() error {
|
||||||
|
// Write hello to client.
|
||||||
|
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
|
||||||
|
ourHandshake := &protoHandshake{
|
||||||
|
Version: 5,
|
||||||
|
Caps: c.caps,
|
||||||
|
ID: pub0,
|
||||||
|
}
|
||||||
|
if err := c.Write(baseProto, handshakeMsg, ourHandshake); err != nil {
|
||||||
|
return fmt.Errorf("write to connection failed: %v", err)
|
||||||
|
}
|
||||||
|
// Read hello from client.
|
||||||
|
code, data, err := c.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("erroring reading handshake: %v", err)
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case handshakeMsg:
|
||||||
|
msg := new(protoHandshake)
|
||||||
|
if err := rlp.DecodeBytes(data, &msg); err != nil {
|
||||||
|
return fmt.Errorf("error decoding handshake msg: %v", err)
|
||||||
|
}
|
||||||
|
// Set snappy if version is at least 5.
|
||||||
|
if msg.Version >= 5 {
|
||||||
|
c.SetSnappy(true)
|
||||||
|
}
|
||||||
|
c.negotiateEthProtocol(msg.Caps)
|
||||||
|
if c.negotiatedProtoVersion == 0 {
|
||||||
|
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion)
|
||||||
|
}
|
||||||
|
// If we require snap, verify that it was negotiated.
|
||||||
|
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion {
|
||||||
|
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("bad handshake: got msg code %d", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
||||||
|
// advertised capability from peer.
|
||||||
|
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
|
||||||
|
var highestEthVersion uint
|
||||||
|
var highestSnapVersion uint
|
||||||
|
for _, capability := range caps {
|
||||||
|
switch capability.Name {
|
||||||
|
case "eth":
|
||||||
|
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion {
|
||||||
|
highestEthVersion = capability.Version
|
||||||
|
}
|
||||||
|
case "snap":
|
||||||
|
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion {
|
||||||
|
highestSnapVersion = capability.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.negotiatedProtoVersion = highestEthVersion
|
||||||
|
c.negotiatedSnapProtoVersion = highestSnapVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// statusExchange performs a `Status` message exchange with the given node.
|
||||||
|
func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error {
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
code, data, err := c.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read from connection: %w", err)
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case eth.StatusMsg + protoOffset(ethProto):
|
||||||
|
msg := new(eth.StatusPacket)
|
||||||
|
if err := rlp.DecodeBytes(data, &msg); err != nil {
|
||||||
|
return fmt.Errorf("error decoding status packet: %w", err)
|
||||||
|
}
|
||||||
|
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want {
|
||||||
|
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
|
||||||
|
want, chain.blocks[chain.Len()-1].NumberU64(), have)
|
||||||
|
}
|
||||||
|
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want {
|
||||||
|
return fmt.Errorf("wrong TD in status: have %v want %v", have, want)
|
||||||
|
}
|
||||||
|
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
|
||||||
|
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
|
||||||
|
}
|
||||||
|
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) {
|
||||||
|
return fmt.Errorf("wrong protocol version: have %v, want %v", have, want)
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
case discMsg:
|
||||||
|
var msg []p2p.DiscReason
|
||||||
|
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
||||||
|
return errors.New("invalid disconnect message")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("disconnect received: %v", pretty.Sdump(msg))
|
||||||
|
case pingMsg:
|
||||||
|
// TODO (renaynay): in the future, this should be an error
|
||||||
|
// (PINGs should not be a response upon fresh connection)
|
||||||
|
c.Write(baseProto, pongMsg, nil)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("bad status message: code %d", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// make sure eth protocol version is set for negotiation
|
||||||
|
if c.negotiatedProtoVersion == 0 {
|
||||||
|
return errors.New("eth protocol version must be set in Conn")
|
||||||
|
}
|
||||||
|
if status == nil {
|
||||||
|
// default status message
|
||||||
|
status = ð.StatusPacket{
|
||||||
|
ProtocolVersion: uint32(c.negotiatedProtoVersion),
|
||||||
|
NetworkID: chain.config.ChainID.Uint64(),
|
||||||
|
TD: chain.TD(),
|
||||||
|
Head: chain.blocks[chain.Len()-1].Hash(),
|
||||||
|
Genesis: chain.blocks[0].Hash(),
|
||||||
|
ForkID: chain.ForkID(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil {
|
||||||
|
return fmt.Errorf("write to connection failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
69
cmd/devp2p/internal/ethtest/engine.go
Normal file
69
cmd/devp2p/internal/ethtest/engine.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EngineClient is a wrapper around engine-related data.
|
||||||
|
type EngineClient struct {
|
||||||
|
url string
|
||||||
|
jwt [32]byte
|
||||||
|
headfcu []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEngineClient creates a new engine client.
|
||||||
|
func NewEngineClient(dir, url, jwt string) (*EngineClient, error) {
|
||||||
|
headfcu, err := os.ReadFile(path.Join(dir, "headfcu.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read headfcu: %w", err)
|
||||||
|
}
|
||||||
|
return &EngineClient{url, common.HexToHash(jwt), headfcu}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// token returns the jwt claim token for authorization.
|
||||||
|
func (ec *EngineClient) token() string {
|
||||||
|
claims := jwt.RegisteredClaims{IssuedAt: jwt.NewNumericDate(time.Now())}
|
||||||
|
token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(ec.jwt[:])
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendForkchoiceUpdated sends an fcu for the head of the generated chain.
|
||||||
|
func (ec *EngineClient) sendForkchoiceUpdated() error {
|
||||||
|
var (
|
||||||
|
req, _ = http.NewRequest(http.MethodPost, ec.url, io.NopCloser(bytes.NewReader(ec.headfcu)))
|
||||||
|
header = make(http.Header)
|
||||||
|
)
|
||||||
|
// Set header
|
||||||
|
header.Set("accept", "application/json")
|
||||||
|
header.Set("content-type", "application/json")
|
||||||
|
header.Set("Authorization", fmt.Sprintf("Bearer %v", ec.token()))
|
||||||
|
req.Header = header
|
||||||
|
|
||||||
|
_, err := new(http.Client).Do(req)
|
||||||
|
return err
|
||||||
|
}
|
@ -1,650 +0,0 @@
|
|||||||
// Copyright 2021 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package ethtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pretty = spew.ConfigState{
|
|
||||||
Indent: " ",
|
|
||||||
DisableCapacities: true,
|
|
||||||
DisablePointerAddresses: true,
|
|
||||||
SortKeys: true,
|
|
||||||
}
|
|
||||||
timeout = 20 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// dial attempts to dial the given node and perform a handshake,
|
|
||||||
// returning the created Conn if successful.
|
|
||||||
func (s *Suite) dial() (*Conn, error) {
|
|
||||||
// dial
|
|
||||||
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())}
|
|
||||||
// do encHandshake
|
|
||||||
conn.ourKey, _ = crypto.GenerateKey()
|
|
||||||
_, err = conn.Handshake(conn.ourKey)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// set default p2p capabilities
|
|
||||||
conn.caps = []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 67},
|
|
||||||
{Name: "eth", Version: 68},
|
|
||||||
}
|
|
||||||
conn.ourHighestProtoVersion = 68
|
|
||||||
return &conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dialSnap creates a connection with snap/1 capability.
|
|
||||||
func (s *Suite) dialSnap() (*Conn, error) {
|
|
||||||
conn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1})
|
|
||||||
conn.ourHighestSnapProtoVersion = 1
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// peer performs both the protocol handshake and the status message
|
|
||||||
// exchange with the node in order to peer with it.
|
|
||||||
func (c *Conn) peer(chain *Chain, status *Status) error {
|
|
||||||
if err := c.handshake(); err != nil {
|
|
||||||
return fmt.Errorf("handshake failed: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := c.statusExchange(chain, status); err != nil {
|
|
||||||
return fmt.Errorf("status exchange failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handshake performs a protocol handshake with the node.
|
|
||||||
func (c *Conn) handshake() error {
|
|
||||||
defer c.SetDeadline(time.Time{})
|
|
||||||
c.SetDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
// write hello to client
|
|
||||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
|
|
||||||
ourHandshake := &Hello{
|
|
||||||
Version: 5,
|
|
||||||
Caps: c.caps,
|
|
||||||
ID: pub0,
|
|
||||||
}
|
|
||||||
if err := c.Write(ourHandshake); err != nil {
|
|
||||||
return fmt.Errorf("write to connection failed: %v", err)
|
|
||||||
}
|
|
||||||
// read hello from client
|
|
||||||
switch msg := c.Read().(type) {
|
|
||||||
case *Hello:
|
|
||||||
// set snappy if version is at least 5
|
|
||||||
if msg.Version >= 5 {
|
|
||||||
c.SetSnappy(true)
|
|
||||||
}
|
|
||||||
c.negotiateEthProtocol(msg.Caps)
|
|
||||||
if c.negotiatedProtoVersion == 0 {
|
|
||||||
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion)
|
|
||||||
}
|
|
||||||
// If we require snap, verify that it was negotiated
|
|
||||||
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion {
|
|
||||||
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("bad handshake: %#v", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
|
||||||
// advertised capability from peer.
|
|
||||||
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
|
|
||||||
var highestEthVersion uint
|
|
||||||
var highestSnapVersion uint
|
|
||||||
for _, capability := range caps {
|
|
||||||
switch capability.Name {
|
|
||||||
case "eth":
|
|
||||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion {
|
|
||||||
highestEthVersion = capability.Version
|
|
||||||
}
|
|
||||||
case "snap":
|
|
||||||
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion {
|
|
||||||
highestSnapVersion = capability.Version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.negotiatedProtoVersion = highestEthVersion
|
|
||||||
c.negotiatedSnapProtoVersion = highestSnapVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// statusExchange performs a `Status` message exchange with the given node.
|
|
||||||
func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) {
|
|
||||||
defer c.SetDeadline(time.Time{})
|
|
||||||
c.SetDeadline(time.Now().Add(20 * time.Second))
|
|
||||||
|
|
||||||
// read status message from client
|
|
||||||
var message Message
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
switch msg := c.Read().(type) {
|
|
||||||
case *Status:
|
|
||||||
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want {
|
|
||||||
return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
|
|
||||||
want, chain.blocks[chain.Len()-1].NumberU64(), have)
|
|
||||||
}
|
|
||||||
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want {
|
|
||||||
return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want)
|
|
||||||
}
|
|
||||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
|
|
||||||
return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) {
|
|
||||||
return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
message = msg
|
|
||||||
break loop
|
|
||||||
case *Disconnect:
|
|
||||||
return nil, fmt.Errorf("disconnect received: %v", msg.Reason)
|
|
||||||
case *Ping:
|
|
||||||
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
|
||||||
// (PINGs should not be a response upon fresh connection)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// make sure eth protocol version is set for negotiation
|
|
||||||
if c.negotiatedProtoVersion == 0 {
|
|
||||||
return nil, errors.New("eth protocol version must be set in Conn")
|
|
||||||
}
|
|
||||||
if status == nil {
|
|
||||||
// default status message
|
|
||||||
status = &Status{
|
|
||||||
ProtocolVersion: uint32(c.negotiatedProtoVersion),
|
|
||||||
NetworkID: chain.chainConfig.ChainID.Uint64(),
|
|
||||||
TD: chain.TD(),
|
|
||||||
Head: chain.blocks[chain.Len()-1].Hash(),
|
|
||||||
Genesis: chain.blocks[0].Hash(),
|
|
||||||
ForkID: chain.ForkID(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := c.Write(status); err != nil {
|
|
||||||
return nil, fmt.Errorf("write to connection failed: %v", err)
|
|
||||||
}
|
|
||||||
return message, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSendAndRecvConns creates two connections, one for sending messages to the
|
|
||||||
// node, and one for receiving messages from the node.
|
|
||||||
func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) {
|
|
||||||
sendConn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
recvConn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
sendConn.Close()
|
|
||||||
return nil, nil, fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
return sendConn, recvConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readAndServe serves GetBlockHeaders requests while waiting
|
|
||||||
// on another message from the node.
|
|
||||||
func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message {
|
|
||||||
start := time.Now()
|
|
||||||
for time.Since(start) < timeout {
|
|
||||||
c.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
|
|
||||||
msg := c.Read()
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *Ping:
|
|
||||||
c.Write(&Pong{})
|
|
||||||
case *GetBlockHeaders:
|
|
||||||
headers, err := chain.GetHeaders(msg)
|
|
||||||
if err != nil {
|
|
||||||
return errorf("could not get headers for inbound header request: %v", err)
|
|
||||||
}
|
|
||||||
resp := &BlockHeaders{
|
|
||||||
RequestId: msg.ReqID(),
|
|
||||||
BlockHeadersRequest: eth.BlockHeadersRequest(headers),
|
|
||||||
}
|
|
||||||
if err := c.Write(resp); err != nil {
|
|
||||||
return errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errorf("no message received within %v", timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// headersRequest executes the given `GetBlockHeaders` request.
|
|
||||||
func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint64) ([]*types.Header, error) {
|
|
||||||
defer c.SetReadDeadline(time.Time{})
|
|
||||||
c.SetReadDeadline(time.Now().Add(20 * time.Second))
|
|
||||||
|
|
||||||
// write request
|
|
||||||
request.RequestId = reqID
|
|
||||||
if err := c.Write(request); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for response
|
|
||||||
msg := c.waitForResponse(chain, timeout, request.RequestId)
|
|
||||||
resp, ok := msg.(*BlockHeaders)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
headers := []*types.Header(resp.BlockHeadersRequest)
|
|
||||||
return headers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) {
|
|
||||||
defer c.SetReadDeadline(time.Time{})
|
|
||||||
c.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
if err := c.Write(msg); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
return c.ReadSnap(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// headersMatch returns whether the received headers match the given request
|
|
||||||
func headersMatch(expected []*types.Header, headers []*types.Header) bool {
|
|
||||||
return reflect.DeepEqual(expected, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForResponse reads from the connection until a response with the expected
|
|
||||||
// request ID is received.
|
|
||||||
func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message {
|
|
||||||
for {
|
|
||||||
msg := c.readAndServe(chain, timeout)
|
|
||||||
if msg.ReqID() == requestID {
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendNextBlock broadcasts the next block in the chain and waits
|
|
||||||
// for the node to propagate the block and import it into its chain.
|
|
||||||
func (s *Suite) sendNextBlock() error {
|
|
||||||
// set up sending and receiving connections
|
|
||||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sendConn.Close()
|
|
||||||
defer recvConn.Close()
|
|
||||||
if err = sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err = recvConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
// create new block announcement
|
|
||||||
nextBlock := s.fullChain.blocks[s.chain.Len()]
|
|
||||||
blockAnnouncement := &NewBlock{
|
|
||||||
Block: nextBlock,
|
|
||||||
TD: s.fullChain.TotalDifficultyAt(s.chain.Len()),
|
|
||||||
}
|
|
||||||
// send announcement and wait for node to request the header
|
|
||||||
if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil {
|
|
||||||
return fmt.Errorf("failed to announce block: %v", err)
|
|
||||||
}
|
|
||||||
// wait for client to update its chain
|
|
||||||
if err = s.waitForBlockImport(recvConn, nextBlock); err != nil {
|
|
||||||
return fmt.Errorf("failed to receive confirmation of block import: %v", err)
|
|
||||||
}
|
|
||||||
// update test suite chain
|
|
||||||
s.chain.blocks = append(s.chain.blocks, nextBlock)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testAnnounce writes a block announcement to the node and waits for the node
|
|
||||||
// to propagate it.
|
|
||||||
func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error {
|
|
||||||
if err := sendConn.Write(blockAnnouncement); err != nil {
|
|
||||||
return fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
return s.waitAnnounce(receiveConn, blockAnnouncement)
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node.
|
|
||||||
func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error {
|
|
||||||
for {
|
|
||||||
switch msg := conn.readAndServe(s.chain, timeout).(type) {
|
|
||||||
case *NewBlock:
|
|
||||||
if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) {
|
|
||||||
return fmt.Errorf("wrong header in block announcement: \nexpected %v "+
|
|
||||||
"\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header())
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) {
|
|
||||||
return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case *NewBlockHashes:
|
|
||||||
hashes := *msg
|
|
||||||
if blockAnnouncement.Block.Hash() != hashes[0].Hash {
|
|
||||||
return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
// ignore tx announcements from previous tests
|
|
||||||
case *NewPooledTransactionHashes66:
|
|
||||||
continue
|
|
||||||
case *NewPooledTransactionHashes:
|
|
||||||
continue
|
|
||||||
case *Transactions:
|
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error {
|
|
||||||
defer conn.SetReadDeadline(time.Time{})
|
|
||||||
conn.SetReadDeadline(time.Now().Add(20 * time.Second))
|
|
||||||
// create request
|
|
||||||
req := &GetBlockHeaders{
|
|
||||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{
|
|
||||||
Origin: eth.HashOrNumber{Hash: block.Hash()},
|
|
||||||
Amount: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop until BlockHeaders response contains desired block, confirming the
|
|
||||||
// node imported the block
|
|
||||||
for {
|
|
||||||
requestID := uint64(54)
|
|
||||||
headers, err := conn.headersRequest(req, s.chain, requestID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("GetBlockHeader request failed: %v", err)
|
|
||||||
}
|
|
||||||
// if headers response is empty, node hasn't imported block yet, try again
|
|
||||||
if len(headers) == 0 {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(block.Header(), headers[0]) {
|
|
||||||
return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) oldAnnounce() error {
|
|
||||||
sendConn, receiveConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sendConn.Close()
|
|
||||||
defer receiveConn.Close()
|
|
||||||
if err := sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := receiveConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
// create old block announcement
|
|
||||||
oldBlockAnnounce := &NewBlock{
|
|
||||||
Block: s.chain.blocks[len(s.chain.blocks)/2],
|
|
||||||
TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(),
|
|
||||||
}
|
|
||||||
if err := sendConn.Write(oldBlockAnnounce); err != nil {
|
|
||||||
return fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
// wait to see if the announcement is propagated
|
|
||||||
switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) {
|
|
||||||
case *NewBlock:
|
|
||||||
block := *msg
|
|
||||||
if block.Block.Hash() == oldBlockAnnounce.Block.Hash() {
|
|
||||||
return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
case *NewBlockHashes:
|
|
||||||
hashes := *msg
|
|
||||||
for _, hash := range hashes {
|
|
||||||
if hash.Hash == oldBlockAnnounce.Block.Hash() {
|
|
||||||
return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *Error:
|
|
||||||
errMsg := *msg
|
|
||||||
// check to make sure error is timeout (propagation didn't come through == test successful)
|
|
||||||
if !strings.Contains(errMsg.String(), "timeout") {
|
|
||||||
return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) maliciousHandshakes(t *utesting.T) error {
|
|
||||||
conn, err := s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
// write hello to client
|
|
||||||
pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:]
|
|
||||||
handshakes := []*Hello{
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: largeString(2), Version: 64},
|
|
||||||
},
|
|
||||||
ID: pub0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 64},
|
|
||||||
{Name: "eth", Version: 65},
|
|
||||||
},
|
|
||||||
ID: append(pub0, byte(0)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 64},
|
|
||||||
{Name: "eth", Version: 65},
|
|
||||||
},
|
|
||||||
ID: append(pub0, pub0...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: "eth", Version: 64},
|
|
||||||
{Name: "eth", Version: 65},
|
|
||||||
},
|
|
||||||
ID: largeBuffer(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 5,
|
|
||||||
Caps: []p2p.Cap{
|
|
||||||
{Name: largeString(2), Version: 64},
|
|
||||||
},
|
|
||||||
ID: largeBuffer(2),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, handshake := range handshakes {
|
|
||||||
t.Logf("Testing malicious handshake %v\n", i)
|
|
||||||
if err := conn.Write(handshake); err != nil {
|
|
||||||
return fmt.Errorf("could not write to connection: %v", err)
|
|
||||||
}
|
|
||||||
// check that the peer disconnected
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) {
|
|
||||||
case *Disconnect:
|
|
||||||
case *Error:
|
|
||||||
case *Hello:
|
|
||||||
// Discard one hello as Hello's are sent concurrently
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// dial for the next round
|
|
||||||
conn, err = s.dial()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dial failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) maliciousStatus(conn *Conn) error {
|
|
||||||
if err := conn.handshake(); err != nil {
|
|
||||||
return fmt.Errorf("handshake failed: %v", err)
|
|
||||||
}
|
|
||||||
status := &Status{
|
|
||||||
ProtocolVersion: uint32(conn.negotiatedProtoVersion),
|
|
||||||
NetworkID: s.chain.chainConfig.ChainID.Uint64(),
|
|
||||||
TD: largeNumber(2),
|
|
||||||
Head: s.chain.blocks[s.chain.Len()-1].Hash(),
|
|
||||||
Genesis: s.chain.blocks[0].Hash(),
|
|
||||||
ForkID: s.chain.ForkID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// get status
|
|
||||||
msg, err := conn.statusExchange(s.chain, status)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("status exchange failed: %v", err)
|
|
||||||
}
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *Status:
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("expected status, got: %#v ", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for disconnect
|
|
||||||
switch msg := conn.readAndServe(s.chain, timeout).(type) {
|
|
||||||
case *Disconnect:
|
|
||||||
return nil
|
|
||||||
case *Error:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) hashAnnounce() error {
|
|
||||||
// create connections
|
|
||||||
sendConn, recvConn, err := s.createSendAndRecvConns()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create connections: %v", err)
|
|
||||||
}
|
|
||||||
defer sendConn.Close()
|
|
||||||
defer recvConn.Close()
|
|
||||||
if err := sendConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := recvConn.peer(s.chain, nil); err != nil {
|
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create NewBlockHashes announcement
|
|
||||||
type anno struct {
|
|
||||||
Hash common.Hash // Hash of one particular block being announced
|
|
||||||
Number uint64 // Number of one particular block being announced
|
|
||||||
}
|
|
||||||
nextBlock := s.fullChain.blocks[s.chain.Len()]
|
|
||||||
announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()}
|
|
||||||
newBlockHash := &NewBlockHashes{announcement}
|
|
||||||
if err := sendConn.Write(newBlockHash); err != nil {
|
|
||||||
return fmt.Errorf("failed to write to connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Announcement sent, now wait for a header request
|
|
||||||
msg := sendConn.Read()
|
|
||||||
blockHeaderReq, ok := msg.(*GetBlockHeaders)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
if blockHeaderReq.Amount != 1 {
|
|
||||||
return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount)
|
|
||||||
}
|
|
||||||
if blockHeaderReq.Origin.Hash != announcement.Hash {
|
|
||||||
return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v",
|
|
||||||
pretty.Sdump(announcement),
|
|
||||||
pretty.Sdump(blockHeaderReq))
|
|
||||||
}
|
|
||||||
err = sendConn.Write(&BlockHeaders{
|
|
||||||
RequestId: blockHeaderReq.ReqID(),
|
|
||||||
BlockHeadersRequest: eth.BlockHeadersRequest{nextBlock.Header()},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write to connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for block announcement
|
|
||||||
msg = recvConn.readAndServe(s.chain, timeout)
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *NewBlockHashes:
|
|
||||||
hashes := *msg
|
|
||||||
if len(hashes) != 1 {
|
|
||||||
return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes))
|
|
||||||
}
|
|
||||||
if nextBlock.Hash() != hashes[0].Hash {
|
|
||||||
return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(),
|
|
||||||
hashes[0].Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *NewBlock:
|
|
||||||
// node should only propagate NewBlock without having requested the body if the body is empty
|
|
||||||
nextBlockBody := nextBlock.Body()
|
|
||||||
if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 {
|
|
||||||
return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
if msg.Block.Hash() != nextBlock.Hash() {
|
|
||||||
return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v",
|
|
||||||
nextBlock.Hash(), msg.Block.Hash())
|
|
||||||
}
|
|
||||||
// check to make sure header matches header that was sent to the node
|
|
||||||
if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) {
|
|
||||||
return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
|
|
||||||
}
|
|
||||||
// confirm node imported block
|
|
||||||
if err := s.waitForBlockImport(recvConn, nextBlock); err != nil {
|
|
||||||
return fmt.Errorf("error waiting for node to import new block: %v", err)
|
|
||||||
}
|
|
||||||
// update the chain
|
|
||||||
s.chain.blocks = append(s.chain.blocks, nextBlock)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
// Copyright 2020 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package ethtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// largeNumber returns a very large big.Int.
|
|
||||||
func largeNumber(megabytes int) *big.Int {
|
|
||||||
buf := make([]byte, megabytes*1024*1024)
|
|
||||||
rand.Read(buf)
|
|
||||||
bigint := new(big.Int)
|
|
||||||
bigint.SetBytes(buf)
|
|
||||||
return bigint
|
|
||||||
}
|
|
||||||
|
|
||||||
// largeBuffer returns a very large buffer.
|
|
||||||
func largeBuffer(megabytes int) []byte {
|
|
||||||
buf := make([]byte, megabytes*1024*1024)
|
|
||||||
rand.Read(buf)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// largeString returns a very large string.
|
|
||||||
func largeString(megabytes int) string {
|
|
||||||
buf := make([]byte, megabytes*1024*1024)
|
|
||||||
rand.Read(buf)
|
|
||||||
return hexutil.Encode(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func largeBlock() *types.Block {
|
|
||||||
return types.NewBlockWithHeader(largeHeader())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a random hash
|
|
||||||
func randHash() common.Hash {
|
|
||||||
var h common.Hash
|
|
||||||
rand.Read(h[:])
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func largeHeader() *types.Header {
|
|
||||||
return &types.Header{
|
|
||||||
MixDigest: randHash(),
|
|
||||||
ReceiptHash: randHash(),
|
|
||||||
TxHash: randHash(),
|
|
||||||
Nonce: types.BlockNonce{},
|
|
||||||
Extra: []byte{},
|
|
||||||
Bloom: types.Bloom{},
|
|
||||||
GasUsed: 0,
|
|
||||||
Coinbase: common.Address{},
|
|
||||||
GasLimit: 0,
|
|
||||||
UncleHash: types.EmptyUncleHash,
|
|
||||||
Time: 1337,
|
|
||||||
ParentHash: randHash(),
|
|
||||||
Root: randHash(),
|
|
||||||
Number: largeNumber(2),
|
|
||||||
Difficulty: largeNumber(2),
|
|
||||||
}
|
|
||||||
}
|
|
9
cmd/devp2p/internal/ethtest/mkchain.sh
Normal file
9
cmd/devp2p/internal/ethtest/mkchain.sh
Normal file
@ -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
|
87
cmd/devp2p/internal/ethtest/protocol.go
Normal file
87
cmd/devp2p/internal/ethtest/protocol.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
package ethtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported devp2p message codes from p2p/peer.go.
|
||||||
|
const (
|
||||||
|
handshakeMsg = 0x00
|
||||||
|
discMsg = 0x01
|
||||||
|
pingMsg = 0x02
|
||||||
|
pongMsg = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported devp2p protocol lengths from p2p package.
|
||||||
|
const (
|
||||||
|
baseProtoLen = 16
|
||||||
|
ethProtoLen = 17
|
||||||
|
snapProtoLen = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unexported handshake structure from p2p/peer.go.
|
||||||
|
type protoHandshake struct {
|
||||||
|
Version uint64
|
||||||
|
Name string
|
||||||
|
Caps []p2p.Cap
|
||||||
|
ListenPort uint64
|
||||||
|
ID []byte
|
||||||
|
Rest []rlp.RawValue `rlp:"tail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hello = protoHandshake
|
||||||
|
|
||||||
|
// Proto is an enum representing devp2p protocol types.
|
||||||
|
type Proto int
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseProto Proto = iota
|
||||||
|
ethProto
|
||||||
|
snapProto
|
||||||
|
)
|
||||||
|
|
||||||
|
// getProto returns the protocol a certain message code is associated with
|
||||||
|
// (assuming the negotiated capabilities are exactly {eth,snap})
|
||||||
|
func getProto(code uint64) Proto {
|
||||||
|
switch {
|
||||||
|
case code < baseProtoLen:
|
||||||
|
return baseProto
|
||||||
|
case code < baseProtoLen+ethProtoLen:
|
||||||
|
return ethProto
|
||||||
|
case code < baseProtoLen+ethProtoLen+snapProtoLen:
|
||||||
|
return snapProto
|
||||||
|
default:
|
||||||
|
panic("unhandled msg code beyond last protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protoOffset will return the offset at which the specified protocol's messages
|
||||||
|
// begin.
|
||||||
|
func protoOffset(proto Proto) uint64 {
|
||||||
|
switch proto {
|
||||||
|
case baseProto:
|
||||||
|
return 0
|
||||||
|
case ethProto:
|
||||||
|
return baseProtoLen
|
||||||
|
case snapProto:
|
||||||
|
return baseProtoLen + ethProtoLen
|
||||||
|
default:
|
||||||
|
panic("unhandled protocol")
|
||||||
|
}
|
||||||
|
}
|
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
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
crand "crypto/rand"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func makeJWTSecret() (string, [32]byte, error) {
|
||||||
genesisFile = "./testdata/genesis.json"
|
var secret [32]byte
|
||||||
halfchainFile = "./testdata/halfchain.rlp"
|
if _, err := crand.Read(secret[:]); err != nil {
|
||||||
fullchainFile = "./testdata/chain.rlp"
|
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) {
|
func TestEthSuite(t *testing.T) {
|
||||||
t.Parallel()
|
jwtPath, secret, err := makeJWTSecret()
|
||||||
geth, err := runGeth()
|
if err != nil {
|
||||||
|
t.Fatalf("could not make jwt secret: %v", err)
|
||||||
|
}
|
||||||
|
geth, err := runGeth("./testdata", jwtPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not run geth: %v", err)
|
t.Fatalf("could not run geth: %v", err)
|
||||||
}
|
}
|
||||||
defer geth.Close()
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("could not create new test suite: %v", err)
|
t.Fatalf("could not create new test suite: %v", err)
|
||||||
}
|
}
|
||||||
for _, test := range suite.EthTests() {
|
for _, test := range suite.EthTests() {
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
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 {
|
if result[0].Failed {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@ -57,20 +72,23 @@ func TestEthSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSnapSuite(t *testing.T) {
|
func TestSnapSuite(t *testing.T) {
|
||||||
t.Parallel()
|
jwtPath, secret, err := makeJWTSecret()
|
||||||
geth, err := runGeth()
|
if err != nil {
|
||||||
|
t.Fatalf("could not make jwt secret: %v", err)
|
||||||
|
}
|
||||||
|
geth, err := runGeth("./testdata", jwtPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not run geth: %v", err)
|
t.Fatalf("could not run geth: %v", err)
|
||||||
}
|
}
|
||||||
defer geth.Close()
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("could not create new test suite: %v", err)
|
t.Fatalf("could not create new test suite: %v", err)
|
||||||
}
|
}
|
||||||
for _, test := range suite.SnapTests() {
|
for _, test := range suite.SnapTests() {
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
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 {
|
if result[0].Failed {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@ -79,20 +97,23 @@ func TestSnapSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runGeth creates and starts a geth node
|
// 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{
|
stack, err := node.New(&node.Config{
|
||||||
|
AuthAddr: "127.0.0.1",
|
||||||
|
AuthPort: 0,
|
||||||
P2P: p2p.Config{
|
P2P: p2p.Config{
|
||||||
ListenAddr: "127.0.0.1:0",
|
ListenAddr: "127.0.0.1:0",
|
||||||
NoDiscovery: true,
|
NoDiscovery: true,
|
||||||
MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future
|
MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future
|
||||||
NoDial: true,
|
NoDial: true,
|
||||||
},
|
},
|
||||||
|
JWTSecret: jwtPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setupGeth(stack)
|
err = setupGeth(stack, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stack.Close()
|
stack.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -104,12 +125,11 @@ func runGeth() (*node.Node, error) {
|
|||||||
return stack, nil
|
return stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupGeth(stack *node.Node) error {
|
func setupGeth(stack *node.Node, dir string) error {
|
||||||
chain, err := loadChain(halfchainFile, genesisFile)
|
chain, err := NewChain(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
backend, err := eth.New(stack, ðconfig.Config{
|
backend, err := eth.New(stack, ðconfig.Config{
|
||||||
Genesis: &chain.genesis,
|
Genesis: &chain.genesis,
|
||||||
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
|
NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763
|
||||||
@ -122,8 +142,9 @@ func setupGeth(stack *node.Node) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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:])
|
_, err = backend.BlockChain().InsertChain(chain.blocks[1:])
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
62
cmd/devp2p/internal/ethtest/testdata/accounts.json
vendored
Normal file
62
cmd/devp2p/internal/ethtest/testdata/accounts.json
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"0x0c2c51a0990aee1d73c1228de158688341557508": {
|
||||||
|
"key": "0xbfcd0e032489319f4e5ca03e643b2025db624be6cf99cbfed90c4502e3754850"
|
||||||
|
},
|
||||||
|
"0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
||||||
|
"key": "0x457075f6822ac29481154792f65c5f1ec335b4fea9ca20f3fea8fa1d78a12c68"
|
||||||
|
},
|
||||||
|
"0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
|
||||||
|
"key": "0x865898edcf43206d138c93f1bbd86311f4657b057658558888aa5ac4309626a6"
|
||||||
|
},
|
||||||
|
"0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
|
||||||
|
"key": "0xee7f7875d826d7443ccc5c174e38b2c436095018774248a8074ee92d8914dcdb"
|
||||||
|
},
|
||||||
|
"0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
|
||||||
|
"key": "0x25e6ce8611cefb5cd338aeaa9292ed2139714668d123a4fb156cabb42051b5b7"
|
||||||
|
},
|
||||||
|
"0x2d389075be5be9f2246ad654ce152cf05990b209": {
|
||||||
|
"key": "0x19168cd7767604b3d19b99dc3da1302b9ccb6ee9ad61660859e07acd4a2625dd"
|
||||||
|
},
|
||||||
|
"0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
|
||||||
|
"key": "0x71aa7d299c7607dabfc3d0e5213d612b5e4a97455b596c2f642daac43fa5eeaa"
|
||||||
|
},
|
||||||
|
"0x4340ee1b812acb40a1eb561c019c327b243b92df": {
|
||||||
|
"key": "0x47f666f20e2175606355acec0ea1b37870c15e5797e962340da7ad7972a537e8"
|
||||||
|
},
|
||||||
|
"0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
|
||||||
|
"key": "0xa88293fefc623644969e2ce6919fb0dbd0fd64f640293b4bf7e1a81c97e7fc7f"
|
||||||
|
},
|
||||||
|
"0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
|
||||||
|
"key": "0x6e1e16a9c15641c73bf6e237f9293ab1d4e7c12b9adf83cfc94bcf969670f72d"
|
||||||
|
},
|
||||||
|
"0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
|
||||||
|
"key": "0x41be4e00aac79f7ffbb3455053ec05e971645440d594c047cdcc56a3c7458bd6"
|
||||||
|
},
|
||||||
|
"0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
|
||||||
|
"key": "0xc825f31cd8792851e33a290b3d749e553983111fc1f36dfbbdb45f101973f6a9"
|
||||||
|
},
|
||||||
|
"0x717f8aa2b982bee0e29f573d31df288663e1ce16": {
|
||||||
|
"key": "0x8d0faa04ae0f9bc3cd4c890aa025d5f40916f4729538b19471c0beefe11d9e19"
|
||||||
|
},
|
||||||
|
"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||||
|
"key": "0x4552dbe6ca4699322b5d923d0c9bcdd24644f5db8bf89a085b67c6c49b8a1b91"
|
||||||
|
},
|
||||||
|
"0x83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||||
|
"key": "0x34391cbbf06956bb506f45ec179cdd84df526aa364e27bbde65db9c15d866d00"
|
||||||
|
},
|
||||||
|
"0x84e75c28348fb86acea1a93a39426d7d60f4cc46": {
|
||||||
|
"key": "0xf6a8f1603b8368f3ca373292b7310c53bec7b508aecacd442554ebc1c5d0c856"
|
||||||
|
},
|
||||||
|
"0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||||
|
"key": "0x8d56bcbcf2c1b7109e1396a28d7a0234e33544ade74ea32c460ce4a443b239b1"
|
||||||
|
},
|
||||||
|
"0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
||||||
|
"key": "0xfc39d1c9ddbba176d806ebb42d7460189fe56ca163ad3eb6143bfc6beb6f6f72"
|
||||||
|
},
|
||||||
|
"0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
||||||
|
"key": "0x9ee3fd550664b246ad7cdba07162dd25530a3b1d51476dd1d85bbc29f0592684"
|
||||||
|
},
|
||||||
|
"0xeda8645ba6948855e3b3cd596bbb07596d59c603": {
|
||||||
|
"key": "0x14cdde09d1640eb8c3cda063891b0453073f57719583381ff78811efa6d4199f"
|
||||||
|
}
|
||||||
|
}
|
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
Binary file not shown.
20
cmd/devp2p/internal/ethtest/testdata/forkenv.json
vendored
Normal file
20
cmd/devp2p/internal/ethtest/testdata/forkenv.json
vendored
Normal file
@ -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"
|
||||||
|
}
|
107
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
107
cmd/devp2p/internal/ethtest/testdata/genesis.json
vendored
@ -1,27 +1,112 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"chainId": 19763,
|
"chainId": 3503995874084926,
|
||||||
"homesteadBlock": 0,
|
"homesteadBlock": 0,
|
||||||
"eip150Block": 0,
|
"eip150Block": 6,
|
||||||
"eip155Block": 0,
|
"eip155Block": 12,
|
||||||
"eip158Block": 0,
|
"eip158Block": 12,
|
||||||
"byzantiumBlock": 0,
|
"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,
|
"terminalTotalDifficultyPassed": true,
|
||||||
"ethash": {}
|
"ethash": {}
|
||||||
},
|
},
|
||||||
"nonce": "0xdeadbeefdeadbeef",
|
"nonce": "0x0",
|
||||||
"timestamp": "0x0",
|
"timestamp": "0x0",
|
||||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"extraData": "0x68697665636861696e",
|
||||||
"gasLimit": "0x80000000",
|
"gasLimit": "0x23f3e20",
|
||||||
"difficulty": "0x20000",
|
"difficulty": "0x20000",
|
||||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
"alloc": {
|
"alloc": {
|
||||||
"71562b71999873db5b286df957af199ec94617f7": {
|
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
|
||||||
"balance": "0xffffffffffffffffffffffffff"
|
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
|
||||||
|
"balance": "0x2a"
|
||||||
|
},
|
||||||
|
"0c2c51a0990aee1d73c1228de158688341557508": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"2d389075be5be9f2246ad654ce152cf05990b209": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"4340ee1b812acb40a1eb561c019c327b243b92df": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"717f8aa2b982bee0e29f573d31df288663e1ce16": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"84e75c28348fb86acea1a93a39426d7d60f4cc46": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"8bebc8ba651aee624937e7d897853ac30c95a067": {
|
||||||
|
"storage": {
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
|
||||||
|
},
|
||||||
|
"balance": "0x1",
|
||||||
|
"nonce": "0x1"
|
||||||
|
},
|
||||||
|
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
|
},
|
||||||
|
"eda8645ba6948855e3b3cd596bbb07596d59c603": {
|
||||||
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": "0x0",
|
"number": "0x0",
|
||||||
"gasUsed": "0x0",
|
"gasUsed": "0x0",
|
||||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"baseFeePerGas": null,
|
||||||
|
"excessBlobGas": null,
|
||||||
|
"blobGasUsed": null
|
||||||
}
|
}
|
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp
vendored
Binary file not shown.
23
cmd/devp2p/internal/ethtest/testdata/headblock.json
vendored
Normal file
23
cmd/devp2p/internal/ethtest/testdata/headblock.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"parentHash": "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"
|
||||||
|
}
|
13
cmd/devp2p/internal/ethtest/testdata/headfcu.json
vendored
Normal file
13
cmd/devp2p/internal/ethtest/testdata/headfcu.json
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "fcu500",
|
||||||
|
"method": "engine_forkchoiceUpdatedV3",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||||
|
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||||
|
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||||
|
},
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
4204
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
Normal file
4204
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13268
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
Normal file
13268
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3018
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
Normal file
3018
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -19,429 +19,141 @@ package ethtest
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
// sendTxs sends the given transactions to the node and
|
||||||
var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
// expects the node to accept and propagate them.
|
||||||
|
func (s *Suite) sendTxs(txs []*types.Transaction) error {
|
||||||
func (s *Suite) sendSuccessfulTxs(t *utesting.T) error {
|
// Open sending conn.
|
||||||
tests := []*types.Transaction{
|
sendConn, err := s.dial()
|
||||||
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()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sendConn.Close()
|
defer sendConn.Close()
|
||||||
defer recvConn.Close()
|
|
||||||
if err = sendConn.peer(s.chain, nil); err != nil {
|
if err = sendConn.peer(s.chain, nil); err != nil {
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
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
|
// Open receiving conn.
|
||||||
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
|
|
||||||
recvConn, err := s.dial()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sendConn.Close()
|
|
||||||
defer recvConn.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 {
|
if err = recvConn.peer(s.chain, nil); err != nil {
|
||||||
return fmt.Errorf("peering failed: %v", err)
|
return fmt.Errorf("peering failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the transactions
|
if err = sendConn.Write(ethProto, eth.TransactionsMsg, eth.TransactionsPacket(txs)); err != nil {
|
||||||
if err = sendConn.Write(&txMsg); err != nil {
|
|
||||||
return fmt.Errorf("failed to write message to connection: %v", err)
|
return fmt.Errorf("failed to write message to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update nonce
|
var (
|
||||||
nonce = txs[len(txs)-1].Nonce()
|
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.
|
// Wait for the transaction announcements, make sure all txs ar propagated.
|
||||||
// all txs should be announced within a couple announcements.
|
for time.Now().Before(end) {
|
||||||
recvHashes := make([]common.Hash, 0)
|
msg, err := recvConn.ReadEth()
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
|
|
||||||
case *Transactions:
|
|
||||||
for _, tx := range *msg {
|
|
||||||
recvHashes = append(recvHashes, tx.Hash())
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read from connection: %w", err)
|
||||||
|
}
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *eth.TransactionsPacket:
|
||||||
|
for _, tx := range *msg {
|
||||||
|
got[tx.Hash()] = true
|
||||||
|
}
|
||||||
|
case *eth.NewPooledTransactionHashesPacket68:
|
||||||
|
for _, hash := range msg.Hashes {
|
||||||
|
got[hash] = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if allReceived() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return signedTx
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("timed out waiting for txs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Suite) sendInvalidTxs(txs []*types.Transaction) error {
|
||||||
|
// Open sending conn.
|
||||||
|
sendConn, err := s.dial()
|
||||||
|
if err != 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/cmd/devp2p/internal/ethtest"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"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/p2p/rlpx"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -46,22 +47,30 @@ var (
|
|||||||
}
|
}
|
||||||
rlpxEthTestCommand = &cli.Command{
|
rlpxEthTestCommand = &cli.Command{
|
||||||
Name: "eth-test",
|
Name: "eth-test",
|
||||||
Usage: "Runs tests against a node",
|
Usage: "Runs eth protocol tests against a node",
|
||||||
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
|
ArgsUsage: "<node>",
|
||||||
Action: rlpxEthTest,
|
Action: rlpxEthTest,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
testPatternFlag,
|
testPatternFlag,
|
||||||
testTAPFlag,
|
testTAPFlag,
|
||||||
|
testChainDirFlag,
|
||||||
|
testNodeFlag,
|
||||||
|
testNodeJWTFlag,
|
||||||
|
testNodeEngineFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rlpxSnapTestCommand = &cli.Command{
|
rlpxSnapTestCommand = &cli.Command{
|
||||||
Name: "snap-test",
|
Name: "snap-test",
|
||||||
Usage: "Runs tests against a node",
|
Usage: "Runs snap protocol tests against a node",
|
||||||
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
|
ArgsUsage: "",
|
||||||
Action: rlpxSnapTest,
|
Action: rlpxSnapTest,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
testPatternFlag,
|
testPatternFlag,
|
||||||
testTAPFlag,
|
testTAPFlag,
|
||||||
|
testChainDirFlag,
|
||||||
|
testNodeFlag,
|
||||||
|
testNodeJWTFlag,
|
||||||
|
testNodeEngineFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -103,10 +112,8 @@ func rlpxPing(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// rlpxEthTest runs the eth protocol test suite.
|
// rlpxEthTest runs the eth protocol test suite.
|
||||||
func rlpxEthTest(ctx *cli.Context) error {
|
func rlpxEthTest(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 3 {
|
p := cliTestParams(ctx)
|
||||||
exit("missing path to chain.rlp as command-line argument")
|
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
|
||||||
}
|
|
||||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
@ -115,12 +122,44 @@ func rlpxEthTest(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// rlpxSnapTest runs the snap protocol test suite.
|
// rlpxSnapTest runs the snap protocol test suite.
|
||||||
func rlpxSnapTest(ctx *cli.Context) error {
|
func rlpxSnapTest(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 3 {
|
p := cliTestParams(ctx)
|
||||||
exit("missing path to chain.rlp as command-line argument")
|
suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt)
|
||||||
}
|
|
||||||
suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit(err)
|
exit(err)
|
||||||
}
|
}
|
||||||
return runTests(ctx, suite.SnapTests())
|
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"
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
"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/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -29,21 +30,49 @@ var (
|
|||||||
testPatternFlag = &cli.StringFlag{
|
testPatternFlag = &cli.StringFlag{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "Pattern of test suite(s) to run",
|
Usage: "Pattern of test suite(s) to run",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
testTAPFlag = &cli.BoolFlag{
|
testTAPFlag = &cli.BoolFlag{
|
||||||
Name: "tap",
|
Name: "tap",
|
||||||
Usage: "Output 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.
|
// These two are specific to the discovery tests.
|
||||||
testListen1Flag = &cli.StringFlag{
|
testListen1Flag = &cli.StringFlag{
|
||||||
Name: "listen1",
|
Name: "listen1",
|
||||||
Usage: "IP address of the first tester",
|
Usage: "IP address of the first tester",
|
||||||
Value: v4test.Listen1,
|
Value: v4test.Listen1,
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
testListen2Flag = &cli.StringFlag{
|
testListen2Flag = &cli.StringFlag{
|
||||||
Name: "listen2",
|
Name: "listen2",
|
||||||
Usage: "IP address of the second tester",
|
Usage: "IP address of the second tester",
|
||||||
Value: v4test.Listen2,
|
Value: v4test.Listen2,
|
||||||
|
Category: flags.TestingCategory,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
case *number < threshold:
|
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
|
backoff = true
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -977,6 +977,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error
|
|||||||
// in transactions before obtaining lock
|
// in transactions before obtaining lock
|
||||||
if err := pool.validateTxBasics(tx, local); err != nil {
|
if err := pool.validateTxBasics(tx, local); err != nil {
|
||||||
errs[i] = err
|
errs[i] = err
|
||||||
|
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
|
||||||
invalidTxMeter.Mark(1)
|
invalidTxMeter.Mark(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ const (
|
|||||||
LoggingCategory = "LOGGING AND DEBUGGING"
|
LoggingCategory = "LOGGING AND DEBUGGING"
|
||||||
MetricsCategory = "METRICS AND STATS"
|
MetricsCategory = "METRICS AND STATS"
|
||||||
MiscCategory = "MISC"
|
MiscCategory = "MISC"
|
||||||
|
TestingCategory = "TESTING"
|
||||||
DeprecatedCategory = "ALIASED (deprecated)"
|
DeprecatedCategory = "ALIASED (deprecated)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user