diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 604b90868..ac3907ce8 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -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) } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 0348751f8..5d0cdda72 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -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" @@ -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 { @@ -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}, } } @@ -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: @@ -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 @@ -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)) @@ -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) @@ -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: @@ -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) + } +} diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp new file mode 100644 index 000000000..5ebc2f3bb Binary files /dev/null and b/cmd/devp2p/internal/ethtest/testdata/chain.rlp differ diff --git a/cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz deleted file mode 100644 index 50f52eafa..000000000 Binary files a/cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz and /dev/null differ diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index ed78488b6..d8b5d2250 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -17,7 +17,7 @@ "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xffffffff" + "balance": "0xffffffffffffffffffffffffff" } }, "number": "0x0", diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp new file mode 100644 index 000000000..1a820734e Binary files /dev/null and b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp differ diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz deleted file mode 100644 index 82d527136..000000000 Binary files a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz and /dev/null differ diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go new file mode 100644 index 000000000..4aaab8bf9 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -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 . + +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 +} diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index a20e88c37..f768d61d5 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -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 { @@ -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) @@ -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 @@ -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) }