github.com/core-coin/go-core/v2@v2.1.9/cmd/devp2p/internal/xcbtest/suite.go (about)

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