From d55d6a5c199ac40ffb4dec0dc25922f04e46f88e Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:24:46 +0000 Subject: [PATCH 01/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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 zcmd7ZcR1CL-^X!V);ab(NH*D9vdKsYkv&St2&E_-K32#ci8xV&Y#G_vGiA^0Ju|Z1 z-`n?ce*gT=^||irI)B{$bUm+JugCR1oa;k}H`@;}`42I<5f~sJ2*xh0rO8$^uiU_| zkIK6G>HEl47~(>oQfz%tJeBW{y%qQ0{sBbGt0#LyeK@faKga8Jq}ks3kz>$(UKHkh zI(BAQYU3J?xVOYi!_Z}rc@C&7#MB=+o2X4OBsu2QV_FM*owX@t0SzkT|JxV-zkCkB z!oYw7P(^t-9FD+A(tb2!E$Y)T2vdwDbxhr3mTvS1oIq@2|#6C-~sxX zOK1O}ICl|-#7bzC-dpo6+!4XED=pxpr5dR=A}*{n5ob6-q8tc6CSanNqNGxIGMDnM zQpkwdwJ8HGwd&Iv6Kc;ncpn8y;C#TYU$%_vZsSA!6<&zF{(xaj^`8~ThvzxqzZaJ8E zT)ACGz%mr@)I??+bTepx1{5p|A8Y_B-g}8sDs-&s;}z)PDRQqRXjTtk>FB8sh$nhk zXi2>m8wM08^n))FLNE__ zSG}zL!vw^fB)ppY(>n zchX~`VB`4ES&NWrd)CjqVp2$y-rhteK9~sgM8(N_B|R)xRr`d_30+RXpJN|LwK#1K zP@p`=#M#!VZ>IHs8{pBushtW7=6xXZs@8VZDLkkqFCrUsGiZPg6igpn04gAMfpQK< zXsnE^hMs!|X>RB&GD?r`oZ+@J>-{a5H?8>qgo_2KAW=#d-yIQWv=B<*uGj`srTgI6K!gp%te^v0G}WJ(){Ev$AzhVdqI)CT}0RD)*N;0%s()x|E$HM7?Ifvt70#BAcAE{W$UsDA-`HhExwkS}Tqq6qDKQ#;|@%xpdljPOeu zRJlnFpEZr%mu7a?F_VB;79mmca5K5o?UcT84>s?U5PWd8=!8#UXXa+SSQ@4p`XQQz z0_ECp`@}g;5RICdvu=~4f)2mv>ey{(yYSU2fi`t<8$r;`paD8iuzd&s=vAT%6ihfG zF3rsW{j?h0cm zwA5IyE_d|@-3%I_0|m#22!PUhU7{Q<-jDbed3EK<8Q(yb$47Z6EcE+wx&J(c7L*;8l3Ria!wYI(*Am8yI+<&)1QnUJAMyG(ZOm z9wr2Ul5kz3@O>ry_%b|Ki{!R;i}mllALVbZC*FO{l_{J{_UWsz;Ws3T(wCB!7x7Oz zMvhH7v~sl-h*%ZM$f^>WO>Zz6>6*5DM1eBnF<){cME%T5{#%9Bq(v&zSq|wwQSx!7 zw?+`nGh7VN&7c8VQ2e0)l;FVy3N{?UEl8-ocBb@D*w__YYeM|nI~Fkxp=}qLh{=Ky ztuGw|NEGpUv8$=2I4pIxE$bDtz^%G$xjx6Q{;Tp8E2T=Fub-np*^n!ccOf#gl&aJa z^Za&7Et1)mmT2m%DAmCz+2qsy{{#gen-qZJez-(&<6;cX)ih*>pO?RWT0MZ7F7l2- zSndbuk3lWwa1((CNR*$0H`ZsPUtvm6ci%rh@s@uUI84c=ei}$w;yf9}Jn;YpiWG@p zScoF$`HK>^#C7`0CfMU4;+p%a?;H+Ns}kKB0nkGL4bX8Z_yJ@96!Z5b$`3|oL3cJ9 ziWmK#0_;$r6gk*H%|2pjQqvXTHH7Tq!u&pLCa>$=qPE;m@c#VO z40JPSfEJWs3IK9seu*-j1AAb4ROm_VRp(QNd+bkpcL}c%hF{%b2kXE{d-n>7Vya)= z5q!Y8U*G03w%bKq7s<5SZ5IKw)Ma_ zDE>{>u>RAG(*PwTimN$m^}Z5T8{^|8rjxvhsw!)q9ry9fJSpj*p+^+FRVYw2Fs@*b zNRPyaal+3zX*O6kSDUh|-ArTNl?l=O1E1UdIlfCv>??z7U$;-cNzb0SO zNk;MrL1YbIHSQsEClm7Q$g%mg-y3O_=c zbc;YYg9d0piKGD_t9BPCxNt;C_d2ub=h9zvfeOXH=Jw6H1mkIr2OUkPin8HyR>AE^ zln)*>#<+7hmCxook}3Zr);PvJ6~aVBsvF>W(A6NmrJ_K=eIX(>6s!$<^Ju1%dkt5< z1Mg<#1nYew9|o?$ME}eU(9NI$I#7tnXaUG#-USLC9Kq&O5wWZ+y}p`Zn~#4zdF{)I zZ$@P2sf=ICqBX>VWDSY3)U#9H=J!GLw+`-t-yhbL+eyVdI3u>_nmTyPzrWO)pg_rI zwIeH%ju@_FDwzy33`}m*<0g=AKEtNy_-nmWX*>nG88ko(N-P}!nccZWNmOjv8`yep zlQJy1-7QT;pDvz*!E^JQ*@}LT!pz3B6%u7dl8FXZmpc^iq?qSjseRxi_B`V~Ms0=9 zXj%U{mJmG(6kUGn#lR>QDj#m0z$_jTBF%O19( zr(Mu-WO6hYjuQg{bTepx4ipjw7yub=zd*r(`OA*oSM(uwa^)mwBA8&MCs88ko(N-84&86dwz$x{|&vUl;coZ|En z{%xk8U^)1vN}#!OahY+QycGXt8xp0=6DgjPD9hTfBN#Qi(aB+@PC519YH3SeO9FPtyyO zD{zE61yrR?Al)_Uc7#8!)VBFVipvF;1m`BdeGWisA%UFL|t8@&2Kb)grl9^(zCc)~IgMZY~g znEWeVHudWCtvTt&L>hJ}V~B^1xiF{dWJ`fRK8i~jN*D3kaw=xs_Fp`OJ!yx3#L+pI zbcUao_3KiHsS@jgZUzm|fdb{c3P9Tb{gF)Y&of%TT=$1$3fh$!GZx)fJ*4I`Y5W(i z^UtqbmAXxeBlHf5;@>$PXT(LVEUKf;KVsZYUd$oq6SQP1Y?4$aSY=U<>cMq4pMz;^ zOw)tK4QgWiu*1*VuX(POlrAlcvT^;=)I4_w-3%I_1to_CfV9Y8T*^NvyX^*ocC|py z4ZBWuh=~<|Q7$RbMlxhfShSWMV#&mZMER)n9J2?@3z0CLK$P#WD}1s|nhjyBlrs+H zcZeiY|BB*L=&N7be6oY8+Q-LfT4c$Uy68RH3Mq<$oU=Nc&5LCff^G&4(1AkAcMX7i zkGMq94JtprJHbja8urcqR(6W3?Guar5}?qAyL{s)Yjg+O+H?1E@5}H zvnc=T-AB*W?oSz2M%d+Vt~AM@KoM@tqI`HixvnOjmO0~&>aFC)^XZ}dK0^J+!SVSX z#Ot7&K?Ag)6tM!3+KCGkLO9|T1_`E>ctYlr19DuLB^$$EWBXT2f9@{L2E(2x*W&3R zQ8KuVi+V@p20pKFbf#rQc!d)MLR`2{7f$)2lOe_le^8+ClgL6T-A3fS(uCBM6uo{Y z%|dRIM@tsMe;Cm}tGp2mx*0S;2MU=O8vv=|yF_V{Esr5t_E%D0AZk6mqxZB%+JKTq z;8a(fd~E1P+eix%<=OGu9u8JjzJaJ)+P9{5>sutROMhhIFG_+w418|WEPw)qSXflC z)&`-8_+vqbh7E3Z&W;;0px*0S;2MYO34ggYe z@#kI3|Ga#dWD|2%jq+xNW8reij|Ptjj6d{9;;EFRarSp}M%bGmQ8>pelj;mqte!j8 zwNbXeeX_$RfJ+M#Tf4tyk=!?hw~7MgETXeTrG>+2OQWV!r$Bz8Q21QuyHP<(s!Z!UEJj~L+i!f09TQ#G-{+0RVt;N% zqUfYiWuyg;DxM!umnVE!p_GRNmFawRO6eoz^fw!5sz!m*&D#5mx6P4yB*5%0mOAVs ze$HtCo+)C;{)~_=o#Agh=pldx=(rRL6)phs#p4pC_U674J9fKnYhT^agEIHy!B{+s zf~ljN)ulAPaEJvx5@qiSyd1$An?2q2$?WOE)$27uv3|w2_Ebp@^8WLZcU0hUjCs#; z2a?}%eUKu2RZJgEFYwTY5Fx72NO?t|VDD)D&YF}-QLU{5-+AYnj;UfXv&pI%@c`&% z&;Ttc?c4z5bH@b=1dbRfeKAa1hv6AtK%vB9M3(3+<|&y=e$H0OwIrm{(jbFGF~h1U zuX6iXCFh=fkWTvA^r_9PChM)uobT7@S}Gs&^rAp9V(UmsKi}~EOybBb7SYa@s=ZIJ z7RXdPs>i3C(&@znx*0S;2MVPY4*>bZe2FrePxqs^JHWv)+?si#u_#ai4ZE81b@tCD z*=VW;q(t3Jv@x8H9J1~EmB0+}B&EqQW7-xb%XR|l=+fq## z1xkofj(J#6B=%}+>DnDn(Ki8{nTWsaYVH|0gDJlJ5idYDg9hk8p)$M&X)b!@(z z=tkC)$GQPZDM!bJwvH%J?lXrAJQ;gFp(`>^vfK47Q_B;WscfHUA?gt~a^?&V0o@E5 zpao@|4}iS;_g5VMpi~%Z(JHBs2R!Q8%&b1W7ZjhbGtAD3@l$r;40=0T^bZncPOZlX zpjCLdLb zf*=c34c}S1?t*Rx4bXu?ZN(2j61*=jrE;R*Do|Ew_?yrbmUyKno*FJ0urxDq!ng&NV=QZ~U{vzn%4Z#H!ig086YOjhmooNdMv`@cwX>@2 z#?J>rVIfIGLa;wSbhi8IQfv>e3yFem1`W`HvLFCJVumhINa2X#dDV*Fg^cM(Out_! zgjzjS{xlbj*(_g6B;3+hNkHTUHr_ri z9hLY!w%6I{nR|~hlcNCziXnby7qMT7dYA+oPKCF`6Ont@)t9{CEGANSOuavQm4j{u z4bXzJB?Lgi{(W@t&!wa`glf*~27Zj*Rm7xQj~el z#LoKFFboM2^JfHWD1Z3d;5doLEt_jcEc`i1^+AE+uP+!yniC}TiRFB|7MB#tU!xmV zAK;3uN~onVbwuI}x*0S;2MX;oVE_`mdT}Xaa70r*Y_xEfm(+!r*7Xh*1qY(N>v)2` z-SNmY-KZS>q|j8(3rDE;IV704 z@~v6{DQ;%E){*`}Oxu8u*q@F6yv+{~y#u`55a0fZCm;A;>~+ic)n`r5Ew4Q?=;6J> z{k0C&%Lj%SmQG}My~ekV5jOS~$DGPlcW=OT((!wmtQAJuGe*$OpaD8i==?+h2>jm% z*Z*9~Hw;~(d=g30TclrK#lC%lJLi&xwP|4yWS-T{f-A*8g}jt2a!0DsryT-~WEj`t zl$~!(c@DUh700<_+-lZsbUErnaVb}&=em^cC0&ieiZ_n&_nmEvMmBp8ASlP88ko(${TS2;%R$ zLKXIAuZvc#hNtd7|KH~ZzehAiUbvIs7XPem-3^{A&H=eTC+~QodhK2FxMrD9pi~R$ z=TFf=i!JRpZ_&fP2AJ=im6!gSFDog{O2}Uqv<2M^8lVG(fkFa+JZrr~!7F-dfZwoD zp4tg}cI*b@6FzQr-!l7i+?`^`S8cAYhP;$2t)!P@T%pV@8n_z2zhj%Z&ZnEKQhwOM zda-$K%jyz>0wu>;TzfLOzPNlj8p=-7^kcNk4kJUMD13^j|GP%b`$f>rpaEJ?k|Y6$ r8}}uOa)ZD`$l-GAtihC2Ke62yMk?At{k#i6X)Uh($i+xykv7$-q#fOozh>F0ni({xm;G;l@&C{70T^g# zFaRnq3xmPnSgD$}v-;(kfogRZy$fO?)n%1Ux@(Zqg8>m z|3h&Z#1D%XSF3)o6Hxw37{{)P@&+R@+EWC|k&q)E1KA=uh1`>sjI;*9 zhlFljxiHCX|BrFu_M8)ske~#wzB~vlkaj!hwAS0=h1lzj8^ksL+;XzMC~TLm-iQR< z3>u&WPTTEDM^p55sLHSEr?)i14+4_mYbW#x1z_?-0!N}{{ zy*51N$(No+(zBqOK?4+^V4(S90#LD$E0k)%6J>wjU@srx2R$Lnx&U+kaQn+dg69>k z=SOmm)WZmrqx8r$T5dhAPg`xh1;wO{bqgPKGiZPklyfWqDvWW3Vix$JijruI@%PE;NEqx_ zPFxJ#EG~4}EUej{^*yhsBm!k*B$FIMlwb4`NBezfBcbGnl zoX7EsMnsIZA-;%t3>FfUw%~PN57koQcMtMr*}0bREjZ08-7~}ONnFdi6-{$L0ieA0S1790o|rs}9WhbphapkBKXT%fa~fRB2Tl|6c*oe?03ig*AI;WaTgg8# zuOykQQp%WC%d_ihl^^B=3jO&AZti|fLxS?r(|Oarg}8t6?>oF`X55|=``!t{F^g3i z2Ce3TcP+`Fn?VCqpkVmp0#MG(|A(@P27~vH>%-h;Vnu5nY^_!J%1y6n8b2Knzwvrc zLI}^qbc!8;k`8T_u#8mnT02`a<#1Y0;>p+JBNnatV=PB4``tR&1_=sdqN&5~vTo1} zw=?$)@wYvnXm>fuY26grp1J2@7qQ*M->WKSB>=h^G(ZIkravA4Wg@slL5IN; zvfLfey!SkbQ>1F`g4d=>Ah=UW;!5=f0iAExu*-%JD8Dm)@IQ8=RFNyw-+Cm{EbRP9 z6Pm~?mtmwmPnUn|fF23TiP4j+7ZWV^)DzkifqnN_7uul*c0b0(dKW$fsE3swgKh>5 zP=a!f4?yYUuTb(ctkn)?aqiu)_?Z6;k|JFVr=dGq1A^xTOb*p*GNTbF)2)g7Ik(Ww z4f6K4Ve8_KnO~#icM>9`P4}@dt<*YPkf5~1sCoo_1}T&_yTE+Oq78lqKI^LW1%)%0nQU)e`#Ah-glg z1MA~g*}RC~Fo6Q<(tE`;@hVW&@P@ws z&|n>;ZNsntSWj`NusMH&OX)7aoA4VNd+);V#*~`VLK6|!^ z1m)ZHiBz5_FS;{V9UTFnyfxXsN0@7tLe5=wk9w!@4=w0s&;S)E*f1ghO1yi8Qt=TB ztJr|_nQkkI2N5kz0853Fj|*{UhI~fT2Ux-lf5fE}U=d+ff9%Ghg%0w^_Zh!?{A-lg z{@#edl0X!|h;3|+1jWJ}XZx!T9gWS|98SG2U1c^tuR~+*&iMGNQojBS)oReqpaCjS zaL^$Dl!)sJg|CMA?emBt4WfIRJ(df$hP+ZmXEb&SI z^odcwMv6dMh6GiZPklprVo#d~~-f(e6j3*f8noGVxh8M7y&6iba z6!(uKP{i6rnKG-fnA>c6cI%gcyKMzBV@@?e+p_gr)e1hZypf=MmnoBVB``3TY)}*R zX*{D8&g;!eHg=Jh?5CIL^6&dUf`W@l3_!87uTb2%=tGOt4cK89b+0^|$I)|y)5wHm zJ`sPK&|r)(;(v@l`8;uJcRBV2x;W*~ql;5N*{8u%hXQOViLr=Qv5&@<=(&;V82y*}W40Jp|AI6_hql{WTd?!HWPTpod0wv>ki-t3wbvz>Rt@8H`@#&$o+JJsJnwyz6 z)dCO?XAUGN(DQV4k`x7=nL9qF3Q>nll6n<#w6-Q36Hem88x(SVpqoJhRG?hLCIukp z3|A=BQZ!w2nUe!dr`q9T6p6!CDygpQLvV??I3;5WqG2Zlig?%h4$e14@(JN*-zWc? zEE!`a$6(NXVd(B@GdwzaX@>-*!odn^@)kp#lDYz?BkTa1E-?E?`mXL>O7p`czxOpJ zpqoJhl%Rx?0gz+UE0m=|y2r-H==#yJ(q14?jP;uP zLytHQ+k0JS4tkqhET_F?ch>MF8(K{8oGBg&B0(7(RC!%m89vn+I3hW={-Zp0B?rey z#?n+UT)16uoJj?AGiZPc6g)g~0CITwkf5lcT|*<1 znofMp3A^B=`p*1gyDQ()-8e3-R*>osaKQ<>88ko%%4-S$@^k6xQbw)hUAE~XSp*4Y znTv;*UAV9Rk=qpi5}b-NkDru9#DhTjq=jLXd&llj(P$y-4vVAit-_+y=YQBn=bvAf za^xDhofNA4dFF*mP=)0NM%YX3zi?DELH_0OSYT zB?=Y{F0qK;v&b8}>3A5Eo*@D;Zw;Tg+pAyIBKB)0e^_t!BLb!AK{TO-f-(7Vno-2$ zO38@Qwo+uOlgkA;&Hf!t${7_TDBaMm!vDRZ{4JzQ zy8?7GXn+!wXet1*ZFh-+4TDz=?J^p_uO6iima80HIW!p*NTfQMa57%3D1gaWg!Um& zvc0Gbu~)Dfp04(%lmAR^aZ2zMM2APK>f?A(x4;`Sk)U8d6PBC|)ufBG{Wie8gDu;S zbGu=V^$~$TE!RYHP~LaY&7c7)PzXq_1CaHSOB5U!oXx*JYEw~acRSan6!%8@PVs3# zZuG#JbYRcACB%$q2Z6FN{Hwh;Fk56n3wtf_Cu_#N)Jh(#X`2gmEu77T;#MOhD5b1+ zBo$IoQwSpI4-Tm;<=8lVIvo*IBG|GGj+mhbsJzW2r| zV@hIwNQ#0cN30Ny=XRsXmfo=3x9>|92$U@e1}eI?qRB*O`4Ybd%_C<~@7y~s3K{^i zD0PLR9>UdpVqMgx%k^*$&`YgN9eGJL81J4glT@@wyIf&{Krs!W3f}*Ghrw64!bXFc zX?6OpG8?I3I&S=%yL!`>;>aFchX7M=s0VPxITbiN2}4&!J4DXO@DwiCsGfL?v61{V z0^JN6padm_7J$qKUZLoaQ{fhHC}`fBtrDGGp%8<-i{2No= z$1da~Iz5&P%b6Aqx*0S;1qu-@9RQi?yF|f-!4-_i3+@Ms?xPoz0G2J2hU?B7XbnTB zM!JTSjDX2(9RekKG-*+6e@<8J59v3ydAf8=q4R6UTy=P_vTT6YPd#oU`S@hU+JtZO zp?BE6z{;8CHc=aVCZX0&G=Lk}G*a;Z6W$KG88ko%N+vx387I9$DNz()uy^$_U*z-^ zS}@T|GM|WS;_n_<-=yCqt;W6Gi$EDPR(*jcxoQ}Q^MzA+!2y?t{8*(_?TEM+-6Yh; zI6VgmO02_RL{X4rHm||z-roc+J9v;QeOmchkMGDhfC>}{GXntmYlhgDUm%=eWh(iweS)+&4WKVKI|-yk_Fw0-3RuvSmb|_%R(A_3twElGYE82K*?d zOErY;`Qbi?1dhGc6`43f_K=C^y61pF`KwI6HTn{d;My5!J-%6vz5&MudCFb3V^wp| z&7c8FP~I{EkfHo5l*$~8M}l_#O+~Ns=ylvSITCe3x3iVkMljmZB00s9HW4U#eL10| zH8=&7+e>#>q&kzS*d+}iURI_;oXYb(WkI+|E@d)D*muvll6605{fzEOAM7ob)`f%% z?4tIIHsxz<%8^paCjSpqxwqr0+lfB;);iMjKe3HAawU%{Xzj+$ z)h#B;d&F3RX$X{{fu)3pT$G9;TADYf4f{wdIb{4pHjIUgQfmd8%-WGXxE|tjFpiJw zdc6Lfk`VXx-}joYcvz~cH#SAsxJK30FFZgug9a!;DP#s9J+haV@(<;pPhY^U6&Su{ zH^2@tvH;NPB*c4(CJpf`b_&AG8Tb$=Zxy`JhcSHNNlQrtr49!|r~AYO5c&og!|JE*dKVuHF^zD%{NuI*k}MGWME)x~60G`}2lGiZPc6kKz zb~Np+&-T!}+@^<^b&=P)q>-Qqb>@>>KT2pxh+710{E#mi5gNR8f%kT}WMq z+#`*ZD2IJ|Nb|JeRw(FZ&;S)EB%*8pq>1kerRz>z9MNWwg6bN<$Fuvoo-I=PoCmf>baD%3jI+p3!%2@0W*hqr0s}ZSBIzv!S>`odaUo89+MHc?ongXn+!w26g~a=YNGl@=F`v{Dg{3M^uvI zv-J&!=+D1_@GQg!)y z*XF;E55t}0!tIA8%R=!mnRMHUV?4v`VF?_isw~bgL!43eMhFzn8S~UOeI*NTr?y`5 zzLY1w`1rA}(~0gp+A~WZTg2H$f^r@;(4*AD@o-PAWk9P;cCB3KLhHlBvW(8vBPHS` z%KszJQ%E~F0Z2LP<)sk9;9efoSs^gV+?Gt~k7*e60R}dWe4YJsgE!uk%*129??#|# zWl`j21%H*lIHIge%HATEg@n{-qQBjyY;8SiRFf-=N9GRoWQL^=J^LU1lDq6~Tp_AjIA=KdnUlY%m+YP@LZbuTCGl2!dH%2m-3%I_ z0)@1L8-Tp;zeFL2!3p+c=H*4N-C{j%uCITv-7Z9;k#QSCc?4rscWI42r7UG2#0ePFGlr9Zlk)SNgDbVtsEx-HYl&Wcp@jATG!=b_3 zz14(!9ES5@q@WRWGiZPc6fz|q0P>FU5{2U5OFLgo^&z($eTvxyOXNscyRycao3s6* zUlg{t=*9aH93xPwHm(tTD3|Aw=@XlS4im62uoedjf3R=nY!6Zy5HFiRg5s%TloO9- z>oseEefN3&1%K<07t8huNsc`3Jgx@NK}pcfpaDuy`gj3I_R}krxYG1*!+B0!y(<-Z zn=~Dc#Lv`abBPSuM7n~+8eUM`M4;O&%-pE@RRLraWk|f z8-IcX#m7k>Q@DmmE@mFzic+SGMfqrp>HzqgWhszj}x+3~IkEW2IFpV`AH#7EHcj)#qbCNxp%q?sB z>9w7`F^21DtSIQ}3%VIJKm`hg!A$^?W~<`A z&lsNDE|4X0!~F9Kx3e{uxR!0M{$ke;JJ8Lb0ZLG2`2k4W%Kpd$j^aqjr#B6* zhNYsm36I4)+woey)VBslkhuQu8`rJRd`+58IQ)vAKYGE#ruLR!YN7de+VDc}r>Sh) zOu^?!P>wo3PgtuHy@?qvkk&aYHVc0<7xc%6!kxLoHxPPcQ3ARdG(ZIkrG)?hi59v- zv7#ea=MXsKw`3m++hNX5nHP2c8ar@8mU7n>!lROHjJOn!9gfg53f#mos}ITp{S-bO zia6qhIg;t-X94w<e?L>YZF)pm+f9gA4bjo(*7S3#Y)s7hH4SxcJi}v@J^oyRK-uU@ zYp1p3HyC%T`EYk0bTepx3KS|wApjD(eR(OAF!-B^r;*shPoxfu1C`6=|8S~t zEd4T&i-VWT_mj->+A%kj8io>$BmJOkHz7Zwjx1M z^lG>-tQ)4da4VhMc)Km`1=k%B@d~wtnYe7V(?;I^BVY5W_Jje*OVukB{Y|`PRZWYi zwJ_ph@5iPrPirJCqaxm)hZn_Kl`@*&L0n4Yn;P7QB}_s1$2dWY)QPv%bh%d_S}*xd z3bU=$;=UL|g5ol?ufC{ow>7cmz7~(wgUj!~Tyv!SdC;o#$sB>w3maf*KjN$- zY_#MwNx^$C^m#H;C6PwG=x>2}^|Z#n_e=ll$LA^cd6uJYW=gqg-pbHd(SYOrWIa+h znmGll@lDF_ECHaKK?9VaoQMJtKb*@;p@P9R<4#9w%#8aBh_}Y+Xj&BR_-T=|ANXsh zT23vVbbpvcpy(BIYRfUZJ#V#mCw^2jWVIq#RkR@##CDp^BJqMj*9{4Z=I%{&`r%l; z`-YOqY7QApIK<-L_h0Zi;N2g!RHJeo0^JN6paO+DPz-?h*j%B^ktzy*8uh6zpqQ{! 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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/91] 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: From cde204401b0e3a583f2d04b175fb384b12ebe14e Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:24:46 +0000 Subject: [PATCH 32/91] 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 a49243d5ef8c8b4ca7baa421c3d29d8355dcea44 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:27:06 +0000 Subject: [PATCH 33/91] 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 7c5d09489b..851a0cd00f 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 5399fced473b7d4e6f8ddda2aca8756531428269 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:28:51 +0000 Subject: [PATCH 34/91] 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 16f64fd52360af7332c382a4f2930441f9e41299 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:29:28 +0000 Subject: [PATCH 35/91] logger rename --- eth/backend.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index bf6c912826..393254820b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "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" @@ -39,7 +39,7 @@ type Config struct { KeyManager *crypto.KeyManager } -var logger = ethlogger.NewLogger("SERV") +var ethlogger = logger.NewLogger("SERV") type Ethereum struct { // Channel for shutting down the ethereum @@ -232,20 +232,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 } @@ -270,7 +270,7 @@ func (s *Ethereum) Stop() { s.blockPool.Stop() s.whisper.Stop() - logger.Infoln("Server stopped") + ethlogger.Infoln("Server stopped") close(s.shutdownChan) } From febd1d779b5a28c4d04ae096973da5c52b5b9c27 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:31:13 +0000 Subject: [PATCH 36/91] 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 aae5cbc74533e385bad5151a67526c7bb779feef Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 00:41:54 +0000 Subject: [PATCH 37/91] 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 b59044bfce..f48f04ad4f 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 4757aa0c2e9e118ab252639b3574d4f4440e9fd1 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 17:17:50 +0000 Subject: [PATCH 38/91] 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 d82df7e30ffb41f4df2aeeaa97e23e0e0212169f Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 18:54:59 +0000 Subject: [PATCH 39/91] 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 393254820b..352316ad54 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -142,12 +142,15 @@ func New(config *Config) (*Ethereum, error) { } eth.net = &p2p.Server{ - Identity: clientId, - MaxPeers: config.MaxPeers, - Protocols: protocols, - ListenAddr: ":" + config.Port, - Blacklist: eth.blacklist, - NAT: nat, + Identity: clientId, + MaxPeers: config.MaxPeers, + Protocols: protocols, + Blacklist: eth.blacklist, + NAT: nat, + } + + if len(config.Port) > 0 { + eth.net.ListenAddr = ":" + config.Port } return eth, nil From 470812464e9caecc5523b1aaceeaff60f0a3e211 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 20:32:43 +0000 Subject: [PATCH 40/91] 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 b238522820..389d9f45f7 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -112,13 +112,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) } @@ -129,6 +122,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 f129798a77069eef59ab33de0708396f325b7cb2 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 20:33:58 +0000 Subject: [PATCH 41/91] 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 81fe46897ce40fa0b15973f8e704b69b12ff65f3 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 2 Jan 2015 22:39:37 +0000 Subject: [PATCH 42/91] 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 851a0cd00f..4f70990901 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 340eac708caecbeca216760eff5db1ec3f6b372d Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 00:11:50 +0000 Subject: [PATCH 43/91] 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 4f70990901..cad769f7f1 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 e2b8e05a712abd169f2e073d88719e22523df575 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 03:09:25 +0000 Subject: [PATCH 44/91] 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 cad769f7f1..5d784fdcde 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 = 51 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 b768d146207ca7882248bca75cfa39dea189f2a1 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 03:39:44 +0000 Subject: [PATCH 45/91] 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 60d73fa26197375cedd7f681404a3da4dc609c3c Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 03:41:29 +0000 Subject: [PATCH 46/91] 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 eb88474d8423702a43f84f787c8aa48eaf7b06a2 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 04:28:24 +0000 Subject: [PATCH 47/91] 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 5d784fdcde..cd8092080f 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 0e5e7eb31071e0db60b6138c1d5d6a683b2e45f3 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 05:25:55 +0000 Subject: [PATCH 48/91] 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 cd8092080f..ead0791a79 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 b356a9fb64271f9c3fc36d61936945635c1dc2a5 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 09:37:12 +0000 Subject: [PATCH 49/91] 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 ead0791a79..745dd23425 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 f91a39e43cd6032d75a00404f3e17182107be469 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 09:41:01 +0000 Subject: [PATCH 50/91] 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 51/91] 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 f48f04ad4f..0a5c8eaed7 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 745dd23425..6f843162a7 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 a77468bddd746e40a4296061ef053386eb5bac56 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 13:57:04 +0000 Subject: [PATCH 52/91] 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 0a5c8eaed7..7e19d003f5 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 b63ca7a01aaabfb8b5f8578afa1f354fd235a3ce Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 13:57:57 +0000 Subject: [PATCH 53/91] 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 6f843162a7..067d2bfaf2 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 5f1bce095d76cff90157a061d9c0578ea00561de Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 13:59:18 +0000 Subject: [PATCH 54/91] 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 50f8ae81a15078261d062bed3bf6d1fbb5fc43a1 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 14:00:04 +0000 Subject: [PATCH 55/91] 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 43877af5b5e46a86e625b75300cf484419dc4492 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 14:25:14 +0000 Subject: [PATCH 56/91] 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 c2184512bb4e83cc71312d626844316d283488a7 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 14:57:31 +0000 Subject: [PATCH 57/91] 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 b44289ec1279bf1e6ac4ffc49df20119fde74175 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 16:34:33 +0000 Subject: [PATCH 58/91] add shh command line option to switch on/off whisper --- cmd/ethereum/flags.go | 2 ++ cmd/ethereum/main.go | 3 +++ eth/backend.go | 25 ++++++++++++++++++------- 3 files changed, 23 insertions(+), 7 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 389d9f45f7..85ed20171b 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -64,10 +64,13 @@ func main() { NATType: PMPGateway, PMPGateway: PMPGateway, KeyRing: KeyRing, + Shh: SHH, }) + if err != nil { clilogger.Fatalln(err) } + utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive) if Dump { diff --git a/eth/backend.go b/eth/backend.go index 352316ad54..8f6ef32f9c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -36,6 +36,8 @@ type Config struct { NATType string PMPGateway string + Shh bool + KeyManager *crypto.KeyManager } @@ -124,17 +126,18 @@ func New(config *Config) (*Ethereum, error) { eth.txPool = core.NewTxPool(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 eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify) - // Start services - eth.txPool.Start() - ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) - protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} + protocols := []p2p.Protocol{ethProto} + + if config.Shh { + eth.whisper = whisper.New() + protocols = append(protocols, eth.whisper.Protocol()) + } nat, err := p2p.ParseNAT(config.NATType, config.PMPGateway) if err != nil { @@ -222,8 +225,14 @@ func (s *Ethereum) Start(seed bool) error { if err != nil { return err } + + // Start services + s.txPool.Start() s.blockPool.Start() - s.whisper.Start() + + if s.whisper != nil { + s.whisper.Start() + } // broadcast transactions s.txSub = s.eventMux.Subscribe(core.TxPreEvent{}) @@ -271,7 +280,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 ca719a271166e97da008cad7ec0d38aa16a4df6c Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 3 Jan 2015 20:49:05 +0000 Subject: [PATCH 59/91] switch off whisper for tests --- eth/test/tests/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/test/tests/common.sh b/eth/test/tests/common.sh index d119533af4..3db84deb8e 100644 --- a/eth/test/tests/common.sh +++ b/eth/test/tests/common.sh @@ -3,7 +3,7 @@ # launched by run.sh function peer { rm -rf $DIR/$1 - ARGS="-datadir $DIR/$1 -debug debug -seed=false -id test$1" + ARGS="-datadir $DIR/$1 -debug debug -seed=false -shh=false -id test$1" if [ "" != "$2" ]; then chain="chains/$2.chain" echo "import chain $chain" From 50c03756caed866726a07eb74b01f5918732ec75 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:12:55 +0100 Subject: [PATCH 60/91] 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 1869e5fd7a61b7633a7c9686d65f2f020b055d29 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:13:44 +0100 Subject: [PATCH 61/91] 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 a7c9534512a97f038838ded4d32913e15f0e05da Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:14:56 +0100 Subject: [PATCH 62/91] 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 362189f85d8df20a73a6ac58256b0713fc122f45 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 00:21:14 +0100 Subject: [PATCH 63/91] 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 067d2bfaf2..641d54ab7e 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: From 2916e3a287a28d69d53961ad3288b9bf53233fe5 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 4 Jan 2015 14:47:11 +0000 Subject: [PATCH 64/91] give moer time to network test 01 --- eth/test/tests/01.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/test/tests/01.sh b/eth/test/tests/01.sh index b7293fbf9d..43672ced37 100644 --- a/eth/test/tests/01.sh +++ b/eth/test/tests/01.sh @@ -1,7 +1,7 @@ #!/bin/bash . `dirname $BASH_SOURCE`/common.sh -TIMEOUT=10 +TIMEOUT=20 ID=01 JSFILE="$DIR/js/$ID.js" @@ -9,10 +9,10 @@ echo $JSFILE cat > $JSFILE < Date: Sun, 4 Jan 2015 15:25:33 +0000 Subject: [PATCH 65/91] eth logger imported consistently as ethlogger --- eth/backend.go | 12 ++++++------ eth/block_pool.go | 4 ++-- eth/protocol.go | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 8f6ef32f9c..4c24a5b1b5 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/logger" + ethlogger "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" @@ -41,7 +41,7 @@ type Config struct { KeyManager *crypto.KeyManager } -var ethlogger = logger.NewLogger("SERV") +var logger = ethlogger.NewLogger("SERV") type Ethereum struct { // Channel for shutting down the ethereum @@ -244,20 +244,20 @@ func (s *Ethereum) Start(seed bool) error { // TODO: read peers here if seed { - ethlogger.Infof("Connect to seed node %v", seedNodeAddress) + logger.Infof("Connect to seed node %v", seedNodeAddress) if err := s.SuggestPeer(seedNodeAddress); err != nil { return err } } - ethlogger.Infoln("Server started") + logger.Infoln("Server started") return nil } func (self *Ethereum) SuggestPeer(addr string) error { netaddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { - ethlogger.Errorf("couldn't resolve %s:", addr, err) + logger.Errorf("couldn't resolve %s:", addr, err) return err } @@ -284,7 +284,7 @@ func (s *Ethereum) Stop() { s.whisper.Stop() } - ethlogger.Infoln("Server stopped") + logger.Infoln("Server stopped") close(s.shutdownChan) } diff --git a/eth/block_pool.go b/eth/block_pool.go index ea788d71e1..ae7252b86a 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -11,11 +11,11 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" + ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/pow" ) -var poolLogger = logger.NewLogger("Blockpool") +var poolLogger = ethlogger.NewLogger("Blockpool") const ( blockHashesBatchSize = 256 diff --git a/eth/protocol.go b/eth/protocol.go index 641d54ab7e..f26350c87c 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -8,12 +8,12 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" + ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" ) -var protologger = logger.NewLogger("ETH") +var protologger = ethlogger.NewLogger("ETH") const ( ProtocolVersion = 51 From 8dc088871892b03f77877d057cd1516796bff90d Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 4 Jan 2015 16:04:33 +0000 Subject: [PATCH 66/91] ethlogger -> logger --- eth/backend.go | 8 ++++---- eth/block_pool_test.go | 6 +++--- eth/protocol_test.go | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index c1fd59bf3e..4c24a5b1b5 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -244,20 +244,20 @@ func (s *Ethereum) Start(seed bool) error { // TODO: read peers here if seed { - ethlogger.Infof("Connect to seed node %v", seedNodeAddress) + logger.Infof("Connect to seed node %v", seedNodeAddress) if err := s.SuggestPeer(seedNodeAddress); err != nil { return err } } - ethlogger.Infoln("Server started") + logger.Infoln("Server started") return nil } func (self *Ethereum) SuggestPeer(addr string) error { netaddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { - ethlogger.Errorf("couldn't resolve %s:", addr, err) + logger.Errorf("couldn't resolve %s:", addr, err) return err } @@ -284,7 +284,7 @@ func (s *Ethereum) Stop() { s.whisper.Stop() } - ethlogger.Infoln("Server stopped") + logger.Infoln("Server stopped") close(s.shutdownChan) } diff --git a/eth/block_pool_test.go b/eth/block_pool_test.go index d450ab7d6d..b50a314ead 100644 --- a/eth/block_pool_test.go +++ b/eth/block_pool_test.go @@ -12,19 +12,19 @@ import ( "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" + ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/pow" ) const waitTimeout = 60 // seconds -var logsys = logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(logger.DebugLevel)) +var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugLevel)) var ini = false func logInit() { if !ini { - logger.AddLogSystem(logsys) + ethlogger.AddLogSystem(logsys) ini = true } } diff --git a/eth/protocol_test.go b/eth/protocol_test.go index d5c2bad467..ab2aa289f0 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -12,11 +12,11 @@ import ( "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" + ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" ) -var sys = logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(logger.DebugDetailLevel)) +var sys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel)) type testMsgReadWriter struct { in chan p2p.Msg @@ -227,7 +227,7 @@ func (self *ethProtocolTester) run() { } func TestStatusMsgErrors(t *testing.T) { - logger.AddLogSystem(sys) + logInit() eth := newEth(t) td := ethutil.Big1 currentBlock := []byte{1} From bc59160b77c45cd9ce56ff34c361687a5003c386 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 4 Jan 2015 16:18:35 +0000 Subject: [PATCH 67/91] fix p2p tests broken due to clientId.pubkey now []byte --- p2p/client_identity_test.go | 2 +- p2p/peer_test.go | 5 ++--- p2p/protocol_test.go | 4 ++-- p2p/server_test.go | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/p2p/client_identity_test.go b/p2p/client_identity_test.go index 40b0e6f5e1..7248a7b1a7 100644 --- a/p2p/client_identity_test.go +++ b/p2p/client_identity_test.go @@ -7,7 +7,7 @@ import ( ) func TestClientIdentity(t *testing.T) { - clientIdentity := NewSimpleClientIdentity("Ethereum(G)", "0.5.16", "test", "pubkey") + clientIdentity := NewSimpleClientIdentity("Ethereum(G)", "0.5.16", "test", []byte("pubkey")) clientString := clientIdentity.String() expected := fmt.Sprintf("Ethereum(G)/v0.5.16/test/%s/%s", runtime.GOOS, runtime.Version()) if clientString != expected { diff --git a/p2p/peer_test.go b/p2p/peer_test.go index ecf7146609..5b9e9e7847 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -30,9 +30,8 @@ var discard = Protocol{ func testPeer(protos []Protocol) (net.Conn, *Peer, <-chan error) { conn1, conn2 := net.Pipe() - id := NewSimpleClientIdentity("test", "0", "0", "public key") peer := newPeer(conn1, protos, nil) - peer.ourID = id + peer.ourID = &peerId{} peer.pubkeyHook = func(*peerAddr) error { return nil } errc := make(chan error, 1) go func() { @@ -233,8 +232,8 @@ func TestPeerActivity(t *testing.T) { } func TestNewPeer(t *testing.T) { - id := NewSimpleClientIdentity("clientid", "version", "customid", "pubkey") caps := []Cap{{"foo", 2}, {"bar", 3}} + id := &peerId{} p := NewPeer(id, caps) if !reflect.DeepEqual(p.Caps(), caps) { t.Errorf("Caps mismatch: got %v, expected %v", p.Caps(), caps) diff --git a/p2p/protocol_test.go b/p2p/protocol_test.go index 5a0793cc95..ce25b3e1b5 100644 --- a/p2p/protocol_test.go +++ b/p2p/protocol_test.go @@ -83,8 +83,8 @@ func TestBaseProtocolPeers(t *testing.T) { } func TestBaseProtocolDisconnect(t *testing.T) { - peer := NewPeer(NewSimpleClientIdentity("p1", "", "", "foo"), nil) - peer.ourID = NewSimpleClientIdentity("p2", "", "", "bar") + peer := NewPeer(&peerId{}, nil) + peer.ourID = &peerId{} peer.pubkeyHook = func(*peerAddr) error { return nil } rw1, rw2 := MsgPipe() diff --git a/p2p/server_test.go b/p2p/server_test.go index 5c0d08d398..ceb89e3f7f 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -11,7 +11,7 @@ import ( func startTestServer(t *testing.T, pf peerFunc) *Server { server := &Server{ - Identity: NewSimpleClientIdentity("clientIdentifier", "version", "customIdentifier", "pubkey"), + Identity: &peerId{}, MaxPeers: 10, ListenAddr: "127.0.0.1:0", newPeerFunc: pf, From 51d7ab79249952a7802be6ca38171926d886165f Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 4 Jan 2015 21:39:53 +0000 Subject: [PATCH 68/91] peer / section interaction redone - now register peer on active chain: section.controlC now chan *peerInfo - call requestBlockHashes on this best peer - stopping sections now simply done via peer.quitC (reassigned via controlC when new best peer promoted) - now new best peer always registers on all remembered sections - simplified process start (only switchPeer function remained) - blockpool.requestBlocks now async goroutine so that peer activation (locking peersLock) cannot cause deadlock by catching section process requesting Blocks and waiting for peersLock --- eth/block_pool.go | 236 ++++++++++++++++++++-------------------------- 1 file changed, 100 insertions(+), 136 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index ae7252b86a..a44b69a6c0 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -91,7 +91,7 @@ type section struct { top *poolNode bottom *poolNode nodes []*poolNode - controlC chan bool + controlC chan *peerInfo suicideC chan bool blockChainC chan bool forkC chan chan bool @@ -287,25 +287,6 @@ func (self *BlockPool) RemovePeer(peerId string) { } } -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 @@ -314,7 +295,8 @@ func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { // this function needs to run asynchronously for one peer since the message is discarded??? func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) { - // check if this peer is the best + // register with peer manager loop + peer, best := self.getPeer(peerId) if !best { return @@ -322,13 +304,9 @@ 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) - - // go func() { var size, n int var hash []byte - var ok bool = true + var ok bool var section, child, parent *section var entry *poolEntry var nodes []*poolNode @@ -339,9 +317,10 @@ LOOP: n++ select { case <-self.quit: - break LOOP + return case <-peer.quitC: // if the peer is demoted, no more hashes taken + peer = nil break LOOP default: } @@ -393,7 +372,7 @@ LOOP: if parent != nil && entry != nil && entry.node != parent.top { poolLogger.Debugf("[%s] fork section", sectionName(parent)) - parent.controlC <- false + parent.controlC <- nil waiter := make(chan bool) parent.forkC <- waiter chain := parent.nodes @@ -402,7 +381,7 @@ LOOP: orphan := newSection() self.link(orphan, parent.child) self.processSection(orphan, chain[0:entry.index]) - orphan.controlC <- false + orphan.controlC <- nil close(waiter) } @@ -419,7 +398,7 @@ LOOP: self.chainLock.Unlock() poolLogger.Debugf("[%s] unlock chain lock", sectionName(section)) - if parent != nil { + if parent != nil && peer != 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)) @@ -428,12 +407,8 @@ LOOP: if section != nil { poolLogger.Debugf("[%s] activate new section process", sectionName(section)) peer.addSection(section.top.hash, section) - section.controlC <- true + section.controlC <- peer } - // self.procWg.Done() - // self.wg.Done() - - // }() } func name(hash []byte) (name string) { @@ -556,15 +531,9 @@ LOOP: 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 + section.controlC <- peer 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 @@ -601,6 +570,8 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // absolute time after which sub-chain is killed if not complete (some blocks are missing) suicideTimer := time.After(blockTimeout * time.Minute) + var peer, newPeer *peerInfo + var blocksRequestTimer, blockHashesRequestTimer <-chan time.Time var blocksRequestTime, blockHashesRequestTime bool var blocksRequests, blockHashesRequests int @@ -613,8 +584,9 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { var i, total, missing, lastMissing, depth int var idle int - var init, done, same, running, ready bool + var init, done, same, ready bool var insertChain bool + var quitC chan bool var blockChainC = section.blockChainC @@ -709,7 +681,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { } else { blockHashesRequests++ poolLogger.Debugf("[%s] hash request on root (%v total attempts)\n", sectionName(section), blockHashesRequests) - self.requestBlockHashes(section.bottom.hash) + peer.requestBlockHashes(section.bottom.hash) blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Millisecond) } blockHashesRequestTime = false @@ -723,6 +695,13 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { case <-self.quit: break LOOP + case <-quitC: + // peer quit or demoted, put section in idle mode + quitC = nil + go func() { + section.controlC <- nil + }() + case <-self.purgeC: suicideTimer = time.After(0) @@ -733,15 +712,18 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { case <-section.suicideC: poolLogger.Debugf("[%s] suicide", sectionName(section)) + // first delink from child and parent under chainlock self.chainLock.Lock() self.link(nil, section) self.link(section, nil) self.chainLock.Unlock() + // delete node entries from pool index under pool lock self.lock.Lock() for _, node := range section.nodes { delete(self.pool, string(node.hash)) } self.lock.Unlock() + break LOOP case <-blocksRequestTimer: @@ -752,16 +734,16 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { poolLogger.Debugf("[%s] hash request time again", sectionName(section)) blockHashesRequestTime = true - case r := <-section.controlC: + case newPeer = <-section.controlC: - if running && !r { + // active -> idle + if peer != nil && newPeer == nil { 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 @@ -771,9 +753,10 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { processC = nil } } - if !running && r { + + // idle -> active + if peer == nil && newPeer != nil { self.procWg.Add(1) - running = true poolLogger.Debugf("[%s] active mode", sectionName(section)) poolLogger.Debugf("[%s] check if complete", sectionName(section)) @@ -786,7 +769,6 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { blockHashesRequestTime = true } if !init { - // if not run at least once fully, launch iterator processC = make(chan *poolNode, blockHashesBatchSize) missingC = make(chan *poolNode, blockHashesBatchSize) poolLogger.Debugf("[%s] initialise section", sectionName(section)) @@ -798,6 +780,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { self.wg.Add(1) self.procWg.Add(1) depth = len(section.nodes) + // if not run at least once fully, launch iterator go func() { var node *poolNode IT: @@ -817,8 +800,14 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { processC = offC } } + // reset quitC to current best peer + if newPeer != nil { + quitC = newPeer.quitC + } + peer = newPeer case waiter := <-section.forkC: + // this case just blocks the process until section is split at the fork poolLogger.Debugf("[%s] locking for fork", sectionName(section)) <-waiter poolLogger.Debugf("[%s] unlocking for fork", sectionName(section)) @@ -897,7 +886,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { poolLogger.Debugf("[%s] process complete done", sectionName(section)) self.wg.Done() - if running { + if peer != nil { self.procWg.Done() } }() @@ -913,45 +902,41 @@ func (self *BlockPool) peerError(peerId string, code int, format string, params } } -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))) - i := 0 - 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 { - break - } + self.wg.Add(1) + self.procWg.Add(1) + go func() { + // 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 } - i++ - } - poolLogger.Debugf("done requesting blocks") - + repetitions := int(math.Min(float64(peerCount), float64(blocksRequestRepetition))) + i := 0 + 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 { + break + } + } + i++ + } + poolLogger.Debugf("done requesting blocks") + self.wg.Done() + self.procWg.Done() + }() } func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { @@ -974,54 +959,32 @@ func (self *peerInfo) addSection(hash []byte, section *section) { self.sections[string(hash)] = section } -// (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) - poolLogger.Debugf("[%s] activate section processes", self.id) - self.controlSections(peer, true) -} - -// (re)starts process without requests, only suicide timer -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) -} - -func (self *peerInfo) controlSections(peer *peerInfo, on bool) { - if peer != nil { - peer.lock.RLock() - defer peer.lock.RUnlock() - } - - 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 +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) } - 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 - poolLogger.Debugf("[%s][%s] section process -> active", self.id, sectionName(section)) - } else { - // (re)starts process without requests, only suicide timer - poolLogger.Debugf("[%s][%s] section process -> inactive", self.id, sectionName(section)) + poolLogger.Debugf("[%s] activate section processes", newPeer.id) + for hash, section := range newPeer.sections { + if section.off { + poolLogger.Debugf("[%s][%x] section process complete - remove", newPeer.id, hash[:4]) + delete(newPeer.sections, hash) + continue } - section.controlC <- on + // this will block if section process is waiting for peer lock + poolLogger.Debugf("[%s][%x] registering peer with section", newPeer.id, hash[:4]) + section.controlC <- newPeer + poolLogger.Debugf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) } + newPeer.quitC = make(chan bool) + } + if oldPeer != nil { + close(oldPeer.quitC) } } @@ -1041,14 +1004,15 @@ func (self *BlockPool) getChild(sec *section) *section { func newSection() (sec *section) { sec = §ion{ - controlC: make(chan bool, 1), - suicideC: make(chan bool, 1), - blockChainC: make(chan bool, 1), + controlC: make(chan *peerInfo), + suicideC: make(chan bool), + blockChainC: make(chan bool), forkC: make(chan chan bool), } return } +// link should only be called under chainLock func (self *BlockPool) link(parent *section, child *section) { if parent != nil { exChild := parent.child From 68335e8aac9fcf3e53fdb73e979e9c2f5640254f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 22:55:50 +0100 Subject: [PATCH 69/91] p2p: print message code alongside decoding errors --- p2p/message.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/message.go b/p2p/message.go index daee17cc12..a6f62ec4c8 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -50,7 +50,10 @@ func encodePayload(params ...interface{}) []byte { // For the decoding rules, please see package rlp. func (msg Msg) Decode(val interface{}) error { s := rlp.NewListStream(msg.Payload, uint64(msg.Size)) - return s.Decode(val) + if err := s.Decode(val); err != nil { + return newPeerError(errInvalidMsg, "(code %#x) (size %d) %v", msg.Code, msg.Size, err) + } + return nil } func (msg Msg) String() string { From c40eebcfd2afcfb2a242c20ee0774323ea5212a9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 4 Jan 2015 23:41:24 +0100 Subject: [PATCH 70/91] rlp: display even more type context in decode errors It now says: rlp: expected input string or byte for uint, decoding into (rlp.recstruct).Child.I --- rlp/decode.go | 57 +++++++++++++++++++++++++++++++--------------- rlp/decode_test.go | 38 +++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/rlp/decode.go b/rlp/decode.go index 712d9fcf18..a2bd042859 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -76,22 +76,37 @@ func Decode(r io.Reader, val interface{}) error { type decodeError struct { msg string typ reflect.Type + ctx []string } -func (err decodeError) Error() string { - return fmt.Sprintf("rlp: %s for %v", err.msg, err.typ) +func (err *decodeError) Error() string { + ctx := "" + if len(err.ctx) > 0 { + ctx = ", decoding into " + for i := len(err.ctx) - 1; i >= 0; i-- { + ctx += err.ctx[i] + } + } + return fmt.Sprintf("rlp: %s for %v%s", err.msg, err.typ, ctx) } func wrapStreamError(err error, typ reflect.Type) error { switch err { case ErrExpectedList: - return decodeError{"expected input list", typ} + return &decodeError{msg: "expected input list", typ: typ} case ErrExpectedString: - return decodeError{"expected input string or byte", typ} + return &decodeError{msg: "expected input string or byte", typ: typ} case errUintOverflow: - return decodeError{"input string too long", typ} + return &decodeError{msg: "input string too long", typ: typ} case errNotAtEOL: - return decodeError{"input list has too many elements", typ} + return &decodeError{msg: "input list has too many elements", typ: typ} + } + return err +} + +func addErrorContext(err error, ctx string) error { + if decErr, ok := err.(*decodeError); ok { + decErr.ctx = append(decErr.ctx, ctx) } return err } @@ -180,13 +195,13 @@ func makeListDecoder(typ reflect.Type) (decoder, error) { return nil, err } - if typ.Kind() == reflect.Array { - return func(s *Stream, val reflect.Value) error { - return decodeListArray(s, val, etypeinfo.decoder) - }, nil - } + isArray := typ.Kind() == reflect.Array return func(s *Stream, val reflect.Value) error { - return decodeListSlice(s, val, etypeinfo.decoder) + if isArray { + return decodeListArray(s, val, etypeinfo.decoder) + } else { + return decodeListSlice(s, val, etypeinfo.decoder) + } }, nil } @@ -219,7 +234,7 @@ func decodeListSlice(s *Stream, val reflect.Value, elemdec decoder) error { if err := elemdec(s, val.Index(i)); err == EOL { break } else if err != nil { - return err + return addErrorContext(err, fmt.Sprint("[", i, "]")) } } if i < val.Len() { @@ -248,7 +263,7 @@ func decodeListArray(s *Stream, val reflect.Value, elemdec decoder) error { if err := elemdec(s, val.Index(i)); err == EOL { break } else if err != nil { - return err + return addErrorContext(err, fmt.Sprint("[", i, "]")) } } if i < vlen { @@ -280,14 +295,14 @@ func decodeByteArray(s *Stream, val reflect.Value) error { switch kind { case Byte: if val.Len() == 0 { - return decodeError{"input string too long", val.Type()} + return &decodeError{msg: "input string too long", typ: val.Type()} } bv, _ := s.Uint() val.Index(0).SetUint(bv) zero(val, 1) case String: if uint64(val.Len()) < size { - return decodeError{"input string too long", val.Type()} + return &decodeError{msg: "input string too long", typ: val.Type()} } slice := val.Slice(0, int(size)).Interface().([]byte) if err := s.readFull(slice); err != nil { @@ -334,7 +349,7 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { // too few elements. leave the rest at their zero value. break } else if err != nil { - return err + return addErrorContext(err, "."+typ.Field(f.index).Name) } } return wrapStreamError(s.ListEnd(), typ) @@ -599,7 +614,13 @@ func (s *Stream) Decode(val interface{}) error { if err != nil { return err } - return info.decoder(s, rval.Elem()) + + err = info.decoder(s, rval.Elem()) + if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 { + // add decode target type to error so context has more meaning + decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")")) + } + return err } // Reset discards any information about the current decoding context diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 7a1743937d..18ea63a090 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -231,7 +231,12 @@ var decodeTests = []decodeTest{ {input: "8D6162636465666768696A6B6C6D", ptr: new([]byte), value: []byte("abcdefghijklm")}, {input: "C0", ptr: new([]byte), value: []byte{}}, {input: "C3010203", ptr: new([]byte), value: []byte{1, 2, 3}}, - {input: "C3820102", ptr: new([]byte), error: "rlp: input string too long for uint8"}, + + { + input: "C3820102", + ptr: new([]byte), + error: "rlp: input string too long for uint8, decoding into ([]uint8)[0]", + }, // byte arrays {input: "01", ptr: new([5]byte), value: [5]byte{1}}, @@ -239,9 +244,22 @@ var decodeTests = []decodeTest{ {input: "850102030405", ptr: new([5]byte), value: [5]byte{1, 2, 3, 4, 5}}, {input: "C0", ptr: new([5]byte), value: [5]byte{}}, {input: "C3010203", ptr: new([5]byte), value: [5]byte{1, 2, 3, 0, 0}}, - {input: "C3820102", ptr: new([5]byte), error: "rlp: input string too long for uint8"}, - {input: "86010203040506", ptr: new([5]byte), error: "rlp: input string too long for [5]uint8"}, - {input: "850101", ptr: new([5]byte), error: io.ErrUnexpectedEOF.Error()}, + + { + input: "C3820102", + ptr: new([5]byte), + error: "rlp: input string too long for uint8, decoding into ([5]uint8)[0]", + }, + { + input: "86010203040506", + ptr: new([5]byte), + error: "rlp: input string too long for [5]uint8", + }, + { + input: "850101", + ptr: new([5]byte), + error: io.ErrUnexpectedEOF.Error(), + }, // byte array reuse (should be zeroed) {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}}, @@ -272,13 +290,23 @@ var decodeTests = []decodeTest{ {input: "C0", ptr: new(simplestruct), value: simplestruct{0, ""}}, {input: "C105", ptr: new(simplestruct), value: simplestruct{5, ""}}, {input: "C50583343434", ptr: new(simplestruct), value: simplestruct{5, "444"}}, - {input: "C3010101", ptr: new(simplestruct), error: "rlp: input list has too many elements for rlp.simplestruct"}, { input: "C501C302C103", ptr: new(recstruct), value: recstruct{1, &recstruct{2, &recstruct{3, nil}}}, }, + { + input: "C3010101", + ptr: new(simplestruct), + error: "rlp: input list has too many elements for rlp.simplestruct", + }, + { + input: "C501C3C00000", + ptr: new(recstruct), + error: "rlp: expected input string or byte for uint, decoding into (rlp.recstruct).Child.I", + }, + // pointers {input: "00", ptr: new(*uint), value: (*uint)(nil)}, {input: "80", ptr: new(*uint), value: (*uint)(nil)}, From 251f37e58945dea32f59b60e4630f897f074602a Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 00:59:11 +0000 Subject: [PATCH 71/91] restore loopback check for self address for peersMsg --- p2p/peer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 72e225c244..0d7eec9f46 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -477,8 +477,7 @@ func (peer *Peer) PeerList() []interface{} { ds = append(ds, addr) } ourAddr := peer.ourListenAddr - if ourAddr != nil && !ourAddr.IP.IsUnspecified() { - // if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { + if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() { ds = append(ds, ourAddr) } return ds From 38edc428eb7dbe9868d2663a355c774b8ca8e17a Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 01:00:23 +0000 Subject: [PATCH 72/91] integration tests - regenerate chains consistently - separate peer (nodial, maxpeer 1, loglevel 0) and test_node - improve scripts, clean up code - include js file, define sleep function - add 02 --- eth/test/bootstrap.sh | 8 ++++---- eth/test/chains/00.chain | Bin 11388 -> 9726 bytes eth/test/chains/01.chain | Bin 15820 -> 13881 bytes eth/test/chains/02.chain | Bin 15266 -> 14989 bytes eth/test/chains/03.chain | Bin 20806 -> 18590 bytes eth/test/chains/04.chain | Bin 18036 -> 20529 bytes eth/test/mine.sh | 14 +++++++------- eth/test/run.sh | 23 +++++++++++------------ eth/test/tests/00.sh | 16 +++++----------- eth/test/tests/01.sh | 25 ++++++++++--------------- eth/test/tests/common.sh | 9 ++++++--- 11 files changed, 43 insertions(+), 52 deletions(-) diff --git a/eth/test/bootstrap.sh b/eth/test/bootstrap.sh index 78114dbe64..3da038be8b 100644 --- a/eth/test/bootstrap.sh +++ b/eth/test/bootstrap.sh @@ -2,8 +2,8 @@ # bootstrap chains - used to regenerate tests/chains/*.chain mkdir -p chains -bash ./mine.sh 00 15 +bash ./mine.sh 00 10 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 +bash ./mine.sh 02 10 00 +bash ./mine.sh 03 5 02 +bash ./mine.sh 04 10 02 \ No newline at end of file diff --git a/eth/test/chains/00.chain b/eth/test/chains/00.chain index b1f53d539848cadb85c7eb8cf5df7e7bf42d2ed7..ad3c05b24af70b3e02afbde9aa0c4911f56432ee 100755 GIT binary patch literal 9726 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
Gm=vJ7a&95t7OOcj%!t-TGvCba{^a2} z*hk~lLSgTML$SAs81jgH+MP_AkjTc{kn;NO&XnQgod|{=o&9Y{EPm4k>%HT_A?5D7V&dP->{(4x~Pk|Gly+Utaq$FKA++RBj^34|FqVKnN5X0d@e|5`{$x mr6Fats&GZvRi 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 TrgZwCaAh%5FbZbH+|u%2k2mOn diff --git a/eth/test/chains/01.chain b/eth/test/chains/01.chain index 1ed47885c1978a9d7949d41f9a9514e5f5cda8a5..56c9aef65f5261789bb709f57bf937d547a4d660 100755 GIT binary patch literal 13881 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 literal 15820 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 ahW1qkqKm@j)fQy-KEG64r_Z~q@*c7t#L diff --git a/eth/test/chains/03.chain b/eth/test/chains/03.chain index 8524881ab35515fc6be7d48f029406bcf95c9fb0..1cc7570ab47355133f5ef06a9a3c89580cf866d3 100755 GIT binary patch literal 18590 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 /tmp/eth.test/mine.tmp & PID=$! sleep 1 -echo "killing $PID" kill $PID +cat /tmp/eth.test/mine.tmp | grep 'exporting' diff --git a/eth/test/run.sh b/eth/test/run.sh index bef8c80bfa..bd5c753a99 100644 --- a/eth/test/run.sh +++ b/eth/test/run.sh @@ -3,10 +3,12 @@ # runs tests tests/testid0.sh tests/testid1.sh ... # without arguments, it runs all tests +. tests/common.sh + if [ "$#" -eq 0 ]; then for file in tests/*.sh; do i=`basename $file .sh` - TESTS="$TESTS $i" + TESTS="$TESTS $NAME" done else TESTS=$@ @@ -16,19 +18,18 @@ ETH=../../ethereum DIR="/tmp/eth.test/nodes" TIMEOUT=10 -mkdir -p $DIR mkdir -p $DIR/js echo "running tests $TESTS" -for i in $TESTS; do +for NAME in $TESTS; do PIDS= - CHAIN="tests/$i.chain" - JSFILE="$DIR/$i/js" - CHAIN_TEST="$DIR/$i/chain" + CHAIN="tests/$NAME.chain" + JSFILE="$DIR/js/$NAME.js" + CHAIN_TEST="$DIR/$NAME/chain" - # OUT="$DIR/out" - echo "RUN: test $i" - . tests/$i.sh + echo "RUN: test $NAME" + cat tests/common.js > $JSFILE + . tests/$NAME.sh sleep $TIMEOUT echo "timeout after $TIMEOUT seconds: killing $PIDS" kill $PIDS @@ -47,6 +48,4 @@ for i in $TESTS; do else echo PASS fi -done - - +done \ No newline at end of file diff --git a/eth/test/tests/00.sh b/eth/test/tests/00.sh index 9d13acb586..9c5077164b 100644 --- a/eth/test/tests/00.sh +++ b/eth/test/tests/00.sh @@ -1,19 +1,13 @@ #!/bin/bash -. `dirname $BASH_SOURCE`/common.sh TIMEOUT=4 -ID=00 -JSFILE="$DIR/js/$ID.js" -echo $JSFILE -cat > $JSFILE <> $JSFILE < $JSFILE <> $JSFILE < Date: Mon, 5 Jan 2015 01:06:34 +0000 Subject: [PATCH 73/91] add dial bool flag to prevent test nodes from dialing out (maxpeers not enough, cos server dialloop steals first slot for dialing) --- cmd/ethereum/flags.go | 2 ++ cmd/ethereum/main.go | 1 + eth/backend.go | 4 +++- p2p/server.go | 2 ++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/ethereum/flags.go b/cmd/ethereum/flags.go index 5e32562085..275fcf248c 100644 --- a/cmd/ethereum/flags.go +++ b/cmd/ethereum/flags.go @@ -60,6 +60,7 @@ var ( VmType int ImportChain string SHH bool + Dial bool ) // flags specific to cli client @@ -96,6 +97,7 @@ func Init() { 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(&Dial, "dial", true, "dial out connections (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 85ed20171b..8b83bbd376 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -65,6 +65,7 @@ func main() { PMPGateway: PMPGateway, KeyRing: KeyRing, Shh: SHH, + Dial: Dial, }) if err != nil { diff --git a/eth/backend.go b/eth/backend.go index 4c24a5b1b5..72149ad2be 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -36,7 +36,8 @@ type Config struct { NATType string PMPGateway string - Shh bool + Shh bool + Dial bool KeyManager *crypto.KeyManager } @@ -150,6 +151,7 @@ func New(config *Config) (*Ethereum, error) { Protocols: protocols, Blacklist: eth.blacklist, NAT: nat, + NoDial: !config.Dial, } if len(config.Port) > 0 { diff --git a/p2p/server.go b/p2p/server.go index c6bb8c561a..7ba41cb53d 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -189,6 +189,7 @@ func (srv *Server) Start() (err error) { // make all slots available for i := range srv.peers { + srvlog.Debugf("add slot %v", i) srv.peerSlots <- i } // note: discLoop is not part of WaitGroup @@ -260,6 +261,7 @@ func (srv *Server) listenLoop() { for { select { case slot := <-srv.peerSlots: + srvlog.Debugf("grabbed slot %v for listening", slot) conn, err := srv.listener.Accept() if err != nil { srv.peerSlots <- slot From 5720bc6e2f5dec106acfa3d213cbb95b0d6376b4 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 02:18:46 +0000 Subject: [PATCH 74/91] complete sections on activate chain loop - no longer blocks on complete section. - reworked complete section signaling with channel closing instead of locking - make connect to blockchain safe by checking closed channel , --- eth/block_pool.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index a44b69a6c0..f0c74d77a3 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -95,7 +95,7 @@ type section struct { suicideC chan bool blockChainC chan bool forkC chan chan bool - off bool + offC chan bool } func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(pow.Block) bool, @@ -472,12 +472,12 @@ func (self *BlockPool) AddBlock(block *types.Block, peerId string) { } func (self *BlockPool) connectToBlockChain(section *section) { - section.lock.RLock() poolLogger.Debugf("connect to blockchain...") - defer section.lock.RUnlock() - if section.off { + select { + case <-section.offC: self.addSectionToBlockChain(section) - } else { + case <-section.blockChainC: + default: close(section.blockChainC) } poolLogger.Debugf("connect to blockchain done") @@ -531,7 +531,10 @@ LOOP: 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 <- peer + select { + case section.controlC <- peer: + case <-section.offC: + } i++ section = self.getParent(section) select { @@ -879,11 +882,8 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { } // 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)) + close(section.offC) + poolLogger.Debugf("[%s] process complete", sectionName(section)) self.wg.Done() if peer != nil { @@ -971,15 +971,14 @@ func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { } poolLogger.Debugf("[%s] activate section processes", newPeer.id) for hash, section := range newPeer.sections { - if section.off { + // this will block if section process is waiting for peer lock + select { + case <-section.offC: poolLogger.Debugf("[%s][%x] section process complete - remove", newPeer.id, hash[:4]) delete(newPeer.sections, hash) - continue + case section.controlC <- newPeer: + poolLogger.Debugf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) } - // this will block if section process is waiting for peer lock - poolLogger.Debugf("[%s][%x] registering peer with section", newPeer.id, hash[:4]) - section.controlC <- newPeer - poolLogger.Debugf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) } newPeer.quitC = make(chan bool) } @@ -1007,6 +1006,7 @@ func newSection() (sec *section) { controlC: make(chan *peerInfo), suicideC: make(chan bool), blockChainC: make(chan bool), + offC: make(chan bool), forkC: make(chan chan bool), } return From 1278173372a014df469c983f8b51c2b546a97cdb Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 03:20:01 +0000 Subject: [PATCH 75/91] set lastMissing count to depth, so that a reinitialised section connected to the blockchain triggers insertChain even if section completes - wow --- eth/block_pool.go | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index f0c74d77a3..76d4afb2b8 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -585,7 +585,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // container for missing block hashes var hashes [][]byte - var i, total, missing, lastMissing, depth int + var i, missing, lastMissing, depth int var idle int var init, done, same, ready bool var insertChain bool @@ -622,14 +622,14 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // 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) + poolLogger.Debugf("[%s] got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) blocksRequestsComplete = true blocksRequestTimer = nil blocksRequestTime = false } else { // some missing blocks blocksRequests++ - poolLogger.Debugf("[%s] block request attempt %v: missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + poolLogger.Debugf("[%s] block request attempt %v: missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) if len(hashes) > 0 { // send block requests to peers self.requestBlocks(blocksRequests, hashes) @@ -643,7 +643,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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) + poolLogger.Debugf("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionName(section), idle, blocksRequests, missing, lastMissing, depth) close(section.suicideC) } } else { @@ -668,7 +668,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // if ready && blocksRequestTime && !blocksRequestsComplete { - poolLogger.Debugf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + poolLogger.Debugf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) blocksRequestTimer = time.After(blocksRequestInterval * time.Millisecond) blocksRequestTime = false processC = offC @@ -710,7 +710,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { case <-suicideTimer: close(section.suicideC) - poolLogger.Debugf("[%s] timeout. (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, total, depth) + poolLogger.Debugf("[%s] timeout. (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) case <-section.suicideC: poolLogger.Debugf("[%s] suicide", sectionName(section)) @@ -744,7 +744,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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) + poolLogger.Debugf("[%s] off (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) } blocksRequestTime = false @@ -777,12 +777,10 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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) + lastMissing = depth // if not run at least once fully, launch iterator go func() { var node *poolNode @@ -824,10 +822,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { init = true done = true processC = make(chan *poolNode, missing) - - total = missing - - poolLogger.Debugf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, total, depth) + poolLogger.Debugf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, lastMissing, depth) continue LOOP } if ready { @@ -857,8 +852,8 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { insertChain = true } } - poolLogger.Debugf("[%s] %v/%v/%v/%v", sectionName(section), i, missing, total, depth) - if i == lastMissing { + poolLogger.Debugf("[%s] %v/%v/%v/%v", sectionName(section), i, missing, lastMissing, depth) + if i == lastMissing && init { poolLogger.Debugf("[%s] done", sectionName(section)) done = true } @@ -880,7 +875,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { } // 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] quit: %v block hashes requests - %v block requests - missing %v/%v/%v", sectionName(section), blockHashesRequests, blocksRequests, missing, lastMissing, depth) close(section.offC) poolLogger.Debugf("[%s] process complete", sectionName(section)) From 89763b33a782a43bc7d99535be43e7e74b74b833 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 04:03:57 +0000 Subject: [PATCH 76/91] optimize section reactivation --- eth/block_pool.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index 76d4afb2b8..a128ebced0 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -527,9 +527,11 @@ func (self *BlockPool) activateChain(section *section, peer *peerInfo) { i := 0 LOOP: for section != nil { - // register this section with the peer + // register this section with the peer and quit if registered poolLogger.Debugf("[%s] register section with peer %s", sectionName(section), peer.id) - peer.addSection(section.top.hash, section) + if peer.addSection(section.top.hash, section) == section { + return + } poolLogger.Debugf("[%s] activate section process", sectionName(section)) select { case section.controlC <- peer: @@ -947,11 +949,14 @@ func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { return info, false } -func (self *peerInfo) addSection(hash []byte, section *section) { +func (self *peerInfo) addSection(hash []byte, section *section) (found *section) { self.lock.Lock() defer self.lock.Unlock() + key := string(hash) + found = self.sections[key] poolLogger.Debugf("section process %s added to %s", sectionName(section), self.id) - self.sections[string(hash)] = section + self.sections[key] = section + return } func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { From 05f122fec527ffc0cf2e77e933639f9bb6e6d72c Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 04:05:45 +0000 Subject: [PATCH 77/91] add common js to test with sleep func --- eth/test/tests/common.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 eth/test/tests/common.js diff --git a/eth/test/tests/common.js b/eth/test/tests/common.js new file mode 100644 index 0000000000..206ebf1455 --- /dev/null +++ b/eth/test/tests/common.js @@ -0,0 +1,9 @@ +function log(text) { + console.log("[JS TEST SCRIPT] " + text); +} + +function sleep(seconds) { + var now = new Date().getTime(); + while(new Date().getTime() < now + seconds){} +} + From 2391eef7d644eeeba945c460c2d1efd58888d84b Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 04:06:23 +0000 Subject: [PATCH 78/91] add result blockchain to test 01 --- eth/test/tests/01.chain | 1 + 1 file changed, 1 insertion(+) create mode 120000 eth/test/tests/01.chain diff --git a/eth/test/tests/01.chain b/eth/test/tests/01.chain new file mode 120000 index 0000000000..ae65ccb37d --- /dev/null +++ b/eth/test/tests/01.chain @@ -0,0 +1 @@ +../chains/02.chain \ No newline at end of file From 4f1f021ede9276c6e376f801579049be9e2d094c Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 04:06:56 +0000 Subject: [PATCH 79/91] add peer switch back integration test 02 --- eth/test/tests/02.chain | 1 + eth/test/tests/02.sh | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 120000 eth/test/tests/02.chain create mode 100644 eth/test/tests/02.sh diff --git a/eth/test/tests/02.chain b/eth/test/tests/02.chain new file mode 120000 index 0000000000..9655cb3df7 --- /dev/null +++ b/eth/test/tests/02.chain @@ -0,0 +1 @@ +../chains/01.chain \ No newline at end of file diff --git a/eth/test/tests/02.sh b/eth/test/tests/02.sh new file mode 100644 index 0000000000..5231dbd788 --- /dev/null +++ b/eth/test/tests/02.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +TIMEOUT=6 + +cat >> $JSFILE < Date: Mon, 5 Jan 2015 04:30:59 +0000 Subject: [PATCH 80/91] add debug logging to peers in integration test --- eth/test/tests/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/test/tests/common.sh b/eth/test/tests/common.sh index 005fd317a3..74db073f73 100644 --- a/eth/test/tests/common.sh +++ b/eth/test/tests/common.sh @@ -16,5 +16,5 @@ function test_node { } function peer { - test_node $@ -loglevel 0 -maxpeer 1 -dial=false + test_node $@ -loglevel 5 -logfile debug.log -maxpeer 1 -dial=false } \ No newline at end of file From eb7db451baa6575fa93aa7adaa0c3a2502a6d17b Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 04:31:40 +0000 Subject: [PATCH 81/91] add 12k chain test (chain not checked in) --- eth/test/tests/03.chain | 1 + eth/test/tests/03.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 120000 eth/test/tests/03.chain create mode 100644 eth/test/tests/03.sh diff --git a/eth/test/tests/03.chain b/eth/test/tests/03.chain new file mode 120000 index 0000000000..b07c49a309 --- /dev/null +++ b/eth/test/tests/03.chain @@ -0,0 +1 @@ +../chains/12k.chain \ No newline at end of file diff --git a/eth/test/tests/03.sh b/eth/test/tests/03.sh new file mode 100644 index 0000000000..8c9d6565ef --- /dev/null +++ b/eth/test/tests/03.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +TIMEOUT=35 + +cat >> $JSFILE < Date: Mon, 5 Jan 2015 04:58:34 +0000 Subject: [PATCH 82/91] fix new block broadcast - was wrong subscription --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index 72149ad2be..c84e69a4e9 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -307,7 +307,7 @@ func (self *Ethereum) txBroadcastLoop() { func (self *Ethereum) blockBroadcastLoop() { // automatically stops if unsubscribe - for obj := range self.txSub.Chan() { + for obj := range self.blockSub.Chan() { switch ev := obj.(type) { case core.NewMinedBlockEvent: self.net.Broadcast("eth", NewBlockMsg, ev.Block.RlpData()) From e39273ef50ef740a3e9c8b1f5eeabe55d4dab9d4 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 05:31:42 +0000 Subject: [PATCH 83/91] remove debug logging --- eth/protocol.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index f26350c87c..753e89a841 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -105,7 +105,6 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo for { err = self.handle() if err != nil { - fmt.Printf("handle err %v", err) self.blockPool.RemovePeer(self.id) break } @@ -119,7 +118,6 @@ 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) } @@ -150,7 +148,6 @@ func (self *ethProtocol) handle() error { case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains - protologger.Debugf("payload size %v", msg.Size) msgStream := rlp.NewStream(msg.Payload) var err error var i int From 81f83726114fd24f2669b8039487e0dfa4e0395e Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 05:33:42 +0000 Subject: [PATCH 84/91] add TD encoding for mined block for newBlockMsg --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index c84e69a4e9..57e24f36a0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -310,7 +310,7 @@ func (self *Ethereum) blockBroadcastLoop() { for obj := range self.blockSub.Chan() { switch ev := obj.(type) { case core.NewMinedBlockEvent: - self.net.Broadcast("eth", NewBlockMsg, ev.Block.RlpData()) + self.net.Broadcast("eth", NewBlockMsg, ev.Block.RlpData(), ev.Block.Td) } } } From aec5c3857f8cba7dae479ce4ba802590a1cf0ce0 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 05:46:51 +0000 Subject: [PATCH 85/91] fix iterator passed when singleton blockHash added when new block arrives --- eth/protocol.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 753e89a841..6e072f3765 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -216,12 +216,12 @@ func (self *ethProtocol) handle() error { // (or selected as new best peer) if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { called := true - iter := func() (hash []byte, ok bool) { + iter := func() ([]byte, bool) { if called { called = false return hash, true } else { - return + return nil, false } } self.blockPool.AddBlockHashes(iter, self.id) From 8477815ad2f908dd0bcc76408ce997ef382b5a76 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 05:52:39 +0000 Subject: [PATCH 86/91] added tests for several simultaneous mining nodes while downloading --- eth/test/tests/04.sh | 17 +++++++++++++++++ eth/test/tests/05.sh | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 eth/test/tests/04.sh create mode 100644 eth/test/tests/05.sh diff --git a/eth/test/tests/04.sh b/eth/test/tests/04.sh new file mode 100644 index 0000000000..d77c360ba9 --- /dev/null +++ b/eth/test/tests/04.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +TIMEOUT=15 + +cat >> $JSFILE <> $JSFILE < Date: Mon, 5 Jan 2015 05:54:45 +0000 Subject: [PATCH 87/91] clean up debug logger in protocol --- eth/protocol.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/eth/protocol.go b/eth/protocol.go index 6e072f3765..b67e5aaeab 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -8,13 +8,10 @@ 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/p2p" "github.com/ethereum/go-ethereum/rlp" ) -var protologger = ethlogger.NewLogger("ETH") - const ( ProtocolVersion = 51 NetworkId = 0 @@ -143,7 +140,6 @@ func (self *ethProtocol) handle() error { return self.protoError(ErrDecode, "->msg %v: %v", msg, err) } hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) - protologger.Debugf("hashes length %v", len(hashes)) return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: From 40f54da68077097e98c11dd481cd20fa094fb676 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 06:52:37 +0000 Subject: [PATCH 88/91] add README to integration testing --- eth/test/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 eth/test/README.md diff --git a/eth/test/README.md b/eth/test/README.md new file mode 100644 index 0000000000..65728efa5b --- /dev/null +++ b/eth/test/README.md @@ -0,0 +1,27 @@ += Integration tests for eth protocol and blockpool + +This is a simple suite of tests to fire up a local test node with peers to test blockchain synchronisation and download. +The scripts call ethereum (assumed to be compiled in go-ethereum root). + +To run a test: + + . run.sh 00 02 + +Without arguments, all tests are run. + +Peers are launched with preloaded imported chains. In order to prevent them from synchronizing with each other they are set with `-dial=false` and `-maxpeer 1` options. They log into `/tmp/eth.test/nodes/XX` where XX is the last two digits of their port. + +Chains to import can be bootstrapped by letting nodes mine for some time. This is done with + + . bootstrap.sh + +Only the relative timing and forks matter so they should work if the bootstrap script is rerun. +The reference blockchain of tests are soft links to these import chains and check at the end of a test run. + +Connecting to peers and exporting blockchain is scripted with JS files executed by the JSRE, see `tests/XX.sh`. + +Each test is set with a timeout. This may vary on different computers so adjust sensibly. +If you kill a test before it completes, do not forget to kill all the background processes, since they will impact the result. Use: + + killall ethereum + From 3eeb3cd156abbe3a61ad48a5a8f1b041863ae0e7 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 06:53:03 +0000 Subject: [PATCH 89/91] fix run called without args --- eth/test/run.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eth/test/run.sh b/eth/test/run.sh index bd5c753a99..5229af0353 100644 --- a/eth/test/run.sh +++ b/eth/test/run.sh @@ -5,10 +5,12 @@ . tests/common.sh +TESTS= + if [ "$#" -eq 0 ]; then - for file in tests/*.sh; do - i=`basename $file .sh` - TESTS="$TESTS $NAME" + for NAME in tests/??.sh; do + i=`basename $NAME .sh` + TESTS="$TESTS $i" done else TESTS=$@ From fbb72c893c6ad56ffdeec9fa7b6cd195ca66a561 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 06:56:14 +0000 Subject: [PATCH 90/91] cleanup debug logs, put most debug as debugdetail --- eth/block_pool.go | 125 ++++++++++++++++------------------------------ 1 file changed, 43 insertions(+), 82 deletions(-) diff --git a/eth/block_pool.go b/eth/block_pool.go index a128ebced0..519c9fc13c 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -139,7 +139,7 @@ func (self *BlockPool) Stop() { self.lock.Unlock() - poolLogger.Infoln("Stopping") + poolLogger.Infoln("Stopping...") close(self.quit) self.wg.Wait() @@ -183,7 +183,7 @@ func (self *BlockPool) Wait(t time.Duration) { } self.lock.Unlock() - poolLogger.Infoln("waiting for processes to complete...") + poolLogger.Infoln("Waiting for processes to complete...") close(self.flushC) w := make(chan bool) go func() { @@ -193,14 +193,11 @@ func (self *BlockPool) Wait(t time.Duration) { select { case <-w: + poolLogger.Infoln("Processes complete") case <-time.After(t): - poolLogger.Debugf("completion timeout") + poolLogger.Warnf("Timeout") } - self.flushC = make(chan bool) - - poolLogger.Infoln("processes complete") - } // AddPeer is called by the eth protocol instance running on the peer after @@ -212,7 +209,7 @@ func (self *BlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string, defer self.peersLock.Unlock() peer, ok := self.peers[peerId] if ok { - poolLogger.Debugf("update peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) + poolLogger.Debugf("Update peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) peer.td = td peer.currentBlock = currentBlock } else { @@ -280,9 +277,9 @@ func (self *BlockPool) RemovePeer(peerId string) { self.peer = newPeer self.switchPeer(peer, newPeer) if newPeer != nil { - poolLogger.Infof("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td) + poolLogger.Debugf("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td) } else { - poolLogger.Warnln("no peers left") + poolLogger.Warnln("no peers") } } } @@ -326,7 +323,7 @@ LOOP: } if self.hasBlock(hash) { // check if known block connecting the downloaded chain to our blockchain - poolLogger.Debugf("[%s] known block", name(hash)) + poolLogger.DebugDetailf("[%s] known block", name(hash)) // mark child as absolute pool root with parent known to blockchain if section != nil { self.connectToBlockChain(section) @@ -340,20 +337,17 @@ 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)) + poolLogger.DebugDetailf("[%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, @@ -366,12 +360,11 @@ LOOP: } //for self.chainLock.Lock() - poolLogger.Debugf("lock chain lock") - poolLogger.Debugf("read %v hashes added by %s", n, peerId) + poolLogger.DebugDetailf("added %v hashes sent by %s", n, peerId) if parent != nil && entry != nil && entry.node != parent.top { - poolLogger.Debugf("[%s] fork section", sectionName(parent)) + poolLogger.DebugDetailf("[%s] split section at fork", sectionName(parent)) parent.controlC <- nil waiter := make(chan bool) parent.forkC <- waiter @@ -387,27 +380,25 @@ LOOP: if size > 0 { self.processSection(section, nodes) - poolLogger.Debugf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(section), size, sectionName(child)) + poolLogger.DebugDetailf("[%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)) + poolLogger.DebugDetailf("[%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 && peer != 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)) + poolLogger.Debugf("[%s] activate parent section [%s]", 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 <- peer + poolLogger.Debugf("[%s] activate new section", sectionName(section)) } } @@ -435,14 +426,13 @@ func sectionName(section *section) (name string) { // only the first PoW-valid block for a hash is considered legit func (self *BlockPool) AddBlock(block *types.Block, peerId string) { hash := block.Hash() - poolLogger.Debugf("adding block [%s] by peer %s", name(hash), peerId) if self.hasBlock(hash) { - poolLogger.Debugf("block [%s] already known", name(hash)) + poolLogger.DebugDetailf("block [%s] already known", name(hash)) return } entry := self.get(hash) if entry == nil { - poolLogger.Debugf("unrequested block [%x] by peer %s", hash, peerId) + poolLogger.Warnf("unrequested block [%x] by peer %s", hash, peerId) self.peerError(peerId, ErrUnrequestedBlock, "%x", hash) return } @@ -450,29 +440,27 @@ func (self *BlockPool) AddBlock(block *types.Block, peerId string) { 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) + poolLogger.DebugDetailf("block [%x] already sent by %s", name(hash), node.blockBy) return } // validate block for PoW if !self.verifyPoW(block) { - poolLogger.Debugf("invalid pow on block [%x] by peer %s", hash, peerId) + poolLogger.Warnf("invalid pow on block [%x] by peer %s", hash, peerId) self.peerError(peerId, ErrInvalidPoW, "%x", hash) return } - poolLogger.Debugf("added block [%s] by peer %s", name(hash), peerId) + poolLogger.Debugf("added block [%s] sent by peer %s", name(hash), peerId) node.block = block node.blockBy = peerId } func (self *BlockPool) connectToBlockChain(section *section) { - poolLogger.Debugf("connect to blockchain...") select { case <-section.offC: self.addSectionToBlockChain(section) @@ -480,7 +468,6 @@ func (self *BlockPool) connectToBlockChain(section *section) { default: close(section.blockChainC) } - poolLogger.Debugf("connect to blockchain done") } func (self *BlockPool) addSectionToBlockChain(section *section) (rest int, err error) { @@ -508,14 +495,14 @@ func (self *BlockPool) addSectionToBlockChain(section *section) (rest int, err e } self.lock.Unlock() - poolLogger.Debugf("insert %v blocks into blockchain", len(blocks)) + poolLogger.Infof("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) + poolLogger.Warnf("invalid block %x", node.hash) + poolLogger.Warnf("penalise peers %v (hash), %v (block)", node.peer, node.blockBy) // penalise peer in node.blockBy // self.disconnect() } @@ -523,16 +510,16 @@ func (self *BlockPool) addSectionToBlockChain(section *section) (rest int, err e } func (self *BlockPool) activateChain(section *section, peer *peerInfo) { - poolLogger.Debugf("[%s] activate known chain for peer %s", sectionName(section), peer.id) + poolLogger.DebugDetailf("[%s] activate known chain for peer %s", sectionName(section), peer.id) i := 0 LOOP: for section != nil { // register this section with the peer and quit if registered - poolLogger.Debugf("[%s] register section with peer %s", sectionName(section), peer.id) + poolLogger.DebugDetailf("[%s] register section with peer %s", sectionName(section), peer.id) if peer.addSection(section.top.hash, section) == section { return } - poolLogger.Debugf("[%s] activate section process", sectionName(section)) + poolLogger.DebugDetailf("[%s] activate section process", sectionName(section)) select { case section.controlC <- peer: case <-section.offC: @@ -567,7 +554,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { section.bottom = nodes[len(nodes)-1] section.top = nodes[0] section.nodes = nodes - poolLogger.Debugf("[%s] setup section process", sectionName(section)) + poolLogger.DebugDetailf("[%s] setup section process", sectionName(section)) self.wg.Add(1) go func() { @@ -624,20 +611,18 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // 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, lastMissing, depth) + poolLogger.DebugDetailf("[%s] got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) blocksRequestsComplete = true blocksRequestTimer = nil blocksRequestTime = false } else { // some missing blocks blocksRequests++ - poolLogger.Debugf("[%s] block request attempt %v: missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, 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 { @@ -645,7 +630,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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, lastMissing, depth) + poolLogger.DebugDetailf("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionName(section), idle, blocksRequests, missing, lastMissing, depth) close(section.suicideC) } } else { @@ -656,7 +641,6 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { same = false } } - poolLogger.Debugf("[%s] done checking missing blocks", sectionName(section)) lastMissing = missing ready = true done = false @@ -665,22 +649,20 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { missingC = processC // put processC offline processC = nil - // poolLogger.Debugf("[%s] ready for round %v", sectionName(section), blocksRequests) } // if ready && blocksRequestTime && !blocksRequestsComplete { - poolLogger.Debugf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) + poolLogger.DebugDetailf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, 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("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(section), blockHashesRequests) + poolLogger.DebugDetailf("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(section), blockHashesRequests) blockHashesRequestTimer = nil blockHashesRequestsComplete = true } else { @@ -690,13 +672,9 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Millisecond) } blockHashesRequestTime = false - poolLogger.Debugf("[%s] hash request done", sectionName(section)) - } - poolLogger.Debugf("[%s] select", sectionName(section)) select { - case <-self.quit: break LOOP @@ -732,11 +710,11 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { break LOOP case <-blocksRequestTimer: - poolLogger.Debugf("[%s] block request time again", sectionName(section)) + poolLogger.DebugDetailf("[%s] block request time", sectionName(section)) blocksRequestTime = true case <-blockHashesRequestTimer: - poolLogger.Debugf("[%s] hash request time again", sectionName(section)) + poolLogger.DebugDetailf("[%s] hash request time", sectionName(section)) blockHashesRequestTime = true case newPeer = <-section.controlC: @@ -744,11 +722,9 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // active -> idle if peer != nil && newPeer == nil { 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, lastMissing, depth) + poolLogger.Debugf("[%s] idle mode (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) } - blocksRequestTime = false blocksRequestTimer = nil blockHashesRequestTime = false @@ -764,19 +740,15 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { self.procWg.Add(1) 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 !blockHashesRequestsComplete { - poolLogger.Debugf("[%s] activate block hashes requests", sectionName(section)) blockHashesRequestTime = true } if !init { processC = make(chan *poolNode, blockHashesBatchSize) missingC = make(chan *poolNode, blockHashesBatchSize) - poolLogger.Debugf("[%s] initialise section", sectionName(section)) i = 0 missing = 0 self.wg.Add(1) @@ -811,9 +783,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { case waiter := <-section.forkC: // this case just blocks the process until section is split at the fork - poolLogger.Debugf("[%s] locking for fork", sectionName(section)) <-waiter - poolLogger.Debugf("[%s] unlocking for fork", sectionName(section)) init = false done = false ready = false @@ -824,7 +794,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { init = true done = true processC = make(chan *poolNode, missing) - poolLogger.Debugf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, lastMissing, depth) + poolLogger.DebugDetailf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, lastMissing, depth) continue LOOP } if ready { @@ -832,14 +802,12 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { missing = 0 ready = false } - poolLogger.Debugf("[%s] process node %v [%x]", sectionName(section), i, node.hash[:4]) i++ // if node has no block node.lock.RLock() block := node.block node.lock.RUnlock() if block == nil { - poolLogger.Debugf("[%s] block missing on [%x]", sectionName(section), node.hash[:4]) missing++ hashes = append(hashes, node.hash) if len(hashes) == blockBatchSize { @@ -850,13 +818,11 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { missingC <- node } else { if blockChainC == nil && i == lastMissing { - poolLogger.Debugf("[%s] insert blocks starting from [%s]", sectionName(section), name(node.hash)) insertChain = true } } poolLogger.Debugf("[%s] %v/%v/%v/%v", sectionName(section), i, missing, lastMissing, depth) if i == lastMissing && init { - poolLogger.Debugf("[%s] done", sectionName(section)) done = true } @@ -877,10 +843,9 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { } // select } // for - poolLogger.Debugf("[%s] quit: %v block hashes requests - %v block requests - missing %v/%v/%v", sectionName(section), blockHashesRequests, blocksRequests, missing, lastMissing, depth) + poolLogger.Debugf("[%s] section complete: %v block hashes requests - %v block requests - missing %v/%v/%v", sectionName(section), blockHashesRequests, blocksRequests, missing, lastMissing, depth) close(section.offC) - poolLogger.Debugf("[%s] process complete", sectionName(section)) self.wg.Done() if peer != nil { @@ -904,7 +869,6 @@ func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { self.procWg.Add(1) go func() { // distribute block request among known peers - poolLogger.Debugf("request blocks") self.peersLock.Lock() defer self.peersLock.Unlock() peerCount := len(self.peers) @@ -921,7 +885,7 @@ func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { 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) + poolLogger.Debugf("request %v missing blocks from peer %s", len(hashes), peer.id) peer.requestBlocks(hashes) indexes = indexes[1:] if len(indexes) == 0 { @@ -930,7 +894,6 @@ func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { } i++ } - poolLogger.Debugf("done requesting blocks") self.wg.Done() self.procWg.Done() }() @@ -954,7 +917,7 @@ func (self *peerInfo) addSection(hash []byte, section *section) (found *section) defer self.lock.Unlock() key := string(hash) found = self.sections[key] - poolLogger.Debugf("section process %s added to %s", sectionName(section), self.id) + poolLogger.DebugDetailf("[%s] section process %s registered", sectionName(section), self.id) self.sections[key] = section return } @@ -969,15 +932,15 @@ func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { 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) } - poolLogger.Debugf("[%s] activate section processes", newPeer.id) + poolLogger.DebugDetailf("[%s] activate section processes", newPeer.id) for hash, section := range newPeer.sections { // this will block if section process is waiting for peer lock select { case <-section.offC: - poolLogger.Debugf("[%s][%x] section process complete - remove", newPeer.id, hash[:4]) + poolLogger.DebugDetailf("[%s][%x] section process complete - remove", newPeer.id, hash[:4]) delete(newPeer.sections, hash) case section.controlC <- newPeer: - poolLogger.Debugf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) + poolLogger.DebugDetailf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) } } newPeer.quitC = make(chan bool) @@ -988,10 +951,8 @@ func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { } func (self *BlockPool) getParent(sec *section) *section { - poolLogger.Debugf("[") self.chainLock.RLock() defer self.chainLock.RUnlock() - poolLogger.Debugf("]") return sec.parent } @@ -1018,14 +979,14 @@ func (self *BlockPool) link(parent *section, child *section) { exChild := parent.child parent.child = child if exChild != nil && exChild != child { - poolLogger.Debugf("[%s] FORK [%s] -> [%s]", sectionName(parent), sectionName(exChild), sectionName(child)) + poolLogger.Debugf("[%s] chain fork [%s] -> [%s]", sectionName(parent), sectionName(exChild), sectionName(child)) exChild.parent = nil } } if child != nil { exParent := child.parent if exParent != nil && exParent != parent { - poolLogger.Debugf("[%s] REV FORK [%s] -> [%s]", sectionName(child), sectionName(exParent), sectionName(parent)) + poolLogger.Debugf("[%s] chain reverse fork [%s] -> [%s]", sectionName(child), sectionName(exParent), sectionName(parent)) exParent.child = nil } child.parent = parent From 871dc14266021198a7fd5eaf3d8cca0d684c2d71 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 5 Jan 2015 07:04:00 +0000 Subject: [PATCH 91/91] remove debug log line --- p2p/server.go | 1 - 1 file changed, 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index 7ba41cb53d..cfff442f71 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -189,7 +189,6 @@ func (srv *Server) Start() (err error) { // make all slots available for i := range srv.peers { - srvlog.Debugf("add slot %v", i) srv.peerSlots <- i } // note: discLoop is not part of WaitGroup