• Felix Lange's avatar
    eth, p2p: remove EncodeMsg from p2p.MsgWriter · eb0e7b1b
    Felix Lange authored
    ...and make it a top-level function instead.
    
    The original idea behind having EncodeMsg in the interface was that
    implementations might be able to encode RLP data to their underlying
    writer directly instead of buffering the encoded data. The encoder
    will buffer anyway, so that doesn't matter anymore.
    
    Given the recent problems with EncodeMsg (copy-pasted implementation
    bug) I'd rather implement once, correctly.
    eb0e7b1b
protocol_test.go 6.81 KB
package eth

import (
	"bytes"
	"io"
	"log"
	"math/big"
	"os"
	"testing"
	"time"

	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethutil"
	ethlogger "github.com/ethereum/go-ethereum/logger"
	"github.com/ethereum/go-ethereum/p2p"
)

var sys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel))

type testMsgReadWriter struct {
	in  chan p2p.Msg
	out []p2p.Msg
}

func (self *testMsgReadWriter) In(msg p2p.Msg) {
	self.in <- msg
}

func (self *testMsgReadWriter) Out() (msg p2p.Msg, ok bool) {
	if len(self.out) > 0 {
		msg = self.out[0]
		self.out = self.out[1:]
		ok = true
	}
	return
}

func (self *testMsgReadWriter) WriteMsg(msg p2p.Msg) error {
	self.out = append(self.out, msg)
	return nil
}

func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) {
	msg, ok := <-self.in
	if !ok {
		return msg, io.EOF
	}
	return msg, nil
}

type testTxPool struct {
	getTransactions func() []*types.Transaction
	addTransactions func(txs []*types.Transaction)
}

type testChainManager struct {
	getBlockHashes func(hash []byte, amount uint64) (hashes [][]byte)
	getBlock       func(hash []byte) *types.Block
	status         func() (td *big.Int, currentBlock []byte, genesisBlock []byte)
}

type testBlockPool struct {
	addBlockHashes func(next func() ([]byte, bool), peerId string)
	addBlock       func(block *types.Block, peerId string) (err error)
	addPeer        func(td *big.Int, currentBlock []byte, peerId string, requestHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) (best bool)
	removePeer     func(peerId string)
}

// func (self *testTxPool) GetTransactions() (txs []*types.Transaction) {
// 	if self.getTransactions != nil {
// 		txs = self.getTransactions()
// 	}
// 	return
// }

func (self *testTxPool) AddTransactions(txs []*types.Transaction) {
	if self.addTransactions != nil {
		self.addTransactions(txs)
	}
}

func (self *testChainManager) GetBlockHashesFromHash(hash []byte, amount uint64) (hashes [][]byte) {
	if self.getBlockHashes != nil {
		hashes = self.getBlockHashes(hash, amount)
	}
	return
}

func (self *testChainManager) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) {
	if self.status != nil {
		td, currentBlock, genesisBlock = self.status()
	}
	return
}

func (self *testChainManager) GetBlock(hash []byte) (block *types.Block) {
	if self.getBlock != nil {
		block = self.getBlock(hash)
	}
	return
}

func (self *testBlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) {
	if self.addBlockHashes != nil {
		self.addBlockHashes(next, peerId)
	}
}

func (self *testBlockPool) AddBlock(block *types.Block, peerId string) {
	if self.addBlock != nil {
		self.addBlock(block, peerId)
	}
}

func (self *testBlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) (best bool) {
	if self.addPeer != nil {
		best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, peerError)
	}
	return
}

func (self *testBlockPool) RemovePeer(peerId string) {
	if self.removePeer != nil {
		self.removePeer(peerId)
	}
}

// TODO: refactor this into p2p/client_identity
type peerId struct {
	pubkey []byte
}

func (self *peerId) String() string {
	return "test peer"
}

func (self *peerId) Pubkey() (pubkey []byte) {
	pubkey = self.pubkey
	if len(pubkey) == 0 {
		pubkey = crypto.GenerateNewKeyPair().PublicKey
		self.pubkey = pubkey
	}
	return
}

func testPeer() *p2p.Peer {
	return p2p.NewPeer(&peerId{}, []p2p.Cap{})
}

