Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/devp2p/internal/ethtest: add transaction tests #21857

Merged
merged 13 commits into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/devp2p/internal/ethtest/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestEthProtocolNegotiation(t *testing.T) {
// TestChain_GetHeaders tests whether the test suite can correctly
// respond to a GetBlockHeaders request from a node.
func TestChain_GetHeaders(t *testing.T) {
chainFile, err := filepath.Abs("./testdata/fullchain.rlp.gz")
chainFile, err := filepath.Abs("./testdata/chain.rlp")
if err != nil {
t.Fatal(err)
}
Expand Down
38 changes: 32 additions & 6 deletions cmd/devp2p/internal/ethtest/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/p2p"
Expand All @@ -37,6 +38,8 @@ var pretty = spew.ConfigState{
SortKeys: true,
}

var timeout = 20 * time.Second

// Suite represents a structure used to test the eth
// protocol of a node(s).
type Suite struct {
Expand Down Expand Up @@ -70,6 +73,8 @@ func (s *Suite) AllTests() []utesting.Test {
{Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce},
{Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake},
{Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus},
{Name: "TestTransactions", Fn: s.TestTransaction},
{Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx},
}
}

Expand Down Expand Up @@ -115,7 +120,6 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) {
default:
t.Fatalf("expected status, got: %#v ", msg)
}
timeout := 20 * time.Second
// wait for disconnect
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
case *Disconnect:
Expand Down Expand Up @@ -151,7 +155,6 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err)
}

timeout := 20 * time.Second
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
case *BlockHeaders:
headers := msg
Expand Down Expand Up @@ -181,7 +184,6 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err)
}

timeout := 20 * time.Second
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
case *BlockBodies:
t.Logf("received %d block bodies", len(*msg))
Expand Down Expand Up @@ -257,7 +259,7 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) {
},
}
for i, handshake := range handshakes {
fmt.Printf("Testing malicious handshake %v\n", i)
t.Logf("Testing malicious handshake %v\n", i)
// Init the handshake
if err := conn.Write(handshake); err != nil {
t.Fatalf("could not write to connection: %v", err)
Expand Down Expand Up @@ -307,13 +309,12 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) {
}

for i, blockAnnouncement := range blocks[0:3] {
fmt.Printf("Testing malicious announcement: %v\n", i)
t.Logf("Testing malicious announcement: %v\n", i)
sendConn := s.setupConnection(t)
if err := sendConn.Write(blockAnnouncement); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// Invalid announcement, check that peer disconnected
timeout := 20 * time.Second
switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) {
case *Disconnect:
case *Error:
Expand Down Expand Up @@ -398,3 +399,28 @@ func (s *Suite) dial() (*Conn, error) {

return &conn, nil
}

func (s *Suite) TestTransaction(t *utesting.T) {
tests := []*types.Transaction{
getNextTxFromChain(t, s),
unknownTx(t, s),
}
for i, tx := range tests {
t.Logf("Testing tx propagation: %v\n", i)
sendSuccessfulTx(t, s, tx)
}
}

func (s *Suite) TestMaliciousTx(t *utesting.T) {
tests := []*types.Transaction{
getOldTxFromChain(t, s),
invalidNonceTx(t, s),
hugeAmount(t, s),
hugeGasPrice(t, s),
hugeData(t, s),
}
for i, tx := range tests {
t.Logf("Testing malicious tx propagation: %v\n", i)
sendFailingTx(t, s, tx)
}
}
Binary file added cmd/devp2p/internal/ethtest/testdata/chain.rlp
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion cmd/devp2p/internal/ethtest/testdata/genesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"71562b71999873db5b286df957af199ec94617f7": {
"balance": "0xffffffff"
"balance": "0xffffffffffffffffffffffffff"
}
},
"number": "0x0",
Expand Down
Binary file not shown.
Binary file not shown.
180 changes: 180 additions & 0 deletions cmd/devp2p/internal/ethtest/transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package ethtest

import (
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/utesting"
)

//var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")

func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) {
sendConn := s.setupConnection(t)
t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas())
// Send the transaction
if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil {
t.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
recvConn := s.setupConnection(t)
// Wait for the transaction announcement
switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) {
case *Transactions:
recTxs := *msg
if len(recTxs) < 1 {
t.Fatalf("received transactions do not match send: %v", recTxs)
}
if tx.Hash() != recTxs[len(recTxs)-1].Hash() {
t.Fatalf("received transactions do not match send: got %v want %v", recTxs, tx)
}
case *NewPooledTransactionHashes:
txHashes := *msg
if len(txHashes) < 1 {
t.Fatalf("received transactions do not match send: %v", txHashes)
}
if tx.Hash() != txHashes[len(txHashes)-1] {
t.Fatalf("wrong announcement received, wanted %v got %v", tx, txHashes)
}
default:
t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg))
}
}

