github.com/juliankolbe/go-ethereum@v1.9.992/cmd/devp2p/internal/ethtest/suite.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package ethtest
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"time"
    23  
    24  	"github.com/davecgh/go-spew/spew"
    25  	"github.com/juliankolbe/go-ethereum/core/types"
    26  	"github.com/juliankolbe/go-ethereum/crypto"
    27  	"github.com/juliankolbe/go-ethereum/eth/protocols/eth"
    28  	"github.com/juliankolbe/go-ethereum/internal/utesting"
    29  	"github.com/juliankolbe/go-ethereum/p2p"
    30  	"github.com/juliankolbe/go-ethereum/p2p/enode"
    31  	"github.com/juliankolbe/go-ethereum/p2p/rlpx"
    32  	"github.com/stretchr/testify/assert"
    33  )
    34  
    35  var pretty = spew.ConfigState{
    36  	Indent:                  "  ",
    37  	DisableCapacities:       true,
    38  	DisablePointerAddresses: true,
    39  	SortKeys:                true,
    40  }
    41  
    42  var timeout = 20 * time.Second
    43  
    44  // Suite represents a structure used to test the eth
    45  // protocol of a node(s).
    46  type Suite struct {
    47  	Dest *enode.Node
    48  
    49  	chain     *Chain
    50  	fullChain *Chain
    51  }
    52  
    53  // NewSuite creates and returns a new eth-test suite that can
    54  // be used to test the given node against the given blockchain
    55  // data.
    56  func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) {
    57  	chain, err := loadChain(chainfile, genesisfile)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return &Suite{
    62  		Dest:      dest,
    63  		chain:     chain.Shorten(1000),
    64  		fullChain: chain,
    65  	}, nil
    66  }
    67  
    68  func (s *Suite) EthTests() []utesting.Test {
    69  	return []utesting.Test{
    70  		// status
    71  		{Name: "Status", Fn: s.TestStatus},
    72  		{Name: "Status_66", Fn: s.TestStatus_66},
    73  		// get block headers
    74  		{Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders},
    75  		{Name: "GetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66},
    76  		{Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66},
    77  		{Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66},
    78  		{Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66},
    79  		// get block bodies
    80  		{Name: "GetBlockBodies", Fn: s.TestGetBlockBodies},
    81  		{Name: "GetBlockBodies_66", Fn: s.TestGetBlockBodies_66},
    82  		// broadcast
    83  		{Name: "Broadcast", Fn: s.TestBroadcast},
    84  		{Name: "Broadcast_66", Fn: s.TestBroadcast_66},
    85  		{Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce},
    86  		{Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66},
    87  		// malicious handshakes + status
    88  		{Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake},
    89  		{Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus},
    90  		{Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66},
    91  		{Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus},
    92  		// test transactions
    93  		{Name: "TestTransactions", Fn: s.TestTransaction},
    94  		{Name: "TestTransactions_66", Fn: s.TestTransaction_66},
    95  		{Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx},
    96  		{Name: "TestMaliciousTransactions_66", Fn: s.TestMaliciousTx_66},
    97  	}
    98  }
    99  
   100  // TestStatus attempts to connect to the given node and exchange
   101  // a status message with it, and then check to make sure
   102  // the chain head is correct.
   103  func (s *Suite) TestStatus(t *utesting.T) {
   104  	conn, err := s.dial()
   105  	if err != nil {
   106  		t.Fatalf("could not dial: %v", err)
   107  	}
   108  	// get protoHandshake
   109  	conn.handshake(t)
   110  	// get status
   111  	switch msg := conn.statusExchange(t, s.chain, nil).(type) {
   112  	case *Status:
   113  		t.Logf("got status message: %s", pretty.Sdump(msg))
   114  	default:
   115  		t.Fatalf("unexpected: %s", pretty.Sdump(msg))
   116  	}
   117  }
   118  
   119  // TestMaliciousStatus sends a status package with a large total difficulty.
   120  func (s *Suite) TestMaliciousStatus(t *utesting.T) {
   121  	conn, err := s.dial()
   122  	if err != nil {
   123  		t.Fatalf("could not dial: %v", err)
   124  	}
   125  	// get protoHandshake
   126  	conn.handshake(t)
   127  	status := &Status{
   128  		ProtocolVersion: uint32(conn.ethProtocolVersion),
   129  		NetworkID:       s.chain.chainConfig.ChainID.Uint64(),
   130  		TD:              largeNumber(2),
   131  		Head:            s.chain.blocks[s.chain.Len()-1].Hash(),
   132  		Genesis:         s.chain.blocks[0].Hash(),
   133  		ForkID:          s.chain.ForkID(),
   134  	}
   135  	// get status
   136  	switch msg := conn.statusExchange(t, s.chain, status).(type) {
   137  	case *Status:
   138  		t.Logf("%+v\n", msg)
   139  	default:
   140  		t.Fatalf("expected status, got: %#v ", msg)
   141  	}
   142  	// wait for disconnect
   143  	switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
   144  	case *Disconnect:
   145  	case *Error:
   146  		return
   147  	default:
   148  		t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg))
   149  	}
   150  }
   151  
   152  // TestGetBlockHeaders tests whether the given node can respond to
   153  // a `GetBlockHeaders` request and that the response is accurate.
   154  func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
   155  	conn, err := s.dial()
   156  	if err != nil {
   157  		t.Fatalf("could not dial: %v", err)
   158  	}
   159  
   160  	conn.handshake(t)
   161  	conn.statusExchange(t, s.chain, nil)
   162  
   163  	// get block headers
   164  	req := &GetBlockHeaders{
   165  		Origin: eth.HashOrNumber{
   166  			Hash: s.chain.blocks[1].Hash(),
   167  		},
   168  		Amount:  2,
   169  		Skip:    1,
   170  		Reverse: false,
   171  	}
   172  
   173  	if err := conn.Write(req); err != nil {
   174  		t.Fatalf("could not write to connection: %v", err)
   175  	}
   176  
   177  	switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
   178  	case *BlockHeaders:
   179  		headers := *msg
   180  		for _, header := range headers {
   181  			num := header.Number.Uint64()
   182  			t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash()))
   183  			assert.Equal(t, s.chain.blocks[int(num)].Header(), header)
   184  		}
   185  	default:
   186  		t.Fatalf("unexpected: %s", pretty.Sdump(msg))
   187  	}
   188  }
   189  
   190  // TestGetBlockBodies tests whether the given node can respond to
   191  // a `GetBlockBodies` request and that the response is accurate.
   192  func (s *Suite) TestGetBlockBodies(t *utesting.T) {
   193  	conn, err := s.dial()
   194  	if err != nil {
   195  		t.Fatalf("could not dial: %v", err)
   196  	}
   197  
   198  	conn.handshake(t)
   199  	conn.statusExchange(t, s.chain, nil)
   200  	// create block bodies request
   201  	req := &GetBlockBodies{
   202  		s.chain.blocks[54].Hash(),
   203  		s.chain.blocks[75].Hash(),
   204  	}
   205  	if err := conn.Write(req); err != nil {
   206  		t.Fatalf("could not write to connection: %v", err)
   207  	}
   208  
   209  	switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
   210  	case *BlockBodies:
   211  		t.Logf("received %d block bodies", len(*msg))
   212  	default:
   213  		t.Fatalf("unexpected: %s", pretty.Sdump(msg))
   214  	}
   215  }
   216  
   217  // TestBroadcast tests whether a block announcement is correctly
   218  // propagated to the given node's peer(s).
   219  func (s *Suite) TestBroadcast(t *utesting.T) {
   220  	sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t)
   221  	nextBlock := len(s.chain.blocks)
   222  	blockAnnouncement := &NewBlock{
   223  		Block: s.fullChain.blocks[nextBlock],
   224  		TD:    s.fullChain.TD(nextBlock + 1),
   225  	}
   226  	s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement)
   227  	// update test suite chain
   228  	s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock])
   229  	// wait for client to update its chain
   230  	if err := receiveConn.waitForBlock(s.chain.Head()); err != nil {
   231  		t.Fatal(err)
   232  	}
   233  }
   234  
   235  // TestMaliciousHandshake tries to send malicious data during the handshake.
   236  func (s *Suite) TestMaliciousHandshake(t *utesting.T) {
   237  	conn, err := s.dial()
   238  	if err != nil {
   239  		t.Fatalf("could not dial: %v", err)
   240  	}
   241  	// write hello to client
   242  	pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:]
   243  	handshakes := []*Hello{
   244  		{
   245  			Version: 5,
   246  			Caps: []p2p.Cap{
   247  				{Name: largeString(2), Version: 64},
   248  			},
   249  			ID: pub0,
   250  		},
   251  		{
   252  			Version: 5,
   253  			Caps: []p2p.Cap{
   254  				{Name: "eth", Version: 64},
   255  				{Name: "eth", Version: 65},
   256  			},
   257  			ID: append(pub0, byte(0)),
   258  		},
   259  		{
   260  			Version: 5,
   261  			Caps: []p2p.Cap{
   262  				{Name: "eth", Version: 64},
   263  				{Name: "eth", Version: 65},
   264  			},
   265  			ID: append(pub0, pub0...),
   266  		},
   267  		{
   268  			Version: 5,
   269  			Caps: []p2p.Cap{
   270  				{Name: "eth", Version: 64},
   271  				{Name: "eth", Version: 65},
   272  			},
   273  			ID: largeBuffer(2),
   274  		},
   275  		{
   276  			Version: 5,
   277  			Caps: []p2p.Cap{
   278  				{Name: largeString(2), Version: 64},
   279  			},
   280  			ID: largeBuffer(2),
   281  		},
   282  	}
   283  	for i, handshake := range handshakes {
   284  		t.Logf("Testing malicious handshake %v\n", i)
   285  		// Init the handshake
   286  		if err := conn.Write(handshake); err != nil {
   287  			t.Fatalf("could not write to connection: %v", err)
   288  		}
   289  		// check that the peer disconnected
   290  		timeout := 20 * time.Second
   291  		// Discard one hello
   292  		for i := 0; i < 2; i++ {
   293  			switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
   294  			case *Disconnect:
   295  			case *Error:
   296  			case *Hello:
   297  				// Hello's are send concurrently, so ignore them
   298  				continue
   299  			default:
   300  				t.Fatalf("unexpected: %s", pretty.Sdump(msg))
   301  			}
   302  		}
   303  		// Dial for the next round
   304  		conn, err = s.dial()
   305  		if err != nil {
   306  			t.Fatalf("could not dial: %v", err)
   307  		}
   308  	}
   309  }
   310  
   311  // TestLargeAnnounce tests the announcement mechanism with a large block.
   312  func (s *Suite) TestLargeAnnounce(t *utesting.T) {
   313  	nextBlock := len(s.chain.blocks)
   314  	blocks := []*NewBlock{
   315  		{
   316  			Block: largeBlock(),
   317  			TD:    s.fullChain.TD(nextBlock + 1),
   318  		},
   319  		{
   320  			Block: s.fullChain.blocks[nextBlock],
   321  			TD:    largeNumber(2),
   322  		},
   323  		{
   324  			Block: largeBlock(),
   325  			TD:    largeNumber(2),
   326  		},
   327  		{
   328  			Block: s.fullChain.blocks[nextBlock],
   329  			TD:    s.fullChain.TD(nextBlock + 1),
   330  		},
   331  	}
   332  
   333  	for i, blockAnnouncement := range blocks[0:3] {
   334  		t.Logf("Testing malicious announcement: %v\n", i)
   335  		sendConn := s.setupConnection(t)
   336  		if err := sendConn.Write(blockAnnouncement); err != nil {
   337  			t.Fatalf("could not write to connection: %v", err)
   338  		}
   339  		// Invalid announcement, check that peer disconnected
   340  		switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) {
   341  		case *Disconnect:
   342  		case *Error:
   343  			break
   344  		default:
   345  			t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg))
   346  		}
   347  	}
   348  	// Test the last block as a valid block
   349  	sendConn := s.setupConnection(t)
   350  	receiveConn := s.setupConnection(t)
   351  	s.testAnnounce(t, sendConn, receiveConn, blocks[3])
   352  	// update test suite chain
   353  	s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock])
   354  	// wait for client to update its chain
   355  	if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil {
   356  		t.Fatal(err)
   357  	}
   358  }
   359  
   360  func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) {
   361  	// Announce the block.
   362  	if err := sendConn.Write(blockAnnouncement); err != nil {
   363  		t.Fatalf("could not write to connection: %v", err)
   364  	}
   365  	s.waitAnnounce(t, receiveConn, blockAnnouncement)
   366  }
   367  
   368  func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) {
   369  	timeout := 20 * time.Second
   370  	switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
   371  	case *NewBlock:
   372  		t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block))
   373  		assert.Equal(t,
   374  			blockAnnouncement.Block.Header(), msg.Block.Header(),
   375  			"wrong block header in announcement",
   376  		)
   377  		assert.Equal(t,
   378  			blockAnnouncement.TD, msg.TD,
   379  			"wrong TD in announcement",
   380  		)
   381  	case *NewBlockHashes:
   382  		message := *msg
   383  		t.Logf("received NewBlockHashes message: %s", pretty.Sdump(message))
   384  		assert.Equal(t, blockAnnouncement.Block.Hash(), message[0].Hash,
   385  			"wrong block hash in announcement",
   386  		)
   387  	default:
   388  		t.Fatalf("unexpected: %s", pretty.Sdump(msg))
   389  	}
   390  }
   391  
   392  func (s *Suite) setupConnection(t *utesting.T) *Conn {
   393  	// create conn
   394  	sendConn, err := s.dial()
   395  	if err != nil {
   396  		t.Fatalf("could not dial: %v", err)
   397  	}
   398  	sendConn.handshake(t)
   399  	sendConn.statusExchange(t, s.chain, nil)
   400  	return sendConn
   401  }
   402  
   403  // dial attempts to dial the given node and perform a handshake,
   404  // returning the created Conn if successful.
   405  func (s *Suite) dial() (*Conn, error) {
   406  	var conn Conn
   407  	// dial
   408  	fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  	conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey())
   413  	// do encHandshake
   414  	conn.ourKey, _ = crypto.GenerateKey()
   415  	_, err = conn.Handshake(conn.ourKey)
   416  	if err != nil {
   417  		return nil, err
   418  	}
   419  	// set default p2p capabilities
   420  	conn.caps = []p2p.Cap{
   421  		{Name: "eth", Version: 64},
   422  		{Name: "eth", Version: 65},
   423  	}
   424  	return &conn, nil
   425  }
   426  
   427  func (s *Suite) TestTransaction(t *utesting.T) {
   428  	tests := []*types.Transaction{
   429  		getNextTxFromChain(t, s),
   430  		unknownTx(t, s),
   431  	}
   432  	for i, tx := range tests {
   433  		t.Logf("Testing tx propagation: %v\n", i)
   434  		sendSuccessfulTx(t, s, tx)
   435  	}
   436  }
   437  
   438  func (s *Suite) TestMaliciousTx(t *utesting.T) {
   439  	tests := []*types.Transaction{
   440  		getOldTxFromChain(t, s),
   441  		invalidNonceTx(t, s),
   442  		hugeAmount(t, s),
   443  		hugeGasPrice(t, s),
   444  		hugeData(t, s),
   445  	}
   446  	for i, tx := range tests {
   447  		t.Logf("Testing malicious tx propagation: %v\n", i)
   448  		sendFailingTx(t, s, tx)
   449  	}
   450  }