type ethProtocolTester struct {
	quit         chan error
	rw           *testMsgReadWriter // p2p.MsgReadWriter
	txPool       *testTxPool        // txPool
	chainManager *testChainManager  // chainManager
	blockPool    *testBlockPool     // blockPool
	t            *testing.T
}

func newEth(t *testing.T) *ethProtocolTester {
	return &ethProtocolTester{
		quit:         make(chan error),
		rw:           &testMsgReadWriter{in: make(chan p2p.Msg, 10)},
		txPool:       &testTxPool{},
		chainManager: &testChainManager{},
		blockPool:    &testBlockPool{},
		t:            t,
	}
}

func (self *ethProtocolTester) reset() {
	self.rw = &testMsgReadWriter{in: make(chan p2p.Msg, 10)}
	self.quit = make(chan error)
}

func (self *ethProtocolTester) checkError(expCode int, delay time.Duration) (err error) {
	var timer = time.After(delay)
	select {
	case err = <-self.quit:
	case <-timer:
		self.t.Errorf("no error after %v, expected %v", delay, expCode)
		return
	}
	perr, ok := err.(*protocolError)
	if ok && perr != nil {
		if code := perr.Code; code != expCode {
			self.t.Errorf("expected protocol error (code %v), got %v (%v)", expCode, code, err)
		}
	} else {
		self.t.Errorf("expected protocol error (code %v), got %v", expCode, err)
	}
	return
}

func (self *ethProtocolTester) In(msg p2p.Msg) {
	self.rw.In(msg)
}

func (self *ethProtocolTester) Out() (p2p.Msg, bool) {
	return self.rw.Out()
}

func (self *ethProtocolTester) checkMsg(i int, code uint64, val interface{}) (msg p2p.Msg) {
	if i >= len(self.rw.out) {
		self.t.Errorf("expected at least %v msgs, got %v", i, len(self.rw.out))
		return
	}
	msg = self.rw.out[i]
	if msg.Code != code {
		self.t.Errorf("expected msg code %v, got %v", code, msg.Code)
	}
	if val != nil {
		if err := msg.Decode(val); err != nil {
			self.t.Errorf("rlp encoding error: %v", err)
		}
	}
	return
}

func (self *ethProtocolTester) run() {
	err := runEthProtocol(self.txPool, self.chainManager, self.blockPool, testPeer(), self.rw)
	self.quit <- err
}

func TestStatusMsgErrors(t *testing.T) {
	logInit()
	eth := newEth(t)
	td := ethutil.Big1
	currentBlock := []byte{1}
	genesis := []byte{2}
	eth.chainManager.status = func() (*big.Int, []byte, []byte) { return td, currentBlock, genesis }
	go eth.run()
	statusMsg := p2p.NewMsg(4)
	eth.In(statusMsg)
	delay := 1 * time.Second
	eth.checkError(ErrNoStatusMsg, delay)
	var status statusMsgData
	eth.checkMsg(0, StatusMsg, &status) // first outgoing msg should be StatusMsg
	if status.TD.Cmp(td) != 0 ||
		status.ProtocolVersion != ProtocolVersion ||
		status.NetworkId != NetworkId ||
		status.TD.Cmp(td) != 0 ||
		bytes.Compare(status.CurrentBlock, currentBlock) != 0 ||
		bytes.Compare(status.GenesisBlock, genesis) != 0 {
		t.Errorf("incorrect outgoing status")
	}

	eth.reset()
	go eth.run()
	statusMsg = p2p.NewMsg(0, uint32(48), uint32(0), td, currentBlock, genesis)
	eth.In(statusMsg)
	eth.checkError(ErrProtocolVersionMismatch, delay)

	eth.reset()
	go eth.run()
	statusMsg = p2p.NewMsg(0, uint32(49), uint32(1), td, currentBlock, genesis)
	eth.In(statusMsg)
	eth.checkError(ErrNetworkIdMismatch, delay)

	eth.reset()
	go eth.run()
	statusMsg = p2p.NewMsg(0, uint32(49), uint32(0), td, currentBlock, []byte{3})
	eth.In(statusMsg)
	eth.checkError(ErrGenesisBlockMismatch, delay)

}