func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) {
sendConn, recvConn := s.setupConnection(t), s.setupConnection(t)
// Wait for a transaction announcement
switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) {
case *NewPooledTransactionHashes:
break
default:
t.Logf("unexpected message, logging: %v", pretty.Sdump(msg))
}
// Send the transaction
if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil {
t.Fatal(err)
}
// Wait for another transaction announcement
switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) {
case *Transactions:
t.Fatalf("Received unexpected transaction announcement: %v", msg)
case *NewPooledTransactionHashes:
t.Fatalf("Received unexpected pooledTx announcement: %v", msg)
case *Error:
// Transaction should not be announced -> wait for timeout
return
default:
t.Fatalf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg))
}
}

func unknownTx(t *utesting.T, s *Suite) *types.Transaction {
tx := getNextTxFromChain(t, s)
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(t, txNew)
}

func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction {
// Get a new transaction
var tx *types.Transaction
for _, blocks := range s.fullChain.blocks[s.chain.Len():] {
txs := blocks.Transactions()
if txs.Len() != 0 {
tx = txs[0]
break
}
}
if tx == nil {
t.Fatal("could not find transaction")
}
return tx
}

func getOldTxFromChain(t *utesting.T, s *Suite) *types.Transaction {
var tx *types.Transaction
for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] {
txs := blocks.Transactions()
if txs.Len() != 0 {
tx = txs[0]
break
}
}
if tx == nil {
t.Fatal("could not find transaction")
}
return tx
}

func invalidNonceTx(t *utesting.T, s *Suite) *types.Transaction {
tx := getNextTxFromChain(t, s)
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(t, txNew)
}

func hugeAmount(t *utesting.T, s *Suite) *types.Transaction {
tx := getNextTxFromChain(t, s)
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(t, txNew)
}

func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction {
tx := getNextTxFromChain(t, s)
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(t, txNew)
}

func hugeData(t *utesting.T, s *Suite) *types.Transaction {
tx := getNextTxFromChain(t, s)
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(t, txNew)
}

func signWithFaucet(t *utesting.T, tx *types.Transaction) *types.Transaction {
signer := types.HomesteadSigner{}
signedTx, err := types.SignTx(tx, signer, faucetKey)
if err != nil {
t.Fatalf("could not sign tx: %v\n", err)
}
return signedTx
}
45 changes: 29 additions & 16 deletions cmd/devp2p/internal/ethtest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,9 @@ type NewBlockHashes []struct {

func (nbh NewBlockHashes) Code() int { return 17 }

// NewBlock is the network packet for the block propagation message.
type NewBlock struct {
Block *types.Block
TD *big.Int
}
type Transactions []*types.Transaction

func (nb NewBlock) Code() int { return 23 }
func (t Transactions) Code() int { return 18 }

// GetBlockHeaders represents a block header query.
type GetBlockHeaders struct {
Expand All @@ -122,6 +118,29 @@ type BlockHeaders []*types.Header

func (bh BlockHeaders) Code() int { return 20 }

// GetBlockBodies represents a GetBlockBodies request
type GetBlockBodies []common.Hash

func (gbb GetBlockBodies) Code() int { return 21 }

// BlockBodies is the network packet for block content distribution.
type BlockBodies []*types.Body

func (bb BlockBodies) Code() int { return 22 }

// NewBlock is the network packet for the block propagation message.
type NewBlock struct {
Block *types.Block
TD *big.Int
}

func (nb NewBlock) Code() int { return 23 }

// NewPooledTransactionHashes is the network packet for the tx hash propagation message.
type NewPooledTransactionHashes [][32]byte

func (nb NewPooledTransactionHashes) Code() int { return 24 }

// HashOrNumber is a combined field for specifying an origin block.
type hashOrNumber struct {
Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
Expand Down Expand Up @@ -158,16 +177,6 @@ func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
return err
}

// GetBlockBodies represents a GetBlockBodies request
type GetBlockBodies []common.Hash

func (gbb GetBlockBodies) Code() int { return 21 }

// BlockBodies is the network packet for block content distribution.
type BlockBodies []*types.Body

func (bb BlockBodies) Code() int { return 22 }

// Conn represents an individual connection with a peer
type Conn struct {
*rlpx.Conn
Expand Down Expand Up @@ -205,6 +214,10 @@ func (c *Conn) Read() Message {
msg = new(NewBlock)
case (NewBlockHashes{}).Code():
msg = new(NewBlockHashes)
case (Transactions{}).Code():
msg = new(Transactions)
case (NewPooledTransactionHashes{}).Code():
msg = new(NewPooledTransactionHashes)
default:
return errorf("invalid message code: %d", code)
}
Expand Down