eth, eth/downloader: handle header requests, table driven proto tests
This commit is contained in:
@ -18,19 +18,16 @@ package eth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -40,8 +37,15 @@ func init() {
|
||||
|
||||
var testAccount = crypto.NewKey(rand.Reader)
|
||||
|
||||
func TestStatusMsgErrors(t *testing.T) {
|
||||
pm := newProtocolManagerForTesting(nil)
|
||||
// Tests that handshake failures are detected and reported correctly.
|
||||
func TestStatusMsgErrors60(t *testing.T) { testStatusMsgErrors(t, 60) }
|
||||
func TestStatusMsgErrors61(t *testing.T) { testStatusMsgErrors(t, 61) }
|
||||
func TestStatusMsgErrors62(t *testing.T) { testStatusMsgErrors(t, 62) }
|
||||
func TestStatusMsgErrors63(t *testing.T) { testStatusMsgErrors(t, 63) }
|
||||
func TestStatusMsgErrors64(t *testing.T) { testStatusMsgErrors(t, 64) }
|
||||
|
||||
func testStatusMsgErrors(t *testing.T, protocol int) {
|
||||
pm := newTestProtocolManager(0, nil, nil)
|
||||
td, currentBlock, genesis := pm.chainman.Status()
|
||||
defer pm.Stop()
|
||||
|
||||
@ -56,23 +60,23 @@ func TestStatusMsgErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{10, NetworkId, td, currentBlock, genesis},
|
||||
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= 0)"),
|
||||
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", protocol),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{uint32(ProtocolVersions[0]), 999, td, currentBlock, genesis},
|
||||
code: StatusMsg, data: statusData{uint32(protocol), 999, td, currentBlock, genesis},
|
||||
wantError: errResp(ErrNetworkIdMismatch, "999 (!= 1)"),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{uint32(ProtocolVersions[0]), NetworkId, td, currentBlock, common.Hash{3}},
|
||||
code: StatusMsg, data: statusData{uint32(protocol), NetworkId, td, currentBlock, common.Hash{3}},
|
||||
wantError: errResp(ErrGenesisBlockMismatch, "0300000000000000000000000000000000000000000000000000000000000000 (!= %x)", genesis),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
p, errc := newTestPeer(pm)
|
||||
p, errc := newTestPeer("peer", protocol, pm, false)
|
||||
// The send call might hang until reset because
|
||||
// the protocol might not read the payload.
|
||||
go p2p.Send(p, test.code, test.data)
|
||||
go p2p.Send(p.app, test.code, test.data)
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
@ -89,16 +93,21 @@ func TestStatusMsgErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// This test checks that received transactions are added to the local pool.
|
||||
func TestRecvTransactions(t *testing.T) {
|
||||
func TestRecvTransactions60(t *testing.T) { testRecvTransactions(t, 60) }
|
||||
func TestRecvTransactions61(t *testing.T) { testRecvTransactions(t, 61) }
|
||||
func TestRecvTransactions62(t *testing.T) { testRecvTransactions(t, 62) }
|
||||
func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) }
|
||||
func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) }
|
||||
|
||||
func testRecvTransactions(t *testing.T, protocol int) {
|
||||
txAdded := make(chan []*types.Transaction)
|
||||
pm := newProtocolManagerForTesting(txAdded)
|
||||
p, _ := newTestPeer(pm)
|
||||
pm := newTestProtocolManager(0, nil, txAdded)
|
||||
p, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer pm.Stop()
|
||||
defer p.close()
|
||||
p.handshake(t)
|
||||
|
||||
tx := newtx(testAccount, 0, 0)
|
||||
if err := p2p.Send(p, TxMsg, []interface{}{tx}); err != nil {
|
||||
tx := newTestTransaction(testAccount, 0, 0)
|
||||
if err := p2p.Send(p.app, TxMsg, []interface{}{tx}); err != nil {
|
||||
t.Fatalf("send error: %v", err)
|
||||
}
|
||||
select {
|
||||
@ -114,15 +123,21 @@ func TestRecvTransactions(t *testing.T) {
|
||||
}
|
||||
|
||||
// This test checks that pending transactions are sent.
|
||||
func TestSendTransactions(t *testing.T) {
|
||||
pm := newProtocolManagerForTesting(nil)
|
||||
func TestSendTransactions60(t *testing.T) { testSendTransactions(t, 60) }
|
||||
func TestSendTransactions61(t *testing.T) { testSendTransactions(t, 61) }
|
||||
func TestSendTransactions62(t *testing.T) { testSendTransactions(t, 62) }
|
||||
func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
|
||||
func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) }
|
||||
|
||||
func testSendTransactions(t *testing.T, protocol int) {
|
||||
pm := newTestProtocolManager(0, nil, nil)
|
||||
defer pm.Stop()
|
||||
|
||||
// Fill the pool with big transactions.
|
||||
const txsize = txsyncPackSize / 10
|
||||
alltxs := make([]*types.Transaction, 100)
|
||||
for nonce := range alltxs {
|
||||
alltxs[nonce] = newtx(testAccount, uint64(nonce), txsize)
|
||||
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize)
|
||||
}
|
||||
pm.txpool.AddTransactions(alltxs)
|
||||
|
||||
@ -137,7 +152,7 @@ func TestSendTransactions(t *testing.T) {
|
||||
}
|
||||
for n := 0; n < len(alltxs) && !t.Failed(); {
|
||||
var txs []*types.Transaction
|
||||
msg, err := p.ReadMsg()
|
||||
msg, err := p.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("%v: read error: %v", p.Peer, err)
|
||||
} else if msg.Code != TxMsg {
|
||||
@ -161,97 +176,53 @@ func TestSendTransactions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
p, _ := newTestPeer(pm)
|
||||
p.handshake(t)
|
||||
p, _ := newTestPeer(fmt.Sprintf("peer #%d", i), protocol, pm, true)
|
||||
wg.Add(1)
|
||||
go checktxs(p)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// testPeer wraps all peer-related data for tests.
|
||||
type testPeer struct {
|
||||
p2p.MsgReadWriter // writing to the test peer feeds the protocol
|
||||
pipe *p2p.MsgPipeRW // the protocol read/writes on this end
|
||||
pm *ProtocolManager
|
||||
*peer
|
||||
}
|
||||
|
||||
func newProtocolManagerForTesting(txAdded chan<- []*types.Transaction) *ProtocolManager {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
core.WriteTestNetGenesisBlock(db, 0)
|
||||
var (
|
||||
em = new(event.TypeMux)
|
||||
chain, _ = core.NewChainManager(db, core.FakePow{}, em)
|
||||
txpool = &fakeTxPool{added: txAdded}
|
||||
pm = NewProtocolManager(NetworkId, em, txpool, core.FakePow{}, chain)
|
||||
)
|
||||
pm.Start()
|
||||
return pm
|
||||
}
|
||||
|
||||
func newTestPeer(pm *ProtocolManager) (*testPeer, <-chan error) {
|
||||
var id discover.NodeID
|
||||
rand.Read(id[:])
|
||||
rw1, rw2 := p2p.MsgPipe()
|
||||
peer := pm.newPeer(pm.protVer, pm.netId, p2p.NewPeer(id, "test peer", nil), rw2)
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
pm.newPeerCh <- peer
|
||||
errc <- pm.handle(peer)
|
||||
}()
|
||||
return &testPeer{rw1, rw2, pm, peer}, errc
|
||||
}
|
||||
|
||||
func (p *testPeer) handshake(t *testing.T) {
|
||||
td, currentBlock, genesis := p.pm.chainman.Status()
|
||||
msg := &statusData{
|
||||
ProtocolVersion: uint32(p.pm.protVer),
|
||||
NetworkId: uint32(p.pm.netId),
|
||||
TD: td,
|
||||
CurrentBlock: currentBlock,
|
||||
GenesisBlock: genesis,
|
||||
// Tests that the custom union field encoder and decoder works correctly.
|
||||
func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
|
||||
// Create a "random" hash for testing
|
||||
var hash common.Hash
|
||||
for i, _ := range hash {
|
||||
hash[i] = byte(i)
|
||||
}
|
||||
if err := p2p.ExpectMsg(p, StatusMsg, msg); err != nil {
|
||||
t.Fatalf("status recv: %v", err)
|
||||
// Assemble some table driven tests
|
||||
tests := []struct {
|
||||
packet *getBlockHeadersData
|
||||
fail bool
|
||||
}{
|
||||
// Providing the origin as either a hash or a number should both work
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}}},
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}}},
|
||||
|
||||
// Providing arbitrary query field should also work
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},
|
||||
|
||||
// Providing both the origin hash and origin number must fail
|
||||
{fail: true, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash, Number: 314}}},
|
||||
}
|
||||
if err := p2p.Send(p, StatusMsg, msg); err != nil {
|
||||
t.Fatalf("status send: %v", err)
|
||||
// Iterate over each of the tests and try to encode and then decode
|
||||
for i, tt := range tests {
|
||||
bytes, err := rlp.EncodeToBytes(tt.packet)
|
||||
if err != nil && !tt.fail {
|
||||
t.Fatalf("test %d: failed to encode packet: %v", i, err)
|
||||
} else if err == nil && tt.fail {
|
||||
t.Fatalf("test %d: encode should have failed", i)
|
||||
}
|
||||
if !tt.fail {
|
||||
packet := new(getBlockHeadersData)
|
||||
if err := rlp.DecodeBytes(bytes, packet); err != nil {
|
||||
t.Fatalf("test %d: failed to decode packet: %v", i, err)
|
||||
}
|
||||
if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
|
||||
packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
|
||||
t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *testPeer) close() {
|
||||
p.pipe.Close()
|
||||
}
|
||||
|
||||
type fakeTxPool struct {
|
||||
// all transactions are collected.
|
||||
mu sync.Mutex
|
||||
all []*types.Transaction
|
||||
// if added is non-nil, it receives added transactions.
|
||||
added chan<- []*types.Transaction
|
||||
}
|
||||
|
||||
func (pool *fakeTxPool) AddTransactions(txs []*types.Transaction) {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
pool.all = append(pool.all, txs...)
|
||||
if pool.added != nil {
|
||||
pool.added <- txs
|
||||
}
|
||||
}
|
||||
|
||||
func (pool *fakeTxPool) GetTransactions() types.Transactions {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
txs := make([]*types.Transaction, len(pool.all))
|
||||
copy(txs, pool.all)
|
||||
return types.Transactions(txs)
|
||||
}
|
||||
|
||||
func newtx(from *crypto.Key, nonce uint64, datasize int) *types.Transaction {
|
||||
data := make([]byte, datasize)
|
||||
tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), big.NewInt(100000), big.NewInt(0), data)
|
||||
tx, _ = tx.SignECDSA(from.PrivateKey)
|
||||
return tx
|
||||
}
|
||||
|
Reference in New Issue
Block a user