From d55d6a5c199ac40ffb4dec0dc25922f04e46f88e Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:24:46 +0000 Subject: [PATCH 01/31] fix protocol error message memoization --- eth/error.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/eth/error.go b/eth/error.go index d1daad5750..1d9f806380 100644 --- a/eth/error.go +++ b/eth/error.go @@ -52,18 +52,17 @@ func ProtocolError(code int, format string, params ...interface{}) (err *protoco } func (self protocolError) Error() (message string) { - message = self.message - if message == "" { - message, ok := errorToString[self.Code] + if len(message) == 0 { + var ok bool + self.message, ok = errorToString[self.Code] if !ok { panic("invalid error code") } if self.format != "" { - message += ": " + fmt.Sprintf(self.format, self.params...) + self.message += ": " + fmt.Sprintf(self.format, self.params...) } - self.message = message } - return + return self.message } func (self *protocolError) Fatal() bool { From 52706be77b3f7daf76b7316076423549043996c1 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:27:06 +0000 Subject: [PATCH 02/31] ProtocolError -> self.protoError --- eth/protocol.go | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 963d417940..047c351e77 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -117,7 +117,7 @@ func (self *ethProtocol) handle() error { return err } if msg.Size > ProtocolMaxMsgSize { - return ProtocolError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) + return self.protoError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) } // make sure that the payload has been fully consumed defer msg.Discard() @@ -125,20 +125,20 @@ func (self *ethProtocol) handle() error { switch msg.Code { case StatusMsg: - return ProtocolError(ErrExtraStatusMsg, "") + return self.protoError(ErrExtraStatusMsg, "") case TxMsg: // TODO: rework using lazy RLP stream var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } self.txPool.AddTransactions(txs) case GetBlockHashesMsg: var request getBlockHashesMsgData if err := msg.Decode(&request); err != nil { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) @@ -156,13 +156,13 @@ func (self *ethProtocol) handle() error { } self.blockPool.AddBlockHashes(iter, self.id) if err != nil && err != rlp.EOL { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } case GetBlocksMsg: var blockHashes [][]byte if err := msg.Decode(&blockHashes); err != nil { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) var blocks []interface{} @@ -185,7 +185,7 @@ func (self *ethProtocol) handle() error { if err == rlp.EOL { break } else { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } } self.blockPool.AddBlock(block, self.id) @@ -194,7 +194,7 @@ func (self *ethProtocol) handle() error { case NewBlockMsg: var request newBlockMsgData if err := msg.Decode(&request); err != nil { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } hash := request.Block.Hash() // to simplify backend interface adding a new block @@ -215,7 +215,7 @@ func (self *ethProtocol) handle() error { } default: - return ProtocolError(ErrInvalidMsgCode, "%v", msg.Code) + return self.protoError(ErrInvalidMsgCode, "%v", msg.Code) } return nil } @@ -253,36 +253,35 @@ func (self *ethProtocol) handleStatus() error { } if msg.Code != StatusMsg { - return ProtocolError(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) + return self.protoError(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) } if msg.Size > ProtocolMaxMsgSize { - return ProtocolError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) + return self.protoError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) } var status statusMsgData if err := msg.Decode(&status); err != nil { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "%v", err) } _, _, genesisBlock := self.chainManager.Status() if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { - return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) + return self.protoError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) } if status.NetworkId != NetworkId { - return ProtocolError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, NetworkId) + return self.protoError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, NetworkId) } if ProtocolVersion != status.ProtocolVersion { - return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) + return self.protoError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) } self.peer.Infof("Peer is [eth] capable (%d/%d). TD=%v H=%x\n", status.ProtocolVersion, status.NetworkId, status.TD, status.CurrentBlock[:4]) - //self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) - self.peer.Infoln("AddPeer(IGNORED)") + self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) return nil } @@ -300,9 +299,10 @@ func (self *ethProtocol) requestBlocks(hashes [][]byte) error { func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { err = ProtocolError(code, format, params...) if err.Fatal() { - self.peer.Errorln(err) + self.peer.Errorln("err %v", err) + // disconnect } else { - self.peer.Debugln(err) + self.peer.Debugf("fyi %v", err) } return } @@ -310,10 +310,10 @@ func (self *ethProtocol) protoError(code int, format string, params ...interface func (self *ethProtocol) protoErrorDisconnect(code int, format string, params ...interface{}) { err := ProtocolError(code, format, params...) if err.Fatal() { - self.peer.Errorln(err) + self.peer.Errorln("err %v", err) // disconnect } else { - self.peer.Debugln(err) + self.peer.Debugf("fyi %v", err) } } From 98f98f9fd8994e7b0ba63970c6292e5f4a1bdefd Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:28:51 +0000 Subject: [PATCH 03/31] add status msg error tests, improve test setup --- eth/protocol_test.go | 334 ++++++++++++++++++++++++------------------- 1 file changed, 186 insertions(+), 148 deletions(-) diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 322aec7b70..81926322d8 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -1,30 +1,43 @@ 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" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" ) +var sys = logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(logger.DebugDetailLevel)) + type testMsgReadWriter struct { in chan p2p.Msg - out chan p2p.Msg + out []p2p.Msg } func (self *testMsgReadWriter) In(msg p2p.Msg) { self.in <- msg } -func (self *testMsgReadWriter) Out(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 <- msg + self.out = append(self.out, msg) return nil } @@ -40,145 +53,83 @@ func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { return msg, nil } -func errorCheck(t *testing.T, expCode int, err error) { - perr, ok := err.(*protocolError) - if ok && perr != nil { - if code := perr.Code; code != expCode { - ok = false - } - } - if !ok { - t.Errorf("expected error code %v, got %v", ErrNoStatusMsg, err) - } -} - -type TestBackend struct { +type testTxPool struct { getTransactions func() []*types.Transaction addTransactions func(txs []*types.Transaction) - getBlockHashes func(hash []byte, amount uint32) (hashes [][]byte) - addBlockHashes func(next func() ([]byte, bool), peerId string) - getBlock func(hash []byte) *types.Block - 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, invalidBlock func(error)) (best bool) - removePeer func(peerId string) - status func() (td *big.Int, currentBlock []byte, genesisBlock []byte) } -func (self *TestBackend) GetTransactions() (txs []*types.Transaction) { - if self.getTransactions != nil { - txs = self.getTransactions() - } - return +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) } -func (self *TestBackend) AddTransactions(txs []*types.Transaction) { +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 *TestBackend) GetBlockHashes(hash []byte, amount uint32) (hashes [][]byte) { +func (self *testChainManager) GetBlockHashesFromHash(hash []byte, amount uint64) (hashes [][]byte) { if self.getBlockHashes != nil { hashes = self.getBlockHashes(hash, amount) } return } -<<<<<<< HEAD -<<<<<<< HEAD -func (self *TestBackend) AddBlockHashes(next func() ([]byte, bool), peerId string) { - if self.addBlockHashes != nil { - self.addBlockHashes(next, peerId) - } -} - -======= -func (self *TestBackend) AddHash(hash []byte, peer *p2p.Peer) (more bool) { - if self.addHash != nil { - more = self.addHash(hash, peer) -======= -func (self *TestBackend) AddBlockHashes(next func() ([]byte, bool), peerId string) { - if self.addBlockHashes != nil { - self.addBlockHashes(next, peerId) ->>>>>>> eth protocol changes - } -} -<<<<<<< HEAD ->>>>>>> initial commit for eth-p2p integration -======= - ->>>>>>> eth protocol changes -func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { - if self.getBlock != nil { - block = self.getBlock(hash) - } - return -} - -<<<<<<< HEAD -<<<<<<< HEAD -func (self *TestBackend) AddBlock(block *types.Block, peerId string) (err error) { - if self.addBlock != nil { - err = self.addBlock(block, peerId) -======= -func (self *TestBackend) AddBlock(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) { - if self.addBlock != nil { - fetchHashes, err = self.addBlock(td, block, peer) ->>>>>>> initial commit for eth-p2p integration -======= -func (self *TestBackend) AddBlock(block *types.Block, peerId string) (err error) { - if self.addBlock != nil { - err = self.addBlock(block, peerId) ->>>>>>> eth protocol changes - } - return -} - -<<<<<<< HEAD -<<<<<<< HEAD -func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) { - if self.addPeer != nil { - best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, invalidBlock) -======= -func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) { - if self.addPeer != nil { - fetchHashes = self.addPeer(td, currentBlock, peer) ->>>>>>> initial commit for eth-p2p integration -======= -func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) { - if self.addPeer != nil { - best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, invalidBlock) ->>>>>>> eth protocol changes - } - return -} - -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes -func (self *TestBackend) RemovePeer(peerId string) { - if self.removePeer != nil { - self.removePeer(peerId) - } -} - -<<<<<<< HEAD -======= ->>>>>>> initial commit for eth-p2p integration -======= ->>>>>>> eth protocol changes -func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { +func (self *testChainManager) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { if self.status != nil { td, currentBlock, genesisBlock = self.status() } return } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes +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 @@ -201,32 +152,119 @@ func testPeer() *p2p.Peer { return p2p.NewPeer(&peerId{}, []p2p.Cap{}) } -func TestErrNoStatusMsg(t *testing.T) { -<<<<<<< HEAD -======= -func TestEth(t *testing.T) { ->>>>>>> initial commit for eth-p2p integration -======= ->>>>>>> eth protocol changes - quit := make(chan bool) - rw := &testMsgReadWriter{make(chan p2p.Msg, 10), make(chan p2p.Msg, 10)} - testBackend := &TestBackend{} - var err error - go func() { -<<<<<<< HEAD -<<<<<<< HEAD - err = runEthProtocol(testBackend, testPeer(), rw) -======= - err = runEthProtocol(testBackend, nil, rw) ->>>>>>> initial commit for eth-p2p integration -======= - err = runEthProtocol(testBackend, testPeer(), rw) ->>>>>>> eth protocol changes - close(quit) - }() - statusMsg := p2p.NewMsg(4) - rw.In(statusMsg) - <-quit - errorCheck(t, ErrNoStatusMsg, err) - // read(t, remote, []byte("hello, world"), nil) +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 ðProtocolTester{ + 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) { + logger.AddLogSystem(sys) + 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) + } From 7a09ad7763cb054cbae9c06d5aeae1bef502bd93 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:29:28 +0000 Subject: [PATCH 04/31] logger rename --- eth/backend.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 383cda46f4..2ca1430d8c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" - ethlogger "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/rpc" @@ -19,7 +19,7 @@ const ( seedNodeAddress = "poc-7.ethdev.com:30300" ) -var logger = ethlogger.NewLogger("SERV") +var ethlogger = logger.NewLogger("SERV") type Ethereum struct { // Channel for shutting down the ethereum @@ -174,20 +174,20 @@ func (s *Ethereum) Start(seed bool) error { // TODO: read peers here if seed { - logger.Infof("Connect to seed node %v", seedNodeAddress) + ethlogger.Infof("Connect to seed node %v", seedNodeAddress) if err := s.SuggestPeer(seedNodeAddress); err != nil { return err } } - logger.Infoln("Server started") + ethlogger.Infoln("Server started") return nil } func (self *Ethereum) SuggestPeer(addr string) error { netaddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { - logger.Errorf("couldn't resolve %s:", addr, err) + ethlogger.Errorf("couldn't resolve %s:", addr, err) return err } @@ -212,7 +212,7 @@ func (s *Ethereum) Stop() { s.blockPool.Stop() s.whisper.Stop() - logger.Infoln("Server stopped") + ethlogger.Infoln("Server stopped") close(s.shutdownChan) } From 9b203faa23462009b2d2d2391dd44d33150d51bd Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:31:13 +0000 Subject: [PATCH 05/31] major rewrite and simplification using minimal locking. add many new tests, test comments --- eth/block_pool.go | 1350 +++++++++++++++++++++------------------- eth/block_pool_test.go | 932 +++++++++++++++++++++++---- 2 files changed, 1523 insertions(+), 759 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index 7cfbc63f86..65d58ab022 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -1,6 +1,7 @@ package eth import ( + "fmt" "math" "math/big" "math/rand" @@ -10,46 +11,54 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - ethlogger "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/pow" ) -var poolLogger = ethlogger.NewLogger("Blockpool") +var poolLogger = logger.NewLogger("Blockpool") const ( blockHashesBatchSize = 256 blockBatchSize = 64 - blocksRequestInterval = 10 // seconds + blocksRequestInterval = 500 // ms blocksRequestRepetition = 1 - blockHashesRequestInterval = 10 // seconds - blocksRequestMaxIdleRounds = 10 + blockHashesRequestInterval = 500 // ms + blocksRequestMaxIdleRounds = 100 cacheTimeout = 3 // minutes blockTimeout = 5 // minutes ) type poolNode struct { - lock sync.RWMutex - hash []byte - block *types.Block - child *poolNode - parent *poolNode - section *section - knownParent bool - peer string - source string - complete bool + lock sync.RWMutex + hash []byte + td *big.Int + block *types.Block + parent *poolNode + peer string + blockBy string +} + +type poolEntry struct { + node *poolNode + section *section + index int } type BlockPool struct { - lock sync.RWMutex - pool map[string]*poolNode + lock sync.RWMutex + chainLock sync.RWMutex + + pool map[string]*poolEntry peersLock sync.RWMutex peers map[string]*peerInfo peer *peerInfo quit chan bool + purgeC chan bool + flushC chan bool wg sync.WaitGroup + procWg sync.WaitGroup running bool // the minimal interface with blockchain @@ -70,8 +79,23 @@ type peerInfo struct { peerError func(int, string, ...interface{}) sections map[string]*section - roots []*poolNode - quitC chan bool + + quitC chan bool +} + +// structure to store long range links on chain to skip along +type section struct { + lock sync.RWMutex + parent *section + child *section + top *poolNode + bottom *poolNode + nodes []*poolNode + controlC chan bool + suicideC chan bool + blockChainC chan bool + forkC chan chan bool + off bool } func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(pow.Block) bool, @@ -92,7 +116,9 @@ func (self *BlockPool) Start() { } self.running = true self.quit = make(chan bool) - self.pool = make(map[string]*poolNode) + self.flushC = make(chan bool) + self.pool = make(map[string]*poolEntry) + self.lock.Unlock() self.peersLock.Lock() @@ -110,20 +136,70 @@ func (self *BlockPool) Stop() { return } self.running = false + self.lock.Unlock() poolLogger.Infoln("Stopping") close(self.quit) - self.lock.Lock() + self.wg.Wait() + self.peersLock.Lock() self.peers = nil - self.pool = nil self.peer = nil - self.wg.Wait() - self.lock.Unlock() self.peersLock.Unlock() + + self.lock.Lock() + self.pool = nil + self.lock.Unlock() + poolLogger.Infoln("Stopped") +} + +func (self *BlockPool) Purge() { + self.lock.Lock() + if !self.running { + self.lock.Unlock() + return + } + self.lock.Unlock() + + poolLogger.Infoln("Purging...") + + close(self.purgeC) + self.wg.Wait() + + self.purgeC = make(chan bool) + + poolLogger.Infoln("Stopped") + +} + +func (self *BlockPool) Wait(t time.Duration) { + self.lock.Lock() + if !self.running { + self.lock.Unlock() + return + } + self.lock.Unlock() + + poolLogger.Infoln("waiting for processes to complete...") + close(self.flushC) + w := make(chan bool) + go func() { + self.procWg.Wait() + close(w) + }() + + select { + case <-w: + case <-time.After(t): + poolLogger.Debugf("completion timeout") + } + + self.flushC = make(chan bool) + + poolLogger.Infoln("processes complete") } @@ -131,29 +207,48 @@ func (self *BlockPool) Stop() { // the status message has been received with total difficulty and current block hash // AddPeer can only be used once, RemovePeer needs to be called when the peer disconnects func (self *BlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) bool { + self.peersLock.Lock() defer self.peersLock.Unlock() - if self.peers[peerId] != nil { - panic("peer already added") + peer, ok := self.peers[peerId] + if ok { + poolLogger.Debugf("update peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) + peer.td = td + peer.currentBlock = currentBlock + } else { + peer = &peerInfo{ + td: td, + currentBlock: currentBlock, + id: peerId, //peer.Identity().Pubkey() + requestBlockHashes: requestBlockHashes, + requestBlocks: requestBlocks, + peerError: peerError, + sections: make(map[string]*section), + } + self.peers[peerId] = peer + poolLogger.Debugf("add new peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) } - peer := &peerInfo{ - td: td, - currentBlock: currentBlock, - id: peerId, //peer.Identity().Pubkey() - requestBlockHashes: requestBlockHashes, - requestBlocks: requestBlocks, - peerError: peerError, + // check peer current head + if self.hasBlock(currentBlock) { + // peer not ahead + return false } - self.peers[peerId] = peer - poolLogger.Debugf("add new peer %v with td %v", peerId, td) + + if self.peer == peer { + // new block update + // peer is already active best peer, request hashes + poolLogger.Debugf("[%s] already the best peer. request hashes from %s", peerId, name(currentBlock)) + peer.requestBlockHashes(currentBlock) + return true + } + currentTD := ethutil.Big0 if self.peer != nil { currentTD = self.peer.td } if td.Cmp(currentTD) > 0 { - self.peer.stop(peer) - peer.start(self.peer) - poolLogger.Debugf("peer %v promoted to best peer", peerId) + poolLogger.Debugf("peer %v promoted best peer", peerId) + self.switchPeer(self.peer, peer) self.peer = peer return true } @@ -164,15 +259,15 @@ func (self *BlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string, func (self *BlockPool) RemovePeer(peerId string) { self.peersLock.Lock() defer self.peersLock.Unlock() - peer := self.peers[peerId] - if peer == nil { + peer, ok := self.peers[peerId] + if !ok { return } - self.peers[peerId] = nil - poolLogger.Debugf("remove peer %v", peerId[0:4]) + delete(self.peers, peerId) + poolLogger.Debugf("remove peer %v", peerId) // if current best peer is removed, need find a better one - if self.peer != nil && peerId == self.peer.id { + if self.peer == peer { var newPeer *peerInfo max := ethutil.Big0 // peer with the highest self-acclaimed TD is chosen @@ -182,16 +277,35 @@ func (self *BlockPool) RemovePeer(peerId string) { newPeer = info } } - self.peer.stop(peer) - peer.start(self.peer) + self.peer = newPeer + self.switchPeer(peer, newPeer) if newPeer != nil { - poolLogger.Debugf("peer %v with td %v promoted to best peer", newPeer.id[0:4], newPeer.td) + poolLogger.Infof("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td) } else { poolLogger.Warnln("no peers left") } } } +func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { + if newPeer != nil { + entry := self.get(newPeer.currentBlock) + if entry == nil { + poolLogger.Debugf("[%s] head block [%s] not found, requesting hashes", newPeer.id, name(newPeer.currentBlock)) + newPeer.requestBlockHashes(newPeer.currentBlock) + } else { + poolLogger.Debugf("[%s] head block [%s] found, activate chain at section [%s]", newPeer.id, name(newPeer.currentBlock), sectionName(entry.section)) + self.activateChain(entry.section, newPeer) + } + } + if oldPeer != nil { + oldPeer.stop(newPeer) + } + if newPeer != nil { + newPeer.start(oldPeer) + } +} + // Entry point for eth protocol to add block hashes received via BlockHashesMsg // only hashes from the best peer is handled // this method is always responsible to initiate further hash requests until @@ -206,160 +320,259 @@ func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) return } // peer is still the best + poolLogger.Debugf("adding hashes for best peer %s", peerId) - var child *poolNode - var depth int - - // iterate using next (rlp stream lazy decoder) feeding hashesC self.wg.Add(1) + self.procWg.Add(1) + go func() { - for { + var size, n int + var hash []byte + var ok bool = true + var section, child, parent *section + var entry *poolEntry + var nodes []*poolNode + + LOOP: + // iterate using next (rlp stream lazy decoder) feeding hashesC + for hash, ok = next(); ok; hash, ok = next() { + n++ select { case <-self.quit: - return + break LOOP case <-peer.quitC: // if the peer is demoted, no more hashes taken - break + break LOOP default: - hash, ok := next() - if !ok { - // message consumed chain skeleton built - break - } - // check if known block connecting the downloaded chain to our blockchain - if self.hasBlock(hash) { - poolLogger.Infof("known block (%x...)\n", hash[0:4]) - if child != nil { - child.Lock() - // mark child as absolute pool root with parent known to blockchain - child.knownParent = true - child.Unlock() - } - break - } - // - var parent *poolNode - // look up node in pool - parent = self.get(hash) - if parent != nil { - // reached a known chain in the pool - // request blocks on the newly added part of the chain - if child != nil { - self.link(parent, child) - - // activate the current chain - self.activateChain(parent, peer, true) - poolLogger.Debugf("potential chain of %v blocks added, reached blockpool, activate chain", depth) - break - } - // if this is the first hash, we expect to find it - parent.RLock() - grandParent := parent.parent - parent.RUnlock() - if grandParent != nil { - // activate the current chain - self.activateChain(parent, peer, true) - poolLogger.Debugf("block hash found, activate chain") - break - } - // the first node is the root of a chain in the pool, rejoice and continue - } - // if node does not exist, create it and index in the pool - section := §ion{} - if child == nil { - section.top = parent - } - parent = &poolNode{ - hash: hash, - child: child, - section: section, - peer: peerId, - } - self.set(hash, parent) - poolLogger.Debugf("create potential block for %x...", hash[0:4]) - - depth++ - child = parent } + if self.hasBlock(hash) { + // check if known block connecting the downloaded chain to our blockchain + poolLogger.Debugf("[%s] known block", name(hash)) + // mark child as absolute pool root with parent known to blockchain + if section != nil { + self.connectToBlockChain(section) + } else { + if child != nil { + self.connectToBlockChain(child) + } + } + break LOOP + } + // look up node in pool + entry = self.get(hash) + if entry != nil { + poolLogger.Debugf("[%s] found block", name(hash)) + // reached a known chain in the pool + if entry.node == entry.section.bottom && n == 1 { + // the first block hash received is an orphan in the pool, so rejoice and continue + poolLogger.Debugf("[%s] first hash is orphan block, keep building", name(hash)) + child = entry.section + continue LOOP + } + poolLogger.Debugf("[%s] reached blockpool chain", name(hash)) + parent = entry.section + break LOOP + } + // if node for block hash does not exist, create it and index in the pool + poolLogger.Debugf("[%s] create node %v", name(hash), size) + node := &poolNode{ + hash: hash, + peer: peerId, + } + if size == 0 { + section = newSection() + } + nodes = append(nodes, node) + size++ + } //for + + self.chainLock.Lock() + poolLogger.Debugf("lock chain lock") + + poolLogger.Debugf("read %v hashes added by %s", n, peerId) + + if parent != nil && entry != nil && entry.node != parent.top { + poolLogger.Debugf("[%s] fork section", sectionName(parent)) + parent.controlC <- false + waiter := make(chan bool) + parent.forkC <- waiter + chain := parent.nodes + parent.nodes = chain[entry.index:] + parent.top = parent.nodes[0] + orphan := newSection() + self.link(orphan, parent.child) + self.processSection(orphan, chain[0:entry.index]) + orphan.controlC <- false + close(waiter) } - if child != nil { - poolLogger.Debugf("chain of %v hashes added", depth) - // start a processSection on the last node, but switch off asking - // hashes and blocks until next peer confirms this chain - section := self.processSection(child) - peer.addSection(child.hash, section) - section.start() + + if size > 0 { + self.processSection(section, nodes) + poolLogger.Debugf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(section), size, sectionName(child)) + self.link(parent, section) + self.link(section, child) + } else { + poolLogger.Debugf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child)) + self.link(parent, child) } + + self.chainLock.Unlock() + poolLogger.Debugf("[%s] unlock chain lock", sectionName(section)) + + if parent != nil { + poolLogger.Debugf("[%s] activating parent chain [%s]...", name(parent.top.hash), sectionName(parent)) + self.activateChain(parent, peer) + poolLogger.Debugf("[%s] activated parent chain [%s]. done", name(parent.top.hash), sectionName(parent)) + } + + if section != nil { + poolLogger.Debugf("[%s] activate new section process", sectionName(section)) + peer.addSection(section.top.hash, section) + section.controlC <- true + } + self.procWg.Done() + self.wg.Done() + }() } +func name(hash []byte) (name string) { + if hash == nil { + name = "" + } else { + name = fmt.Sprintf("%x", hash[:4]) + } + return +} + +func sectionName(section *section) (name string) { + if section == nil { + name = "" + } else { + name = fmt.Sprintf("%x-%x", section.bottom.hash[:4], section.top.hash[:4]) + } + return +} + // AddBlock is the entry point for the eth protocol when blockmsg is received upon requests // It has a strict interpretation of the protocol in that if the block received has not been requested, it results in an error (which can be ignored) // block is checked for PoW // only the first PoW-valid block for a hash is considered legit func (self *BlockPool) AddBlock(block *types.Block, peerId string) { hash := block.Hash() - node := self.get(hash) - node.RLock() - b := node.block - node.RUnlock() - if b != nil { + poolLogger.Debugf("adding block [%s] by peer %s", name(hash), peerId) + if self.hasBlock(hash) { + poolLogger.Debugf("block [%s] already known", name(hash)) return } - if node == nil && !self.hasBlock(hash) { + entry := self.get(hash) + if entry == nil { + poolLogger.Debugf("unrequested block [%x] by peer %s", hash, peerId) self.peerError(peerId, ErrUnrequestedBlock, "%x", hash) return } + + node := entry.node + node.lock.Lock() + defer node.lock.Unlock() + poolLogger.Debugf("adding block [%s] by peer %s", name(hash), peerId) + + // check if block already present + if node.block != nil { + poolLogger.Debugf("block [%x] already sent by %s", hash, node.blockBy) + return + } + // validate block for PoW if !self.verifyPoW(block) { + poolLogger.Debugf("invalid pow on block [%x] by peer %s", hash, peerId) self.peerError(peerId, ErrInvalidPoW, "%x", hash) + return } - node.Lock() + + poolLogger.Debugf("added block [%s] by peer %s", name(hash), peerId) node.block = block - node.source = peerId - node.Unlock() + node.blockBy = peerId + } -// iterates down a known poolchain and activates fetching processes -// on each chain section for the peer -// stops if the peer is demoted -// registers last section root as root for the peer (in case peer is promoted a second time, to remember) -func (self *BlockPool) activateChain(node *poolNode, peer *peerInfo, on bool) { - self.wg.Add(1) - go func() { - for { - node.sectionRLock() - bottom := node.section.bottom - if bottom == nil { // the chain section is being created or killed - break - } - // register this section with the peer - if peer != nil { - peer.addSection(bottom.hash, bottom.section) - if on { - bottom.section.start() - } else { - bottom.section.start() - } - } - if bottom.parent == nil { - node = bottom - break - } - // if peer demoted stop activation - select { - case <-peer.quitC: - break - default: - } +func (self *BlockPool) connectToBlockChain(section *section) { + section.lock.RLock() + poolLogger.Debugf("connect to blockchain...") + defer section.lock.RUnlock() + if section.off { + self.addSectionToBlockChain(section) + } else { + close(section.blockChainC) + } + poolLogger.Debugf("connect to blockchain done") +} - node = bottom.parent - bottom.sectionRUnlock() +func (self *BlockPool) addSectionToBlockChain(section *section) (rest int, err error) { + + var blocks types.Blocks + var node *poolNode + var keys []string + rest = len(section.nodes) + for rest > 0 { + rest-- + node = section.nodes[rest] + node.lock.RLock() + block := node.block + node.lock.RUnlock() + if block == nil { + break } - // remember root for this peer - peer.addRoot(node) - self.wg.Done() - }() + keys = append(keys, string(node.hash)) + blocks = append(blocks, block) + } + + self.lock.Lock() + for _, key := range keys { + delete(self.pool, key) + } + self.lock.Unlock() + + poolLogger.Debugf("insert %v blocks into blockchain", len(blocks)) + err = self.insertChain(blocks) + if err != nil { + // TODO: not clear which peer we need to address + // peerError should dispatch to peer if still connected and disconnect + self.peerError(node.blockBy, ErrInvalidBlock, "%v", err) + poolLogger.Debugf("invalid block %x", node.hash) + poolLogger.Debugf("penalise peers %v (hash), %v (block)", node.peer, node.blockBy) + // penalise peer in node.blockBy + // self.disconnect() + } + return +} + +func (self *BlockPool) activateChain(section *section, peer *peerInfo) { + poolLogger.Debugf("[%s] activate known chain for peer %s", sectionName(section), peer.id) + i := 0 +LOOP: + for section != nil { + // register this section with the peer + poolLogger.Debugf("[%s] register section with peer %s", sectionName(section), peer.id) + peer.addSection(section.top.hash, section) + poolLogger.Debugf("[%s] activate section process", sectionName(section)) + section.controlC <- true + i++ + // section.lock.RLock() + // parent := section.parent + // section.lock.RUnlock() + // section = parent + poolLogger.Debugf(" before") + section = self.getParent(section) + poolLogger.Debugf(" after") + select { + case <-peer.quitC: + break LOOP + case <-self.quit: + break LOOP + default: + } + } } // main worker thread on each section in the poolchain @@ -370,261 +583,325 @@ func (self *BlockPool) activateChain(node *poolNode, peer *peerInfo, on bool) { // - when turned off (if peer disconnects and new peer connects with alternative chain), no blockrequests are made but absolute expiry timer is ticking // - when turned back on it recursively calls itself on the root of the next chain section // - when exits, signals to -func (self *BlockPool) processSection(node *poolNode) *section { - // absolute time after which sub-chain is killed if not complete (some blocks are missing) - suicideTimer := time.After(blockTimeout * time.Minute) - var blocksRequestTimer, blockHashesRequestTimer <-chan time.Time - var nodeC, missingC, processC chan *poolNode - controlC := make(chan bool) - resetC := make(chan bool) - var hashes [][]byte - var i, total, missing, lastMissing, depth int - var blockHashesRequests, blocksRequests int - var idle int - var init, alarm, done, same, running, once bool - orignode := node - hash := node.hash +func (self *BlockPool) processSection(section *section, nodes []*poolNode) { - node.sectionLock() - defer node.sectionUnlock() - section := §ion{controlC: controlC, resetC: resetC} - node.section = section + for i, node := range nodes { + entry := &poolEntry{node: node, section: section, index: i} + self.set(node.hash, entry) + } + section.bottom = nodes[len(nodes)-1] + section.top = nodes[0] + section.nodes = nodes + poolLogger.Debugf("[%s] setup section process", sectionName(section)) + + self.wg.Add(1) go func() { - self.wg.Add(1) + + // absolute time after which sub-chain is killed if not complete (some blocks are missing) + suicideTimer := time.After(blockTimeout * time.Minute) + + var blocksRequestTimer, blockHashesRequestTimer <-chan time.Time + var blocksRequestTime, blockHashesRequestTime bool + var blocksRequests, blockHashesRequests int + var blocksRequestsComplete, blockHashesRequestsComplete bool + + // node channels for the section + var missingC, processC, offC chan *poolNode + // container for missing block hashes + var hashes [][]byte + + var i, total, missing, lastMissing, depth int + var idle int + var init, done, same, running, ready bool + var insertChain bool + + var blockChainC = section.blockChainC + + LOOP: for { - node.sectionRLock() - controlC = node.section.controlC - node.sectionRUnlock() - if init { - // missing blocks read from nodeC - // initialized section - if depth == 0 { - break + if insertChain { + insertChain = false + rest, err := self.addSectionToBlockChain(section) + if err != nil { + close(section.suicideC) + continue LOOP } - // enable select case to read missing block when ready - processC = missingC - missingC = make(chan *poolNode, lastMissing) - nodeC = nil - // only do once - init = false - } else { - if !once { - missingC = nil - processC = nil - i = 0 - total = 0 - lastMissing = 0 + if rest == 0 { + blocksRequestsComplete = true + child := self.getChild(section) + if child != nil { + self.connectToBlockChain(child) + } } } - // went through all blocks in section - if i != 0 && i == lastMissing { - if len(hashes) > 0 { - // send block requests to peers - self.requestBlocks(blocksRequests, hashes) - } - blocksRequests++ - poolLogger.Debugf("[%x] block request attempt %v: missing %v/%v/%v", hash[0:4], blocksRequests, missing, total, depth) - if missing == lastMissing { - // idle round - if same { - // more than once - idle++ - // too many idle rounds - if idle > blocksRequestMaxIdleRounds { - poolLogger.Debugf("[%x] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", hash[0:4], idle, blocksRequests, missing, total, depth) - self.killChain(node, nil) - break - } - } else { - idle = 0 - } - same = true + if blockHashesRequestsComplete && blocksRequestsComplete { + // not waiting for hashes any more + poolLogger.Debugf("[%s] section complete %v blocks retrieved (%v attempts), hash requests complete on root (%v attempts)", sectionName(section), depth, blocksRequests, blockHashesRequests) + break LOOP + } // otherwise suicide if no hashes coming + + if done { + // went through all blocks in section + if missing == 0 { + // no missing blocks + poolLogger.Debugf("[%s] got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + blocksRequestsComplete = true + blocksRequestTimer = nil + blocksRequestTime = false } else { - if missing == 0 { - // no missing nodes - poolLogger.Debugf("block request process complete on section %x... (%v total blocksRequests): missing %v/%v/%v", hash[0:4], blockHashesRequests, blocksRequests, missing, total, depth) - node.Lock() - orignode.complete = true - node.Unlock() - blocksRequestTimer = nil - if blockHashesRequestTimer == nil { - // not waiting for hashes any more - poolLogger.Debugf("hash request on root %x... successful (%v total attempts)\nquitting...", hash[0:4], blockHashesRequests) - break - } // otherwise suicide if no hashes coming + // some missing blocks + blocksRequests++ + poolLogger.Debugf("[%s] block request attempt %v: missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + if len(hashes) > 0 { + // send block requests to peers + self.requestBlocks(blocksRequests, hashes) + hashes = nil + } + poolLogger.Debugf("[%s] check if there is missing blocks", sectionName(section)) + if missing == lastMissing { + // idle round + if same { + // more than once + idle++ + // too many idle rounds + if idle >= blocksRequestMaxIdleRounds { + poolLogger.Debugf("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionName(section), idle, blocksRequests, missing, total, depth) + close(section.suicideC) + } + } else { + idle = 0 + } + same = true + } else { + same = false } - same = false } + poolLogger.Debugf("[%s] done checking missing blocks", sectionName(section)) lastMissing = missing - i = 0 - missing = 0 - // ready for next round - done = true - } - if done && alarm { - poolLogger.Debugf("start checking if new blocks arrived (attempt %v): missing %v/%v/%v", blocksRequests, missing, total, depth) - blocksRequestTimer = time.After(blocksRequestInterval * time.Second) - alarm = false + ready = true done = false - // processC supposed to be empty and never closed so just swap, no need to allocate - tempC := processC - processC = missingC - missingC = tempC + // save a new processC (blocks still missing) + offC = missingC + missingC = processC + // put processC offline + processC = nil + // poolLogger.Debugf("[%s] ready for round %v", sectionName(section), blocksRequests) } - select { - case <-self.quit: - break - case <-suicideTimer: - self.killChain(node, nil) - poolLogger.Warnf("[%x] timeout. (%v total attempts): missing %v/%v/%v", hash[0:4], blocksRequests, missing, total, depth) - break - case <-blocksRequestTimer: - alarm = true - case <-blockHashesRequestTimer: - orignode.RLock() - parent := orignode.parent - orignode.RUnlock() - if parent != nil { + // + + if ready && blocksRequestTime && !blocksRequestsComplete { + poolLogger.Debugf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + blocksRequestTimer = time.After(blocksRequestInterval * time.Millisecond) + blocksRequestTime = false + processC = offC + } + + if blockHashesRequestTime { + poolLogger.Debugf("[%s] hash request start", sectionName(section)) + if self.getParent(section) != nil { // if not root of chain, switch off - poolLogger.Debugf("[%x] parent found, hash requests deactivated (after %v total attempts)\n", hash[0:4], blockHashesRequests) + poolLogger.Debugf("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(section), blockHashesRequests) blockHashesRequestTimer = nil + blockHashesRequestsComplete = true } else { blockHashesRequests++ - poolLogger.Debugf("[%x] hash request on root (%v total attempts)\n", hash[0:4], blockHashesRequests) - self.requestBlockHashes(parent.hash) - blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Second) + poolLogger.Debugf("[%s] hash request on root (%v total attempts)\n", sectionName(section), blockHashesRequests) + self.requestBlockHashes(section.bottom.hash) + blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Millisecond) } - case r, ok := <-controlC: - if !ok { - break - } - if running && !r { - poolLogger.Debugf("process on section %x... (%v total attempts): missing %v/%v/%v", hash[0:4], blocksRequests, missing, total, depth) + blockHashesRequestTime = false + poolLogger.Debugf("[%s] hash request done", sectionName(section)) - alarm = false + } + + poolLogger.Debugf("[%s] select", sectionName(section)) + select { + + case <-self.quit: + break LOOP + + case <-self.purgeC: + suicideTimer = time.After(0) + + case <-suicideTimer: + close(section.suicideC) + poolLogger.Debugf("[%s] timeout. (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + + case <-section.suicideC: + poolLogger.Debugf("[%s] suicide", sectionName(section)) + + self.chainLock.Lock() + self.link(nil, section) + self.link(section, nil) + self.chainLock.Unlock() + self.lock.Lock() + for _, node := range section.nodes { + delete(self.pool, string(node.hash)) + } + self.lock.Unlock() + break LOOP + + case <-blocksRequestTimer: + poolLogger.Debugf("[%s] block request time again", sectionName(section)) + blocksRequestTime = true + + case <-blockHashesRequestTimer: + poolLogger.Debugf("[%s] hash request time again", sectionName(section)) + blockHashesRequestTime = true + + case r := <-section.controlC: + + if running && !r { + self.procWg.Done() + poolLogger.Debugf("[%s] idle mode", sectionName(section)) + if init { + poolLogger.Debugf("[%s] off (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + } + + running = false + blocksRequestTime = false blocksRequestTimer = nil + blockHashesRequestTime = false blockHashesRequestTimer = nil - processC = nil + if processC != nil { + offC = processC + processC = nil + } } if !running && r { - poolLogger.Debugf("[%x] on", hash[0:4]) + self.procWg.Add(1) + running = true - orignode.RLock() - parent := orignode.parent - complete := orignode.complete - knownParent := orignode.knownParent - orignode.RUnlock() - if !complete { - poolLogger.Debugf("[%x] activate block requests", hash[0:4]) - blocksRequestTimer = time.After(0) + poolLogger.Debugf("[%s] active mode", sectionName(section)) + poolLogger.Debugf("[%s] check if complete", sectionName(section)) + if !blocksRequestsComplete { + poolLogger.Debugf("[%s] activate block requests", sectionName(section)) + blocksRequestTime = true } - if parent == nil && !knownParent { - // if no parent but not connected to blockchain - poolLogger.Debugf("[%x] activate block hashes requests", hash[0:4]) - blockHashesRequestTimer = time.After(0) - } else { - blockHashesRequestTimer = nil + if !blockHashesRequestsComplete { + poolLogger.Debugf("[%s] activate block hashes requests", sectionName(section)) + blockHashesRequestTime = true } - alarm = true - processC = missingC - if !once { + if !init { // if not run at least once fully, launch iterator - processC = make(chan *poolNode) - missingC = make(chan *poolNode) - self.foldUp(orignode, processC) - once = true + processC = make(chan *poolNode, blockHashesBatchSize) + missingC = make(chan *poolNode, blockHashesBatchSize) + poolLogger.Debugf("[%s] initialise section", sectionName(section)) + i = 0 + missing = 0 + total = 0 + lastMissing = 0 + depth = 0 + self.wg.Add(1) + self.procWg.Add(1) + depth = len(section.nodes) + go func() { + var node *poolNode + IT: + for _, node = range section.nodes { + select { + case processC <- node: + case <-self.quit: + break IT + } + } + close(processC) + self.wg.Done() + self.procWg.Done() + }() + } else { + poolLogger.Debugf("[%s] restore earlier state", sectionName(section)) + processC = offC } } - total = lastMissing - case <-resetC: - once = false + + case waiter := <-section.forkC: + poolLogger.Debugf("[%s] locking for fork", sectionName(section)) + <-waiter + poolLogger.Debugf("[%s] unlocking for fork", sectionName(section)) init = false done = false + ready = false + case node, ok := <-processC: - if !ok { + if !ok && !init { // channel closed, first iteration finished init = true - once = true - continue + done = true + processC = make(chan *poolNode, missing) + + total = missing + + poolLogger.Debugf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, total, depth) + continue LOOP } + if ready { + i = 0 + missing = 0 + ready = false + } + poolLogger.Debugf("[%s] process node %v [%x]", sectionName(section), i, node.hash[:4]) i++ // if node has no block - node.RLock() + node.lock.RLock() block := node.block - nhash := node.hash - knownParent := node.knownParent - node.RUnlock() - if !init { - depth++ - } + node.lock.RUnlock() if block == nil { + poolLogger.Debugf("[%s] block missing on [%x]", sectionName(section), node.hash[:4]) missing++ - if !init { - total++ - } - hashes = append(hashes, nhash) + hashes = append(hashes, node.hash) if len(hashes) == blockBatchSize { + poolLogger.Debugf("[%s] request %v missing blocks", sectionName(section), len(hashes)) self.requestBlocks(blocksRequests, hashes) hashes = nil } missingC <- node } else { - // block is found - if knownParent { - // connected to the blockchain, insert the longest chain of blocks - var blocks types.Blocks - child := node - parent := node - node.sectionRLock() - for child != nil && child.block != nil { - parent = child - blocks = append(blocks, parent.block) - child = parent.child - } - node.sectionRUnlock() - poolLogger.Debugf("[%x] insert %v blocks into blockchain", hash[0:4], len(blocks)) - if err := self.insertChain(blocks); err != nil { - // TODO: not clear which peer we need to address - // peerError should dispatch to peer if still connected and disconnect - self.peerError(node.source, ErrInvalidBlock, "%v", err) - poolLogger.Debugf("invalid block %v", node.hash) - poolLogger.Debugf("penalise peers %v (hash), %v (block)", node.peer, node.source) - // penalise peer in node.source - self.killChain(node, nil) - // self.disconnect() - break - } - // if suceeded mark the next one (no block yet) as connected to blockchain - if child != nil { - child.Lock() - child.knownParent = true - child.Unlock() - } - // reset starting node to first node with missing block - orignode = child - // pop the inserted ancestors off the channel - for i := 1; i < len(blocks); i++ { - <-processC - } - // delink inserted chain section - self.killChain(node, parent) + if blockChainC == nil && i == lastMissing { + poolLogger.Debugf("[%s] insert blocks starting from [%s]", sectionName(section), name(node.hash)) + insertChain = true } } - } - } - poolLogger.Debugf("[%x] quit after\n%v block hashes requests\n%v block requests: missing %v/%v/%v", hash[0:4], blockHashesRequests, blocksRequests, missing, total, depth) + poolLogger.Debugf("[%s] %v/%v/%v/%v", sectionName(section), i, missing, total, depth) + if i == lastMissing { + poolLogger.Debugf("[%s] done", sectionName(section)) + done = true + } + + case <-blockChainC: + // closed blockChain channel indicates that the blockpool is reached + // connected to the blockchain, insert the longest chain of blocks + poolLogger.Debugf("[%s] reached blockchain", sectionName(section)) + blockChainC = nil + // switch off hash requests in case they were on + blockHashesRequestTime = false + blockHashesRequestTimer = nil + blockHashesRequestsComplete = true + // section root has block + if len(section.nodes) > 0 && section.nodes[len(section.nodes)-1].block != nil { + insertChain = true + } + continue LOOP + + } // select + } // for + poolLogger.Debugf("[%s] quit: %v block hashes requests - %v block requests - missing %v/%v/%v", sectionName(section), blockHashesRequests, blocksRequests, missing, total, depth) + + poolLogger.Debugf("[%s] process complete...", sectionName(section)) + section.lock.Lock() + section.off = true + section.lock.Unlock() + poolLogger.Debugf("[%s] process complete done", sectionName(section)) self.wg.Done() - node.sectionLock() - node.section.controlC = nil - node.sectionUnlock() - // this signals that controller not available + if running { + self.procWg.Done() + } }() - return section - + return } func (self *BlockPool) peerError(peerId string, code int, format string, params ...interface{}) { @@ -640,27 +917,31 @@ func (self *BlockPool) requestBlockHashes(hash []byte) { self.peersLock.Lock() defer self.peersLock.Unlock() if self.peer != nil { + poolLogger.Debugf("request hashes starting on %x from best peer %s", hash[:4], self.peer.id) self.peer.requestBlockHashes(hash) } } func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { // distribute block request among known peers + poolLogger.Debugf("request blocks") self.peersLock.Lock() defer self.peersLock.Unlock() peerCount := len(self.peers) // on first attempt use the best peer if attempts == 0 { + poolLogger.Debugf("request %v missing blocks from best peer %s", len(hashes), self.peer.id) self.peer.requestBlocks(hashes) return } repetitions := int(math.Min(float64(peerCount), float64(blocksRequestRepetition))) - poolLogger.Debugf("request %v missing blocks from %v/%v peers", len(hashes), repetitions, peerCount) i := 0 - indexes := rand.Perm(peerCount)[0:(repetitions - 1)] + indexes := rand.Perm(peerCount)[0:repetitions] sort.Ints(indexes) + poolLogger.Debugf("request %v missing blocks from %v/%v peers: chosen %v", len(hashes), repetitions, peerCount, indexes) for _, peer := range self.peers { if i == indexes[0] { + poolLogger.Debugf("request %v missing blocks from %s", len(hashes), peer.id) peer.requestBlocks(hashes) indexes = indexes[1:] if len(indexes) == 0 { @@ -669,6 +950,8 @@ func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { } i++ } + poolLogger.Debugf("done requesting blocks") + } func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { @@ -679,7 +962,7 @@ func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { } info, ok := self.peers[peerId] if !ok { - panic("unknown peer") + return nil, false } return info, false } @@ -687,30 +970,16 @@ func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { func (self *peerInfo) addSection(hash []byte, section *section) { self.lock.Lock() defer self.lock.Unlock() + poolLogger.Debugf("section process %s added to %s", sectionName(section), self.id) self.sections[string(hash)] = section } -func (self *peerInfo) addRoot(node *poolNode) { - self.lock.Lock() - defer self.lock.Unlock() - self.roots = append(self.roots, node) -} - // (re)starts processes registered for this peer (self) func (self *peerInfo) start(peer *peerInfo) { self.lock.Lock() defer self.lock.Unlock() self.quitC = make(chan bool) - for _, root := range self.roots { - root.sectionRLock() - if root.section.bottom != nil { - if root.parent == nil { - self.requestBlockHashes(root.hash) - } - } - root.sectionRUnlock() - } - self.roots = nil + poolLogger.Debugf("[%s] activate section processes", self.id) self.controlSections(peer, true) } @@ -719,6 +988,7 @@ func (self *peerInfo) stop(peer *peerInfo) { self.lock.RLock() defer self.lock.RUnlock() close(self.quitC) + poolLogger.Debugf("[%s] inactivate section processes", self.id) self.controlSections(peer, false) } @@ -727,289 +997,85 @@ func (self *peerInfo) controlSections(peer *peerInfo, on bool) { peer.lock.RLock() defer peer.lock.RUnlock() } - for hash, section := range peer.sections { - if section.done() { + + for hash, section := range self.sections { + + if section.off { + poolLogger.Debugf("[%s][%x] section process complete - remove", self.id, hash[:4]) delete(self.sections, hash) + continue } - _, exists := peer.sections[hash] - if on || peer == nil || exists { + var found bool + if peer != nil { + _, found = peer.sections[hash] + } + + // switch on processes not found in old peer + // and switch off processes not found in new peer + if !found { if on { // self is best peer - section.start() + poolLogger.Debugf("[%s][%s] section process -> active", self.id, sectionName(section)) } else { // (re)starts process without requests, only suicide timer - section.stop() + poolLogger.Debugf("[%s][%s] section process -> inactive", self.id, sectionName(section)) } + section.controlC <- on } } } -// called when parent is found in pool -// parent and child are guaranteed to be on different sections -func (self *BlockPool) link(parent, child *poolNode) { - var top bool - parent.sectionLock() - if child != nil { - child.sectionLock() - } - if parent == parent.section.top && parent.section.top != nil { - top = true - } - var bottom bool +func (self *BlockPool) getParent(sec *section) *section { + poolLogger.Debugf("[") + self.chainLock.RLock() + defer self.chainLock.RUnlock() + poolLogger.Debugf("]") + return sec.parent +} - if child == child.section.bottom { - bottom = true +func (self *BlockPool) getChild(sec *section) *section { + self.chainLock.RLock() + defer self.chainLock.RUnlock() + return sec.child +} + +func newSection() (sec *section) { + sec = §ion{ + controlC: make(chan bool, 1), + suicideC: make(chan bool, 1), + blockChainC: make(chan bool, 1), + forkC: make(chan chan bool), } - if parent.child != child { - orphan := parent.child - if orphan != nil { - // got a fork in the chain - if top { - orphan.lock.Lock() - // make old child orphan - orphan.parent = nil - orphan.lock.Unlock() - } else { // we are under section lock - // make old child orphan - orphan.parent = nil - // reset section objects above the fork - nchild := orphan.child - node := orphan - section := §ion{bottom: orphan} - for node.section == nchild.section { - node = nchild - node.section = section - nchild = node.child - } - section.top = node - // set up a suicide - self.processSection(orphan).stop() - } - } else { - // child is on top of a chain need to close section - child.section.bottom = child - } - // adopt new child + return +} + +func (self *BlockPool) link(parent *section, child *section) { + if parent != nil { + exChild := parent.child parent.child = child - if !top { - parent.section.top = parent - // restart section process so that shorter section is scanned for blocks - parent.section.reset() + if exChild != nil && exChild != child { + poolLogger.Debugf("[%s] FORK [%s] -> [%s]", sectionName(parent), sectionName(exChild), sectionName(child)) + exChild.parent = nil } } - if child != nil { - if child.parent != parent { - stepParent := child.parent - if stepParent != nil { - if bottom { - stepParent.Lock() - stepParent.child = nil - stepParent.Unlock() - } else { - // we are on the same section - // if it is a aberrant reverse fork, - stepParent.child = nil - node := stepParent - nparent := stepParent.child - section := §ion{top: stepParent} - for node.section == nparent.section { - node = nparent - node.section = section - node = node.parent - } - } - } else { - // linking to a root node, ie. parent is under the root of a chain - parent.section.top = parent - } + exParent := child.parent + if exParent != nil && exParent != parent { + poolLogger.Debugf("[%s] REV FORK [%s] -> [%s]", sectionName(child), sectionName(exParent), sectionName(parent)) + exParent.child = nil } child.parent = parent - child.section.bottom = child - } - // this needed if someone lied about the parent before - child.knownParent = false - - parent.sectionUnlock() - if child != nil { - child.sectionUnlock() } } -// this immediately kills the chain from node to end (inclusive) section by section -func (self *BlockPool) killChain(node *poolNode, end *poolNode) { - poolLogger.Debugf("kill chain section with root node %v", node) - - node.sectionLock() - node.section.abort() - self.set(node.hash, nil) - child := node.child - top := node.section.top - i := 1 - self.wg.Add(1) - go func() { - var quit bool - for node != top && node != end && child != nil { - node = child - select { - case <-self.quit: - quit = true - break - default: - } - self.set(node.hash, nil) - child = node.child - } - poolLogger.Debugf("killed chain section of %v blocks with root node %v", i, node) - if !quit { - if node == top { - if node != end && child != nil && end != nil { - // - self.killChain(child, end) - } - } else { - if child != nil { - // delink rest of this section if ended midsection - child.section.bottom = child - child.parent = nil - } - } - } - node.section.bottom = nil - node.sectionUnlock() - self.wg.Done() - }() -} - -// structure to store long range links on chain to skip along -type section struct { - lock sync.RWMutex - bottom *poolNode - top *poolNode - controlC chan bool - resetC chan bool -} - -func (self *section) start() { +func (self *BlockPool) get(hash []byte) (node *poolEntry) { self.lock.RLock() defer self.lock.RUnlock() - if self.controlC != nil { - self.controlC <- true - } -} - -func (self *section) stop() { - self.lock.RLock() - defer self.lock.RUnlock() - if self.controlC != nil { - self.controlC <- false - } -} - -func (self *section) reset() { - self.lock.RLock() - defer self.lock.RUnlock() - if self.controlC != nil { - self.resetC <- true - self.controlC <- false - } -} - -func (self *section) abort() { - self.lock.Lock() - defer self.lock.Unlock() - if self.controlC != nil { - close(self.controlC) - self.controlC = nil - } -} - -func (self *section) done() bool { - self.lock.Lock() - defer self.lock.Unlock() - if self.controlC != nil { - return true - } - return false -} - -func (self *BlockPool) get(hash []byte) (node *poolNode) { - self.lock.Lock() - defer self.lock.Unlock() return self.pool[string(hash)] } -func (self *BlockPool) set(hash []byte, node *poolNode) { +func (self *BlockPool) set(hash []byte, node *poolEntry) { self.lock.Lock() defer self.lock.Unlock() self.pool[string(hash)] = node } - -// first time for block request, this iteration retrieves nodes of the chain -// from node up to top (all the way if nil) via child links -// copies the controller -// and feeds nodeC channel -// this is performed under section readlock to prevent top from going away -// when -func (self *BlockPool) foldUp(node *poolNode, nodeC chan *poolNode) { - self.wg.Add(1) - go func() { - node.sectionRLock() - defer node.sectionRUnlock() - for node != nil { - select { - case <-self.quit: - break - case nodeC <- node: - if node == node.section.top { - break - } - node = node.child - } - } - close(nodeC) - self.wg.Done() - }() -} - -func (self *poolNode) Lock() { - self.sectionLock() - self.lock.Lock() -} - -func (self *poolNode) Unlock() { - self.lock.Unlock() - self.sectionUnlock() -} - -func (self *poolNode) RLock() { - self.lock.RLock() -} - -func (self *poolNode) RUnlock() { - self.lock.RUnlock() -} - -func (self *poolNode) sectionLock() { - self.lock.RLock() - defer self.lock.RUnlock() - self.section.lock.Lock() -} - -func (self *poolNode) sectionUnlock() { - self.lock.RLock() - defer self.lock.RUnlock() - self.section.lock.Unlock() -} - -func (self *poolNode) sectionRLock() { - self.lock.RLock() - defer self.lock.RUnlock() - self.section.lock.RLock() -} - -func (self *poolNode) sectionRUnlock() { - self.lock.RLock() - defer self.lock.RUnlock() - self.section.lock.RUnlock() -} diff --git a/eth/block_pool_test.go b/eth/block_pool_test.go index 315cc748db..09392a82be 100644 --- a/eth/block_pool_test.go +++ b/eth/block_pool_test.go @@ -1,115 +1,65 @@ package eth import ( - "bytes" "fmt" "log" + "math/big" "os" "sync" "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/logger" + "github.com/ethereum/go-ethereum/pow" ) -var sys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel)) +const waitTimeout = 60 // seconds -type testChainManager struct { - knownBlock func(hash []byte) bool - addBlock func(*types.Block) error - checkPoW func(*types.Block) bool -} +var logsys = logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(logger.DebugLevel)) -func (self *testChainManager) KnownBlock(hash []byte) bool { - if self.knownBlock != nil { - return self.knownBlock(hash) +var ini = false + +func logInit() { + if !ini { + logger.AddLogSystem(logsys) + ini = true } - return false } -func (self *testChainManager) AddBlock(block *types.Block) error { - if self.addBlock != nil { - return self.addBlock(block) - } - return nil -} - -func (self *testChainManager) CheckPoW(block *types.Block) bool { - if self.checkPoW != nil { - return self.checkPoW(block) - } - return false -} - -func knownBlock(hashes ...[]byte) (f func([]byte) bool) { - f = func(block []byte) bool { - for _, hash := range hashes { - if bytes.Compare(block, hash) == 0 { - return true - } - } +// test helpers +func arrayEq(a, b []int) bool { + if len(a) != len(b) { return false } - return -} - -func addBlock(hashes ...[]byte) (f func(*types.Block) error) { - f = func(block *types.Block) error { - for _, hash := range hashes { - if bytes.Compare(block.Hash(), hash) == 0 { - return fmt.Errorf("invalid by test") - } + for i := range a { + if a[i] != b[i] { + return false } - return nil - } - return -} - -func checkPoW(hashes ...[]byte) (f func(*types.Block) bool) { - f = func(block *types.Block) bool { - for _, hash := range hashes { - if bytes.Compare(block.Hash(), hash) == 0 { - return false - } - } - return true - } - return -} - -func newTestChainManager(knownBlocks [][]byte, invalidBlocks [][]byte, invalidPoW [][]byte) *testChainManager { - return &testChainManager{ - knownBlock: knownBlock(knownBlocks...), - addBlock: addBlock(invalidBlocks...), - checkPoW: checkPoW(invalidPoW...), } + return true } type intToHash map[int][]byte type hashToInt map[string]int +// hashPool is a test helper, that allows random hashes to be referred to by integers type testHashPool struct { intToHash hashToInt + lock sync.Mutex } func newHash(i int) []byte { return crypto.Sha3([]byte(string(i))) } -func newTestBlockPool(knownBlockIndexes []int, invalidBlockIndexes []int, invalidPoWIndexes []int) (hashPool *testHashPool, blockPool *BlockPool) { - hashPool = &testHashPool{make(intToHash), make(hashToInt)} - knownBlocks := hashPool.indexesToHashes(knownBlockIndexes) - invalidBlocks := hashPool.indexesToHashes(invalidBlockIndexes) - invalidPoW := hashPool.indexesToHashes(invalidPoWIndexes) - blockPool = NewBlockPool(newTestChainManager(knownBlocks, invalidBlocks, invalidPoW)) - return -} - func (self *testHashPool) indexesToHashes(indexes []int) (hashes [][]byte) { + self.lock.Lock() + defer self.lock.Unlock() for _, i := range indexes { hash, found := self.intToHash[i] if !found { @@ -123,6 +73,8 @@ func (self *testHashPool) indexesToHashes(indexes []int) (hashes [][]byte) { } func (self *testHashPool) hashesToIndexes(hashes [][]byte) (indexes []int) { + self.lock.Lock() + defer self.lock.Unlock() for _, hash := range hashes { i, found := self.hashToInt[string(hash)] if !found { @@ -133,66 +85,812 @@ func (self *testHashPool) hashesToIndexes(hashes [][]byte) (indexes []int) { return } -type protocolChecker struct { +// test blockChain is an integer trie +type blockChain map[int][]int + +// blockPoolTester provides the interface between tests and a blockPool +// +// refBlockChain is used to guide which blocks will be accepted as valid +// blockChain gives the current state of the blockchain and +// accumulates inserts so that we can check the resulting chain +type blockPoolTester struct { + hashPool *testHashPool + lock sync.RWMutex + refBlockChain blockChain + blockChain blockChain + blockPool *BlockPool + t *testing.T +} + +func newTestBlockPool(t *testing.T) (hashPool *testHashPool, blockPool *BlockPool, b *blockPoolTester) { + hashPool = &testHashPool{intToHash: make(intToHash), hashToInt: make(hashToInt)} + b = &blockPoolTester{ + t: t, + hashPool: hashPool, + blockChain: make(blockChain), + refBlockChain: make(blockChain), + } + b.blockPool = NewBlockPool(b.hasBlock, b.insertChain, b.verifyPoW) + blockPool = b.blockPool + return +} + +func (self *blockPoolTester) Errorf(format string, params ...interface{}) { + fmt.Printf(format+"\n", params...) + self.t.Errorf(format, params...) +} + +// blockPoolTester implements the 3 callbacks needed by the blockPool: +// hasBlock, insetChain, verifyPoW +func (self *blockPoolTester) hasBlock(block []byte) (ok bool) { + self.lock.RLock() + defer self.lock.RUnlock() + indexes := self.hashPool.hashesToIndexes([][]byte{block}) + i := indexes[0] + _, ok = self.blockChain[i] + fmt.Printf("has block %v (%x...): %v\n", i, block[0:4], ok) + return +} + +func (self *blockPoolTester) insertChain(blocks types.Blocks) error { + self.lock.RLock() + defer self.lock.RUnlock() + var parent, child int + var children, refChildren []int + var ok bool + for _, block := range blocks { + child = self.hashPool.hashesToIndexes([][]byte{block.Hash()})[0] + _, ok = self.blockChain[child] + if ok { + fmt.Printf("block %v already in blockchain\n", child) + continue // already in chain + } + parent = self.hashPool.hashesToIndexes([][]byte{block.ParentHeaderHash})[0] + children, ok = self.blockChain[parent] + if !ok { + return fmt.Errorf("parent %v not in blockchain ", parent) + } + ok = false + var found bool + refChildren, found = self.refBlockChain[parent] + if found { + for _, c := range refChildren { + if c == child { + ok = true + } + } + if !ok { + return fmt.Errorf("invalid block %v", child) + } + } else { + ok = true + } + if ok { + // accept any blocks if parent not in refBlockChain + fmt.Errorf("blockchain insert %v -> %v\n", parent, child) + self.blockChain[parent] = append(children, child) + self.blockChain[child] = nil + } + } + return nil +} + +func (self *blockPoolTester) verifyPoW(pblock pow.Block) bool { + return true +} + +// test helper that compares the resulting blockChain to the desired blockChain +func (self *blockPoolTester) checkBlockChain(blockChain map[int][]int) { + for k, v := range self.blockChain { + fmt.Printf("got: %v -> %v\n", k, v) + } + for k, v := range blockChain { + fmt.Printf("expected: %v -> %v\n", k, v) + } + if len(blockChain) != len(self.blockChain) { + self.Errorf("blockchain incorrect (zlength differ)") + } + for k, v := range blockChain { + vv, ok := self.blockChain[k] + if !ok || !arrayEq(v, vv) { + self.Errorf("blockchain incorrect on %v -> %v (!= %v)", k, vv, v) + } + } +} + +// + +// peerTester provides the peer callbacks for the blockPool +// it registers actual callbacks so that result can be compared to desired behaviour +// provides helper functions to mock the protocol calls to the blockPool +type peerTester struct { blockHashesRequests []int blocksRequests [][]int - invalidBlocks []error + blocksRequestsMap map[int]bool + peerErrors []int + blockPool *BlockPool hashPool *testHashPool - lock sync.Mutex + lock sync.RWMutex + id string + td int + currentBlock int + t *testing.T } +// peerTester constructor takes hashPool and blockPool from the blockPoolTester +func (self *blockPoolTester) newPeer(id string, td int, cb int) *peerTester { + return &peerTester{ + id: id, + td: td, + currentBlock: cb, + hashPool: self.hashPool, + blockPool: self.blockPool, + t: self.t, + blocksRequestsMap: make(map[int]bool), + } +} + +func (self *peerTester) Errorf(format string, params ...interface{}) { + fmt.Printf(format+"\n", params...) + self.t.Errorf(format, params...) +} + +// helper to compare actual and expected block requests +func (self *peerTester) checkBlocksRequests(blocksRequests ...[]int) { + if len(blocksRequests) > len(self.blocksRequests) { + self.Errorf("blocks requests incorrect (length differ)\ngot %v\nexpected %v", self.blocksRequests, blocksRequests) + } else { + for i, rr := range blocksRequests { + r := self.blocksRequests[i] + if !arrayEq(r, rr) { + self.Errorf("blocks requests incorrect\ngot %v\nexpected %v", self.blocksRequests, blocksRequests) + } + } + } +} + +// helper to compare actual and expected block hash requests +func (self *peerTester) checkBlockHashesRequests(blocksHashesRequests ...int) { + rr := blocksHashesRequests + self.lock.RLock() + r := self.blockHashesRequests + self.lock.RUnlock() + if len(r) != len(rr) { + self.Errorf("block hashes requests incorrect (length differ)\ngot %v\nexpected %v", r, rr) + } else { + if !arrayEq(r, rr) { + self.Errorf("block hashes requests incorrect\ngot %v\nexpected %v", r, rr) + } + } +} + +// waiter function used by peer.AddBlocks +// blocking until requests appear +// since block requests are sent to any random peers +// block request map is shared between peers +// times out after a period +func (self *peerTester) waitBlocksRequests(blocksRequest ...int) { + timeout := time.After(waitTimeout * time.Second) + rr := blocksRequest + for { + self.lock.RLock() + r := self.blocksRequestsMap + fmt.Printf("[%s] blocks request check %v (%v)\n", self.id, rr, r) + i := 0 + for i = 0; i < len(rr); i++ { + _, ok := r[rr[i]] + if !ok { + break + } + } + self.lock.RUnlock() + + if i == len(rr) { + return + } + time.Sleep(100 * time.Millisecond) + select { + case <-timeout: + default: + } + } +} + +// waiter function used by peer.AddBlockHashes +// blocking until requests appear +// times out after a period +func (self *peerTester) waitBlockHashesRequests(blocksHashesRequest int) { + timeout := time.After(waitTimeout * time.Second) + rr := blocksHashesRequest + for i := 0; ; { + self.lock.RLock() + r := self.blockHashesRequests + self.lock.RUnlock() + fmt.Printf("[%s] block hash request check %v (%v)\n", self.id, rr, r) + for ; i < len(r); i++ { + if rr == r[i] { + return + } + } + time.Sleep(100 * time.Millisecond) + select { + case <-timeout: + default: + } + } +} + +// mocks a simple blockchain 0 (genesis) ... n (head) +func (self *blockPoolTester) initRefBlockChain(n int) { + for i := 0; i < n; i++ { + self.refBlockChain[i] = []int{i + 1} + } +} + +// peerTester functions that mimic protocol calls to the blockpool +// registers the peer with the blockPool +func (self *peerTester) AddPeer() bool { + hash := self.hashPool.indexesToHashes([]int{self.currentBlock})[0] + return self.blockPool.AddPeer(big.NewInt(int64(self.td)), hash, self.id, self.requestBlockHashes, self.requestBlocks, self.peerError) +} + +// peer sends blockhashes if and when gets a request +func (self *peerTester) AddBlockHashes(indexes ...int) { + i := 0 + fmt.Printf("ready to add block hashes %v\n", indexes) + + self.waitBlockHashesRequests(indexes[0]) + fmt.Printf("adding block hashes %v\n", indexes) + hashes := self.hashPool.indexesToHashes(indexes) + next := func() (hash []byte, ok bool) { + if i < len(hashes) { + hash = hashes[i] + ok = true + i++ + } + return + } + self.blockPool.AddBlockHashes(next, self.id) +} + +// peer sends blocks if and when there is a request +// (in the shared request store, not necessarily to a person) +func (self *peerTester) AddBlocks(indexes ...int) { + hashes := self.hashPool.indexesToHashes(indexes) + fmt.Printf("ready to add blocks %v\n", indexes[1:]) + self.waitBlocksRequests(indexes[1:]...) + fmt.Printf("adding blocks %v \n", indexes[1:]) + for i := 1; i < len(hashes); i++ { + fmt.Printf("adding block %v %x\n", indexes[i], hashes[i][:4]) + self.blockPool.AddBlock(&types.Block{HeaderHash: ethutil.Bytes(hashes[i]), ParentHeaderHash: ethutil.Bytes(hashes[i-1])}, self.id) + } +} + +// peer callbacks // -1 is special: not found (a hash never seen) -func (self *protocolChecker) requestBlockHashesCallBack() (requestBlockHashesCallBack func([]byte) error) { - requestBlockHashesCallBack = func(hash []byte) error { - indexes := self.hashPool.hashesToIndexes([][]byte{hash}) - self.lock.Lock() - defer self.lock.Unlock() - self.blockHashesRequests = append(self.blockHashesRequests, indexes[0]) - return nil - } - return +// records block hashes requests by the blockPool +func (self *peerTester) requestBlockHashes(hash []byte) error { + indexes := self.hashPool.hashesToIndexes([][]byte{hash}) + fmt.Printf("[%s] blocks hash request %v %x\n", self.id, indexes[0], hash[:4]) + self.lock.Lock() + defer self.lock.Unlock() + self.blockHashesRequests = append(self.blockHashesRequests, indexes[0]) + return nil } -func (self *protocolChecker) requestBlocksCallBack() (requestBlocksCallBack func([][]byte) error) { - requestBlocksCallBack = func(hashes [][]byte) error { - indexes := self.hashPool.hashesToIndexes(hashes) - self.lock.Lock() - defer self.lock.Unlock() - self.blocksRequests = append(self.blocksRequests, indexes) - return nil +// records block requests by the blockPool +func (self *peerTester) requestBlocks(hashes [][]byte) error { + indexes := self.hashPool.hashesToIndexes(hashes) + fmt.Printf("blocks request %v %x...\n", indexes, hashes[0][:4]) + self.lock.Lock() + defer self.lock.Unlock() + self.blocksRequests = append(self.blocksRequests, indexes) + for _, i := range indexes { + self.blocksRequestsMap[i] = true } - return + return nil } -func (self *protocolChecker) invalidBlockCallBack() (invalidBlockCallBack func(error)) { - invalidBlockCallBack = func(err error) { - self.invalidBlocks = append(self.invalidBlocks, err) - } - return +// records the error codes of all the peerErrors found the blockPool +func (self *peerTester) peerError(code int, format string, params ...interface{}) { + self.peerErrors = append(self.peerErrors, code) } +// the actual tests func TestAddPeer(t *testing.T) { - ethlogger.AddLogSystem(sys) - knownBlockIndexes := []int{0, 1} - invalidBlockIndexes := []int{2, 3} - invalidPoWIndexes := []int{4, 5} - hashPool, blockPool := newTestBlockPool(knownBlockIndexes, invalidBlockIndexes, invalidPoWIndexes) - // TODO: - // hashPool, blockPool, blockChainChecker = newTestBlockPool(knownBlockIndexes, invalidBlockIndexes, invalidPoWIndexes) - peer0 := &protocolChecker{ - // blockHashesRequests: make([]int), - // blocksRequests: make([][]int), - // invalidBlocks: make([]error), - hashPool: hashPool, - } - best := blockPool.AddPeer(ethutil.Big1, newHash(100), "0", - peer0.requestBlockHashesCallBack(), - peer0.requestBlocksCallBack(), - peer0.invalidBlockCallBack(), - ) + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + peer0 := blockPoolTester.newPeer("peer0", 1, 0) + peer1 := blockPoolTester.newPeer("peer1", 2, 1) + peer2 := blockPoolTester.newPeer("peer2", 3, 2) + var peer *peerInfo + + blockPool.Start() + + // pool + best := peer0.AddPeer() if !best { - t.Errorf("peer not accepted as best") + t.Errorf("peer0 (TD=1) not accepted as best") } + if blockPool.peer.id != "peer0" { + t.Errorf("peer0 (TD=1) not set as best") + } + peer0.checkBlockHashesRequests(0) + + best = peer2.AddPeer() + if !best { + t.Errorf("peer2 (TD=3) not accepted as best") + } + if blockPool.peer.id != "peer2" { + t.Errorf("peer2 (TD=3) not set as best") + } + peer2.checkBlockHashesRequests(2) + + best = peer1.AddPeer() + if best { + t.Errorf("peer1 (TD=2) accepted as best") + } + if blockPool.peer.id != "peer2" { + t.Errorf("peer2 (TD=3) not set any more as best") + } + if blockPool.peer.td.Cmp(big.NewInt(int64(3))) != 0 { + t.Errorf("peer1 TD not set") + } + + peer2.td = 4 + peer2.currentBlock = 3 + best = peer2.AddPeer() + if !best { + t.Errorf("peer2 (TD=4) not accepted as best") + } + if blockPool.peer.id != "peer2" { + t.Errorf("peer2 (TD=4) not set as best") + } + if blockPool.peer.td.Cmp(big.NewInt(int64(4))) != 0 { + t.Errorf("peer2 TD not updated") + } + peer2.checkBlockHashesRequests(2, 3) + + peer1.td = 3 + peer1.currentBlock = 2 + best = peer1.AddPeer() + if best { + t.Errorf("peer1 (TD=3) should not be set as best") + } + if blockPool.peer.id == "peer1" { + t.Errorf("peer1 (TD=3) should not be set as best") + } + peer, best = blockPool.getPeer("peer1") + if peer.td.Cmp(big.NewInt(int64(3))) != 0 { + t.Errorf("peer1 TD should be updated") + } + + blockPool.RemovePeer("peer2") + peer, best = blockPool.getPeer("peer2") + if peer != nil { + t.Errorf("peer2 not removed") + } + + if blockPool.peer.id != "peer1" { + t.Errorf("existing peer1 (TD=3) should be set as best peer") + } + peer1.checkBlockHashesRequests(2) + + blockPool.RemovePeer("peer1") + peer, best = blockPool.getPeer("peer1") + if peer != nil { + t.Errorf("peer1 not removed") + } + + if blockPool.peer.id != "peer0" { + t.Errorf("existing peer0 (TD=1) should be set as best peer") + } + + blockPool.RemovePeer("peer0") + peer, best = blockPool.getPeer("peer0") + if peer != nil { + t.Errorf("peer1 not removed") + } + + // adding back earlier peer ok + peer0.currentBlock = 3 + best = peer0.AddPeer() + if !best { + t.Errorf("peer0 (TD=1) should be set as best") + } + + if blockPool.peer.id != "peer0" { + t.Errorf("peer0 (TD=1) should be set as best") + } + peer0.checkBlockHashesRequests(0, 0, 3) + blockPool.Stop() } + +func TestPeerWithKnownBlock(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.refBlockChain[0] = nil + blockPoolTester.blockChain[0] = nil + // hashPool, blockPool, blockPoolTester := newTestBlockPool() + blockPool.Start() + + peer0 := blockPoolTester.newPeer("0", 1, 0) + peer0.AddPeer() + + blockPool.Stop() + // no request on known block + peer0.checkBlockHashesRequests() +} + +func TestSimpleChain(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(2) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 2) + peer1.AddPeer() + go peer1.AddBlockHashes(2, 1, 0) + peer1.AddBlocks(0, 1, 2) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[2] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestInvalidBlock(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(2) + blockPoolTester.refBlockChain[2] = []int{} + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 3) + peer1.AddPeer() + go peer1.AddBlockHashes(3, 2, 1, 0) + peer1.AddBlocks(0, 1, 2, 3) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[2] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) + if len(peer1.peerErrors) == 1 { + if peer1.peerErrors[0] != ErrInvalidBlock { + t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidBlock) + } + } else { + t.Errorf("expected invalid block error, got nothing") + } +} + +func TestVerifyPoW(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(3) + first := false + blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool { + bb, _ := b.(*types.Block) + indexes := blockPoolTester.hashPool.hashesToIndexes([][]byte{bb.Hash()}) + if indexes[0] == 1 && !first { + first = true + return false + } else { + return true + } + + } + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 2) + peer1.AddPeer() + go peer1.AddBlockHashes(2, 1, 0) + peer1.AddBlocks(0, 1, 2) + peer1.AddBlocks(0, 1) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[2] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) + if len(peer1.peerErrors) == 1 { + if peer1.peerErrors[0] != ErrInvalidPoW { + t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidPoW) + } + } else { + t.Errorf("expected invalid pow error, got nothing") + } +} + +func TestMultiSectionChain(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(5) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 5) + + peer1.AddPeer() + go peer1.AddBlockHashes(5, 4, 3) + go peer1.AddBlocks(2, 3, 4, 5) + go peer1.AddBlockHashes(3, 2, 1, 0) + peer1.AddBlocks(0, 1, 2) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[5] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestNewBlocksOnPartialChain(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(7) + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 5) + + peer1.AddPeer() + go peer1.AddBlockHashes(5, 4, 3) + peer1.AddBlocks(2, 3) // partially complete section + // peer1 found new blocks + peer1.td = 2 + peer1.currentBlock = 7 + peer1.AddPeer() + go peer1.AddBlockHashes(7, 6, 5) + go peer1.AddBlocks(3, 4, 5, 6, 7) + go peer1.AddBlockHashes(3, 2, 1, 0) // tests that hash request from known chain root is remembered + peer1.AddBlocks(0, 1, 2) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[7] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestPeerSwitch(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(6) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 5) + peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer1.AddPeer() + go peer1.AddBlockHashes(5, 4, 3) + peer1.AddBlocks(2, 3) // section partially complete, block 3 will be preserved after peer demoted + peer2.AddPeer() // peer2 is promoted as best peer, peer1 is demoted + go peer2.AddBlockHashes(6, 5) // + go peer2.AddBlocks(4, 5, 6) // tests that block request for earlier section is remembered + go peer1.AddBlocks(3, 4) // tests that connecting section by demoted peer is remembered and blocks are accepted from demoted peer + go peer2.AddBlockHashes(3, 2, 1, 0) // tests that known chain section is activated, hash requests from 3 is remembered + peer2.AddBlocks(0, 1, 2) // final blocks linking to blockchain sent + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[6] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestPeerDownSwitch(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(6) + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 4) + peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer2.AddPeer() + go peer2.AddBlockHashes(6, 5, 4) + peer2.AddBlocks(5, 6) // partially complete, section will be preserved + blockPool.RemovePeer("peer2") // peer2 disconnects + peer1.AddPeer() // inferior peer1 is promoted as best peer + go peer1.AddBlockHashes(4, 3, 2, 1, 0) // + go peer1.AddBlocks(3, 4, 5) // tests that section set by demoted peer is remembered and blocks are accepted + peer1.AddBlocks(0, 1, 2, 3) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[6] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestPeerSwitchBack(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(8) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 2, 11) + peer2 := blockPoolTester.newPeer("peer2", 1, 8) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer2.AddPeer() + go peer2.AddBlockHashes(8, 7, 6) + go peer2.AddBlockHashes(6, 5, 4) + peer2.AddBlocks(5, 6) // section partially complete + peer1.AddPeer() // peer1 is promoted as best peer + go peer1.AddBlockHashes(11, 10) // only gives useless results + blockPool.RemovePeer("peer1") // peer1 disconnects + go peer2.AddBlockHashes(4, 3, 2, 1, 0) // tests that asking for hashes from 4 is remembered + go peer2.AddBlocks(3, 4, 5, 6, 7, 8) // tests that section 4, 5, 6 and 7, 8 are remembered for missing blocks + peer2.AddBlocks(0, 1, 2, 3) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[8] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestForkSimple(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(9) + blockPoolTester.refBlockChain[3] = []int{4, 7} + delete(blockPoolTester.refBlockChain, 6) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 9) + peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer1.AddPeer() + go peer1.AddBlockHashes(9, 8, 7, 3, 2) + peer1.AddBlocks(1, 2, 3, 7, 8, 9) + peer2.AddPeer() // peer2 is promoted as best peer + go peer2.AddBlockHashes(6, 5, 4, 3, 2) // fork on 3 -> 4 (earlier child: 7) + go peer2.AddBlocks(1, 2, 3, 4, 5, 6) + go peer2.AddBlockHashes(2, 1, 0) + peer2.AddBlocks(0, 1, 2) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[6] = []int{} + blockPoolTester.refBlockChain[3] = []int{4} + delete(blockPoolTester.refBlockChain, 7) + delete(blockPoolTester.refBlockChain, 8) + delete(blockPoolTester.refBlockChain, 9) + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) + +} + +func TestForkSwitchBackByNewBlocks(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(11) + blockPoolTester.refBlockChain[3] = []int{4, 7} + delete(blockPoolTester.refBlockChain, 6) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 9) + peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer1.AddPeer() + go peer1.AddBlockHashes(9, 8, 7, 3, 2) + peer1.AddBlocks(8, 9) // partial section + peer2.AddPeer() // + go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 + peer2.AddBlocks(1, 2, 3, 4, 5, 6) // + + // peer1 finds new blocks + peer1.td = 3 + peer1.currentBlock = 11 + peer1.AddPeer() + go peer1.AddBlockHashes(11, 10, 9) + peer1.AddBlocks(7, 8, 9, 10, 11) + go peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered + go peer1.AddBlocks(3, 7) // tests that block requests on earlier fork are remembered + // go peer1.AddBlockHashes(1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered + go peer1.AddBlockHashes(2, 1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered + peer1.AddBlocks(0, 1, 2, 3) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[11] = []int{} + blockPoolTester.refBlockChain[3] = []int{7} + delete(blockPoolTester.refBlockChain, 6) + delete(blockPoolTester.refBlockChain, 5) + delete(blockPoolTester.refBlockChain, 4) + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) + +} + +func TestForkSwitchBackByPeerSwitchBack(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(9) + blockPoolTester.refBlockChain[3] = []int{4, 7} + delete(blockPoolTester.refBlockChain, 6) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 9) + peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer1.AddPeer() + go peer1.AddBlockHashes(9, 8, 7, 3, 2) + peer1.AddBlocks(8, 9) + peer2.AddPeer() // + go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 + peer2.AddBlocks(2, 3, 4, 5, 6) // + blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer + peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered + go peer1.AddBlocks(3, 7, 8) // tests that block requests on earlier fork are remembered + go peer1.AddBlockHashes(2, 1, 0) // + peer1.AddBlocks(0, 1, 2, 3) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[9] = []int{} + blockPoolTester.refBlockChain[3] = []int{7} + delete(blockPoolTester.refBlockChain, 6) + delete(blockPoolTester.refBlockChain, 5) + delete(blockPoolTester.refBlockChain, 4) + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) + +} + +func TestForkCompleteSectionSwitchBackByPeerSwitchBack(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(9) + blockPoolTester.refBlockChain[3] = []int{4, 7} + delete(blockPoolTester.refBlockChain, 6) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 9) + peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer2.blocksRequestsMap = peer1.blocksRequestsMap + + peer1.AddPeer() + go peer1.AddBlockHashes(9, 8, 7) + peer1.AddBlocks(3, 7, 8, 9) // make sure this section is complete + time.Sleep(1 * time.Second) + go peer1.AddBlockHashes(7, 3, 2) // block 3/7 is section boundary + peer1.AddBlocks(2, 3) // partially complete sections + peer2.AddPeer() // + go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 + peer2.AddBlocks(2, 3, 4, 5, 6) // block 2 still missing. + blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer + peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered even though section process completed + go peer1.AddBlockHashes(2, 1, 0) // + peer1.AddBlocks(0, 1, 2) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[9] = []int{} + blockPoolTester.refBlockChain[3] = []int{7} + delete(blockPoolTester.refBlockChain, 6) + delete(blockPoolTester.refBlockChain, 5) + delete(blockPoolTester.refBlockChain, 4) + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) + +} From 254fc1fe3f9de37381a183e0e066af8c6dd3abfb Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:41:54 +0000 Subject: [PATCH 06/31] changes to core/types/block - add HeaderHash and ParentHeaderHash public fields to allow block mocking for blockpool tests - Hash() and ParentHash() checks these fields, if unset falls back to orig - HashNoNonce() just returns self.header.HashNoNonce() - better implement with interfaces, so this may be temporary --- core/types/block.go | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 7b4695f733..3dd2e3bd36 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -67,10 +67,13 @@ func (self *Header) HashNoNonce() []byte { } type Block struct { - header *Header - uncles []*Header - transactions Transactions - Td *big.Int + // Preset Hash for mock + HeaderHash []byte + ParentHeaderHash []byte + header *Header + uncles []*Header + transactions Transactions + Td *big.Int receipts Receipts Reward *big.Int @@ -189,23 +192,35 @@ func (self *Block) RlpDataForStorage() interface{} { // Header accessors (add as you need them) func (self *Block) Number() *big.Int { return self.header.Number } func (self *Block) NumberU64() uint64 { return self.header.Number.Uint64() } -func (self *Block) ParentHash() []byte { return self.header.ParentHash } func (self *Block) Bloom() []byte { return self.header.Bloom } func (self *Block) Coinbase() []byte { return self.header.Coinbase } func (self *Block) Time() int64 { return int64(self.header.Time) } func (self *Block) GasLimit() *big.Int { return self.header.GasLimit } func (self *Block) GasUsed() *big.Int { return self.header.GasUsed } -func (self *Block) Hash() []byte { return self.header.Hash() } func (self *Block) Trie() *ptrie.Trie { return ptrie.New(self.header.Root, ethutil.Config.Db) } +func (self *Block) SetRoot(root []byte) { self.header.Root = root } func (self *Block) State() *state.StateDB { return state.New(self.Trie()) } func (self *Block) Size() ethutil.StorageSize { return ethutil.StorageSize(len(ethutil.Encode(self))) } -func (self *Block) SetRoot(root []byte) { self.header.Root = root } -// Implement block.Pow +// Implement pow.Block func (self *Block) Difficulty() *big.Int { return self.header.Difficulty } func (self *Block) N() []byte { return self.header.Nonce } -func (self *Block) HashNoNonce() []byte { - return crypto.Sha3(ethutil.Encode(self.header.rlpData(false))) +func (self *Block) HashNoNonce() []byte { return self.header.HashNoNonce() } + +func (self *Block) Hash() []byte { + if self.HeaderHash != nil { + return self.HeaderHash + } else { + return self.header.Hash() + } +} + +func (self *Block) ParentHash() []byte { + if self.ParentHeaderHash != nil { + return self.ParentHeaderHash + } else { + return self.header.ParentHash + } } func (self *Block) String() string { From acc8ff30791eca8320fe159d274cd8a43bc4644e Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 17:17:50 +0000 Subject: [PATCH 07/31] typo --- cmd/ethereum/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ethereum/flags.go b/cmd/ethereum/flags.go index d27b739c36..40bb1318db 100644 --- a/cmd/ethereum/flags.go +++ b/cmd/ethereum/flags.go @@ -105,7 +105,7 @@ func Init() { flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0") flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false") flag.BoolVar(&ShowGenesis, "genesis", false, "Dump the genesis block") - flag.StringVar(&ImportChain, "chain", "", "Imports fiven chain") + flag.StringVar(&ImportChain, "chain", "", "Imports given chain") flag.BoolVar(&Dump, "dump", false, "output the ethereum state in JSON format. Sub args [number, hash]") flag.StringVar(&DumpHash, "hash", "", "specify arg in hex") From c610c53a9e13dcfab373b223461fed3b65d7dde5 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 18:54:59 +0000 Subject: [PATCH 08/31] if port is empty string, no listening --- eth/backend.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 2ca1430d8c..ac696eca7f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -86,12 +86,15 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} server := &p2p.Server{ - Identity: identity, - MaxPeers: maxPeers, - Protocols: protocols, - ListenAddr: ":" + port, - Blacklist: eth.blacklist, - NAT: nat, + Identity: identity, + MaxPeers: maxPeers, + Protocols: protocols, + Blacklist: eth.blacklist, + NAT: nat, + } + + if len(port) > 0 { + server.ListenAddr = ":" + port } eth.server = server From 53aa639c5113874914d3918aa4e56921634acc0d Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 20:32:43 +0000 Subject: [PATCH 09/31] jsre executes js file AFTER ethereum starts (allows scripted add peer without wait, etc) --- cmd/ethereum/main.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 7efee31e78..8d46b279e4 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -124,13 +124,6 @@ func main() { return } - // better reworked as cases - if StartJsConsole { - InitJsConsole(ethereum) - } else if len(InputFile) > 0 { - ExecJsFile(ethereum, InputFile) - } - if StartRpc { utils.StartRpc(ethereum, RpcPort) } @@ -141,6 +134,11 @@ func main() { utils.StartEthereum(ethereum, UseSeed) + if StartJsConsole { + InitJsConsole(ethereum) + } else if len(InputFile) > 0 { + ExecJsFile(ethereum, InputFile) + } // this blocks the thread ethereum.WaitForShutdown() } From b39aaa044195da74d122b581fddbfacfe60d1f59 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 20:33:58 +0000 Subject: [PATCH 10/31] add some logging to server dialout and ignored peer suggestion --- p2p/server.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index 3267812343..c6bb8c561a 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -113,9 +113,11 @@ func (srv *Server) PeerCount() int { // SuggestPeer injects an address into the outbound address pool. func (srv *Server) SuggestPeer(ip net.IP, port int, nodeID []byte) { + addr := &peerAddr{ip, uint64(port), nodeID} select { - case srv.peerConnect <- &peerAddr{ip, uint64(port), nodeID}: + case srv.peerConnect <- addr: default: // don't block + srvlog.Warnf("peer suggestion %v ignored", addr) } } @@ -330,6 +332,7 @@ func (srv *Server) dialLoop() { case desc := <-suggest: // candidate peer found, will dial out asyncronously // if connection fails slot will be released + srvlog.Infof("dial %v (%v)", desc, *slot) go srv.dialPeer(desc, *slot) // we can watch if more peers needed in the next loop slots = srv.peerSlots From add19a68826db939d11f47c9412e6cd94608a74d Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 22:39:37 +0000 Subject: [PATCH 11/31] for blockpool logging peer id is fmt.Sprintf("%x", peer.Identity().Pubkey()) --- eth/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocol.go b/eth/protocol.go index 047c351e77..bc43bdaca8 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -95,7 +95,7 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo blockPool: blockPool, rw: rw, peer: peer, - id: (string)(peer.Identity().Pubkey()), + id: fmt.Sprintf("%x", peer.Identity().Pubkey()), } err = self.handleStatus() if err == nil { From 63bbf4571e94e02d218acedef0856638bcce5270 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 00:11:50 +0000 Subject: [PATCH 12/31] fix getBlockHashesMsg decoder (flat, see peer disconnect msg decoding) + add msg logging to rlp decode errors --- eth/protocol.go | 24 ++++++++++++------------ p2p/message.go | 5 +++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index bc43bdaca8..696cec52df 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -131,16 +131,16 @@ func (self *ethProtocol) handle() error { // TODO: rework using lazy RLP stream var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } self.txPool.AddTransactions(txs) case GetBlockHashesMsg: - var request getBlockHashesMsgData + var request [1]getBlockHashesMsgData if err := msg.Decode(&request); err != nil { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "->msg %v: %v", msg, err) } - hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) + hashes := self.chainManager.GetBlockHashesFromHash(request[0].Hash, request[0].Amount) return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: @@ -156,13 +156,13 @@ func (self *ethProtocol) handle() error { } self.blockPool.AddBlockHashes(iter, self.id) if err != nil && err != rlp.EOL { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } case GetBlocksMsg: var blockHashes [][]byte if err := msg.Decode(&blockHashes); err != nil { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) var blocks []interface{} @@ -185,7 +185,7 @@ func (self *ethProtocol) handle() error { if err == rlp.EOL { break } else { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } } self.blockPool.AddBlock(block, self.id) @@ -194,7 +194,7 @@ func (self *ethProtocol) handle() error { case NewBlockMsg: var request newBlockMsgData if err := msg.Decode(&request); err != nil { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } hash := request.Block.Hash() // to simplify backend interface adding a new block @@ -221,8 +221,8 @@ func (self *ethProtocol) handle() error { } type statusMsgData struct { - ProtocolVersion uint - NetworkId uint + ProtocolVersion uint32 + NetworkId uint32 TD *big.Int CurrentBlock []byte GenesisBlock []byte @@ -262,7 +262,7 @@ func (self *ethProtocol) handleStatus() error { var status statusMsgData if err := msg.Decode(&status); err != nil { - return self.protoError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } _, _, genesisBlock := self.chainManager.Status() @@ -288,7 +288,7 @@ func (self *ethProtocol) handleStatus() error { func (self *ethProtocol) requestBlockHashes(from []byte) error { self.peer.Debugf("fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) - return self.rw.EncodeMsg(GetBlockHashesMsg, from, blockHashesBatchSize) + return self.rw.EncodeMsg(GetBlockHashesMsg, interface{}(from), uint64(blockHashesBatchSize)) } func (self *ethProtocol) requestBlocks(hashes [][]byte) error { diff --git a/p2p/message.go b/p2p/message.go index f5418ff473..daee17cc12 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "fmt" "io" "io/ioutil" "math/big" @@ -52,6 +53,10 @@ func (msg Msg) Decode(val interface{}) error { return s.Decode(val) } +func (msg Msg) String() string { + return fmt.Sprintf("msg #%v (%v bytes)", msg.Code, msg.Size) +} + // Discard reads any remaining payload data into a black hole. func (msg Msg) Discard() error { _, err := io.Copy(ioutil.Discard, msg.Payload) From 45c7944a0224ff46c6bbd3bbaffe3a1f77d16b9e Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 03:09:25 +0000 Subject: [PATCH 13/31] protocol and rlp - getBlockHashes lazy encoder NewListStream -> NewStream - need stream.List() - add logging to protocol - fix newBlockMsgData flat rlp --- eth/protocol.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 696cec52df..a4c5d8e752 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -8,10 +8,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" ) +var protologger = logger.NewLogger("ETH") + const ( ProtocolVersion = 49 NetworkId = 0 @@ -141,23 +144,31 @@ func (self *ethProtocol) handle() error { return self.protoError(ErrDecode, "->msg %v: %v", msg, err) } hashes := self.chainManager.GetBlockHashesFromHash(request[0].Hash, request[0].Amount) + protologger.Debugf("hashes length %v", len(hashes)) return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains - msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) + protologger.Debugf("payload size %v", msg.Size) + msgStream := rlp.NewStream(msg.Payload) + msgStream.List() var err error + var i int + iter := func() (hash []byte, ok bool) { hash, err = msgStream.Bytes() if err == nil { + i++ ok = true + } else { + if err != rlp.EOL { + self.protoError(ErrDecode, "msg %v: after %v hashes : %v", msg, i, err) + } } return } + self.blockPool.AddBlockHashes(iter, self.id) - if err != nil && err != rlp.EOL { - return self.protoError(ErrDecode, "msg %v: %v", msg, err) - } case GetBlocksMsg: var blockHashes [][]byte @@ -192,15 +203,15 @@ func (self *ethProtocol) handle() error { } case NewBlockMsg: - var request newBlockMsgData + var request [1]newBlockMsgData if err := msg.Decode(&request); err != nil { return self.protoError(ErrDecode, "msg %v: %v", msg, err) } - hash := request.Block.Hash() + hash := request[0].Block.Hash() // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) - if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { + if self.blockPool.AddPeer(request[0].TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { called := true iter := func() (hash []byte, ok bool) { if called { @@ -211,7 +222,7 @@ func (self *ethProtocol) handle() error { } } self.blockPool.AddBlockHashes(iter, self.id) - self.blockPool.AddBlock(request.Block, self.id) + self.blockPool.AddBlock(request[0].Block, self.id) } default: From a7ac361c8f50328563e9e93f457d7f16de40ff19 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 03:39:44 +0000 Subject: [PATCH 14/31] fix TestPeerSwitchBack test --- eth/block_pool_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/block_pool_test.go b/eth/block_pool_test.go index 09392a82be..d450ab7d6d 100644 --- a/eth/block_pool_test.go +++ b/eth/block_pool_test.go @@ -727,7 +727,7 @@ func TestPeerSwitchBack(t *testing.T) { peer2.AddPeer() go peer2.AddBlockHashes(8, 7, 6) go peer2.AddBlockHashes(6, 5, 4) - peer2.AddBlocks(5, 6) // section partially complete + peer2.AddBlocks(4, 5) // section partially complete peer1.AddPeer() // peer1 is promoted as best peer go peer1.AddBlockHashes(11, 10) // only gives useless results blockPool.RemovePeer("peer1") // peer1 disconnects From 9ab3530df386d67bfc72af116b6a9ae375d71470 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 03:41:29 +0000 Subject: [PATCH 15/31] AddBlockHashes call uses lazy rlp decoding so it cannot be async since message is discarded by protocol --- eth/block_pool.go | 204 +++++++++++++++++++++++----------------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index 65d58ab022..ea788d71e1 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -322,118 +322,118 @@ func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) // peer is still the best poolLogger.Debugf("adding hashes for best peer %s", peerId) - self.wg.Add(1) - self.procWg.Add(1) + // self.wg.Add(1) + // self.procWg.Add(1) - go func() { - var size, n int - var hash []byte - var ok bool = true - var section, child, parent *section - var entry *poolEntry - var nodes []*poolNode + // go func() { + var size, n int + var hash []byte + var ok bool = true + var section, child, parent *section + var entry *poolEntry + var nodes []*poolNode - LOOP: - // iterate using next (rlp stream lazy decoder) feeding hashesC - for hash, ok = next(); ok; hash, ok = next() { - n++ - select { - case <-self.quit: - break LOOP - case <-peer.quitC: - // if the peer is demoted, no more hashes taken - break LOOP - default: - } - if self.hasBlock(hash) { - // check if known block connecting the downloaded chain to our blockchain - poolLogger.Debugf("[%s] known block", name(hash)) - // mark child as absolute pool root with parent known to blockchain - if section != nil { - self.connectToBlockChain(section) - } else { - if child != nil { - self.connectToBlockChain(child) - } +LOOP: + // iterate using next (rlp stream lazy decoder) feeding hashesC + for hash, ok = next(); ok; hash, ok = next() { + n++ + select { + case <-self.quit: + break LOOP + case <-peer.quitC: + // if the peer is demoted, no more hashes taken + break LOOP + default: + } + if self.hasBlock(hash) { + // check if known block connecting the downloaded chain to our blockchain + poolLogger.Debugf("[%s] known block", name(hash)) + // mark child as absolute pool root with parent known to blockchain + if section != nil { + self.connectToBlockChain(section) + } else { + if child != nil { + self.connectToBlockChain(child) } - break LOOP } - // look up node in pool - entry = self.get(hash) - if entry != nil { - poolLogger.Debugf("[%s] found block", name(hash)) - // reached a known chain in the pool - if entry.node == entry.section.bottom && n == 1 { - // the first block hash received is an orphan in the pool, so rejoice and continue - poolLogger.Debugf("[%s] first hash is orphan block, keep building", name(hash)) - child = entry.section - continue LOOP - } - poolLogger.Debugf("[%s] reached blockpool chain", name(hash)) - parent = entry.section - break LOOP + break LOOP + } + // look up node in pool + entry = self.get(hash) + if entry != nil { + poolLogger.Debugf("[%s] found block", name(hash)) + // reached a known chain in the pool + if entry.node == entry.section.bottom && n == 1 { + // the first block hash received is an orphan in the pool, so rejoice and continue + poolLogger.Debugf("[%s] first hash is orphan block, keep building", name(hash)) + child = entry.section + continue LOOP } - // if node for block hash does not exist, create it and index in the pool - poolLogger.Debugf("[%s] create node %v", name(hash), size) - node := &poolNode{ - hash: hash, - peer: peerId, - } - if size == 0 { - section = newSection() - } - nodes = append(nodes, node) - size++ - } //for - - self.chainLock.Lock() - poolLogger.Debugf("lock chain lock") - - poolLogger.Debugf("read %v hashes added by %s", n, peerId) - - if parent != nil && entry != nil && entry.node != parent.top { - poolLogger.Debugf("[%s] fork section", sectionName(parent)) - parent.controlC <- false - waiter := make(chan bool) - parent.forkC <- waiter - chain := parent.nodes - parent.nodes = chain[entry.index:] - parent.top = parent.nodes[0] - orphan := newSection() - self.link(orphan, parent.child) - self.processSection(orphan, chain[0:entry.index]) - orphan.controlC <- false - close(waiter) + poolLogger.Debugf("[%s] reached blockpool chain", name(hash)) + parent = entry.section + break LOOP } - - if size > 0 { - self.processSection(section, nodes) - poolLogger.Debugf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(section), size, sectionName(child)) - self.link(parent, section) - self.link(section, child) - } else { - poolLogger.Debugf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child)) - self.link(parent, child) + // if node for block hash does not exist, create it and index in the pool + poolLogger.Debugf("[%s] create node %v", name(hash), size) + node := &poolNode{ + hash: hash, + peer: peerId, } - - self.chainLock.Unlock() - poolLogger.Debugf("[%s] unlock chain lock", sectionName(section)) - - if parent != nil { - poolLogger.Debugf("[%s] activating parent chain [%s]...", name(parent.top.hash), sectionName(parent)) - self.activateChain(parent, peer) - poolLogger.Debugf("[%s] activated parent chain [%s]. done", name(parent.top.hash), sectionName(parent)) + if size == 0 { + section = newSection() } + nodes = append(nodes, node) + size++ + } //for - if section != nil { - poolLogger.Debugf("[%s] activate new section process", sectionName(section)) - peer.addSection(section.top.hash, section) - section.controlC <- true - } - self.procWg.Done() - self.wg.Done() + self.chainLock.Lock() + poolLogger.Debugf("lock chain lock") - }() + poolLogger.Debugf("read %v hashes added by %s", n, peerId) + + if parent != nil && entry != nil && entry.node != parent.top { + poolLogger.Debugf("[%s] fork section", sectionName(parent)) + parent.controlC <- false + waiter := make(chan bool) + parent.forkC <- waiter + chain := parent.nodes + parent.nodes = chain[entry.index:] + parent.top = parent.nodes[0] + orphan := newSection() + self.link(orphan, parent.child) + self.processSection(orphan, chain[0:entry.index]) + orphan.controlC <- false + close(waiter) + } + + if size > 0 { + self.processSection(section, nodes) + poolLogger.Debugf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(section), size, sectionName(child)) + self.link(parent, section) + self.link(section, child) + } else { + poolLogger.Debugf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child)) + self.link(parent, child) + } + + self.chainLock.Unlock() + poolLogger.Debugf("[%s] unlock chain lock", sectionName(section)) + + if parent != nil { + poolLogger.Debugf("[%s] activating parent chain [%s]...", name(parent.top.hash), sectionName(parent)) + self.activateChain(parent, peer) + poolLogger.Debugf("[%s] activated parent chain [%s]. done", name(parent.top.hash), sectionName(parent)) + } + + if section != nil { + poolLogger.Debugf("[%s] activate new section process", sectionName(section)) + peer.addSection(section.top.hash, section) + section.controlC <- true + } + // self.procWg.Done() + // self.wg.Done() + + // }() } func name(hash []byte) (name string) { From 72432fb16451f96e29ad8a0170b0f58013208087 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 04:28:24 +0000 Subject: [PATCH 16/31] protocol rlp - getBlocksMsg list of hashes parsed with rlp.NewStream - blocksMsg fix lazy rlp - newBlockMsg fix flat rlp decoding --- eth/protocol.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index a4c5d8e752..069211bda9 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -3,7 +3,6 @@ package eth import ( "bytes" "fmt" - "math" "math/big" "github.com/ethereum/go-ethereum/core/types" @@ -171,27 +170,35 @@ func (self *ethProtocol) handle() error { self.blockPool.AddBlockHashes(iter, self.id) case GetBlocksMsg: - var blockHashes [][]byte - if err := msg.Decode(&blockHashes); err != nil { - return self.protoError(ErrDecode, "msg %v: %v", msg, err) - } - max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) + msgStream := rlp.NewStream(msg.Payload) + msgStream.List() var blocks []interface{} - for i, hash := range blockHashes { - if i >= max { - break + var i int + for { + i++ + var hash []byte + if err := msgStream.Decode(&hash); err != nil { + if err == rlp.EOL { + break + } else { + return self.protoError(ErrDecode, "msg %v: %v", msg, err) + } } block := self.chainManager.GetBlock(hash) if block != nil { blocks = append(blocks, block.RlpData()) } + if i == blockHashesBatchSize { + break + } } return self.rw.EncodeMsg(BlocksMsg, blocks...) case BlocksMsg: - msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) + msgStream := rlp.NewStream(msg.Payload) + msgStream.List() for { - var block *types.Block + var block [1]*types.Block if err := msgStream.Decode(&block); err != nil { if err == rlp.EOL { break @@ -199,7 +206,7 @@ func (self *ethProtocol) handle() error { return self.protoError(ErrDecode, "msg %v: %v", msg, err) } } - self.blockPool.AddBlock(block, self.id) + self.blockPool.AddBlock(block[0], self.id) } case NewBlockMsg: @@ -304,7 +311,7 @@ func (self *ethProtocol) requestBlockHashes(from []byte) error { func (self *ethProtocol) requestBlocks(hashes [][]byte) error { self.peer.Debugf("fetching %v blocks", len(hashes)) - return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)) + return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)...) } func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { From 621062f65376a9f56b3a8769fcd9e32cf31cf6cb Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 05:25:55 +0000 Subject: [PATCH 17/31] fix rlp for blocksMsg and getBlocksMsg in eth protocol --- eth/protocol.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 069211bda9..5b09a60e3b 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -186,7 +186,7 @@ func (self *ethProtocol) handle() error { } block := self.chainManager.GetBlock(hash) if block != nil { - blocks = append(blocks, block.RlpData()) + blocks = append(blocks, block) } if i == blockHashesBatchSize { break @@ -198,7 +198,7 @@ func (self *ethProtocol) handle() error { msgStream := rlp.NewStream(msg.Payload) msgStream.List() for { - var block [1]*types.Block + var block *types.Block if err := msgStream.Decode(&block); err != nil { if err == rlp.EOL { break @@ -206,7 +206,7 @@ func (self *ethProtocol) handle() error { return self.protoError(ErrDecode, "msg %v: %v", msg, err) } } - self.blockPool.AddBlock(block[0], self.id) + self.blockPool.AddBlock(block, self.id) } case NewBlockMsg: From 8566350936e59a1ba95326802737f564b85ac8ec Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 09:37:12 +0000 Subject: [PATCH 18/31] make id string only 8byte long for readable logs --- eth/protocol.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 5b09a60e3b..f9f6fac6f4 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -97,14 +97,14 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo blockPool: blockPool, rw: rw, peer: peer, - id: fmt.Sprintf("%x", peer.Identity().Pubkey()), + id: fmt.Sprintf("%x", peer.Identity().Pubkey()[:8]), } err = self.handleStatus() if err == nil { for { err = self.handle() if err != nil { - fmt.Println(err) + fmt.Printf("handle err %v", err) self.blockPool.RemovePeer(self.id) break } @@ -118,6 +118,7 @@ func (self *ethProtocol) handle() error { if err != nil { return err } + fmt.Printf("handle err %v", err) if msg.Size > ProtocolMaxMsgSize { return self.protoError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) } From cf936492759df44b8688bb20ffc45d94f507fab2 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 09:41:01 +0000 Subject: [PATCH 19/31] automated integration tests for eth protocol and blockpool --- eth/test/bootstrap.sh | 9 +++++++ eth/test/chains/00.chain | Bin 0 -> 11388 bytes eth/test/chains/01.chain | Bin 0 -> 15820 bytes eth/test/chains/02.chain | Bin 0 -> 15266 bytes eth/test/chains/03.chain | Bin 0 -> 20806 bytes eth/test/chains/04.chain | Bin 0 -> 18036 bytes eth/test/mine.sh | 20 +++++++++++++++ eth/test/run.sh | 52 +++++++++++++++++++++++++++++++++++++++ eth/test/tests/00.chain | 1 + eth/test/tests/00.sh | 19 ++++++++++++++ eth/test/tests/01.sh | 23 +++++++++++++++++ eth/test/tests/common.sh | 17 +++++++++++++ 12 files changed, 141 insertions(+) create mode 100644 eth/test/bootstrap.sh create mode 100755 eth/test/chains/00.chain create mode 100755 eth/test/chains/01.chain create mode 100755 eth/test/chains/02.chain create mode 100755 eth/test/chains/03.chain create mode 100755 eth/test/chains/04.chain create mode 100644 eth/test/mine.sh create mode 100644 eth/test/run.sh create mode 120000 eth/test/tests/00.chain create mode 100644 eth/test/tests/00.sh create mode 100644 eth/test/tests/01.sh create mode 100644 eth/test/tests/common.sh diff --git a/eth/test/bootstrap.sh b/eth/test/bootstrap.sh new file mode 100644 index 0000000000..78114dbe64 --- /dev/null +++ b/eth/test/bootstrap.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# bootstrap chains - used to regenerate tests/chains/*.chain + +mkdir -p chains +bash ./mine.sh 00 15 +bash ./mine.sh 01 5 00 +bash ./mine.sh 02 5 00 +bash ./mine.sh 03 5 01 +bash ./mine.sh 04 5 01 \ No newline at end of file diff --git a/eth/test/chains/00.chain b/eth/test/chains/00.chain new file mode 100755 index 0000000000000000000000000000000000000000..b1f53d539848cadb85c7eb8cf5df7e7bf42d2ed7 GIT binary patch literal 11388 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 TrgZwCaAh%5FbZbH+|u%2k2mOn literal 0 HcmV?d00001 diff --git a/eth/test/chains/01.chain b/eth/test/chains/01.chain new file mode 100755 index 0000000000000000000000000000000000000000..1ed47885c1978a9d7949d41f9a9514e5f5cda8a5 GIT binary patch literal 15820 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 ahW1qkqKm@j)fQy-KEG64r_Z~q@*c7t#L literal 0 HcmV?d00001 diff --git a/eth/test/chains/03.chain b/eth/test/chains/03.chain new file mode 100755 index 0000000000000000000000000000000000000000..8524881ab35515fc6be7d48f029406bcf95c9fb0 GIT binary patch literal 20806 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/dev/null ; then + echo "chain ok: $CHAIN=$CHAIN_TEST" + else + echo "FAIL: chains differ: expected $CHAIN ; got $CHAIN_TEST" + continue + fi + fi + ERRORS=$DIR/errors + if [ -r "$ERRORS" ]; then + echo "FAIL: " + cat $ERRORS + else + echo PASS + fi +done + + diff --git a/eth/test/tests/00.chain b/eth/test/tests/00.chain new file mode 120000 index 0000000000..9655cb3df7 --- /dev/null +++ b/eth/test/tests/00.chain @@ -0,0 +1 @@ +../chains/01.chain \ No newline at end of file diff --git a/eth/test/tests/00.sh b/eth/test/tests/00.sh new file mode 100644 index 0000000000..9d13acb586 --- /dev/null +++ b/eth/test/tests/00.sh @@ -0,0 +1,19 @@ +#!/bin/bash +. `dirname $BASH_SOURCE`/common.sh + +TIMEOUT=4 +ID=00 +JSFILE="$DIR/js/$ID.js" + +echo $JSFILE +cat > $JSFILE < $JSFILE < Date: Sat, 3 Jan 2015 10:21:08 +0000 Subject: [PATCH 20/31] fix block rlp decode --- core/types/block.go | 44 +++++++++++--------------------------------- eth/protocol.go | 2 +- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 3dd2e3bd36..fa9bcc56db 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -102,41 +102,19 @@ func NewBlockWithHeader(header *Header) *Block { } func (self *Block) DecodeRLP(s *rlp.Stream) error { - if _, err := s.List(); err != nil { + var extblock struct { + Header + Txs []*Transaction + Uncles *Header + TD *big.Int // optional + } + if err := s.Decode(&extblock); err != nil { return err } - - var header Header - if err := s.Decode(&header); err != nil { - return err - } - - var transactions []*Transaction - if err := s.Decode(&transactions); err != nil { - return err - } - - var uncleHeaders []*Header - if err := s.Decode(&uncleHeaders); err != nil { - return err - } - - var tdBytes []byte - if err := s.Decode(&tdBytes); err != nil { - // If this block comes from the network that's fine. If loaded from disk it should be there - // Blocks don't store their Td when propagated over the network - } else { - self.Td = ethutil.BigD(tdBytes) - } - - if err := s.ListEnd(); err != nil { - return err - } - - self.header = &header - self.uncles = uncleHeaders - self.transactions = transactions - + self.header = extblock.Header + self.uncles = extblock.Uncles + self.transactions = extblock.Txs + self.TD = extblock.TD return nil } diff --git a/eth/protocol.go b/eth/protocol.go index f9f6fac6f4..a1bc4e1335 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -199,7 +199,7 @@ func (self *ethProtocol) handle() error { msgStream := rlp.NewStream(msg.Payload) msgStream.List() for { - var block *types.Block + var block types.Block if err := msgStream.Decode(&block); err != nil { if err == rlp.EOL { break From d50f33d183cb4d0037a9f0d2e498e1a3150de889 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 13:57:04 +0000 Subject: [PATCH 21/31] fix block rlp decoding --- core/types/block.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index fa9bcc56db..23179db3f2 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -103,9 +103,9 @@ func NewBlockWithHeader(header *Header) *Block { func (self *Block) DecodeRLP(s *rlp.Stream) error { var extblock struct { - Header + Header *Header Txs []*Transaction - Uncles *Header + Uncles []*Header TD *big.Int // optional } if err := s.Decode(&extblock); err != nil { @@ -114,7 +114,7 @@ func (self *Block) DecodeRLP(s *rlp.Stream) error { self.header = extblock.Header self.uncles = extblock.Uncles self.transactions = extblock.Txs - self.TD = extblock.TD + self.Td = extblock.TD return nil } From 50d834d3f2c6b3e89826f7398cfe1520d0601068 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 13:57:57 +0000 Subject: [PATCH 22/31] fix block pointer in AddBlock arg --- eth/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocol.go b/eth/protocol.go index a1bc4e1335..621f22c1b4 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -207,7 +207,7 @@ func (self *ethProtocol) handle() error { return self.protoError(ErrDecode, "msg %v: %v", msg, err) } } - self.blockPool.AddBlock(block, self.id) + self.blockPool.AddBlock(&block, self.id) } case NewBlockMsg: From 6d848d0e0e82cd77e500c5557b9ad7e5c966e6dd Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 13:59:18 +0000 Subject: [PATCH 23/31] added test for getPeerMsg/peerMsg - FAILS --- p2p/protocol_test.go | 129 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/p2p/protocol_test.go b/p2p/protocol_test.go index 65f26fb12d..48d2008d16 100644 --- a/p2p/protocol_test.go +++ b/p2p/protocol_test.go @@ -3,8 +3,136 @@ package p2p import ( "fmt" "testing" + + "github.com/ethereum/go-ethereum/crypto" ) +type peerId struct { + pubkey []byte +} + +func (self *peerId) String() string { + return fmt.Sprintf("test peer %x", self.Pubkey()[:4]) +} + +func (self *peerId) Pubkey() (pubkey []byte) { + pubkey = self.pubkey + if len(pubkey) == 0 { + pubkey = crypto.GenerateNewKeyPair().PublicKey + self.pubkey = pubkey + } + return +} + +func testPeerFree() (peer *Peer) { + peer = NewPeer(&peerId{}, []Cap{}) + peer.pubkeyHook = func(*peerAddr) error { return nil } + peer.ourID = &peerId{} + peer.listenAddr = &peerAddr{} + return +} + +func TestPeersMsg(t *testing.T) { + var peers []*Peer + for i := 0; i < 3; i++ { + peers = append(peers, testPeerFree()) + } + peer1 := testPeerFree() + peer1.newPeerAddr = make(chan *peerAddr) + peer1.otherPeers = func() []*Peer { + return peers + } + + peer2 := testPeerFree() + peer2.newPeerAddr = make(chan *peerAddr) + peer2.otherPeers = func() []*Peer { + return peers + } + + rw1, rw2 := MsgPipe() + fmt.Printf("all set up\n ") + + done := make(chan struct{}) + go func() { + fmt.Printf("expect handshake\n ") + + if err := expectMsg(rw2, handshakeMsg); err != nil { + t.Error(err) + } + fmt.Printf("send handshake\n ") + + err := rw2.EncodeMsg(handshakeMsg, + baseProtocolVersion, + "", + []interface{}{}, + 0, + make([]byte, 64), + ) + if err != nil { + t.Error(err) + } + fmt.Printf("send getPeers msg\n") + + if err := rw2.EncodeMsg(getPeersMsg); err != nil { + t.Error(err) + } + fmt.Printf("expecting peersMsg\n") + var msg Msg + if msg, err = rw2.ReadMsg(); err != nil { + t.Error(err) + return + } + + var addrs []*peerAddr + fmt.Printf("got peersMsg\n") + if err := msg.Decode(&addrs); err != nil { + t.Errorf("msg %v : %v", msg, err) + } + fmt.Printf("decoding done\n") + + if len(addrs) != 3 { + t.Errorf("too few peer addresses, expected %v, got %v", 3, len(addrs)) + } + fmt.Printf("count ok\n") + + for i, p := range peers { + if i == len(addrs) { + break + } + addr := addrs[i] + fmt.Printf("addr %v: %v\n", i, addr) + if addr != p.listenAddr { + t.Errorf("incorrect peer address %v (%v)", addr, i) + } + if addr == nil { + t.Errorf("no processing %v", i) + } + } + fmt.Printf("complete\n") + if err := expectMsg(rw2, peersMsg); err != nil { + t.Error(err) + } + + if err := rw2.EncodeMsg(discMsg, DiscQuitting); err != nil { + t.Error(err) + } + + close(done) + fmt.Printf("done channel closed") + }() + + fmt.Printf("proto") + + if err := runBaseProtocol(peer1, rw1); err == nil { + t.Errorf("base protocol returned without error") + } else if reason, ok := err.(discRequestedError); !ok || reason != DiscQuitting { + t.Errorf("base protocol returned wrong error: %v", err) + } + + <-done + t.Error("oops") +} + func TestBaseProtocolDisconnect(t *testing.T) { peer := NewPeer(NewSimpleClientIdentity("p1", "", "", "foo"), nil) peer.ourID = NewSimpleClientIdentity("p2", "", "", "bar") @@ -32,6 +160,7 @@ func TestBaseProtocolDisconnect(t *testing.T) { if err := rw2.EncodeMsg(discMsg, DiscQuitting); err != nil { t.Error(err) } + close(done) }() From 8d779769469192559950c686b3c7682584f08080 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 14:00:04 +0000 Subject: [PATCH 24/31] move PeerList from protocol to peer, add debug logs (temporary) --- p2p/peer.go | 26 +++++++++++++++++++++++++- p2p/protocol.go | 31 +++++-------------------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 86c4d7ab54..e48c71914b 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" ) @@ -45,7 +46,7 @@ func (d peerAddr) String() string { return fmt.Sprintf("%v:%d", d.IP, d.Port) } -func (d peerAddr) RlpData() interface{} { +func (d *peerAddr) RlpData() interface{} { return []interface{}{d.IP, d.Port, d.Pubkey} } @@ -460,3 +461,26 @@ func (r *eofSignal) Read(buf []byte) (int, error) { } return n, err } + +func (peer *Peer) PeerList() []ethutil.RlpEncodable { + peers := peer.otherPeers() + ds := make([]ethutil.RlpEncodable, 0, len(peers)) + for _, p := range peers { + p.infolock.Lock() + addr := p.listenAddr + p.infolock.Unlock() + // filter out this peer and peers that are not listening or + // have not completed the handshake. + // TODO: track previously sent peers and exclude them as well. + if p == peer || addr == nil { + continue + } + ds = append(ds, addr) + } + ourAddr := peer.ourListenAddr + if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { + ds = append(ds, ourAddr) + } + fmt.Printf("address length: %v\n", len(ds)) + return ds +} diff --git a/p2p/protocol.go b/p2p/protocol.go index 3f52205f59..f0e5480897 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -2,9 +2,8 @@ package p2p import ( "bytes" + "fmt" "time" - - "github.com/ethereum/go-ethereum/ethutil" ) // Protocol represents a P2P subprotocol implementation. @@ -166,7 +165,9 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error { case pongMsg: case getPeersMsg: - peers := bp.peerList() + peers := bp.peer.PeerList() + fmt.Printf("get Peers Msg: peers length:%v\n", len(peers)) + // this is dangerous. the spec says that we should _delay_ // sending the response if no new information is available. // this means that would need to send a response later when @@ -180,7 +181,7 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error { case peersMsg: var peers []*peerAddr if err := msg.Decode(&peers); err != nil { - return err + return newPeerError(errInvalidMsg, "msg %v : %v", msg, err) } for _, addr := range peers { bp.peer.Debugf("received peer suggestion: %v", addr) @@ -270,25 +271,3 @@ func (bp *baseProtocol) handshakeMsg() Msg { bp.peer.ourID.Pubkey()[1:], ) } - -func (bp *baseProtocol) peerList() []ethutil.RlpEncodable { - peers := bp.peer.otherPeers() - ds := make([]ethutil.RlpEncodable, 0, len(peers)) - for _, p := range peers { - p.infolock.Lock() - addr := p.listenAddr - p.infolock.Unlock() - // filter out this peer and peers that are not listening or - // have not completed the handshake. - // TODO: track previously sent peers and exclude them as well. - if p == bp.peer || addr == nil { - continue - } - ds = append(ds, addr) - } - ourAddr := bp.peer.ourListenAddr - if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { - ds = append(ds, ourAddr) - } - return ds -} From 4249bfb7dc3d0d00b563a47773d2b25f0fb1514c Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 14:25:14 +0000 Subject: [PATCH 25/31] added test for getPeerMsg/peerMsg - FAILS --- p2p/protocol_test.go | 124 ++++++++++++------------------------------- 1 file changed, 34 insertions(+), 90 deletions(-) diff --git a/p2p/protocol_test.go b/p2p/protocol_test.go index 48d2008d16..0844fe7fdf 100644 --- a/p2p/protocol_test.go +++ b/p2p/protocol_test.go @@ -29,108 +29,52 @@ func testPeerFree() (peer *Peer) { peer.pubkeyHook = func(*peerAddr) error { return nil } peer.ourID = &peerId{} peer.listenAddr = &peerAddr{} + peer.otherPeers = func() []*Peer { return nil } return } -func TestPeersMsg(t *testing.T) { - var peers []*Peer - for i := 0; i < 3; i++ { - peers = append(peers, testPeerFree()) +func TestBaseProtocolPeers(t *testing.T) { + cannedPeerList := []*peerAddr{ + {IP: net.ParseIP("1.2.3.4"), Port: 2222, Pubkey: []byte{}}, + {IP: net.ParseIP("5.6.7.8"), Port: 3333, Pubkey: []byte{}}, } - peer1 := testPeerFree() - peer1.newPeerAddr = make(chan *peerAddr) - peer1.otherPeers = func() []*Peer { - return peers - } - - peer2 := testPeerFree() - peer2.newPeerAddr = make(chan *peerAddr) - peer2.otherPeers = func() []*Peer { - return peers - } - rw1, rw2 := MsgPipe() - fmt.Printf("all set up\n ") - - done := make(chan struct{}) + // run matcher, close pipe when addresses have arrived + addrChan := make(chan *peerAddr, len(cannedPeerList)) go func() { - fmt.Printf("expect handshake\n ") - - if err := expectMsg(rw2, handshakeMsg); err != nil { - t.Error(err) - } - fmt.Printf("send handshake\n ") - - err := rw2.EncodeMsg(handshakeMsg, - baseProtocolVersion, - "", - []interface{}{}, - 0, - make([]byte, 64), - ) - if err != nil { - t.Error(err) - } - fmt.Printf("send getPeers msg\n") - - if err := rw2.EncodeMsg(getPeersMsg); err != nil { - t.Error(err) - } - fmt.Printf("expecting peersMsg\n") - var msg Msg - if msg, err = rw2.ReadMsg(); err != nil { - t.Error(err) - return - } - - var addrs []*peerAddr - fmt.Printf("got peersMsg\n") - if err := msg.Decode(&addrs); err != nil { - t.Errorf("msg %v : %v", msg, err) - } - fmt.Printf("decoding done\n") - - if len(addrs) != 3 { - t.Errorf("too few peer addresses, expected %v, got %v", 3, len(addrs)) - } - fmt.Printf("count ok\n") - - for i, p := range peers { - if i == len(addrs) { - break - } - addr := addrs[i] - fmt.Printf("addr %v: %v\n", i, addr) - if addr != p.listenAddr { - t.Errorf("incorrect peer address %v (%v)", addr, i) - } - if addr == nil { - t.Errorf("no processing %v", i) + for _, want := range cannedPeerList { + got := <-addrChan + t.Logf("got peer: %+v", got) + if !reflect.DeepEqual(want, got) { + t.Errorf("mismatch: got %#v, want %#v", got, want) } } - fmt.Printf("complete\n") - if err := expectMsg(rw2, peersMsg); err != nil { - t.Error(err) + close(addrChan) + var own []*peerAddr + for _, got = range addrChan { + own = append(own, got) } - - if err := rw2.EncodeMsg(discMsg, DiscQuitting); err != nil { - t.Error(err) + if len(own) != 1 || !reflect.DeepEqual(own[0], ourAddr) { + t.Errorf("mismatch: peers own address is incorrectly or not given, got %v, want %#v", ownAddr, own) } - - close(done) - fmt.Printf("done channel closed") + rw2.Close() }() - - fmt.Printf("proto") - - if err := runBaseProtocol(peer1, rw1); err == nil { - t.Errorf("base protocol returned without error") - } else if reason, ok := err.(discRequestedError); !ok || reason != DiscQuitting { - t.Errorf("base protocol returned wrong error: %v", err) + // run first peer + peer1 := testPeer() + peer1.otherPeers = func() []*Peer { + pl := make([]*Peer, len(cannedPeerList)) + for i, addr := range cannedPeerList { + pl[i] = &Peer{listenAddr: addr} + } + return pl + } + go runBaseProtocol(peer1, rw1) + // run second peer + peer2 := testPeer() + peer2.newPeerAddr = addrChan // feed peer suggestions into matcher + if err := runBaseProtocol(peer2, rw2); err != ErrPipeClosed { + t.Errorf("peer2 terminated with unexpected error: %v", err) } - - <-done - t.Error("oops") } func TestBaseProtocolDisconnect(t *testing.T) { From 9380644db0657c8e34a1075ab19af27d24f142ae Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 14:57:31 +0000 Subject: [PATCH 26/31] fix getPeerMsg/peerMsg RLP encode/decode, logs. tests pass --- p2p/peer.go | 9 +++++---- p2p/protocol.go | 28 ++++++++++++---------------- p2p/protocol_test.go | 17 +++++++++++------ 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index e48c71914b..f00d7cbc4b 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -11,7 +11,6 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" ) @@ -462,9 +461,10 @@ func (r *eofSignal) Read(buf []byte) (int, error) { return n, err } -func (peer *Peer) PeerList() []ethutil.RlpEncodable { +func (peer *Peer) PeerList() []interface{} { peers := peer.otherPeers() - ds := make([]ethutil.RlpEncodable, 0, len(peers)) + fmt.Printf("address length: %v\n", len(peers)) + ds := make([]interface{}, 0, len(peers)) for _, p := range peers { p.infolock.Lock() addr := p.listenAddr @@ -478,7 +478,8 @@ func (peer *Peer) PeerList() []ethutil.RlpEncodable { ds = append(ds, addr) } ourAddr := peer.ourListenAddr - if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { + if ourAddr != nil && !ourAddr.IP.IsUnspecified() { + // if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { ds = append(ds, ourAddr) } fmt.Printf("address length: %v\n", len(ds)) diff --git a/p2p/protocol.go b/p2p/protocol.go index f0e5480897..381f09dfc5 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -88,20 +88,25 @@ type baseProtocol struct { func runBaseProtocol(peer *Peer, rw MsgReadWriter) error { bp := &baseProtocol{rw, peer} - if err := bp.doHandshake(rw); err != nil { + errc := make(chan error, 1) + go func() { errc <- rw.WriteMsg(bp.handshakeMsg()) }() + if err := bp.readHandshake(); err != nil { + return err + } + // handle write error + if err := <-errc; err != nil { return err } // run main loop - quit := make(chan error, 1) go func() { for { if err := bp.handle(rw); err != nil { - quit <- err + errc <- err break } } }() - return bp.loop(quit) + return bp.loop(errc) } var pingTimeout = 2 * time.Second @@ -175,7 +180,7 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error { // // TODO: add event mechanism to notify baseProtocol for new peers if len(peers) > 0 { - return bp.rw.EncodeMsg(peersMsg, peers) + return bp.rw.EncodeMsg(peersMsg, peers...) } case peersMsg: @@ -194,14 +199,9 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error { return nil } -func (bp *baseProtocol) doHandshake(rw MsgReadWriter) error { - // send our handshake - if err := rw.WriteMsg(bp.handshakeMsg()); err != nil { - return err - } - +func (bp *baseProtocol) readHandshake() error { // read and handle remote handshake - msg, err := rw.ReadMsg() + msg, err := bp.rw.ReadMsg() if err != nil { return err } @@ -211,12 +211,10 @@ func (bp *baseProtocol) doHandshake(rw MsgReadWriter) error { if msg.Size > baseProtocolMaxMsgSize { return newPeerError(errMisc, "message too big") } - var hs handshake if err := msg.Decode(&hs); err != nil { return err } - // validate handshake info if hs.Version != baseProtocolVersion { return newPeerError(errP2PVersionMismatch, "Require protocol %d, received %d\n", @@ -239,9 +237,7 @@ func (bp *baseProtocol) doHandshake(rw MsgReadWriter) error { if err := bp.peer.pubkeyHook(pa); err != nil { return newPeerError(errPubkeyForbidden, "%v", err) } - // TODO: remove Caps with empty name - var addr *peerAddr if hs.ListenPort != 0 { addr = newPeerAddr(bp.peer.conn.RemoteAddr(), hs.NodeID) diff --git a/p2p/protocol_test.go b/p2p/protocol_test.go index 0844fe7fdf..5a0793cc95 100644 --- a/p2p/protocol_test.go +++ b/p2p/protocol_test.go @@ -2,6 +2,8 @@ package p2p import ( "fmt" + "net" + "reflect" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -24,7 +26,7 @@ func (self *peerId) Pubkey() (pubkey []byte) { return } -func testPeerFree() (peer *Peer) { +func newTestPeer() (peer *Peer) { peer = NewPeer(&peerId{}, []Cap{}) peer.pubkeyHook = func(*peerAddr) error { return nil } peer.ourID = &peerId{} @@ -38,6 +40,7 @@ func TestBaseProtocolPeers(t *testing.T) { {IP: net.ParseIP("1.2.3.4"), Port: 2222, Pubkey: []byte{}}, {IP: net.ParseIP("5.6.7.8"), Port: 3333, Pubkey: []byte{}}, } + var ownAddr *peerAddr = &peerAddr{IP: net.ParseIP("1.3.5.7"), Port: 1111, Pubkey: []byte{}} rw1, rw2 := MsgPipe() // run matcher, close pipe when addresses have arrived addrChan := make(chan *peerAddr, len(cannedPeerList)) @@ -51,16 +54,18 @@ func TestBaseProtocolPeers(t *testing.T) { } close(addrChan) var own []*peerAddr - for _, got = range addrChan { + var got *peerAddr + for got = range addrChan { own = append(own, got) } - if len(own) != 1 || !reflect.DeepEqual(own[0], ourAddr) { - t.Errorf("mismatch: peers own address is incorrectly or not given, got %v, want %#v", ownAddr, own) + if len(own) != 1 || !reflect.DeepEqual(ownAddr, own[0]) { + t.Errorf("mismatch: peers own address is incorrectly or not given, got %v, want %#v", ownAddr) } rw2.Close() }() // run first peer - peer1 := testPeer() + peer1 := newTestPeer() + peer1.ourListenAddr = ownAddr peer1.otherPeers = func() []*Peer { pl := make([]*Peer, len(cannedPeerList)) for i, addr := range cannedPeerList { @@ -70,7 +75,7 @@ func TestBaseProtocolPeers(t *testing.T) { } go runBaseProtocol(peer1, rw1) // run second peer - peer2 := testPeer() + peer2 := newTestPeer() peer2.newPeerAddr = addrChan // feed peer suggestions into matcher if err := runBaseProtocol(peer2, rw2); err != ErrPipeClosed { t.Errorf("peer2 terminated with unexpected error: %v", err) From ba0ede120109ec10f685d9943dcf39bec8976f97 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 16:34:33 +0000 Subject: [PATCH 27/31] add shh command line option to switch on/off whisper --- cmd/ethereum/flags.go | 2 ++ cmd/ethereum/main.go | 2 +- cmd/utils/cmd.go | 4 ++-- eth/backend.go | 18 +++++++++++++----- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/ethereum/flags.go b/cmd/ethereum/flags.go index 40bb1318db..5e32562085 100644 --- a/cmd/ethereum/flags.go +++ b/cmd/ethereum/flags.go @@ -59,6 +59,7 @@ var ( DumpNumber int VmType int ImportChain string + SHH bool ) // flags specific to cli client @@ -94,6 +95,7 @@ func Init() { flag.BoolVar(&StartWebSockets, "ws", false, "start websocket server") flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") flag.BoolVar(&UseSeed, "seed", true, "seed peers") + flag.BoolVar(&SHH, "shh", true, "whisper protocol (on)") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)") flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given") diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 8d46b279e4..16aa0c8939 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -76,7 +76,7 @@ func main() { clientIdentity := utils.NewClientIdentity(ClientIdentifier, Version, Identifier, string(keyManager.PublicKey())) - ethereum := utils.NewEthereum(db, clientIdentity, keyManager, utils.NatType(NatType, PMPGateway), OutboundPort, MaxPeer) + ethereum := utils.NewEthereum(db, clientIdentity, keyManager, utils.NatType(NatType, PMPGateway), OutboundPort, MaxPeer, SHH) if Dump { var block *types.Block diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 466c513835..4355760387 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -167,8 +167,8 @@ func NatType(natType string, gateway string) (nat p2p.NAT) { return } -func NewEthereum(db ethutil.Database, clientIdentity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, OutboundPort string, MaxPeer int) *eth.Ethereum { - ethereum, err := eth.New(db, clientIdentity, keyManager, nat, OutboundPort, MaxPeer) +func NewEthereum(db ethutil.Database, clientIdentity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, OutboundPort string, MaxPeer int, SHH bool) *eth.Ethereum { + ethereum, err := eth.New(db, clientIdentity, keyManager, nat, OutboundPort, MaxPeer, SHH) if err != nil { clilogger.Fatalln("eth start err:", err) } diff --git a/eth/backend.go b/eth/backend.go index ac696eca7f..dd14d4aef0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -54,7 +54,7 @@ type Ethereum struct { Mining bool } -func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, port string, maxPeers int) (*Ethereum, error) { +func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, port string, maxPeers int, shh bool) (*Ethereum, error) { saveProtocolVersion(db) ethutil.Config.Db = db @@ -73,7 +73,6 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eth.txPool = core.NewTxPool(eth.chainManager, eth.EventMux()) eth.blockManager = core.NewBlockManager(eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockManager) - eth.whisper = whisper.New() hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain @@ -83,7 +82,12 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eth.txPool.Start() ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) - protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} + protocols := []p2p.Protocol{ethProto} + + if shh { + eth.whisper = whisper.New() + protocols = append(protocols, eth.whisper.Protocol()) + } server := &p2p.Server{ Identity: identity, @@ -165,7 +169,9 @@ func (s *Ethereum) Start(seed bool) error { return err } s.blockPool.Start() - s.whisper.Start() + if s.whisper != nil { + s.whisper.Start() + } // broadcast transactions s.txSub = s.eventMux.Subscribe(core.TxPreEvent{}) @@ -213,7 +219,9 @@ func (s *Ethereum) Stop() { s.txPool.Stop() s.eventMux.Stop() s.blockPool.Stop() - s.whisper.Stop() + if s.whisper != nil { + s.whisper.Stop() + } ethlogger.Infoln("Server stopped") close(s.shutdownChan) From 037866dc3c3a7329a5090b72c0e8f9076d81a9ab Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:12:55 +0100 Subject: [PATCH 28/31] eth, p2p: fix EncodeMsg --- eth/protocol_test.go | 2 +- p2p/peer.go | 2 +- p2p/peer_test.go | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 81926322d8..d5c2bad467 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -42,7 +42,7 @@ func (self *testMsgReadWriter) WriteMsg(msg p2p.Msg) error { } func (self *testMsgReadWriter) EncodeMsg(code uint64, data ...interface{}) error { - return self.WriteMsg(p2p.NewMsg(code, data)) + return self.WriteMsg(p2p.NewMsg(code, data...)) } func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { diff --git a/p2p/peer.go b/p2p/peer.go index f00d7cbc4b..fa99d1d837 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -426,7 +426,7 @@ func (rw *proto) WriteMsg(msg Msg) error { } func (rw *proto) EncodeMsg(code uint64, data ...interface{}) error { - return rw.WriteMsg(NewMsg(code, data)) + return rw.WriteMsg(NewMsg(code, data...)) } func (rw *proto) ReadMsg() (Msg, error) { diff --git a/p2p/peer_test.go b/p2p/peer_test.go index f7759786ef..ecf7146609 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -130,7 +130,7 @@ func TestPeerProtoEncodeMsg(t *testing.T) { if err := rw.EncodeMsg(2); err == nil { t.Error("expected error for out-of-range msg code, got nil") } - if err := rw.EncodeMsg(1); err != nil { + if err := rw.EncodeMsg(1, "foo", "bar"); err != nil { t.Errorf("write error: %v", err) } return nil @@ -148,6 +148,13 @@ func TestPeerProtoEncodeMsg(t *testing.T) { if msg.Code != 17 { t.Errorf("incorrect message code: got %d, expected %d", msg.Code, 17) } + var data []string + if err := msg.Decode(&data); err != nil { + t.Errorf("payload decode error: %v", err) + } + if !reflect.DeepEqual(data, []string{"foo", "bar"}) { + t.Errorf("payload RLP mismatch, got %#v, want %#v", data, []string{"foo", "bar"}) + } } func TestPeerWrite(t *testing.T) { From e771873980ed72a0cf123d47ba24e8b4a40996db Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:13:44 +0100 Subject: [PATCH 29/31] p2p: encode peerAddr.IP as RLP string The spec says we should do that. --- p2p/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/peer.go b/p2p/peer.go index fa99d1d837..37b69df3af 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -46,7 +46,7 @@ func (d peerAddr) String() string { } func (d *peerAddr) RlpData() interface{} { - return []interface{}{d.IP, d.Port, d.Pubkey} + return []interface{}{string(d.IP), d.Port, d.Pubkey} } // Peer represents a remote peer. From 3ce2a440530a7bb861bb956b207e29299b71c049 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:14:56 +0100 Subject: [PATCH 30/31] p2p: remove debugging printf calls --- p2p/peer.go | 2 -- p2p/protocol.go | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 37b69df3af..72e225c244 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -463,7 +463,6 @@ func (r *eofSignal) Read(buf []byte) (int, error) { func (peer *Peer) PeerList() []interface{} { peers := peer.otherPeers() - fmt.Printf("address length: %v\n", len(peers)) ds := make([]interface{}, 0, len(peers)) for _, p := range peers { p.infolock.Lock() @@ -482,6 +481,5 @@ func (peer *Peer) PeerList() []interface{} { // if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { ds = append(ds, ourAddr) } - fmt.Printf("address length: %v\n", len(ds)) return ds } diff --git a/p2p/protocol.go b/p2p/protocol.go index 381f09dfc5..dd8cbc4ecd 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -2,7 +2,6 @@ package p2p import ( "bytes" - "fmt" "time" ) @@ -171,8 +170,6 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error { case getPeersMsg: peers := bp.peer.PeerList() - fmt.Printf("get Peers Msg: peers length:%v\n", len(peers)) - // this is dangerous. the spec says that we should _delay_ // sending the response if no new information is available. // this means that would need to send a response later when @@ -186,7 +183,7 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error { case peersMsg: var peers []*peerAddr if err := msg.Decode(&peers); err != nil { - return newPeerError(errInvalidMsg, "msg %v : %v", msg, err) + return err } for _, addr := range peers { bp.peer.Debugf("received peer suggestion: %v", addr) From 2ef85b4d82d78d4c21c9c223d7b81c93f3ffd965 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:21:14 +0100 Subject: [PATCH 31/31] eth: fix message decoding for working EncodeMsg --- eth/protocol.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 621f22c1b4..056380b04c 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -3,6 +3,7 @@ package eth import ( "bytes" "fmt" + "io" "math/big" "github.com/ethereum/go-ethereum/core/types" @@ -139,11 +140,11 @@ func (self *ethProtocol) handle() error { self.txPool.AddTransactions(txs) case GetBlockHashesMsg: - var request [1]getBlockHashesMsgData + var request getBlockHashesMsgData if err := msg.Decode(&request); err != nil { return self.protoError(ErrDecode, "->msg %v: %v", msg, err) } - hashes := self.chainManager.GetBlockHashesFromHash(request[0].Hash, request[0].Amount) + hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) protologger.Debugf("hashes length %v", len(hashes)) return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) @@ -151,7 +152,6 @@ func (self *ethProtocol) handle() error { // TODO: redo using lazy decode , this way very inefficient on known chains protologger.Debugf("payload size %v", msg.Size) msgStream := rlp.NewStream(msg.Payload) - msgStream.List() var err error var i int @@ -161,7 +161,7 @@ func (self *ethProtocol) handle() error { i++ ok = true } else { - if err != rlp.EOL { + if err != io.EOF { self.protoError(ErrDecode, "msg %v: after %v hashes : %v", msg, i, err) } } @@ -172,14 +172,13 @@ func (self *ethProtocol) handle() error { case GetBlocksMsg: msgStream := rlp.NewStream(msg.Payload) - msgStream.List() var blocks []interface{} var i int for { i++ var hash []byte if err := msgStream.Decode(&hash); err != nil { - if err == rlp.EOL { + if err == io.EOF { break } else { return self.protoError(ErrDecode, "msg %v: %v", msg, err) @@ -197,11 +196,10 @@ func (self *ethProtocol) handle() error { case BlocksMsg: msgStream := rlp.NewStream(msg.Payload) - msgStream.List() for { var block types.Block if err := msgStream.Decode(&block); err != nil { - if err == rlp.EOL { + if err == io.EOF { break } else { return self.protoError(ErrDecode, "msg %v: %v", msg, err) @@ -211,15 +209,15 @@ func (self *ethProtocol) handle() error { } case NewBlockMsg: - var request [1]newBlockMsgData + var request newBlockMsgData if err := msg.Decode(&request); err != nil { return self.protoError(ErrDecode, "msg %v: %v", msg, err) } - hash := request[0].Block.Hash() + hash := request.Block.Hash() // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) - if self.blockPool.AddPeer(request[0].TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { + if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { called := true iter := func() (hash []byte, ok bool) { if called { @@ -230,7 +228,7 @@ func (self *ethProtocol) handle() error { } } self.blockPool.AddBlockHashes(iter, self.id) - self.blockPool.AddBlock(request[0].Block, self.id) + self.blockPool.AddBlock(request.Block, self.id) } default: