diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go
index 27ee9493b0eb..e6714358e63c 100644
--- a/cmd/devp2p/internal/ethtest/chain.go
+++ b/cmd/devp2p/internal/ethtest/chain.go
@@ -89,12 +89,12 @@ func (c *Chain) Head() *types.Block {
return c.blocks[c.Len()-1]
}
-func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) {
+func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
if req.Amount < 1 {
return nil, fmt.Errorf("no block headers requested")
}
- headers := make(BlockHeaders, req.Amount)
+ headers := make([]*types.Header, req.Amount)
var blockNumber uint64
// range over blocks to check if our chain has the requested header
diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go
index 806545925638..c7a0b92ec1c4 100644
--- a/cmd/devp2p/internal/ethtest/chain_test.go
+++ b/cmd/devp2p/internal/ethtest/chain_test.go
@@ -1,18 +1,18 @@
// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
+// This file is part of go-ethereum.
//
-// 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
+// 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.
//
-// The go-ethereum library is distributed in the hope that it will be useful,
+// 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 Lesser General Public License for more details.
+// GNU 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 .
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
package ethtest
@@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/assert"
+ "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/eth/protocols/eth"
"github.com/scroll-tech/go-ethereum/p2p"
)
@@ -112,6 +113,27 @@ func TestEthProtocolNegotiation(t *testing.T) {
},
expected: uint32(64),
},
+ {
+ conn: &Conn{
+ ourHighestProtoVersion: 68,
+ },
+ caps: []p2p.Cap{
+ {Name: "eth", Version: 65},
+ {Name: "eth", Version: 66},
+ {Name: "wrongProto", Version: 68},
+ },
+ expected: uint32(66),
+ },
+ {
+ conn: &Conn{
+ ourHighestProtoVersion: 68,
+ },
+ caps: []p2p.Cap{
+ {Name: "eth", Version: 63},
+ {Name: "eth", Version: 68},
+ },
+ expected: uint32(68),
+ },
}
for i, tt := range tests {
@@ -141,18 +163,18 @@ func TestChain_GetHeaders(t *testing.T) {
var tests = []struct {
req GetBlockHeaders
- expected BlockHeaders
+ expected []*types.Header
}{
{
req: GetBlockHeaders{
- Origin: eth.HashOrNumber{
- Number: uint64(2),
+ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
+ Origin: eth.HashOrNumber{Number: uint64(2)},
+ Amount: uint64(5),
+ Skip: 1,
+ Reverse: false,
},
- Amount: uint64(5),
- Skip: 1,
- Reverse: false,
},
- expected: BlockHeaders{
+ expected: []*types.Header{
chain.blocks[2].Header(),
chain.blocks[4].Header(),
chain.blocks[6].Header(),
@@ -162,14 +184,14 @@ func TestChain_GetHeaders(t *testing.T) {
},
{
req: GetBlockHeaders{
- Origin: eth.HashOrNumber{
- Number: uint64(chain.Len() - 1),
+ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
+ Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)},
+ Amount: uint64(3),
+ Skip: 0,
+ Reverse: true,
},
- Amount: uint64(3),
- Skip: 0,
- Reverse: true,
},
- expected: BlockHeaders{
+ expected: []*types.Header{
chain.blocks[chain.Len()-1].Header(),
chain.blocks[chain.Len()-2].Header(),
chain.blocks[chain.Len()-3].Header(),
@@ -177,14 +199,14 @@ func TestChain_GetHeaders(t *testing.T) {
},
{
req: GetBlockHeaders{
- Origin: eth.HashOrNumber{
- Hash: chain.Head().Hash(),
+ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
+ Origin: eth.HashOrNumber{Hash: chain.Head().Hash()},
+ Amount: uint64(1),
+ Skip: 0,
+ Reverse: false,
},
- Amount: uint64(1),
- Skip: 0,
- Reverse: false,
},
- expected: BlockHeaders{
+ expected: []*types.Header{
chain.Head().Header(),
},
},
@@ -192,7 +214,7 @@ func TestChain_GetHeaders(t *testing.T) {
for i, tt := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
- headers, err := chain.GetHeaders(tt.req)
+ headers, err := chain.GetHeaders(&tt.req)
if err != nil {
t.Fatal(err)
}
diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go
index 70d773f72101..3a3b6df23a23 100644
--- a/cmd/devp2p/internal/ethtest/helpers.go
+++ b/cmd/devp2p/internal/ethtest/helpers.go
@@ -1,18 +1,18 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
+// Copyright 2021 The go-ethereum Authors
+// This file is part of go-ethereum.
//
-// 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
+// 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.
//
-// The go-ethereum library is distributed in the hope that it will be useful,
+// 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 Lesser General Public License for more details.
+// GNU 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 .
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
package ethtest
@@ -44,21 +44,6 @@ var (
timeout = 20 * time.Second
)
-// Is_66 checks if the node supports the eth66 protocol version,
-// and if not, exists the test suite
-func (s *Suite) Is_66(t *utesting.T) {
- conn, err := s.dial66()
- if err != nil {
- t.Fatalf("dial failed: %v", err)
- }
- if err := conn.handshake(); err != nil {
- t.Fatalf("handshake failed: %v", err)
- }
- if conn.negotiatedProtoVersion < 66 {
- t.Fail()
- }
-}
-
// dial attempts to dial the given node and perform a handshake,
// returning the created Conn if successful.
func (s *Suite) dial() (*Conn, error) {
@@ -77,26 +62,14 @@ func (s *Suite) dial() (*Conn, error) {
}
// set default p2p capabilities
conn.caps = []p2p.Cap{
- {Name: "eth", Version: 64},
- {Name: "eth", Version: 65},
+ {Name: "eth", Version: 66},
+ {Name: "eth", Version: 67},
+ {Name: "eth", Version: 68},
}
- conn.ourHighestProtoVersion = 65
+ conn.ourHighestProtoVersion = 68
return &conn, nil
}
-// dial66 attempts to dial the given node and perform a handshake,
-// returning the created Conn with additional eth66 capabilities if
-// successful
-func (s *Suite) dial66() (*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: "eth", Version: 66})
- conn.ourHighestProtoVersion = 66
- 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 {
@@ -213,141 +186,72 @@ loop:
// createSendAndRecvConns creates two connections, one for sending messages to the
// node, and one for receiving messages from the node.
-func (s *Suite) createSendAndRecvConns(isEth66 bool) (*Conn, *Conn, error) {
- var (
- sendConn *Conn
- recvConn *Conn
- err error
- )
- if isEth66 {
- sendConn, err = s.dial66()
- if err != nil {
- return nil, nil, fmt.Errorf("dial failed: %v", err)
- }
- recvConn, err = s.dial66()
- if err != nil {
- sendConn.Close()
- return nil, nil, fmt.Errorf("dial failed: %v", err)
- }
- } else {
- 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)
- }
+func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) {
+ sendConn, err := s.dial()
+ if err != nil {
+ return nil, nil, fmt.Errorf("dial failed: %v", err)
}
- return sendConn, recvConn, nil
-}
-
-func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message {
- if c.negotiatedProtoVersion == 66 {
- _, msg := c.readAndServe66(chain, timeout)
- return msg
+ recvConn, err := s.dial()
+ if err != nil {
+ sendConn.Close()
+ return nil, nil, fmt.Errorf("dial failed: %v", err)
}
- return c.readAndServe65(chain, timeout)
+ return sendConn, recvConn, nil
}
// readAndServe serves GetBlockHeaders requests while waiting
// on another message from the node.
-func (c *Conn) readAndServe65(chain *Chain, timeout time.Duration) Message {
- start := time.Now()
- for time.Since(start) < timeout {
- c.SetReadDeadline(time.Now().Add(5 * time.Second))
- switch msg := c.Read().(type) {
- case *Ping:
- c.Write(&Pong{})
- case *GetBlockHeaders:
- req := *msg
- headers, err := chain.GetHeaders(req)
- if err != nil {
- return errorf("could not get headers for inbound header request: %v", err)
- }
- if err := c.Write(headers); err != nil {
- return errorf("could not write to connection: %v", err)
- }
- default:
- return msg
- }
- }
- return errorf("no message received within %v", timeout)
-}
-
-// readAndServe66 serves eth66 GetBlockHeaders requests while waiting
-// on another message from the node.
-func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) {
+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))
- reqID, msg := c.Read66()
-
+ msg := c.Read()
switch msg := msg.(type) {
case *Ping:
c.Write(&Pong{})
- case GetBlockHeaders:
+ case *GetBlockHeaders:
headers, err := chain.GetHeaders(msg)
if err != nil {
- return 0, errorf("could not get headers for inbound header request: %v", err)
+ return errorf("could not get headers for inbound header request: %v", err)
}
- resp := ð.BlockHeadersPacket66{
- RequestId: reqID,
+ resp := &BlockHeaders{
+ RequestId: msg.ReqID(),
BlockHeadersPacket: eth.BlockHeadersPacket(headers),
}
- if err := c.Write66(resp, BlockHeaders{}.Code()); err != nil {
- return 0, errorf("could not write to connection: %v", err)
+ if err := c.Write(resp); err != nil {
+ return errorf("could not write to connection: %v", err)
}
default:
- return reqID, msg
+ return msg
}
}
- return 0, errorf("no message received within %v", timeout)
+ return errorf("no message received within %v", timeout)
}
// headersRequest executes the given `GetBlockHeaders` request.
-func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, isEth66 bool, reqID uint64) (BlockHeaders, error) {
+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))
- // if on eth66 connection, perform eth66 GetBlockHeaders request
- if isEth66 {
- return getBlockHeaders66(chain, c, request, reqID)
- }
- if err := c.Write(request); err != nil {
- return nil, err
- }
- switch msg := c.readAndServe(chain, timeout).(type) {
- case *BlockHeaders:
- return *msg, nil
- default:
- return nil, fmt.Errorf("invalid message: %s", pretty.Sdump(msg))
- }
-}
-// getBlockHeaders66 executes the given `GetBlockHeaders` request over the eth66 protocol.
-func getBlockHeaders66(chain *Chain, conn *Conn, request *GetBlockHeaders, id uint64) (BlockHeaders, error) {
// write request
- packet := eth.GetBlockHeadersPacket(*request)
- req := ð.GetBlockHeadersPacket66{
- RequestId: id,
- GetBlockHeadersPacket: &packet,
- }
- if err := conn.Write66(req, GetBlockHeaders{}.Code()); err != nil {
+ 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 := conn.waitForResponse(chain, timeout, req.RequestId)
- headers, ok := msg.(BlockHeaders)
+ 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.BlockHeadersPacket)
return headers, nil
}
// headersMatch returns whether the received headers match the given request
-func headersMatch(expected BlockHeaders, headers BlockHeaders) bool {
+func headersMatch(expected []*types.Header, headers []*types.Header) bool {
return reflect.DeepEqual(expected, headers)
}
@@ -355,8 +259,8 @@ func headersMatch(expected BlockHeaders, headers BlockHeaders) bool {
// request ID is received.
func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message {
for {
- id, msg := c.readAndServe66(chain, timeout)
- if id == requestID {
+ msg := c.readAndServe(chain, timeout)
+ if msg.ReqID() == requestID {
return msg
}
}
@@ -364,9 +268,9 @@ func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID ui
// 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(isEth66 bool) error {
+func (s *Suite) sendNextBlock() error {
// set up sending and receiving connections
- sendConn, recvConn, err := s.createSendAndRecvConns(isEth66)
+ sendConn, recvConn, err := s.createSendAndRecvConns()
if err != nil {
return err
}
@@ -389,7 +293,7 @@ func (s *Suite) sendNextBlock(isEth66 bool) error {
return fmt.Errorf("failed to announce block: %v", err)
}
// wait for client to update its chain
- if err = s.waitForBlockImport(recvConn, nextBlock, isEth66); err != nil {
+ if err = s.waitForBlockImport(recvConn, nextBlock); err != nil {
return fmt.Errorf("failed to receive confirmation of block import: %v", err)
}
// update test suite chain
@@ -425,38 +329,37 @@ func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error {
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:
- // ignore tx announcements from previous tests
continue
+ case *Transactions:
+ continue
+
default:
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
}
}
}
-func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block, isEth66 bool) error {
+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{
- Origin: eth.HashOrNumber{
- Hash: block.Hash(),
+ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
+ Origin: eth.HashOrNumber{Hash: block.Hash()},
+ Amount: 1,
},
- Amount: 1,
}
+
// loop until BlockHeaders response contains desired block, confirming the
// node imported the block
for {
- var (
- headers BlockHeaders
- err error
- )
- if isEth66 {
- requestID := uint64(54)
- headers, err = conn.headersRequest(req, s.chain, eth66, requestID)
- } else {
- headers, err = conn.headersRequest(req, s.chain, eth65, 0)
- }
+ requestID := uint64(54)
+ headers, err := conn.headersRequest(req, s.chain, requestID)
if err != nil {
return fmt.Errorf("GetBlockHeader request failed: %v", err)
}
@@ -472,8 +375,8 @@ func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block, isEth66 bool)
}
}
-func (s *Suite) oldAnnounce(isEth66 bool) error {
- sendConn, receiveConn, err := s.createSendAndRecvConns(isEth66)
+func (s *Suite) oldAnnounce() error {
+ sendConn, receiveConn, err := s.createSendAndRecvConns()
if err != nil {
return err
}
@@ -519,23 +422,13 @@ func (s *Suite) oldAnnounce(isEth66 bool) error {
return nil
}
-func (s *Suite) maliciousHandshakes(t *utesting.T, isEth66 bool) error {
- var (
- conn *Conn
- err error
- )
- if isEth66 {
- conn, err = s.dial66()
- if err != nil {
- return fmt.Errorf("dial failed: %v", err)
- }
- } else {
- conn, err = s.dial()
- if err != nil {
- return fmt.Errorf("dial failed: %v", err)
- }
+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{
@@ -596,16 +489,9 @@ func (s *Suite) maliciousHandshakes(t *utesting.T, isEth66 bool) error {
}
}
// dial for the next round
- if isEth66 {
- conn, err = s.dial66()
- if err != nil {
- return fmt.Errorf("dial failed: %v", err)
- }
- } else {
- conn, err = s.dial()
- if err != nil {
- return fmt.Errorf("dial failed: %v", err)
- }
+ conn, err = s.dial()
+ if err != nil {
+ return fmt.Errorf("dial failed: %v", err)
}
}
return nil
@@ -623,6 +509,7 @@ func (s *Suite) maliciousStatus(conn *Conn) error {
Genesis: s.chain.blocks[0].Hash(),
ForkID: s.chain.ForkID(),
}
+
// get status
msg, err := conn.statusExchange(s.chain, status)
if err != nil {
@@ -633,6 +520,7 @@ func (s *Suite) maliciousStatus(conn *Conn) error {
default:
return fmt.Errorf("expected status, got: %#v ", msg)
}
+
// wait for disconnect
switch msg := conn.readAndServe(s.chain, timeout).(type) {
case *Disconnect:
@@ -644,9 +532,9 @@ func (s *Suite) maliciousStatus(conn *Conn) error {
}
}
-func (s *Suite) hashAnnounce(isEth66 bool) error {
+func (s *Suite) hashAnnounce() error {
// create connections
- sendConn, recvConn, err := s.createSendAndRecvConns(isEth66)
+ sendConn, recvConn, err := s.createSendAndRecvConns()
if err != nil {
return fmt.Errorf("failed to create connections: %v", err)
}
@@ -658,6 +546,7 @@ func (s *Suite) hashAnnounce(isEth66 bool) error {
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
@@ -669,56 +558,29 @@ func (s *Suite) hashAnnounce(isEth66 bool) error {
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
- var (
- id uint64
- msg Message
- blockHeaderReq GetBlockHeaders
- )
- if isEth66 {
- id, msg = sendConn.Read66()
- switch msg := msg.(type) {
- case GetBlockHeaders:
- blockHeaderReq = msg
- default:
- 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))
- }
- if err := sendConn.Write66(ð.BlockHeadersPacket66{
- RequestId: id,
- BlockHeadersPacket: eth.BlockHeadersPacket{
- nextBlock.Header(),
- },
- }, BlockHeaders{}.Code()); err != nil {
- return fmt.Errorf("failed to write to connection: %v", err)
- }
- } else {
- msg = sendConn.Read()
- switch msg := msg.(type) {
- case *GetBlockHeaders:
- blockHeaderReq = *msg
- default:
- 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))
- }
- if err := sendConn.Write(&BlockHeaders{nextBlock.Header()}); err != nil {
- return fmt.Errorf("failed to write to connection: %v", err)
- }
+ 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(),
+ BlockHeadersPacket: eth.BlockHeadersPacket{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) {
@@ -731,6 +593,7 @@ func (s *Suite) hashAnnounce(isEth66 bool) error {
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()
@@ -749,7 +612,7 @@ func (s *Suite) hashAnnounce(isEth66 bool) error {
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg))
}
// confirm node imported block
- if err := s.waitForBlockImport(recvConn, nextBlock, isEth66); err != nil {
+ if err := s.waitForBlockImport(recvConn, nextBlock); err != nil {
return fmt.Errorf("error waiting for node to import new block: %v", err)
}
// update the chain
diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go
index 0e555119eaec..3baf8bb0169d 100644
--- a/cmd/devp2p/internal/ethtest/suite.go
+++ b/cmd/devp2p/internal/ethtest/suite.go
@@ -1,18 +1,18 @@
// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
+// This file is part of go-ethereum.
//
-// 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
+// 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.
//
-// The go-ethereum library is distributed in the hope that it will be useful,
+// 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 Lesser General Public License for more details.
+// GNU 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 .
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
package ethtest
@@ -49,90 +49,36 @@ func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, e
}, nil
}
-func (s *Suite) AllEthTests() []utesting.Test {
+func (s *Suite) EthTests() []utesting.Test {
return []utesting.Test{
// status
- {Name: "TestStatus65", Fn: s.TestStatus65},
- {Name: "TestStatus66", Fn: s.TestStatus66},
+ {Name: "TestStatus", Fn: s.TestStatus},
// get block headers
- {Name: "TestGetBlockHeaders65", Fn: s.TestGetBlockHeaders65},
- {Name: "TestGetBlockHeaders66", Fn: s.TestGetBlockHeaders66},
- {Name: "TestSimultaneousRequests66", Fn: s.TestSimultaneousRequests66},
- {Name: "TestSameRequestID66", Fn: s.TestSameRequestID66},
- {Name: "TestZeroRequestID66", Fn: s.TestZeroRequestID66},
+ {Name: "TestGetBlockHeaders", Fn: s.TestGetBlockHeaders},
+ {Name: "TestSimultaneousRequests", Fn: s.TestSimultaneousRequests},
+ {Name: "TestSameRequestID", Fn: s.TestSameRequestID},
+ {Name: "TestZeroRequestID", Fn: s.TestZeroRequestID},
// get block bodies
- {Name: "TestGetBlockBodies65", Fn: s.TestGetBlockBodies65},
- {Name: "TestGetBlockBodies66", Fn: s.TestGetBlockBodies66},
+ {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies},
// broadcast
- {Name: "TestBroadcast65", Fn: s.TestBroadcast65},
- {Name: "TestBroadcast66", Fn: s.TestBroadcast66},
- {Name: "TestLargeAnnounce65", Fn: s.TestLargeAnnounce65},
- {Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66},
- {Name: "TestOldAnnounce65", Fn: s.TestOldAnnounce65},
- {Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66},
- {Name: "TestBlockHashAnnounce65", Fn: s.TestBlockHashAnnounce65},
- {Name: "TestBlockHashAnnounce66", Fn: s.TestBlockHashAnnounce66},
+ {Name: "TestBroadcast", Fn: s.TestBroadcast},
+ {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce},
+ {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce},
+ {Name: "TestBlockHashAnnounce", Fn: s.TestBlockHashAnnounce},
// malicious handshakes + status
- {Name: "TestMaliciousHandshake65", Fn: s.TestMaliciousHandshake65},
- {Name: "TestMaliciousStatus65", Fn: s.TestMaliciousStatus65},
- {Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66},
- {Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66},
+ {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake},
+ {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus},
// test transactions
- {Name: "TestTransaction65", Fn: s.TestTransaction65},
- {Name: "TestTransaction66", Fn: s.TestTransaction66},
- {Name: "TestMaliciousTx65", Fn: s.TestMaliciousTx65},
- {Name: "TestMaliciousTx66", Fn: s.TestMaliciousTx66},
- {Name: "TestLargeTxRequest66", Fn: s.TestLargeTxRequest66},
- {Name: "TestNewPooledTxs66", Fn: s.TestNewPooledTxs66},
- }
-}
-
-func (s *Suite) EthTests() []utesting.Test {
- return []utesting.Test{
- {Name: "TestStatus65", Fn: s.TestStatus65},
- {Name: "TestGetBlockHeaders65", Fn: s.TestGetBlockHeaders65},
- {Name: "TestGetBlockBodies65", Fn: s.TestGetBlockBodies65},
- {Name: "TestBroadcast65", Fn: s.TestBroadcast65},
- {Name: "TestLargeAnnounce65", Fn: s.TestLargeAnnounce65},
- {Name: "TestOldAnnounce65", Fn: s.TestOldAnnounce65},
- {Name: "TestBlockHashAnnounce65", Fn: s.TestBlockHashAnnounce65},
- {Name: "TestMaliciousHandshake65", Fn: s.TestMaliciousHandshake65},
- {Name: "TestMaliciousStatus65", Fn: s.TestMaliciousStatus65},
- {Name: "TestTransaction65", Fn: s.TestTransaction65},
- {Name: "TestMaliciousTx65", Fn: s.TestMaliciousTx65},
+ {Name: "TestTransaction", Fn: s.TestTransaction},
+ {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx},
+ {Name: "TestLargeTxRequest", Fn: s.TestLargeTxRequest},
+ {Name: "TestNewPooledTxs", Fn: s.TestNewPooledTxs},
}
}
-func (s *Suite) Eth66Tests() []utesting.Test {
- return []utesting.Test{
- // only proceed with eth66 test suite if node supports eth 66 protocol
- {Name: "TestStatus66", Fn: s.TestStatus66},
- {Name: "TestGetBlockHeaders66", Fn: s.TestGetBlockHeaders66},
- {Name: "TestSimultaneousRequests66", Fn: s.TestSimultaneousRequests66},
- {Name: "TestSameRequestID66", Fn: s.TestSameRequestID66},
- {Name: "TestZeroRequestID66", Fn: s.TestZeroRequestID66},
- {Name: "TestGetBlockBodies66", Fn: s.TestGetBlockBodies66},
- {Name: "TestBroadcast66", Fn: s.TestBroadcast66},
- {Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66},
- {Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66},
- {Name: "TestBlockHashAnnounce66", Fn: s.TestBlockHashAnnounce66},
- {Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66},
- {Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66},
- {Name: "TestTransaction66", Fn: s.TestTransaction66},
- {Name: "TestMaliciousTx66", Fn: s.TestMaliciousTx66},
- {Name: "TestLargeTxRequest66", Fn: s.TestLargeTxRequest66},
- {Name: "TestNewPooledTxs66", Fn: s.TestNewPooledTxs66},
- }
-}
-
-var (
- eth66 = true // indicates whether suite should negotiate eth66 connection
- eth65 = false // indicates whether suite should negotiate eth65 connection or below.
-)
-
-// TestStatus65 attempts to connect to the given node and exchange
-// a status message with it.
-func (s *Suite) TestStatus65(t *utesting.T) {
+// TestStatus attempts to connect to the given node and exchange
+// a status message with it on the eth protocol.
+func (s *Suite) TestStatus(t *utesting.T) {
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
@@ -143,79 +89,32 @@ func (s *Suite) TestStatus65(t *utesting.T) {
}
}
-// TestStatus66 attempts to connect to the given node and exchange
-// a status message with it on the eth66 protocol.
-func (s *Suite) TestStatus66(t *utesting.T) {
- conn, err := s.dial66()
- if err != nil {
- t.Fatalf("dial failed: %v", err)
- }
- defer conn.Close()
- if err := conn.peer(s.chain, nil); err != nil {
- t.Fatalf("peering failed: %v", err)
- }
-}
-
-// TestGetBlockHeaders65 tests whether the given node can respond to
-// a `GetBlockHeaders` request accurately.
-func (s *Suite) TestGetBlockHeaders65(t *utesting.T) {
+// TestGetBlockHeaders tests whether the given node can respond to
+// an eth `GetBlockHeaders` request and that the response is accurate.
+func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
- if err := conn.peer(s.chain, nil); err != nil {
- t.Fatalf("handshake(s) failed: %v", err)
- }
- // write request
- req := &GetBlockHeaders{
- Origin: eth.HashOrNumber{
- Hash: s.chain.blocks[1].Hash(),
- },
- Amount: 2,
- Skip: 1,
- Reverse: false,
- }
- headers, err := conn.headersRequest(req, s.chain, eth65, 0)
- if err != nil {
- t.Fatalf("GetBlockHeaders request failed: %v", err)
- }
- // check for correct headers
- expected, err := s.chain.GetHeaders(*req)
- if err != nil {
- t.Fatalf("failed to get headers for given request: %v", err)
- }
- if !headersMatch(expected, headers) {
- t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers)
- }
-}
-
-// TestGetBlockHeaders66 tests whether the given node can respond to
-// an eth66 `GetBlockHeaders` request and that the response is accurate.
-func (s *Suite) TestGetBlockHeaders66(t *utesting.T) {
- conn, err := s.dial66()
- if err != nil {
- t.Fatalf("dial failed: %v", err)
- }
- defer conn.Close()
if err = conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
// write request
req := &GetBlockHeaders{
- Origin: eth.HashOrNumber{
- Hash: s.chain.blocks[1].Hash(),
+ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
+ Origin: eth.HashOrNumber{Hash: s.chain.blocks[1].Hash()},
+ Amount: 2,
+ Skip: 1,
+ Reverse: false,
},
- Amount: 2,
- Skip: 1,
- Reverse: false,
}
- headers, err := conn.headersRequest(req, s.chain, eth66, 33)
+ headers, err := conn.headersRequest(req, s.chain, 33)
if err != nil {
t.Fatalf("could not get block headers: %v", err)
}
// check for correct headers
- expected, err := s.chain.GetHeaders(*req)
+ expected, err := s.chain.GetHeaders(req)
if err != nil {
t.Fatalf("failed to get headers for given request: %v", err)
}
@@ -224,12 +123,12 @@ func (s *Suite) TestGetBlockHeaders66(t *utesting.T) {
}
}
-// TestSimultaneousRequests66 sends two simultaneous `GetBlockHeader` requests from
+// TestSimultaneousRequests sends two simultaneous `GetBlockHeader` requests from
// the same connection with different request IDs and checks to make sure the node
// responds with the correct headers per request.
-func (s *Suite) TestSimultaneousRequests66(t *utesting.T) {
+func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
// create a connection
- conn, err := s.dial66()
+ conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
@@ -237,8 +136,9 @@ func (s *Suite) TestSimultaneousRequests66(t *utesting.T) {
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
+
// create two requests
- req1 := ð.GetBlockHeadersPacket66{
+ req1 := &GetBlockHeaders{
RequestId: uint64(111),
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
Origin: eth.HashOrNumber{
@@ -249,7 +149,7 @@ func (s *Suite) TestSimultaneousRequests66(t *utesting.T) {
Reverse: false,
},
}
- req2 := ð.GetBlockHeadersPacket66{
+ req2 := &GetBlockHeaders{
RequestId: uint64(222),
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
Origin: eth.HashOrNumber{
@@ -260,46 +160,49 @@ func (s *Suite) TestSimultaneousRequests66(t *utesting.T) {
Reverse: false,
},
}
+
// write the first request
- if err := conn.Write66(req1, GetBlockHeaders{}.Code()); err != nil {
+ if err := conn.Write(req1); err != nil {
t.Fatalf("failed to write to connection: %v", err)
}
// write the second request
- if err := conn.Write66(req2, GetBlockHeaders{}.Code()); err != nil {
+ if err := conn.Write(req2); err != nil {
t.Fatalf("failed to write to connection: %v", err)
}
+
// wait for responses
msg := conn.waitForResponse(s.chain, timeout, req1.RequestId)
- headers1, ok := msg.(BlockHeaders)
+ headers1, ok := msg.(*BlockHeaders)
if !ok {
t.Fatalf("unexpected %s", pretty.Sdump(msg))
}
msg = conn.waitForResponse(s.chain, timeout, req2.RequestId)
- headers2, ok := msg.(BlockHeaders)
+ headers2, ok := msg.(*BlockHeaders)
if !ok {
t.Fatalf("unexpected %s", pretty.Sdump(msg))
}
+
// check received headers for accuracy
- expected1, err := s.chain.GetHeaders(GetBlockHeaders(*req1.GetBlockHeadersPacket))
+ expected1, err := s.chain.GetHeaders(req1)
if err != nil {
t.Fatalf("failed to get expected headers for request 1: %v", err)
}
- expected2, err := s.chain.GetHeaders(GetBlockHeaders(*req2.GetBlockHeadersPacket))
+ expected2, err := s.chain.GetHeaders(req2)
if err != nil {
t.Fatalf("failed to get expected headers for request 2: %v", err)
}
- if !headersMatch(expected1, headers1) {
+ if !headersMatch(expected1, headers1.BlockHeadersPacket) {
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1)
}
- if !headersMatch(expected2, headers2) {
+ if !headersMatch(expected2, headers2.BlockHeadersPacket) {
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2)
}
}
-// TestSameRequestID66 sends two requests with the same request ID to a
+// TestSameRequestID sends two requests with the same request ID to a
// single node.
-func (s *Suite) TestSameRequestID66(t *utesting.T) {
- conn, err := s.dial66()
+func (s *Suite) TestSameRequestID(t *utesting.T) {
+ conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
@@ -309,7 +212,7 @@ func (s *Suite) TestSameRequestID66(t *utesting.T) {
}
// create requests
reqID := uint64(1234)
- request1 := ð.GetBlockHeadersPacket66{
+ request1 := &GetBlockHeaders{
RequestId: reqID,
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
Origin: eth.HashOrNumber{
@@ -318,7 +221,7 @@ func (s *Suite) TestSameRequestID66(t *utesting.T) {
Amount: 2,
},
}
- request2 := ð.GetBlockHeadersPacket66{
+ request2 := &GetBlockHeaders{
RequestId: reqID,
GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
Origin: eth.HashOrNumber{
@@ -327,45 +230,48 @@ func (s *Suite) TestSameRequestID66(t *utesting.T) {
Amount: 2,
},
}
+
// write the requests
- if err = conn.Write66(request1, GetBlockHeaders{}.Code()); err != nil {
+ if err = conn.Write(request1); err != nil {
t.Fatalf("failed to write to connection: %v", err)
}
- if err = conn.Write66(request2, GetBlockHeaders{}.Code()); err != nil {
+ if err = conn.Write(request2); err != nil {
t.Fatalf("failed to write to connection: %v", err)
}
+
// wait for responses
msg := conn.waitForResponse(s.chain, timeout, reqID)
- headers1, ok := msg.(BlockHeaders)
+ headers1, ok := msg.(*BlockHeaders)
if !ok {
t.Fatalf("unexpected %s", pretty.Sdump(msg))
}
msg = conn.waitForResponse(s.chain, timeout, reqID)
- headers2, ok := msg.(BlockHeaders)
+ headers2, ok := msg.(*BlockHeaders)
if !ok {
t.Fatalf("unexpected %s", pretty.Sdump(msg))
}
+
// check if headers match
- expected1, err := s.chain.GetHeaders(GetBlockHeaders(*request1.GetBlockHeadersPacket))
+ expected1, err := s.chain.GetHeaders(request1)
if err != nil {
t.Fatalf("failed to get expected block headers: %v", err)
}
- expected2, err := s.chain.GetHeaders(GetBlockHeaders(*request2.GetBlockHeadersPacket))
+ expected2, err := s.chain.GetHeaders(request2)
if err != nil {
t.Fatalf("failed to get expected block headers: %v", err)
}
- if !headersMatch(expected1, headers1) {
+ if !headersMatch(expected1, headers1.BlockHeadersPacket) {
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1)
}
- if !headersMatch(expected2, headers2) {
+ if !headersMatch(expected2, headers2.BlockHeadersPacket) {
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2)
}
}
-// TestZeroRequestID_66 checks that a message with a request ID of zero is still handled
+// TestZeroRequestID checks that a message with a request ID of zero is still handled
// by the node.
-func (s *Suite) TestZeroRequestID66(t *utesting.T) {
- conn, err := s.dial66()
+func (s *Suite) TestZeroRequestID(t *utesting.T) {
+ conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
@@ -374,16 +280,16 @@ func (s *Suite) TestZeroRequestID66(t *utesting.T) {
t.Fatalf("peering failed: %v", err)
}
req := &GetBlockHeaders{
- Origin: eth.HashOrNumber{
- Number: 0,
+ GetBlockHeadersPacket: ð.GetBlockHeadersPacket{
+ Origin: eth.HashOrNumber{Number: 0},
+ Amount: 2,
},
- Amount: 2,
}
- headers, err := conn.headersRequest(req, s.chain, eth66, 0)
+ headers, err := conn.headersRequest(req, s.chain, 0)
if err != nil {
t.Fatalf("failed to get block headers: %v", err)
}
- expected, err := s.chain.GetHeaders(*req)
+ expected, err := s.chain.GetHeaders(req)
if err != nil {
t.Fatalf("failed to get expected block headers: %v", err)
}
@@ -392,9 +298,9 @@ func (s *Suite) TestZeroRequestID66(t *utesting.T) {
}
}
-// TestGetBlockBodies65 tests whether the given node can respond to
+// TestGetBlockBodies tests whether the given node can respond to
// a `GetBlockBodies` request and that the response is accurate.
-func (s *Suite) TestGetBlockBodies65(t *utesting.T) {
+func (s *Suite) TestGetBlockBodies(t *utesting.T) {
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
@@ -405,126 +311,39 @@ func (s *Suite) TestGetBlockBodies65(t *utesting.T) {
}
// create block bodies request
req := &GetBlockBodies{
- s.chain.blocks[54].Hash(),
- s.chain.blocks[75].Hash(),
- }
- if err := conn.Write(req); err != nil {
- t.Fatalf("could not write to connection: %v", err)
- }
- // wait for response
- switch msg := conn.readAndServe(s.chain, timeout).(type) {
- case *BlockBodies:
- t.Logf("received %d block bodies", len(*msg))
- if len(*msg) != len(*req) {
- t.Fatalf("wrong bodies in response: expected %d bodies, "+
- "got %d", len(*req), len(*msg))
- }
- default:
- t.Fatalf("unexpected: %s", pretty.Sdump(msg))
- }
-}
-
-// TestGetBlockBodies66 tests whether the given node can respond to
-// a `GetBlockBodies` request and that the response is accurate over
-// the eth66 protocol.
-func (s *Suite) TestGetBlockBodies66(t *utesting.T) {
- conn, err := s.dial66()
- if err != nil {
- t.Fatalf("dial failed: %v", err)
- }
- defer conn.Close()
- if err := conn.peer(s.chain, nil); err != nil {
- t.Fatalf("peering failed: %v", err)
- }
- // create block bodies request
- req := ð.GetBlockBodiesPacket66{
RequestId: uint64(55),
GetBlockBodiesPacket: eth.GetBlockBodiesPacket{
s.chain.blocks[54].Hash(),
s.chain.blocks[75].Hash(),
},
}
- if err := conn.Write66(req, GetBlockBodies{}.Code()); err != nil {
+ if err := conn.Write(req); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// wait for block bodies response
msg := conn.waitForResponse(s.chain, timeout, req.RequestId)
- blockBodies, ok := msg.(BlockBodies)
+ resp, ok := msg.(*BlockBodies)
if !ok {
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
}
- t.Logf("received %d block bodies", len(blockBodies))
- if len(blockBodies) != len(req.GetBlockBodiesPacket) {
+ bodies := resp.BlockBodiesPacket
+ t.Logf("received %d block bodies", len(bodies))
+ if len(bodies) != len(req.GetBlockBodiesPacket) {
t.Fatalf("wrong bodies in response: expected %d bodies, "+
- "got %d", len(req.GetBlockBodiesPacket), len(blockBodies))
+ "got %d", len(req.GetBlockBodiesPacket), len(bodies))
}
}
-// TestBroadcast65 tests whether a block announcement is correctly
-// propagated to the given node's peer(s).
-func (s *Suite) TestBroadcast65(t *utesting.T) {
- if err := s.sendNextBlock(eth65); err != nil {
+// TestBroadcast tests whether a block announcement is correctly
+// propagated to the node's peers.
+func (s *Suite) TestBroadcast(t *utesting.T) {
+ if err := s.sendNextBlock(); err != nil {
t.Fatalf("block broadcast failed: %v", err)
}
}
-// TestBroadcast66 tests whether a block announcement is correctly
-// propagated to the given node's peer(s) on the eth66 protocol.
-func (s *Suite) TestBroadcast66(t *utesting.T) {
- if err := s.sendNextBlock(eth66); err != nil {
- t.Fatalf("block broadcast failed: %v", err)
- }
-}
-
-// TestLargeAnnounce65 tests the announcement mechanism with a large block.
-func (s *Suite) TestLargeAnnounce65(t *utesting.T) {
- nextBlock := len(s.chain.blocks)
- blocks := []*NewBlock{
- {
- Block: largeBlock(),
- TD: s.fullChain.TotalDifficultyAt(nextBlock),
- },
- {
- Block: s.fullChain.blocks[nextBlock],
- TD: largeNumber(2),
- },
- {
- Block: largeBlock(),
- TD: largeNumber(2),
- },
- }
-
- for i, blockAnnouncement := range blocks {
- t.Logf("Testing malicious announcement: %v\n", i)
- conn, err := s.dial()
- if err != nil {
- t.Fatalf("dial failed: %v", err)
- }
- if err = conn.peer(s.chain, nil); err != nil {
- t.Fatalf("peering failed: %v", err)
- }
- if err = conn.Write(blockAnnouncement); err != nil {
- t.Fatalf("could not write to connection: %v", err)
- }
- // Invalid announcement, check that peer disconnected
- switch msg := conn.readAndServe(s.chain, time.Second*8).(type) {
- case *Disconnect:
- case *Error:
- break
- default:
- t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg))
- }
- conn.Close()
- }
- // Test the last block as a valid block
- if err := s.sendNextBlock(eth65); err != nil {
- t.Fatalf("failed to broadcast next block: %v", err)
- }
-}
-
-// TestLargeAnnounce66 tests the announcement mechanism with a large
-// block over the eth66 protocol.
-func (s *Suite) TestLargeAnnounce66(t *utesting.T) {
+// TestLargeAnnounce tests the announcement mechanism with a large block.
+func (s *Suite) TestLargeAnnounce(t *utesting.T) {
nextBlock := len(s.chain.blocks)
blocks := []*NewBlock{
{
@@ -543,7 +362,7 @@ func (s *Suite) TestLargeAnnounce66(t *utesting.T) {
for i, blockAnnouncement := range blocks[0:3] {
t.Logf("Testing malicious announcement: %v\n", i)
- conn, err := s.dial66()
+ conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
@@ -554,7 +373,7 @@ func (s *Suite) TestLargeAnnounce66(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err)
}
// Invalid announcement, check that peer disconnected
- switch msg := conn.readAndServe(s.chain, time.Second*8).(type) {
+ switch msg := conn.readAndServe(s.chain, 8*time.Second).(type) {
case *Disconnect:
case *Error:
break
@@ -564,58 +383,35 @@ func (s *Suite) TestLargeAnnounce66(t *utesting.T) {
conn.Close()
}
// Test the last block as a valid block
- if err := s.sendNextBlock(eth66); err != nil {
+ if err := s.sendNextBlock(); err != nil {
t.Fatalf("failed to broadcast next block: %v", err)
}
}
-// TestOldAnnounce65 tests the announcement mechanism with an old block.
-func (s *Suite) TestOldAnnounce65(t *utesting.T) {
- if err := s.oldAnnounce(eth65); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestOldAnnounce66 tests the announcement mechanism with an old block,
-// over the eth66 protocol.
-func (s *Suite) TestOldAnnounce66(t *utesting.T) {
- if err := s.oldAnnounce(eth66); err != nil {
+// TestOldAnnounce tests the announcement mechanism with an old block.
+func (s *Suite) TestOldAnnounce(t *utesting.T) {
+ if err := s.oldAnnounce(); err != nil {
t.Fatal(err)
}
}
-// TestBlockHashAnnounce65 sends a new block hash announcement and expects
+// TestBlockHashAnnounce sends a new block hash announcement and expects
// the node to perform a `GetBlockHeaders` request.
-func (s *Suite) TestBlockHashAnnounce65(t *utesting.T) {
- if err := s.hashAnnounce(eth65); err != nil {
+func (s *Suite) TestBlockHashAnnounce(t *utesting.T) {
+ if err := s.hashAnnounce(); err != nil {
t.Fatalf("block hash announcement failed: %v", err)
}
}
-// TestBlockHashAnnounce66 sends a new block hash announcement and expects
-// the node to perform a `GetBlockHeaders` request.
-func (s *Suite) TestBlockHashAnnounce66(t *utesting.T) {
- if err := s.hashAnnounce(eth66); err != nil {
- t.Fatalf("block hash announcement failed: %v", err)
- }
-}
-
-// TestMaliciousHandshake65 tries to send malicious data during the handshake.
-func (s *Suite) TestMaliciousHandshake65(t *utesting.T) {
- if err := s.maliciousHandshakes(t, eth65); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestMaliciousHandshake66 tries to send malicious data during the handshake.
-func (s *Suite) TestMaliciousHandshake66(t *utesting.T) {
- if err := s.maliciousHandshakes(t, eth66); err != nil {
+// TestMaliciousHandshake tries to send malicious data during the handshake.
+func (s *Suite) TestMaliciousHandshake(t *utesting.T) {
+ if err := s.maliciousHandshakes(t); err != nil {
t.Fatal(err)
}
}
-// TestMaliciousStatus65 sends a status package with a large total difficulty.
-func (s *Suite) TestMaliciousStatus65(t *utesting.T) {
+// TestMaliciousStatus sends a status package with a large total difficulty.
+func (s *Suite) TestMaliciousStatus(t *utesting.T) {
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
@@ -627,58 +423,28 @@ func (s *Suite) TestMaliciousStatus65(t *utesting.T) {
}
}
-// TestMaliciousStatus66 sends a status package with a large total
-// difficulty over the eth66 protocol.
-func (s *Suite) TestMaliciousStatus66(t *utesting.T) {
- conn, err := s.dial66()
- if err != nil {
- t.Fatalf("dial failed: %v", err)
- }
- defer conn.Close()
-
- if err := s.maliciousStatus(conn); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestTransaction65 sends a valid transaction to the node and
+// TestTransaction sends a valid transaction to the node and
// checks if the transaction gets propagated.
-func (s *Suite) TestTransaction65(t *utesting.T) {
- if err := s.sendSuccessfulTxs(t, eth65); err != nil {
+func (s *Suite) TestTransaction(t *utesting.T) {
+ if err := s.sendSuccessfulTxs(t); err != nil {
t.Fatal(err)
}
}
-// TestTransaction66 sends a valid transaction to the node and
-// checks if the transaction gets propagated.
-func (s *Suite) TestTransaction66(t *utesting.T) {
- if err := s.sendSuccessfulTxs(t, eth66); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestMaliciousTx65 sends several invalid transactions and tests whether
-// the node will propagate them.
-func (s *Suite) TestMaliciousTx65(t *utesting.T) {
- if err := s.sendMaliciousTxs(t, eth65); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestMaliciousTx66 sends several invalid transactions and tests whether
+// TestMaliciousTx sends several invalid transactions and tests whether
// the node will propagate them.
-func (s *Suite) TestMaliciousTx66(t *utesting.T) {
- if err := s.sendMaliciousTxs(t, eth66); err != nil {
+func (s *Suite) TestMaliciousTx(t *utesting.T) {
+ if err := s.sendMaliciousTxs(t); err != nil {
t.Fatal(err)
}
}
-// TestLargeTxRequest66 tests whether a node can fulfill a large GetPooledTransactions
+// TestLargeTxRequest tests whether a node can fulfill a large GetPooledTransactions
// request.
-func (s *Suite) TestLargeTxRequest66(t *utesting.T) {
+func (s *Suite) TestLargeTxRequest(t *utesting.T) {
// send the next block to ensure the node is no longer syncing and
// is able to accept txs
- if err := s.sendNextBlock(eth66); err != nil {
+ if err := s.sendNextBlock(); err != nil {
t.Fatalf("failed to send next block: %v", err)
}
// send 2000 transactions to the node
@@ -691,7 +457,7 @@ func (s *Suite) TestLargeTxRequest66(t *utesting.T) {
}
// set up connection to receive to ensure node is peered with the receiving connection
// before tx request is sent
- conn, err := s.dial66()
+ conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
@@ -704,17 +470,17 @@ func (s *Suite) TestLargeTxRequest66(t *utesting.T) {
for _, hash := range hashMap {
hashes = append(hashes, hash)
}
- getTxReq := ð.GetPooledTransactionsPacket66{
+ getTxReq := &GetPooledTransactions{
RequestId: 1234,
GetPooledTransactionsPacket: hashes,
}
- if err = conn.Write66(getTxReq, GetPooledTransactions{}.Code()); err != nil {
+ if err = conn.Write(getTxReq); err != nil {
t.Fatalf("could not write to conn: %v", err)
}
// check that all received transactions match those that were sent to node
switch msg := conn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) {
- case PooledTransactions:
- for _, gotTx := range msg {
+ case *PooledTransactions:
+ for _, gotTx := range msg.PooledTransactionsPacket {
if _, exists := hashMap[gotTx.Hash()]; !exists {
t.Fatalf("unexpected tx received: %v", gotTx.Hash())
}
@@ -724,30 +490,31 @@ func (s *Suite) TestLargeTxRequest66(t *utesting.T) {
}
}
-// TestNewPooledTxs_66 tests whether a node will do a GetPooledTransactions
+// TestNewPooledTxs tests whether a node will do a GetPooledTransactions
// request upon receiving a NewPooledTransactionHashes announcement.
-func (s *Suite) TestNewPooledTxs66(t *utesting.T) {
+func (s *Suite) TestNewPooledTxs(t *utesting.T) {
// send the next block to ensure the node is no longer syncing and
// is able to accept txs
- if err := s.sendNextBlock(eth66); err != nil {
+ if err := s.sendNextBlock(); err != nil {
t.Fatalf("failed to send next block: %v", err)
}
// generate 50 txs
- hashMap, _, err := generateTxs(s, 50)
+ _, txs, err := generateTxs(s, 50)
if err != nil {
t.Fatalf("failed to generate transactions: %v", err)
}
-
- // create new pooled tx hashes announcement
- hashes := make([]common.Hash, 0)
- for _, hash := range hashMap {
- hashes = append(hashes, hash)
+ hashes := make([]common.Hash, len(txs))
+ types := make([]byte, len(txs))
+ sizes := make([]uint32, len(txs))
+ for i, tx := range txs {
+ hashes[i] = tx.Hash()
+ types[i] = tx.Type()
+ sizes[i] = uint32(tx.Size())
}
- announce := NewPooledTransactionHashes(hashes)
// send announcement
- conn, err := s.dial66()
+ conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
@@ -755,20 +522,28 @@ func (s *Suite) TestNewPooledTxs66(t *utesting.T) {
if err = conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
- if err = conn.Write(announce); err != nil {
+
+ var ann Message = NewPooledTransactionHashes{Types: types, Sizes: sizes, Hashes: hashes}
+ if conn.negotiatedProtoVersion < eth.ETH68 {
+ ann = NewPooledTransactionHashes66(hashes)
+ }
+ err = conn.Write(ann)
+ if err != nil {
t.Fatalf("failed to write to connection: %v", err)
}
// wait for GetPooledTxs request
for {
- _, msg := conn.readAndServe66(s.chain, timeout)
+ msg := conn.readAndServe(s.chain, timeout)
switch msg := msg.(type) {
- case GetPooledTransactions:
- if len(msg) != len(hashes) {
- t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg))
+ case *GetPooledTransactions:
+ if len(msg.GetPooledTransactionsPacket) != len(hashes) {
+ t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsPacket))
}
return
// ignore propagated txs from previous tests
+ case *NewPooledTransactionHashes66:
+ continue
case *NewPooledTransactionHashes:
continue
// ignore block announcements from previous tests
diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go
index 2fc7aa30b50e..1dd63d883b3a 100644
--- a/cmd/devp2p/internal/ethtest/suite_test.go
+++ b/cmd/devp2p/internal/ethtest/suite_test.go
@@ -45,7 +45,7 @@ func TestEthSuite(t *testing.T) {
if err != nil {
t.Fatalf("could not create new test suite: %v", err)
}
- for _, test := range suite.Eth66Tests() {
+ for _, test := range suite.EthTests() {
t.Run(test.Name, func(t *testing.T) {
result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout)
if result[0].Failed {
diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go
index 128b272f08ad..c56738947968 100644
--- a/cmd/devp2p/internal/ethtest/transaction.go
+++ b/cmd/devp2p/internal/ethtest/transaction.go
@@ -29,10 +29,10 @@ import (
"github.com/scroll-tech/go-ethereum/params"
)
-//var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
+// var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-func (s *Suite) sendSuccessfulTxs(t *utesting.T, isEth66 bool) error {
+func (s *Suite) sendSuccessfulTxs(t *utesting.T) error {
tests := []*types.Transaction{
getNextTxFromChain(s),
unknownTx(s),
@@ -48,15 +48,15 @@ func (s *Suite) sendSuccessfulTxs(t *utesting.T, isEth66 bool) error {
prevTx = tests[i-1]
}
// write tx to connection
- if err := sendSuccessfulTx(s, tx, prevTx, isEth66); err != nil {
+ 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, isEth66 bool) error {
- sendConn, recvConn, err := s.createSendAndRecvConns(isEth66)
+func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction) error {
+ sendConn, recvConn, err := s.createSendAndRecvConns()
if err != nil {
return err
}
@@ -73,8 +73,10 @@ func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction
if err = recvConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
+
// update last nonce seen
nonce = tx.Nonce()
+
// Wait for the transaction announcement
for {
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
@@ -93,7 +95,7 @@ func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction
}
}
return fmt.Errorf("missing transaction: got %v missing %v", recTxs, tx.Hash())
- case *NewPooledTransactionHashes:
+ case *NewPooledTransactionHashes66:
txHashes := *msg
// if you receive an old tx propagation, read from connection again
if len(txHashes) == 1 && prevTx != nil {
@@ -108,13 +110,41 @@ func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction
}
}
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, isEth66 bool) error {
+func (s *Suite) sendMaliciousTxs(t *utesting.T) error {
badTxs := []*types.Transaction{
getOldTxFromChain(s),
invalidNonceTx(s),
@@ -122,16 +152,9 @@ func (s *Suite) sendMaliciousTxs(t *utesting.T, isEth66 bool) error {
hugeGasPrice(s),
hugeData(s),
}
+
// setup receiving connection before sending malicious txs
- var (
- recvConn *Conn
- err error
- )
- if isEth66 {
- recvConn, err = s.dial66()
- } else {
- recvConn, err = s.dial()
- }
+ recvConn, err := s.dial()
if err != nil {
return fmt.Errorf("dial failed: %v", err)
}
@@ -139,9 +162,10 @@ func (s *Suite) sendMaliciousTxs(t *utesting.T, isEth66 bool) error {
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, isEth66); err != nil {
+ if err = sendMaliciousTx(s, tx); err != nil {
return fmt.Errorf("malicious tx test failed:\ntx: %v\nerror: %v", tx, err)
}
}
@@ -149,17 +173,8 @@ func (s *Suite) sendMaliciousTxs(t *utesting.T, isEth66 bool) error {
return checkMaliciousTxPropagation(s, badTxs, recvConn)
}
-func sendMaliciousTx(s *Suite, tx *types.Transaction, isEth66 bool) error {
- // setup connection
- var (
- conn *Conn
- err error
- )
- if isEth66 {
- conn, err = s.dial66()
- } else {
- conn, err = s.dial()
- }
+func sendMaliciousTx(s *Suite, tx *types.Transaction) error {
+ conn, err := s.dial()
if err != nil {
return fmt.Errorf("dial failed: %v", err)
}
@@ -167,6 +182,7 @@ func sendMaliciousTx(s *Suite, tx *types.Transaction, isEth66 bool) error {
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)
@@ -182,7 +198,7 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction
txMsg := Transactions(txs)
t.Logf("sending %d txs\n", len(txs))
- sendConn, recvConn, err := s.createSendAndRecvConns(true)
+ sendConn, recvConn, err := s.createSendAndRecvConns()
if err != nil {
return err
}
@@ -194,23 +210,29 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction
if err = recvConn.peer(s.chain, nil); err != nil {
return fmt.Errorf("peering failed: %v", err)
}
+
// Send the transactions
if err = sendConn.Write(&txMsg); err != nil {
return fmt.Errorf("failed to write message to connection: %v", err)
}
+
// update nonce
nonce = txs[len(txs)-1].Nonce()
- // Wait for the transaction announcement(s) and make sure all sent txs are being propagated
+
+ // Wait for the transaction announcement(s) and make sure all sent txs are being propagated.
+ // all txs should be announced within 3 announcements.
recvHashes := make([]common.Hash, 0)
- // all txs should be announced within 3 announcements
+
for i := 0; i < 3; i++ {
switch msg := recvConn.readAndServe(s.chain, timeout).(type) {
case *Transactions:
for _, tx := range *msg {
recvHashes = append(recvHashes, tx.Hash())
}
- case *NewPooledTransactionHashes:
+ 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))
@@ -254,11 +276,16 @@ func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn)
if len(badTxs) > 0 {
return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs)
}
- case *NewPooledTransactionHashes:
+ 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
diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go
index 4aaa06b84acf..1e10f3d75c7f 100644
--- a/cmd/devp2p/internal/ethtest/types.go
+++ b/cmd/devp2p/internal/ethtest/types.go
@@ -1,18 +1,18 @@
// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
+// This file is part of go-ethereum.
//
-// 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
+// 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.
//
-// The go-ethereum library is distributed in the hope that it will be useful,
+// 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 Lesser General Public License for more details.
+// GNU 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 .
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
package ethtest
@@ -28,6 +28,7 @@ import (
type Message interface {
Code() int
+ ReqID() uint64
}
type Error struct {
@@ -36,9 +37,11 @@ type Error struct {
func (e *Error) Unwrap() error { return e.err }
func (e *Error) Error() string { return e.err.Error() }
-func (e *Error) Code() int { return -1 }
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...)}
}
@@ -55,73 +58,94 @@ type Hello struct {
Rest []rlp.RawValue `rlp:"tail"`
}
-func (h Hello) Code() int { return 0x00 }
+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 (d Disconnect) Code() int { return 0x01 }
+func (msg Disconnect) Code() int { return 0x01 }
+func (msg Disconnect) ReqID() uint64 { return 0 }
type Ping struct{}
-func (p Ping) Code() int { return 0x02 }
+func (msg Ping) Code() int { return 0x02 }
+func (msg Ping) ReqID() uint64 { return 0 }
type Pong struct{}
-func (p Pong) Code() int { return 0x03 }
+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 (s Status) Code() int { return 16 }
+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 (nbh NewBlockHashes) Code() int { return 17 }
+func (msg NewBlockHashes) Code() int { return 17 }
+func (msg NewBlockHashes) ReqID() uint64 { return 0 }
type Transactions eth.TransactionsPacket
-func (t Transactions) Code() int { return 18 }
+func (msg Transactions) Code() int { return 18 }
+func (msg Transactions) ReqID() uint64 { return 18 }
// GetBlockHeaders represents a block header query.
-type GetBlockHeaders eth.GetBlockHeadersPacket
+type GetBlockHeaders eth.GetBlockHeadersPacket66
-func (g GetBlockHeaders) Code() int { return 19 }
+func (msg GetBlockHeaders) Code() int { return 19 }
+func (msg GetBlockHeaders) ReqID() uint64 { return msg.RequestId }
-type BlockHeaders eth.BlockHeadersPacket
+type BlockHeaders eth.BlockHeadersPacket66
-func (bh BlockHeaders) Code() int { return 20 }
+func (msg BlockHeaders) Code() int { return 20 }
+func (msg BlockHeaders) ReqID() uint64 { return msg.RequestId }
// GetBlockBodies represents a GetBlockBodies request
-type GetBlockBodies eth.GetBlockBodiesPacket
+type GetBlockBodies eth.GetBlockBodiesPacket66
-func (gbb GetBlockBodies) Code() int { return 21 }
+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
+type BlockBodies eth.BlockBodiesPacket66
-func (bb BlockBodies) Code() int { return 22 }
+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 (nb NewBlock) Code() int { return 23 }
+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.NewPooledTransactionHashesPacket66
+
+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.NewPooledTransactionHashesPacket
+type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket68
-func (nb NewPooledTransactionHashes) Code() int { return 24 }
+func (msg NewPooledTransactionHashes) Code() int { return 24 }
+func (msg NewPooledTransactionHashes) ReqID() uint64 { return 0 }
-type GetPooledTransactions eth.GetPooledTransactionsPacket
+type GetPooledTransactions eth.GetPooledTransactionsPacket66
-func (gpt GetPooledTransactions) Code() int { return 25 }
+func (msg GetPooledTransactions) Code() int { return 25 }
+func (msg GetPooledTransactions) ReqID() uint64 { return msg.RequestId }
-type PooledTransactions eth.PooledTransactionsPacket
+type PooledTransactions eth.PooledTransactionsPacket66
-func (pt PooledTransactions) Code() int { return 26 }
+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 {
@@ -132,62 +156,13 @@ type Conn struct {
caps []p2p.Cap
}
-// Read reads an eth packet from the connection.
+// 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():
- msg = new(GetBlockHeaders)
- case (BlockHeaders{}).Code():
- msg = new(BlockHeaders)
- case (GetBlockBodies{}).Code():
- msg = new(GetBlockBodies)
- case (BlockBodies{}).Code():
- msg = new(BlockBodies)
- case (NewBlock{}).Code():
- msg = new(NewBlock)
- case (NewBlockHashes{}).Code():
- msg = new(NewBlockHashes)
- case (Transactions{}).Code():
- msg = new(Transactions)
- case (NewPooledTransactionHashes{}).Code():
- msg = new(NewPooledTransactionHashes)
- case (GetPooledTransactions{}.Code()):
- msg = new(GetPooledTransactions)
- case (PooledTransactions{}.Code()):
- msg = new(PooledTransactions)
- default:
- return errorf("invalid message code: %d", code)
- }
- // if message is devp2p, decode here
- if err := rlp.DecodeBytes(rawData, msg); err != nil {
- return errorf("could not rlp decode message: %v", err)
- }
- return msg
-}
-
-// Read66 reads an eth66 packet from the connection.
-func (c *Conn) Read66() (uint64, Message) {
- code, rawData, _, err := c.Conn.Read()
- if err != nil {
- return 0, errorf("could not read from connection: %v", err)
- }
-
var msg Message
switch int(code) {
case (Hello{}).Code():
@@ -203,81 +178,71 @@ func (c *Conn) Read66() (uint64, Message) {
case (GetBlockHeaders{}).Code():
ethMsg := new(eth.GetBlockHeadersPacket66)
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket)
+ return (*GetBlockHeaders)(ethMsg)
case (BlockHeaders{}).Code():
ethMsg := new(eth.BlockHeadersPacket66)
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket)
+ return (*BlockHeaders)(ethMsg)
case (GetBlockBodies{}).Code():
ethMsg := new(eth.GetBlockBodiesPacket66)
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket)
+ return (*GetBlockBodies)(ethMsg)
case (BlockBodies{}).Code():
ethMsg := new(eth.BlockBodiesPacket66)
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket)
+ return (*BlockBodies)(ethMsg)
case (NewBlock{}).Code():
msg = new(NewBlock)
case (NewBlockHashes{}).Code():
msg = new(NewBlockHashes)
case (Transactions{}).Code():
msg = new(Transactions)
- case (NewPooledTransactionHashes{}).Code():
- msg = new(NewPooledTransactionHashes)
+ 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.GetPooledTransactionsPacket66)
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return ethMsg.RequestId, GetPooledTransactions(ethMsg.GetPooledTransactionsPacket)
+ return (*GetPooledTransactions)(ethMsg)
case (PooledTransactions{}.Code()):
ethMsg := new(eth.PooledTransactionsPacket66)
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return ethMsg.RequestId, PooledTransactions(ethMsg.PooledTransactionsPacket)
+ return (*PooledTransactions)(ethMsg)
default:
msg = errorf("invalid message code: %d", code)
}
if msg != nil {
if err := rlp.DecodeBytes(rawData, msg); err != nil {
- return 0, errorf("could not rlp decode message: %v", err)
+ return errorf("could not rlp decode message: %v", err)
}
- return 0, msg
+ return msg
}
- return 0, errorf("invalid message: %s", string(rawData))
+ return errorf("invalid message: %s", string(rawData))
}
// Write writes a eth packet to the connection.
func (c *Conn) Write(msg Message) error {
- // check if message is eth protocol message
- var (
- payload []byte
- err error
- )
- payload, err = rlp.EncodeToBytes(msg)
+ payload, err := rlp.EncodeToBytes(msg)
if err != nil {
return err
}
_, err = c.Conn.Write(uint64(msg.Code()), payload)
return err
}
-
-// Write66 writes an eth66 packet to the connection.
-func (c *Conn) Write66(req eth.Packet, code int) error {
- payload, err := rlp.EncodeToBytes(req)
- if err != nil {
- return err
- }
- _, err = c.Conn.Write(uint64(code), payload)
- return err
-}
diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go
index 210639c55e9e..898c6da2f321 100644
--- a/cmd/devp2p/rlpxcmd.go
+++ b/cmd/devp2p/rlpxcmd.go
@@ -24,7 +24,6 @@ import (
"github.com/scroll-tech/go-ethereum/cmd/devp2p/internal/ethtest"
"github.com/scroll-tech/go-ethereum/crypto"
- "github.com/scroll-tech/go-ethereum/internal/utesting"
"github.com/scroll-tech/go-ethereum/p2p"
"github.com/scroll-tech/go-ethereum/p2p/rlpx"
"github.com/scroll-tech/go-ethereum/rlp"
@@ -96,14 +95,9 @@ func rlpxEthTest(ctx *cli.Context) error {
if ctx.NArg() < 3 {
exit("missing path to chain.rlp as command-line argument")
}
- suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2])
+ suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2))
if err != nil {
exit(err)
}
- // check if given node supports eth66, and if so, run eth66 protocol tests as well
- is66Failed, _ := utesting.Run(utesting.Test{Name: "Is_66", Fn: suite.Is_66})
- if is66Failed {
- return runTests(ctx, suite.EthTests())
- }
- return runTests(ctx, suite.AllEthTests())
+ return runTests(ctx, suite.EthTests())
}
diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go
index 15091c444fc2..ed80d12dfc3e 100644
--- a/eth/downloader/peer.go
+++ b/eth/downloader/peer.go
@@ -413,7 +413,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.BlockHeadersMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
@@ -425,7 +425,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.BlockBodiesMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
@@ -437,7 +437,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.ReceiptsMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
@@ -449,7 +449,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.NodeDataMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// idlePeers retrieves a flat list of all currently idle peers satisfying the
diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go
index c4845a34d713..250e7c173cd6 100644
--- a/eth/fetcher/tx_fetcher.go
+++ b/eth/fetcher/tx_fetcher.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The go-ethereum Authors
+// Copyright 2019 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
@@ -20,6 +20,7 @@ import (
"bytes"
"errors"
"fmt"
+ "math"
mrand "math/rand"
"sort"
"time"
@@ -103,6 +104,14 @@ var (
type txAnnounce struct {
origin string // Identifier of the peer originating the notification
hashes []common.Hash // Batch of transaction hashes being announced
+ metas []*txMetadata // Batch of metadatas associated with the hashes (nil before eth/68)
+}
+
+// txMetadata is a set of extra data transmitted along the announcement for better
+// fetch scheduling.
+type txMetadata struct {
+ kind byte // Transaction consensus type
+ size uint32 // Transaction size in bytes
}
// txRequest represents an in-flight transaction retrieval request destined to
@@ -118,10 +127,11 @@ type txRequest struct {
type txDelivery struct {
origin string // Identifier of the peer originating the notification
hashes []common.Hash // Batch of transaction hashes having been delivered
+ metas []txMetadata // Batch of metadatas associated with the delivered hashes
direct bool // Whether this is a direct reply or a broadcast
}
-// txDrop is the notiication that a peer has disconnected.
+// txDrop is the notification that a peer has disconnected.
type txDrop struct {
peer string
}
@@ -153,14 +163,14 @@ type TxFetcher struct {
// Stage 1: Waiting lists for newly discovered transactions that might be
// broadcast without needing explicit request/reply round trips.
- waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast
- waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist
- waitslots map[string]map[common.Hash]struct{} // Waiting announcement sgroupped by peer (DoS protection)
+ waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast
+ waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist
+ waitslots map[string]map[common.Hash]*txMetadata // Waiting announcement sgroupped by peer (DoS protection)
// Stage 2: Queue of transactions that waiting to be allocated to some peer
// to be retrieved directly.
- announces map[string]map[common.Hash]struct{} // Set of announced transactions, grouped by origin peer
- announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash
+ announces map[string]map[common.Hash]*txMetadata // Set of announced transactions, grouped by origin peer
+ announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash
// Stage 3: Set of transactions currently being retrieved, some which may be
// fulfilled and some rescheduled. Note, this step shares 'announces' from the
@@ -173,6 +183,7 @@ type TxFetcher struct {
hasTx func(common.Hash) bool // Retrieves a tx from the local txpool
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
+ dropPeer func(string) // Drops a peer in case of announcement violation
step chan struct{} // Notification channel when the fetcher loop iterates
clock mclock.Clock // Time wrapper to simulate in tests
@@ -181,14 +192,14 @@ type TxFetcher struct {
// NewTxFetcher creates a transaction fetcher to retrieve transaction
// based on hash announcements.
-func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error) *TxFetcher {
- return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, mclock.System{}, nil)
+func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
+ return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, nil)
}
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
// a simulated version and the internal randomness with a deterministic one.
func NewTxFetcherForTests(
- hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
+ hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
clock mclock.Clock, rand *mrand.Rand) *TxFetcher {
return &TxFetcher{
notify: make(chan *txAnnounce),
@@ -197,8 +208,8 @@ func NewTxFetcherForTests(
quit: make(chan struct{}),
waitlist: make(map[common.Hash]map[string]struct{}),
waittime: make(map[common.Hash]mclock.AbsTime),
- waitslots: make(map[string]map[common.Hash]struct{}),
- announces: make(map[string]map[common.Hash]struct{}),
+ waitslots: make(map[string]map[common.Hash]*txMetadata),
+ announces: make(map[string]map[common.Hash]*txMetadata),
announced: make(map[common.Hash]map[string]struct{}),
fetching: make(map[common.Hash]string),
requests: make(map[string]*txRequest),
@@ -207,6 +218,7 @@ func NewTxFetcherForTests(
hasTx: hasTx,
addTxs: addTxs,
fetchTxs: fetchTxs,
+ dropPeer: dropPeer,
clock: clock,
rand: rand,
}
@@ -214,20 +226,23 @@ func NewTxFetcherForTests(
// Notify announces the fetcher of the potential availability of a new batch of
// transactions in the network.
-func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error {
+func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []common.Hash) error {
// Keep track of all the announced transactions
txAnnounceInMeter.Mark(int64(len(hashes)))
// Skip any transaction announcements that we already know of, or that we've
- // previously marked as cheap and discarded. This check is of course racey,
+ // previously marked as cheap and discarded. This check is of course racy,
// because multiple concurrent notifies will still manage to pass it, but it's
// still valuable to check here because it runs concurrent to the internal
// loop, so anything caught here is time saved internally.
var (
- unknowns = make([]common.Hash, 0, len(hashes))
- duplicate, underpriced int64
+ unknownHashes = make([]common.Hash, 0, len(hashes))
+ unknownMetas = make([]*txMetadata, 0, len(hashes))
+
+ duplicate int64
+ underpriced int64
)
- for _, hash := range hashes {
+ for i, hash := range hashes {
switch {
case f.hasTx(hash):
duplicate++
@@ -236,20 +251,22 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error {
underpriced++
default:
- unknowns = append(unknowns, hash)
+ unknownHashes = append(unknownHashes, hash)
+ if types == nil {
+ unknownMetas = append(unknownMetas, nil)
+ } else {
+ unknownMetas = append(unknownMetas, &txMetadata{kind: types[i], size: sizes[i]})
+ }
}
}
txAnnounceKnownMeter.Mark(duplicate)
txAnnounceUnderpricedMeter.Mark(underpriced)
// If anything's left to announce, push it into the internal loop
- if len(unknowns) == 0 {
+ if len(unknownHashes) == 0 {
return nil
}
- announce := &txAnnounce{
- origin: peer,
- hashes: unknowns,
- }
+ announce := &txAnnounce{origin: peer, hashes: unknownHashes, metas: unknownMetas}
select {
case f.notify <- announce:
return nil
@@ -261,7 +278,7 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error {
// Enqueue imports a batch of received transaction into the transaction pool
// and the fetcher. This method may be called by both transaction broadcasts and
// direct request replies. The differentiation is important so the fetcher can
-// re-shedule missing transactions as soon as possible.
+// re-schedule missing transactions as soon as possible.
func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) error {
// Keep track of all the propagated transactions
if direct {
@@ -273,6 +290,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
// re-requesting them and dropping the peer in case of malicious transfers.
var (
added = make([]common.Hash, 0, len(txs))
+ metas = make([]txMetadata, 0, len(txs))
duplicate int64
underpriced int64
otherreject int64
@@ -302,6 +320,10 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
otherreject++
}
added = append(added, txs[i].Hash())
+ metas = append(metas, txMetadata{
+ kind: txs[i].Type(),
+ size: uint32(txs[i].Size()),
+ })
}
if direct {
txReplyKnownMeter.Mark(duplicate)
@@ -313,7 +335,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
txBroadcastOtherRejectMeter.Mark(otherreject)
}
select {
- case f.cleanup <- &txDelivery{origin: peer, hashes: added, direct: direct}:
+ case f.cleanup <- &txDelivery{origin: peer, hashes: added, metas: metas, direct: direct}:
return nil
case <-f.quit:
return errTerminated
@@ -370,13 +392,15 @@ func (f *TxFetcher) loop() {
want := used + len(ann.hashes)
if want > maxTxAnnounces {
txAnnounceDOSMeter.Mark(int64(want - maxTxAnnounces))
+
ann.hashes = ann.hashes[:want-maxTxAnnounces]
+ ann.metas = ann.metas[:want-maxTxAnnounces]
}
// All is well, schedule the remainder of the transactions
idleWait := len(f.waittime) == 0
_, oldPeer := f.announces[ann.origin]
- for _, hash := range ann.hashes {
+ for i, hash := range ann.hashes {
// If the transaction is already downloading, add it to the list
// of possible alternates (in case the current retrieval fails) and
// also account it for the peer.
@@ -385,9 +409,9 @@ func (f *TxFetcher) loop() {
// Stage 2 and 3 share the set of origins per tx
if announces := f.announces[ann.origin]; announces != nil {
- announces[hash] = struct{}{}
+ announces[hash] = ann.metas[i]
} else {
- f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}}
+ f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
}
continue
}
@@ -398,9 +422,9 @@ func (f *TxFetcher) loop() {
// Stage 2 and 3 share the set of origins per tx
if announces := f.announces[ann.origin]; announces != nil {
- announces[hash] = struct{}{}
+ announces[hash] = ann.metas[i]
} else {
- f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}}
+ f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
}
continue
}
@@ -408,12 +432,18 @@ func (f *TxFetcher) loop() {
// yet downloading, add the peer as an alternate origin in the
// waiting list.
if f.waitlist[hash] != nil {
+ // Ignore double announcements from the same peer. This is
+ // especially important if metadata is also passed along to
+ // prevent malicious peers flip-flopping good/bad values.
+ if _, ok := f.waitlist[hash][ann.origin]; ok {
+ continue
+ }
f.waitlist[hash][ann.origin] = struct{}{}
if waitslots := f.waitslots[ann.origin]; waitslots != nil {
- waitslots[hash] = struct{}{}
+ waitslots[hash] = ann.metas[i]
} else {
- f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}}
+ f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
}
continue
}
@@ -422,9 +452,9 @@ func (f *TxFetcher) loop() {
f.waittime[hash] = f.clock.Now()
if waitslots := f.waitslots[ann.origin]; waitslots != nil {
- waitslots[hash] = struct{}{}
+ waitslots[hash] = ann.metas[i]
} else {
- f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}}
+ f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]}
}
}
// If a new item was added to the waitlist, schedule it into the fetcher
@@ -450,9 +480,9 @@ func (f *TxFetcher) loop() {
f.announced[hash] = f.waitlist[hash]
for peer := range f.waitlist[hash] {
if announces := f.announces[peer]; announces != nil {
- announces[hash] = struct{}{}
+ announces[hash] = f.waitslots[peer][hash]
} else {
- f.announces[peer] = map[common.Hash]struct{}{hash: {}}
+ f.announces[peer] = map[common.Hash]*txMetadata{hash: f.waitslots[peer][hash]}
}
delete(f.waitslots[peer], hash)
if len(f.waitslots[peer]) == 0 {
@@ -521,10 +551,27 @@ func (f *TxFetcher) loop() {
case delivery := <-f.cleanup:
// Independent if the delivery was direct or broadcast, remove all
- // traces of the hash from internal trackers
- for _, hash := range delivery.hashes {
+ // traces of the hash from internal trackers. That said, compare any
+ // advertised metadata with the real ones and drop bad peers.
+ for i, hash := range delivery.hashes {
if _, ok := f.waitlist[hash]; ok {
for peer, txset := range f.waitslots {
+ if meta := txset[hash]; meta != nil {
+ if delivery.metas[i].kind != meta.kind {
+ log.Warn("Announced transaction type mismatch", "peer", peer, "tx", hash, "type", delivery.metas[i].kind, "ann", meta.kind)
+ f.dropPeer(peer)
+ } else if delivery.metas[i].size != meta.size {
+ log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size)
+ if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 {
+ // Normally we should drop a peer considering this is a protocol violation.
+ // However, due to the RLP vs consensus format messyness, allow a few bytes
+ // wiggle-room where we only warn, but don't drop.
+ //
+ // TODO(karalabe): Get rid of this relaxation when clients are proven stable.
+ f.dropPeer(peer)
+ }
+ }
+ }
delete(txset, hash)
if len(txset) == 0 {
delete(f.waitslots, peer)
@@ -534,6 +581,22 @@ func (f *TxFetcher) loop() {
delete(f.waittime, hash)
} else {
for peer, txset := range f.announces {
+ if meta := txset[hash]; meta != nil {
+ if delivery.metas[i].kind != meta.kind {
+ log.Warn("Announced transaction type mismatch", "peer", peer, "tx", hash, "type", delivery.metas[i].kind, "ann", meta.kind)
+ f.dropPeer(peer)
+ } else if delivery.metas[i].size != meta.size {
+ log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size)
+ if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 {
+ // Normally we should drop a peer considering this is a protocol violation.
+ // However, due to the RLP vs consensus format messyness, allow a few bytes
+ // wiggle-room where we only warn, but don't drop.
+ //
+ // TODO(karalabe): Get rid of this relaxation when clients are proven stable.
+ f.dropPeer(peer)
+ }
+ }
+ }
delete(txset, hash)
if len(txset) == 0 {
delete(f.announces, peer)
@@ -559,7 +622,7 @@ func (f *TxFetcher) loop() {
// In case of a direct delivery, also reschedule anything missing
// from the original query
if delivery.direct {
- // Mark the reqesting successful (independent of individual status)
+ // Mark the requesting successful (independent of individual status)
txRequestDoneMeter.Mark(int64(len(delivery.hashes)))
// Make sure something was pending, nuke it
@@ -608,7 +671,7 @@ func (f *TxFetcher) loop() {
delete(f.alternates, hash)
delete(f.fetching, hash)
}
- // Something was delivered, try to rechedule requests
+ // Something was delivered, try to reschedule requests
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) // Partial delivery may enable others to deliver too
}
@@ -720,7 +783,7 @@ func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
// should be rescheduled if some request is pending. In practice, a timeout will
// cause the timer to be rescheduled every 5 secs (until the peer comes through or
// disconnects). This is a limitation of the fetcher code because we don't trac
-// pending requests and timed out requests separatey. Without double tracking, if
+// pending requests and timed out requests separately. Without double tracking, if
// we simply didn't reschedule the timer on all-timeout then the timer would never
// be set again since len(request) > 0 => something's running.
func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{}) {
@@ -835,7 +898,7 @@ func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string))
// forEachHash does a range loop over a map of hashes in production, but during
// testing it does a deterministic sorted random to allow reproducing issues.
-func (f *TxFetcher) forEachHash(hashes map[common.Hash]struct{}, do func(hash common.Hash) bool) {
+func (f *TxFetcher) forEachHash(hashes map[common.Hash]*txMetadata, do func(hash common.Hash) bool) {
// If we're running production, use whatever Go's map gives us
if f.rand == nil {
for hash := range hashes {
diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go
index 232c8a97d3f1..e4f726edb0bd 100644
--- a/eth/fetcher/tx_fetcher_test.go
+++ b/eth/fetcher/tx_fetcher_test.go
@@ -41,9 +41,20 @@ var (
testTxsHashes = []common.Hash{testTxs[0].Hash(), testTxs[1].Hash(), testTxs[2].Hash(), testTxs[3].Hash()}
)
+type announce struct {
+ hash common.Hash
+ kind *byte
+ size *uint32
+}
+
+func typeptr(t byte) *byte { return &t }
+func sizeptr(n uint32) *uint32 { return &n }
+
type doTxNotify struct {
peer string
hashes []common.Hash
+ types []byte
+ sizes []uint32
}
type doTxEnqueue struct {
peer string
@@ -57,7 +68,14 @@ type doWait struct {
type doDrop string
type doFunc func()
+type isWaitingWithMeta map[string][]announce
type isWaiting map[string][]common.Hash
+
+type isScheduledWithMeta struct {
+ tracking map[string][]announce
+ fetching map[string][]common.Hash
+ dangling map[string][]common.Hash
+}
type isScheduled struct {
tracking map[string][]common.Hash
fetching map[string][]common.Hash
@@ -81,6 +99,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -162,6 +181,212 @@ func TestTransactionFetcherWaiting(t *testing.T) {
})
}
+// Tests that transaction announcements with associated metadata are added to a
+// waitlist, and none of them are scheduled for retrieval until the wait expires.
+//
+// This test is an extended version of TestTransactionFetcherWaiting. It's mostly
+// to cover the metadata checkes without bloating up the basic behavioral tests
+// with all the useless extra fields.
+func TestTransactionFetcherWaitingWithMeta(t *testing.T) {
+ testTransactionFetcherParallel(t, txFetcherTest{
+ init: func() *TxFetcher {
+ return NewTxFetcher(
+ func(common.Hash) bool { return false },
+ nil,
+ func(string, []common.Hash) error { return nil },
+ nil,
+ )
+ },
+ steps: []interface{}{
+ // Initial announcement to get something into the waitlist
+ doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ },
+ }),
+ // Announce from a new peer to check that no overwrite happens
+ doTxNotify{peer: "B", hashes: []common.Hash{{0x03}, {0x04}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{333, 444}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ }),
+ // Announce clashing hashes but unique new peer
+ doTxNotify{peer: "C", hashes: []common.Hash{{0x01}, {0x04}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 444}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "C": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ }),
+ // Announce existing and clashing hashes from existing peer. Clashes
+ // should not overwrite previous announcements.
+ doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x03}, {0x05}}, types: []byte{types.LegacyTxType, types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{999, 333, 555}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "C": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ }),
+ // Announce clashing hashes with conflicting metadata. Somebody will
+ // be in the wrong, but we don't know yet who.
+ doTxNotify{peer: "D", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.BlobTxType}, sizes: []uint32{999, 222}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "C": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "D": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
+ {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
+ },
+ }),
+ isScheduled{tracking: nil, fetching: nil},
+
+ // Wait for the arrival timeout which should move all expired items
+ // from the wait list to the scheduler
+ doWait{time: txArriveTimeout, step: true},
+ isWaiting(nil),
+ isScheduledWithMeta{
+ tracking: map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "C": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "D": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
+ {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
+ },
+ },
+ fetching: map[string][]common.Hash{ // Depends on deterministic test randomizer
+ "A": {{0x03}, {0x05}},
+ "C": {{0x01}, {0x04}},
+ "D": {{0x02}},
+ },
+ },
+ // Queue up a non-fetchable transaction and then trigger it with a new
+ // peer (weird case to test 1 line in the fetcher)
+ doTxNotify{peer: "C", hashes: []common.Hash{{0x06}, {0x07}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{666, 777}},
+ isWaitingWithMeta(map[string][]announce{
+ "C": {
+ {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
+ {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
+ },
+ }),
+ doWait{time: txArriveTimeout, step: true},
+ isScheduledWithMeta{
+ tracking: map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "C": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
+ {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
+ },
+ "D": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
+ {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
+ },
+ },
+ fetching: map[string][]common.Hash{
+ "A": {{0x03}, {0x05}},
+ "C": {{0x01}, {0x04}},
+ "D": {{0x02}},
+ },
+ },
+ doTxNotify{peer: "E", hashes: []common.Hash{{0x06}, {0x07}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{666, 777}},
+ isScheduledWithMeta{
+ tracking: map[string][]announce{
+ "A": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)},
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)},
+ },
+ "B": {
+ {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ },
+ "C": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)},
+ {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)},
+ {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
+ {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
+ },
+ "D": {
+ {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)},
+ {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)},
+ },
+ "E": {
+ {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)},
+ {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)},
+ },
+ },
+ fetching: map[string][]common.Hash{
+ "A": {{0x03}, {0x05}},
+ "C": {{0x01}, {0x04}},
+ "D": {{0x02}},
+ "E": {{0x06}, {0x07}},
+ },
+ },
+ },
+ })
+}
+
// Tests that transaction announcements skip the waiting list if they are
// already scheduled.
func TestTransactionFetcherSkipWaiting(t *testing.T) {
@@ -171,6 +396,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -234,6 +460,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -314,6 +541,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
<-proceed
return errors.New("peer disconnected")
},
+ nil,
)
},
steps: []interface{}{
@@ -383,6 +611,7 @@ func TestTransactionFetcherCleanup(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -422,6 +651,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -460,6 +690,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -506,6 +737,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -544,6 +776,7 @@ func TestTransactionFetcherBroadcasts(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -592,6 +825,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -649,6 +883,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -714,6 +949,7 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -773,6 +1009,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -811,6 +1048,7 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
func(common.Hash) bool { return false },
nil,
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -878,6 +1116,7 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
return errs
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -947,6 +1186,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
return errs
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: append(steps, []interface{}{
@@ -969,6 +1209,7 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -1022,6 +1263,7 @@ func TestTransactionFetcherDrop(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -1088,6 +1330,7 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -1121,6 +1364,74 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) {
})
}
+// Tests that announced transactions with the wrong transaction type or size will
+// result in a dropped peer.
+func TestInvalidAnnounceMetadata(t *testing.T) {
+ drop := make(chan string, 2)
+ testTransactionFetcherParallel(t, txFetcherTest{
+ init: func() *TxFetcher {
+ return NewTxFetcher(
+ func(common.Hash) bool { return false },
+ func(txs []*types.Transaction) []error {
+ return make([]error, len(txs))
+ },
+ func(string, []common.Hash) error { return nil },
+ func(peer string) { drop <- peer },
+ )
+ },
+ steps: []interface{}{
+ // Initial announcement to get something into the waitlist
+ doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}, types: []byte{testTxs[0].Type(), testTxs[1].Type()}, sizes: []uint32{uint32(testTxs[0].Size()), uint32(testTxs[1].Size())}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))},
+ {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
+ },
+ }),
+ // Announce from new peers conflicting transactions
+ doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{1024 + uint32(testTxs[0].Size())}},
+ doTxNotify{peer: "C", hashes: []common.Hash{testTxsHashes[1]}, types: []byte{1 + testTxs[1].Type()}, sizes: []uint32{uint32(testTxs[1].Size())}},
+ isWaitingWithMeta(map[string][]announce{
+ "A": {
+ {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))},
+ {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
+ },
+ "B": {
+ {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(1024 + uint32(testTxs[0].Size()))},
+ },
+ "C": {
+ {testTxsHashes[1], typeptr(1 + testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
+ },
+ }),
+ // Schedule all the transactions for retrieval
+ doWait{time: txArriveTimeout, step: true},
+ isWaitingWithMeta(nil),
+ isScheduledWithMeta{
+ tracking: map[string][]announce{
+ "A": {
+ {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))},
+ {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
+ },
+ "B": {
+ {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(1024 + uint32(testTxs[0].Size()))},
+ },
+ "C": {
+ {testTxsHashes[1], typeptr(1 + testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))},
+ },
+ },
+ fetching: map[string][]common.Hash{
+ "A": {testTxsHashes[0]},
+ "C": {testTxsHashes[1]},
+ },
+ },
+ // Deliver the transactions and wait for B to be dropped
+ doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}},
+ doFunc(func() { <-drop }),
+ doFunc(func() { <-drop }),
+ },
+ })
+}
+
// This test reproduces a crash caught by the fuzzer. The root cause was a
// dangling transaction timing out and clashing on readd with a concurrently
// announced one.
@@ -1133,6 +1444,7 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -1160,6 +1472,7 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -1189,6 +1502,7 @@ func TestTransactionFetcherFuzzCrash03(t *testing.T) {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
)
},
steps: []interface{}{
@@ -1225,6 +1539,7 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
<-proceed
return errors.New("peer disconnected")
},
+ nil,
)
},
steps: []interface{}{
@@ -1265,9 +1580,34 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
// Crunch through all the test steps and execute them
for i, step := range tt.steps {
+ // Auto-expand certain steps to ones with metadata
+ switch old := step.(type) {
+ case isWaiting:
+ new := make(isWaitingWithMeta)
+ for peer, hashes := range old {
+ for _, hash := range hashes {
+ new[peer] = append(new[peer], announce{hash, nil, nil})
+ }
+ }
+ step = new
+
+ case isScheduled:
+ new := isScheduledWithMeta{
+ tracking: make(map[string][]announce),
+ fetching: old.fetching,
+ dangling: old.dangling,
+ }
+ for peer, hashes := range old.tracking {
+ for _, hash := range hashes {
+ new.tracking[peer] = append(new.tracking[peer], announce{hash, nil, nil})
+ }
+ }
+ step = new
+ }
+ // Process the original or expanded steps
switch step := step.(type) {
case doTxNotify:
- if err := fetcher.Notify(step.peer, step.hashes); err != nil {
+ if err := fetcher.Notify(step.peer, step.types, step.sizes, step.hashes); err != nil {
t.Errorf("step %d: %v", i, err)
}
<-wait // Fetcher needs to process this, wait until it's done
@@ -1298,24 +1638,34 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
case doFunc:
step()
- case isWaiting:
+ case isWaitingWithMeta:
// We need to check that the waiting list (stage 1) internals
// match with the expected set. Check the peer->hash mappings
// first.
- for peer, hashes := range step {
+ for peer, announces := range step {
waiting := fetcher.waitslots[peer]
if waiting == nil {
t.Errorf("step %d: peer %s missing from waitslots", i, peer)
continue
}
- for _, hash := range hashes {
- if _, ok := waiting[hash]; !ok {
- t.Errorf("step %d, peer %s: hash %x missing from waitslots", i, peer, hash)
+ for _, ann := range announces {
+ if meta, ok := waiting[ann.hash]; !ok {
+ t.Errorf("step %d, peer %s: hash %x missing from waitslots", i, peer, ann.hash)
+ } else {
+ if (meta == nil && (ann.kind != nil || ann.size != nil)) ||
+ (meta != nil && (ann.kind == nil || ann.size == nil)) ||
+ (meta != nil && (meta.kind != *ann.kind || meta.size != *ann.size)) {
+ t.Errorf("step %d, peer %s, hash %x: waitslot metadata mismatch: want %v, have %v/%v", i, peer, ann.hash, meta, ann.kind, ann.size)
+ }
}
}
- for hash := range waiting {
- if !containsHash(hashes, hash) {
- t.Errorf("step %d, peer %s: hash %x extra in waitslots", i, peer, hash)
+ for hash, meta := range waiting {
+ ann := announce{hash: hash}
+ if meta != nil {
+ ann.kind, ann.size = &meta.kind, &meta.size
+ }
+ if !containsAnnounce(announces, ann) {
+ t.Errorf("step %d, peer %s: announce %v extra in waitslots", i, peer, ann)
}
}
}
@@ -1325,13 +1675,13 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
}
}
// Peer->hash sets correct, check the hash->peer and timeout sets
- for peer, hashes := range step {
- for _, hash := range hashes {
- if _, ok := fetcher.waitlist[hash][peer]; !ok {
- t.Errorf("step %d, hash %x: peer %s missing from waitlist", i, hash, peer)
+ for peer, announces := range step {
+ for _, ann := range announces {
+ if _, ok := fetcher.waitlist[ann.hash][peer]; !ok {
+ t.Errorf("step %d, hash %x: peer %s missing from waitlist", i, ann.hash, peer)
}
- if _, ok := fetcher.waittime[hash]; !ok {
- t.Errorf("step %d: hash %x missing from waittime", i, hash)
+ if _, ok := fetcher.waittime[ann.hash]; !ok {
+ t.Errorf("step %d: hash %x missing from waittime", i, ann.hash)
}
}
}
@@ -1340,15 +1690,15 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
t.Errorf("step %d, hash %x: empty peerset in waitlist", i, hash)
}
for peer := range peers {
- if !containsHash(step[peer], hash) {
+ if !containsHashInAnnounces(step[peer], hash) {
t.Errorf("step %d, hash %x: peer %s extra in waitlist", i, hash, peer)
}
}
}
for hash := range fetcher.waittime {
var found bool
- for _, hashes := range step {
- if containsHash(hashes, hash) {
+ for _, announces := range step {
+ if containsHashInAnnounces(announces, hash) {
found = true
break
}
@@ -1358,23 +1708,33 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
}
}
- case isScheduled:
+ case isScheduledWithMeta:
// Check that all scheduled announces are accounted for and no
// extra ones are present.
- for peer, hashes := range step.tracking {
+ for peer, announces := range step.tracking {
scheduled := fetcher.announces[peer]
if scheduled == nil {
t.Errorf("step %d: peer %s missing from announces", i, peer)
continue
}
- for _, hash := range hashes {
- if _, ok := scheduled[hash]; !ok {
- t.Errorf("step %d, peer %s: hash %x missing from announces", i, peer, hash)
+ for _, ann := range announces {
+ if meta, ok := scheduled[ann.hash]; !ok {
+ t.Errorf("step %d, peer %s: hash %x missing from announces", i, peer, ann.hash)
+ } else {
+ if (meta == nil && (ann.kind != nil || ann.size != nil)) ||
+ (meta != nil && (ann.kind == nil || ann.size == nil)) ||
+ (meta != nil && (meta.kind != *ann.kind || meta.size != *ann.size)) {
+ t.Errorf("step %d, peer %s, hash %x: announce metadata mismatch: want %v, have %v/%v", i, peer, ann.hash, meta, ann.kind, ann.size)
+ }
}
}
- for hash := range scheduled {
- if !containsHash(hashes, hash) {
- t.Errorf("step %d, peer %s: hash %x extra in announces", i, peer, hash)
+ for hash, meta := range scheduled {
+ ann := announce{hash: hash}
+ if meta != nil {
+ ann.kind, ann.size = &meta.kind, &meta.size
+ }
+ if !containsAnnounce(announces, ann) {
+ t.Errorf("step %d, peer %s: announce %x extra in announces", i, peer, hash)
}
}
}
@@ -1474,17 +1834,17 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
// retrieval but not actively being downloaded are tracked only
// in the stage 2 `announced` map.
var queued []common.Hash
- for _, hashes := range step.tracking {
- for _, hash := range hashes {
+ for _, announces := range step.tracking {
+ for _, ann := range announces {
var found bool
for _, hs := range step.fetching {
- if containsHash(hs, hash) {
+ if containsHash(hs, ann.hash) {
found = true
break
}
}
if !found {
- queued = append(queued, hash)
+ queued = append(queued, ann.hash)
}
}
}
@@ -1517,6 +1877,42 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
}
}
+// containsAnnounce returns whether an announcement is contained within a slice
+// of announcements.
+func containsAnnounce(slice []announce, ann announce) bool {
+ for _, have := range slice {
+ if have.hash == ann.hash {
+ if have.kind == nil || ann.kind == nil {
+ if have.kind != ann.kind {
+ return false
+ }
+ } else if *have.kind != *ann.kind {
+ return false
+ }
+ if have.size == nil || ann.size == nil {
+ if have.size != ann.size {
+ return false
+ }
+ } else if *have.size != *ann.size {
+ return false
+ }
+ return true
+ }
+ }
+ return false
+}
+
+// containsHashInAnnounces returns whether a hash is contained within a slice
+// of announcements.
+func containsHashInAnnounces(slice []announce, hash common.Hash) bool {
+ for _, have := range slice {
+ if have.hash == hash {
+ return true
+ }
+ }
+ return false
+}
+
// containsHash returns whether a hash is contained within a hash slice.
func containsHash(slice []common.Hash, hash common.Hash) bool {
for _, have := range slice {
diff --git a/eth/handler.go b/eth/handler.go
index 6939a4d6823b..f41319c82003 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -238,13 +238,13 @@ func newHandler(config *handlerConfig) (*handler, error) {
}
return p.RequestTxs(hashes)
}
- h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx)
+ h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx, h.removePeer)
h.chainSync = newChainSyncer(h)
return h, nil
}
// runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to
-// various subsistems and starts handling messages.
+// various subsystems and starts handling messages.
func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
// If the peer has a `snap` extension, wait for it to connect so we can have
// a uniform initialization/teardown mechanism
diff --git a/eth/handler_eth.go b/eth/handler_eth.go
index 1a3aff8aa097..fc829d02715d 100644
--- a/eth/handler_eth.go
+++ b/eth/handler_eth.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The go-ethereum Authors
+// 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
@@ -90,8 +90,11 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
case *eth.NewBlockPacket:
return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
- case *eth.NewPooledTransactionHashesPacket:
- return h.txFetcher.Notify(peer.ID(), *packet)
+ case *eth.NewPooledTransactionHashesPacket66:
+ return h.txFetcher.Notify(peer.ID(), nil, nil, *packet)
+
+ case *eth.NewPooledTransactionHashesPacket68:
+ return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes)
case *eth.TransactionsPacket:
return h.txFetcher.Enqueue(peer.ID(), *packet, false)
diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go
index d24705cec0df..0e97f3489af2 100644
--- a/eth/handler_eth_test.go
+++ b/eth/handler_eth_test.go
@@ -1,4 +1,4 @@
-// Copyright 2014 The go-ethereum Authors
+// 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
@@ -61,10 +61,14 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
h.blockBroadcasts.Send(packet.Block)
return nil
- case *eth.NewPooledTransactionHashesPacket:
+ case *eth.NewPooledTransactionHashesPacket66:
h.txAnnounces.Send(([]common.Hash)(*packet))
return nil
+ case *eth.NewPooledTransactionHashesPacket68:
+ h.txAnnounces.Send(packet.Hashes)
+ return nil
+
case *eth.TransactionsPacket:
h.txBroadcasts.Send(([]*types.Transaction)(*packet))
return nil
@@ -81,6 +85,8 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
// Tests that peers are correctly accepted (or rejected) based on the advertised
// fork IDs in the protocol handshake.
func TestForkIDSplit66(t *testing.T) { testForkIDSplit(t, eth.ETH66) }
+func TestForkIDSplit67(t *testing.T) { testForkIDSplit(t, eth.ETH67) }
+func TestForkIDSplit68(t *testing.T) { testForkIDSplit(t, eth.ETH68) }
func testForkIDSplit(t *testing.T, protocol uint) {
t.Parallel()
@@ -236,6 +242,8 @@ func testForkIDSplit(t *testing.T, protocol uint) {
// Tests that received transactions are added to the local pool.
func TestRecvTransactions66(t *testing.T) { testRecvTransactions(t, eth.ETH66) }
+func TestRecvTransactions67(t *testing.T) { testRecvTransactions(t, eth.ETH67) }
+func TestRecvTransactions68(t *testing.T) { testRecvTransactions(t, eth.ETH68) }
func testRecvTransactions(t *testing.T, protocol uint) {
t.Parallel()
@@ -293,6 +301,8 @@ func testRecvTransactions(t *testing.T, protocol uint) {
// This test checks that pending transactions are sent.
func TestSendTransactions66(t *testing.T) { testSendTransactions(t, eth.ETH66) }
+func TestSendTransactions67(t *testing.T) { testSendTransactions(t, eth.ETH67) }
+func TestSendTransactions68(t *testing.T) { testSendTransactions(t, eth.ETH68) }
func testSendTransactions(t *testing.T, protocol uint) {
t.Parallel()
@@ -351,7 +361,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
seen := make(map[common.Hash]struct{})
for len(seen) < len(insert) {
switch protocol {
- case 65, 66:
+ case 65, 66, 67, 68:
select {
case hashes := <-anns:
for _, hash := range hashes {
@@ -378,6 +388,8 @@ func testSendTransactions(t *testing.T, protocol uint) {
// Tests that transactions get propagated to all attached peers, either via direct
// broadcasts or via announcements/retrievals.
func TestTransactionPropagation66(t *testing.T) { testTransactionPropagation(t, eth.ETH66) }
+func TestTransactionPropagation67(t *testing.T) { testTransactionPropagation(t, eth.ETH67) }
+func TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) }
func testTransactionPropagation(t *testing.T, protocol uint) {
t.Parallel()
@@ -435,12 +447,13 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
// Iterate through all the sinks and ensure they all got the transactions
for i := range sinks {
- for arrived := 0; arrived < len(txs); {
+ for arrived, timeout := 0, false; arrived < len(txs) && !timeout; {
select {
case event := <-txChs[i]:
arrived += len(event.Txs)
- case <-time.NewTimer(time.Second).C:
+ case <-time.After(time.Second):
t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs))
+ timeout = true
}
}
}
@@ -486,7 +499,6 @@ func TestCheckpointChallenge(t *testing.T) {
}
func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
-
// Reduce the checkpoint handshake challenge timeout
defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
syncChallengeTimeout = 250 * time.Millisecond
@@ -676,6 +688,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) {
// Tests that a propagated malformed block (uncles or transactions don't match
// with the hashes in the header) gets discarded and not broadcast forward.
func TestBroadcastMalformedBlock66(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH66) }
+func TestBroadcastMalformedBlock67(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH67) }
+func TestBroadcastMalformedBlock68(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH68) }
func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
t.Parallel()
diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go
index 95bb05ac7a56..e73d7aafb1c8 100644
--- a/eth/protocols/eth/broadcast.go
+++ b/eth/protocols/eth/broadcast.go
@@ -142,13 +142,17 @@ func (p *Peer) announceTransactions() {
if done == nil && len(queue) > 0 {
// Pile transaction hashes until we reach our allowed network limit
var (
- count int
- pending []common.Hash
- size common.StorageSize
+ count int
+ pending []common.Hash
+ pendingTypes []byte
+ pendingSizes []uint32
+ size common.StorageSize
)
for count = 0; count < len(queue) && size < maxTxPacketSize; count++ {
- if p.txpool.Get(queue[count]) != nil {
+ if tx := p.txpool.Get(queue[count]); tx != nil {
pending = append(pending, queue[count])
+ pendingTypes = append(pendingTypes, tx.Type())
+ pendingSizes = append(pendingSizes, uint32(tx.Size()))
size += common.HashLength
}
}
@@ -159,9 +163,16 @@ func (p *Peer) announceTransactions() {
if len(pending) > 0 {
done = make(chan struct{})
go func() {
- if err := p.sendPooledTransactionHashes(pending); err != nil {
- fail <- err
- return
+ if p.version >= ETH68 {
+ if err := p.sendPooledTransactionHashes68(pending, pendingTypes, pendingSizes); err != nil {
+ fail <- err
+ return
+ }
+ } else {
+ if err := p.sendPooledTransactionHashes66(pending); err != nil {
+ fail <- err
+ return
+ }
}
close(done)
p.Log().Trace("Sent transaction announcements", "count", len(pending))
diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go
index 427e63a135c4..620370383ff6 100644
--- a/eth/protocols/eth/handler.go
+++ b/eth/protocols/eth/handler.go
@@ -96,7 +96,7 @@ type Backend interface {
// TxPool defines the methods needed by the protocol handler to serve transactions.
type TxPool interface {
- // Get retrieves the the transaction from the local txpool with the given hash.
+ // Get retrieves the transaction from the local txpool with the given hash.
Get(hash common.Hash) *types.Transaction
}
@@ -175,7 +175,7 @@ var eth66 = map[uint64]msgHandler{
NewBlockHashesMsg: handleNewBlockhashes,
NewBlockMsg: handleNewBlock,
TransactionsMsg: handleTransactions,
- NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
+ NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes66,
GetBlockHeadersMsg: handleGetBlockHeaders66,
BlockHeadersMsg: handleBlockHeaders66,
GetBlockBodiesMsg: handleGetBlockBodies66,
@@ -188,6 +188,36 @@ var eth66 = map[uint64]msgHandler{
PooledTransactionsMsg: handlePooledTransactions66,
}
+var eth67 = map[uint64]msgHandler{
+ NewBlockHashesMsg: handleNewBlockhashes,
+ NewBlockMsg: handleNewBlock,
+ TransactionsMsg: handleTransactions,
+ NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes66,
+ GetBlockHeadersMsg: handleGetBlockHeaders66,
+ BlockHeadersMsg: handleBlockHeaders66,
+ GetBlockBodiesMsg: handleGetBlockBodies66,
+ BlockBodiesMsg: handleBlockBodies66,
+ GetReceiptsMsg: handleGetReceipts66,
+ ReceiptsMsg: handleReceipts66,
+ GetPooledTransactionsMsg: handleGetPooledTransactions66,
+ PooledTransactionsMsg: handlePooledTransactions66,
+}
+
+var eth68 = map[uint64]msgHandler{
+ NewBlockHashesMsg: handleNewBlockhashes,
+ NewBlockMsg: handleNewBlock,
+ TransactionsMsg: handleTransactions,
+ NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes68,
+ GetBlockHeadersMsg: handleGetBlockHeaders66,
+ BlockHeadersMsg: handleBlockHeaders66,
+ GetBlockBodiesMsg: handleGetBlockBodies66,
+ BlockBodiesMsg: handleBlockBodies66,
+ GetReceiptsMsg: handleGetReceipts66,
+ ReceiptsMsg: handleReceipts66,
+ GetPooledTransactionsMsg: handleGetPooledTransactions66,
+ PooledTransactionsMsg: handlePooledTransactions66,
+}
+
// handleMessage is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error.
func handleMessage(backend Backend, peer *Peer) error {
@@ -202,9 +232,12 @@ func handleMessage(backend Backend, peer *Peer) error {
defer msg.Discard()
var handlers = eth66
- //if peer.Version() >= ETH67 { // Left in as a sample when new protocol is added
- // handlers = eth67
- //}
+ if peer.Version() == ETH67 {
+ handlers = eth67
+ }
+ if peer.Version() >= ETH68 {
+ handlers = eth68
+ }
// Track the amount of time it takes to serve the request and run the handler
if metrics.Enabled {
diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go
index ec5a5e76482b..cad82d9c90ea 100644
--- a/eth/protocols/eth/handler_test.go
+++ b/eth/protocols/eth/handler_test.go
@@ -111,6 +111,8 @@ func (b *testBackend) Handle(*Peer, Packet) error {
// Tests that block headers can be retrieved from a remote chain based on user queries.
func TestGetBlockHeaders66(t *testing.T) { testGetBlockHeaders(t, ETH66) }
+func TestGetBlockHeaders67(t *testing.T) { testGetBlockHeaders(t, ETH67) }
+func TestGetBlockHeaders68(t *testing.T) { testGetBlockHeaders(t, ETH68) }
func testGetBlockHeaders(t *testing.T, protocol uint) {
t.Parallel()
@@ -291,6 +293,8 @@ func testGetBlockHeaders(t *testing.T, protocol uint) {
// Tests that block contents can be retrieved from a remote chain based on their hashes.
func TestGetBlockBodies66(t *testing.T) { testGetBlockBodies(t, ETH66) }
+func TestGetBlockBodies67(t *testing.T) { testGetBlockBodies(t, ETH67) }
+func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) }
func testGetBlockBodies(t *testing.T, protocol uint) {
t.Parallel()
@@ -373,9 +377,11 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
}
// Tests that the state trie nodes can be retrieved based on hashes.
-func TestGetNodeData66(t *testing.T) { testGetNodeData(t, ETH66) }
+func TestGetNodeData66(t *testing.T) { testGetNodeData(t, ETH66, false) }
+func TestGetNodeData67(t *testing.T) { testGetNodeData(t, ETH67, true) }
+func TestGetNodeData68(t *testing.T) { testGetNodeData(t, ETH68, true) }
-func testGetNodeData(t *testing.T, protocol uint) {
+func testGetNodeData(t *testing.T, protocol uint, drop bool) {
t.Parallel()
// Define three accounts to simulate transactions with
@@ -436,8 +442,15 @@ func testGetNodeData(t *testing.T, protocol uint) {
GetNodeDataPacket: hashes,
})
msg, err := peer.app.ReadMsg()
- if err != nil {
- t.Fatalf("failed to read node data response: %v", err)
+ if !drop {
+ if err != nil {
+ t.Fatalf("failed to read node data response: %v", err)
+ }
+ } else {
+ if err != nil {
+ return
+ }
+ t.Fatalf("succeeded to read node data response on non-supporting protocol: %v", msg)
}
if msg.Code != NodeDataMsg {
t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg)
@@ -483,6 +496,8 @@ func testGetNodeData(t *testing.T, protocol uint) {
// Tests that the transaction receipts can be retrieved based on hashes.
func TestGetBlockReceipts66(t *testing.T) { testGetBlockReceipts(t, ETH66) }
+func TestGetBlockReceipts67(t *testing.T) { testGetBlockReceipts(t, ETH67) }
+func TestGetBlockReceipts68(t *testing.T) { testGetBlockReceipts(t, ETH68) }
func testGetBlockReceipts(t *testing.T, protocol uint) {
t.Parallel()
diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go
index ba091f9cee1b..8fb52f402c38 100644
--- a/eth/protocols/eth/handlers.go
+++ b/eth/protocols/eth/handlers.go
@@ -315,13 +315,13 @@ func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error {
return backend.Handle(peer, &res.ReceiptsPacket)
}
-func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error {
+func handleNewPooledTransactionHashes66(backend Backend, msg Decoder, peer *Peer) error {
// New transaction announcement arrived, make sure we have
// a valid and fresh chain to handle them
if !backend.AcceptTxs() {
return nil
}
- ann := new(NewPooledTransactionHashesPacket)
+ ann := new(NewPooledTransactionHashesPacket66)
if err := msg.Decode(ann); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
}
@@ -332,6 +332,26 @@ func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer)
return backend.Handle(peer, ann)
}
+func handleNewPooledTransactionHashes68(backend Backend, msg Decoder, peer *Peer) error {
+ // New transaction announcement arrived, make sure we have
+ // a valid and fresh chain to handle them
+ if !backend.AcceptTxs() {
+ return nil
+ }
+ ann := new(NewPooledTransactionHashesPacket68)
+ if err := msg.Decode(ann); err != nil {
+ return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+ }
+ if len(ann.Hashes) != len(ann.Types) || len(ann.Hashes) != len(ann.Sizes) {
+ return fmt.Errorf("%w: message %v: invalid len of fields: %v %v %v", errDecode, msg, len(ann.Hashes), len(ann.Types), len(ann.Sizes))
+ }
+ // Schedule all the unknown hashes for retrieval
+ for _, hash := range ann.Hashes {
+ peer.markTransaction(hash)
+ }
+ return backend.Handle(peer, ann)
+}
+
func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) error {
// Decode the pooled transactions retrieval message
var query GetPooledTransactionsPacket66
diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go
index 20f959a8edf9..ef42ebf011f7 100644
--- a/eth/protocols/eth/peer.go
+++ b/eth/protocols/eth/peer.go
@@ -203,16 +203,29 @@ func (p *Peer) AsyncSendTransactions(hashes []common.Hash) {
}
}
-// sendPooledTransactionHashes sends transaction hashes to the peer and includes
+// sendPooledTransactionHashes66 sends transaction hashes to the peer and includes
// them in its transaction hash set for future reference.
//
// This method is a helper used by the async transaction announcer. Don't call it
// directly as the queueing (memory) and transmission (bandwidth) costs should
// not be managed directly.
-func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash) error {
+func (p *Peer) sendPooledTransactionHashes66(hashes []common.Hash) error {
// Mark all the transactions as known, but ensure we don't overflow our limits
p.knownTxs.Add(hashes...)
- return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket(hashes))
+ return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket66(hashes))
+}
+
+// sendPooledTransactionHashes68 sends transaction hashes (tagged with their type
+// and size) to the peer and includes them in its transaction hash set for future
+// reference.
+//
+// This method is a helper used by the async transaction announcer. Don't call it
+// directly as the queueing (memory) and transmission (bandwidth) costs should
+// not be managed directly.
+func (p *Peer) sendPooledTransactionHashes68(hashes []common.Hash, types []byte, sizes []uint32) error {
+ // Mark all the transactions as known, but ensure we don't overflow our limits
+ p.knownTxs.Add(hashes...)
+ return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket68{Types: types, Sizes: sizes, Hashes: hashes})
}
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go
index 2a149de0a7a6..d73e5f9c7ee3 100644
--- a/eth/protocols/eth/peer_test.go
+++ b/eth/protocols/eth/peer_test.go
@@ -48,6 +48,8 @@ func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan
peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool())
errc := make(chan error, 1)
go func() {
+ defer app.Close()
+
errc <- backend.RunPeer(peer, func(peer *Peer) error {
return Handle(backend, peer)
})
diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go
index 5681f74eacd2..2442b40d8239 100644
--- a/eth/protocols/eth/protocol.go
+++ b/eth/protocols/eth/protocol.go
@@ -1,4 +1,4 @@
-// Copyright 2014 The go-ethereum Authors
+// 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
@@ -31,6 +31,8 @@ import (
// Constants to match up protocol versions and messages
const (
ETH66 = 66
+ ETH67 = 67
+ ETH68 = 68
)
// ProtocolName is the official short name of the `eth` protocol used during
@@ -39,11 +41,11 @@ const ProtocolName = "eth"
// ProtocolVersions are the supported versions of the `eth` protocol (first
// is primary).
-var ProtocolVersions = []uint{ETH66}
+var ProtocolVersions = []uint{ETH68, ETH67, ETH66}
// protocolLengths are the number of implemented message corresponding to
// different protocol versions.
-var protocolLengths = map[uint]uint64{ETH66: 17}
+var protocolLengths = map[uint]uint64{ETH68: 17, ETH67: 17, ETH66: 17}
// maxMessageSize is the maximum cap on the size of a protocol message.
const maxMessageSize = 10 * 1024 * 1024
@@ -169,12 +171,22 @@ func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error {
// BlockHeadersPacket represents a block header response.
type BlockHeadersPacket []*types.Header
-// BlockHeadersPacket represents a block header response over eth/66.
+// BlockHeadersPacket66 represents a block header response over eth/66.
type BlockHeadersPacket66 struct {
RequestId uint64
BlockHeadersPacket
}
+// BlockHeadersRLPPacket represents a block header response, to use when we already
+// have the headers rlp encoded.
+type BlockHeadersRLPPacket []rlp.RawValue
+
+// BlockHeadersRLPPacket66 represents a block header response over eth/66.
+type BlockHeadersRLPPacket66 struct {
+ RequestId uint64
+ BlockHeadersRLPPacket
+}
+
// NewBlockPacket is the network packet for the block propagation message.
type NewBlockPacket struct {
Block *types.Block
@@ -197,7 +209,7 @@ func (request *NewBlockPacket) sanityCheck() error {
// GetBlockBodiesPacket represents a block body query.
type GetBlockBodiesPacket []common.Hash
-// GetBlockBodiesPacket represents a block body query over eth/66.
+// GetBlockBodiesPacket66 represents a block body query over eth/66.
type GetBlockBodiesPacket66 struct {
RequestId uint64
GetBlockBodiesPacket
@@ -206,7 +218,7 @@ type GetBlockBodiesPacket66 struct {
// BlockBodiesPacket is the network packet for block content distribution.
type BlockBodiesPacket []*BlockBody
-// BlockBodiesPacket is the network packet for block content distribution over eth/66.
+// BlockBodiesPacket66 is the network packet for block content distribution over eth/66.
type BlockBodiesPacket66 struct {
RequestId uint64
BlockBodiesPacket
@@ -245,7 +257,7 @@ func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header)
// GetNodeDataPacket represents a trie node data query.
type GetNodeDataPacket []common.Hash
-// GetNodeDataPacket represents a trie node data query over eth/66.
+// GetNodeDataPacket66 represents a trie node data query over eth/66.
type GetNodeDataPacket66 struct {
RequestId uint64
GetNodeDataPacket
@@ -254,7 +266,7 @@ type GetNodeDataPacket66 struct {
// NodeDataPacket is the network packet for trie node data distribution.
type NodeDataPacket [][]byte
-// NodeDataPacket is the network packet for trie node data distribution over eth/66.
+// NodeDataPacket66 is the network packet for trie node data distribution over eth/66.
type NodeDataPacket66 struct {
RequestId uint64
NodeDataPacket
@@ -263,7 +275,7 @@ type NodeDataPacket66 struct {
// GetReceiptsPacket represents a block receipts query.
type GetReceiptsPacket []common.Hash
-// GetReceiptsPacket represents a block receipts query over eth/66.
+// GetReceiptsPacket66 represents a block receipts query over eth/66.
type GetReceiptsPacket66 struct {
RequestId uint64
GetReceiptsPacket
@@ -272,7 +284,7 @@ type GetReceiptsPacket66 struct {
// ReceiptsPacket is the network packet for block receipts distribution.
type ReceiptsPacket [][]*types.Receipt
-// ReceiptsPacket is the network packet for block receipts distribution over eth/66.
+// ReceiptsPacket66 is the network packet for block receipts distribution over eth/66.
type ReceiptsPacket66 struct {
RequestId uint64
ReceiptsPacket
@@ -281,14 +293,21 @@ type ReceiptsPacket66 struct {
// ReceiptsRLPPacket is used for receipts, when we already have it encoded
type ReceiptsRLPPacket []rlp.RawValue
-// ReceiptsPacket66 is the eth-66 version of ReceiptsRLPPacket
+// ReceiptsRLPPacket66 is the eth-66 version of ReceiptsRLPPacket
type ReceiptsRLPPacket66 struct {
RequestId uint64
ReceiptsRLPPacket
}
-// NewPooledTransactionHashesPacket represents a transaction announcement packet.
-type NewPooledTransactionHashesPacket []common.Hash
+// NewPooledTransactionHashesPacket66 represents a transaction announcement packet on eth/66 and eth/67.
+type NewPooledTransactionHashesPacket66 []common.Hash
+
+// NewPooledTransactionHashesPacket68 represents a transaction announcement packet on eth/68 and newer.
+type NewPooledTransactionHashesPacket68 struct {
+ Types []byte
+ Sizes []uint32
+ Hashes []common.Hash
+}
// GetPooledTransactionsPacket represents a transaction query.
type GetPooledTransactionsPacket []common.Hash
@@ -301,13 +320,13 @@ type GetPooledTransactionsPacket66 struct {
// PooledTransactionsPacket is the network packet for transaction distribution.
type PooledTransactionsPacket []*types.Transaction
-// PooledTransactionsPacket is the network packet for transaction distribution over eth/66.
+// PooledTransactionsPacket66 is the network packet for transaction distribution over eth/66.
type PooledTransactionsPacket66 struct {
RequestId uint64
PooledTransactionsPacket
}
-// PooledTransactionsPacket is the network packet for transaction distribution, used
+// PooledTransactionsRLPPacket is the network packet for transaction distribution, used
// in the cases we already have them in rlp-encoded form
type PooledTransactionsRLPPacket []rlp.RawValue
@@ -353,8 +372,11 @@ func (*GetReceiptsPacket) Kind() byte { return GetReceiptsMsg }
func (*ReceiptsPacket) Name() string { return "Receipts" }
func (*ReceiptsPacket) Kind() byte { return ReceiptsMsg }
-func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" }
-func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg }
+func (*NewPooledTransactionHashesPacket66) Name() string { return "NewPooledTransactionHashes" }
+func (*NewPooledTransactionHashesPacket66) Kind() byte { return NewPooledTransactionHashesMsg }
+
+func (*NewPooledTransactionHashesPacket68) Name() string { return "NewPooledTransactionHashes" }
+func (*NewPooledTransactionHashesPacket68) Kind() byte { return NewPooledTransactionHashesMsg }
func (*GetPooledTransactionsPacket) Name() string { return "GetPooledTransactions" }
func (*GetPooledTransactionsPacket) Kind() byte { return GetPooledTransactionsMsg }
diff --git a/les/downloader/peer.go b/les/downloader/peer.go
index 15091c444fc2..ed80d12dfc3e 100644
--- a/les/downloader/peer.go
+++ b/les/downloader/peer.go
@@ -413,7 +413,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.BlockHeadersMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
@@ -425,7 +425,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.BlockBodiesMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
@@ -437,7 +437,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.ReceiptsMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
@@ -449,7 +449,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
throughput := func(p *peerConnection) int {
return p.rates.Capacity(eth.NodeDataMsg, time.Second)
}
- return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
+ return ps.idlePeers(eth.ETH66, eth.ETH68, idle, throughput)
}
// idlePeers retrieves a flat list of all currently idle peers satisfying the
diff --git a/params/version.go b/params/version.go
index 98b880c7a82f..77bb908849c5 100644
--- a/params/version.go
+++ b/params/version.go
@@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 6 // Minor version component of the current release
- VersionPatch = 0 // Patch version component of the current release
+ VersionPatch = 1 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)
diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go
index e85d942ee056..303af2379dda 100644
--- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go
+++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go
@@ -83,6 +83,7 @@ func Fuzz(input []byte) int {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
+ nil,
clock, rand,
)
f.Start()
@@ -116,6 +117,8 @@ func Fuzz(input []byte) int {
var (
announceIdxs = make([]int, announce)
announces = make([]common.Hash, announce)
+ types = make([]byte, announce)
+ sizes = make([]uint32, announce)
)
for i := 0; i < len(announces); i++ {
annBuf := make([]byte, 2)
@@ -124,11 +127,13 @@ func Fuzz(input []byte) int {
}
announceIdxs[i] = (int(annBuf[0])*256 + int(annBuf[1])) % len(txs)
announces[i] = txs[announceIdxs[i]].Hash()
+ types[i] = txs[announceIdxs[i]].Type()
+ sizes[i] = uint32(txs[announceIdxs[i]].Size())
}
if verbose {
fmt.Println("Notify", peer, announceIdxs)
}
- if err := f.Notify(peer, announces); err != nil {
+ if err := f.Notify(peer, types, sizes, announces); err != nil {
panic(err)
}