From 6abf8ef78f1474fdeb7a6a6ce084bf994cc055f2 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 5 Jan 2015 17:10:42 +0100 Subject: [PATCH] Merge --- cmd/ethereum/flags.go | 6 +- cmd/ethereum/main.go | 16 +- core/transaction_pool.go | 54 +- core/types/block.go | 79 +- eth/backend.go | 47 +- eth/block_pool.go | 1417 +++++++++++++++++------------------ eth/block_pool_test.go | 930 ++++++++++++++++++++--- eth/error.go | 11 +- eth/protocol.go | 100 +-- eth/protocol_test.go | 336 +++++---- eth/test/README.md | 27 + eth/test/bootstrap.sh | 9 + eth/test/chains/00.chain | Bin 0 -> 9726 bytes eth/test/chains/01.chain | Bin 0 -> 13881 bytes eth/test/chains/02.chain | Bin 0 -> 14989 bytes eth/test/chains/03.chain | Bin 0 -> 18590 bytes eth/test/chains/04.chain | Bin 0 -> 20529 bytes eth/test/mine.sh | 20 + eth/test/run.sh | 53 ++ eth/test/tests/00.chain | 1 + eth/test/tests/00.sh | 13 + eth/test/tests/01.chain | 1 + eth/test/tests/01.sh | 18 + eth/test/tests/02.chain | 1 + eth/test/tests/02.sh | 19 + eth/test/tests/03.chain | 1 + eth/test/tests/03.sh | 14 + eth/test/tests/04.sh | 17 + eth/test/tests/05.sh | 20 + eth/test/tests/common.js | 9 + eth/test/tests/common.sh | 20 + p2p/client_identity_test.go | 2 +- p2p/message.go | 10 +- p2p/peer.go | 28 +- p2p/peer_test.go | 14 +- p2p/protocol.go | 54 +- p2p/protocol_test.go | 82 +- p2p/server.go | 6 +- p2p/server_test.go | 2 +- rlp/decode.go | 57 +- rlp/decode_test.go | 38 +- 41 files changed, 2329 insertions(+), 1203 deletions(-) create mode 100644 eth/test/README.md 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 120000 eth/test/tests/01.chain create mode 100644 eth/test/tests/01.sh create mode 120000 eth/test/tests/02.chain create mode 100644 eth/test/tests/02.sh create mode 120000 eth/test/tests/03.chain create mode 100644 eth/test/tests/03.sh create mode 100644 eth/test/tests/04.sh create mode 100644 eth/test/tests/05.sh create mode 100644 eth/test/tests/common.js create mode 100644 eth/test/tests/common.sh diff --git a/cmd/ethereum/flags.go b/cmd/ethereum/flags.go index d27b739c36..275fcf248c 100644 --- a/cmd/ethereum/flags.go +++ b/cmd/ethereum/flags.go @@ -59,6 +59,8 @@ var ( DumpNumber int VmType int ImportChain string + SHH bool + Dial bool ) // flags specific to cli client @@ -94,6 +96,8 @@ 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(&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") @@ -105,7 +109,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") diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index b238522820..8b83bbd376 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -64,10 +64,14 @@ func main() { NATType: PMPGateway, PMPGateway: PMPGateway, KeyRing: KeyRing, + Shh: SHH, + Dial: Dial, }) + if err != nil { clilogger.Fatalln(err) } + utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive) if Dump { @@ -112,13 +116,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 +126,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() } diff --git a/core/transaction_pool.go b/core/transaction_pool.go index fa284e52d0..ff6c21aa97 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" - "gopkg.in/fatih/set.v0" ) var txplogger = logger.NewLogger("TXP") @@ -38,7 +37,7 @@ type TxPool struct { quit chan bool // The actual pool //pool *list.List - pool *set.Set + txs map[string]*types.Transaction SecondaryProcessor TxProcessor @@ -49,21 +48,19 @@ type TxPool struct { func NewTxPool(eventMux *event.TypeMux) *TxPool { return &TxPool{ - pool: set.New(), + txs: make(map[string]*types.Transaction), queueChan: make(chan *types.Transaction, txPoolQueueSize), quit: make(chan bool), eventMux: eventMux, } } -func (pool *TxPool) addTransaction(tx *types.Transaction) { - pool.pool.Add(tx) - - // Broadcast the transaction to the rest of the peers - pool.eventMux.Post(TxPreEvent{tx}) -} - func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error { + hash := tx.Hash() + if pool.txs[string(hash)] != nil { + return fmt.Errorf("Known transaction (%x)", hash[0:4]) + } + if len(tx.To()) != 0 && len(tx.To()) != 20 { return fmt.Errorf("Invalid recipient. len = %d", len(tx.To())) } @@ -95,18 +92,17 @@ func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error { return nil } -func (self *TxPool) Add(tx *types.Transaction) error { - hash := tx.Hash() - if self.pool.Has(tx) { - return fmt.Errorf("Known transaction (%x)", hash[0:4]) - } +func (self *TxPool) addTx(tx *types.Transaction) { + self.txs[string(tx.Hash())] = tx +} +func (self *TxPool) Add(tx *types.Transaction) error { err := self.ValidateTransaction(tx) if err != nil { return err } - self.addTransaction(tx) + self.addTx(tx) var to string if len(tx.To()) > 0 { @@ -124,7 +120,7 @@ func (self *TxPool) Add(tx *types.Transaction) error { } func (self *TxPool) Size() int { - return self.pool.Size() + return len(self.txs) } func (self *TxPool) AddTransactions(txs []*types.Transaction) { @@ -137,43 +133,39 @@ func (self *TxPool) AddTransactions(txs []*types.Transaction) { } } -func (pool *TxPool) GetTransactions() []*types.Transaction { - txList := make([]*types.Transaction, pool.Size()) +func (self *TxPool) GetTransactions() (txs types.Transactions) { + txs = make(types.Transactions, self.Size()) i := 0 - pool.pool.Each(func(v interface{}) bool { - txList[i] = v.(*types.Transaction) + for _, tx := range self.txs { + txs[i] = tx i++ + } - return true - }) - - return txList + return } func (pool *TxPool) RemoveInvalid(query StateQuery) { var removedTxs types.Transactions - pool.pool.Each(func(v interface{}) bool { - tx := v.(*types.Transaction) + for _, tx := range pool.txs { sender := query.GetAccount(tx.From()) err := pool.ValidateTransaction(tx) if err != nil || sender.Nonce >= tx.Nonce() { removedTxs = append(removedTxs, tx) } + } - return true - }) pool.RemoveSet(removedTxs) } func (self *TxPool) RemoveSet(txs types.Transactions) { for _, tx := range txs { - self.pool.Remove(tx) + delete(self.txs, string(tx.Hash())) } } func (pool *TxPool) Flush() []*types.Transaction { txList := pool.GetTransactions() - pool.pool.Clear() + pool.txs = make(map[string]*types.Transaction) return txList } diff --git a/core/types/block.go b/core/types/block.go index b59044bfce..7e19d003f5 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 @@ -99,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 *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 } @@ -189,23 +170,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 { diff --git a/eth/backend.go b/eth/backend.go index db8e8e029e..065a4f7d84 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -36,6 +36,9 @@ type Config struct { NATType string PMPGateway string + Shh bool + Dial bool + KeyManager *crypto.KeyManager } @@ -130,11 +133,13 @@ func New(config *Config) (*Ethereum, error) { 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 { @@ -142,12 +147,16 @@ 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, + NoDial: !config.Dial, + } + + if len(config.Port) > 0 { + eth.net.ListenAddr = ":" + config.Port } return eth, nil @@ -219,8 +228,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{}) @@ -268,7 +283,9 @@ func (s *Ethereum) Stop() { s.txPool.Stop() s.eventMux.Stop() s.blockPool.Stop() - s.whisper.Stop() + if s.whisper != nil { + s.whisper.Stop() + } logger.Infoln("Server stopped") close(s.shutdownChan) @@ -285,16 +302,16 @@ func (self *Ethereum) txBroadcastLoop() { // automatically stops if unsubscribe for obj := range self.txSub.Chan() { event := obj.(core.TxPreEvent) - self.net.Broadcast("eth", TxMsg, []interface{}{event.Tx.RlpData()}) + self.net.Broadcast("eth", TxMsg, event.Tx.RlpData()) } } 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()) + self.net.Broadcast("eth", NewBlockMsg, ev.Block.RlpData(), ev.Block.Td) } } } diff --git a/eth/block_pool.go b/eth/block_pool.go index 7cfbc63f86..519c9fc13c 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -1,6 +1,7 @@ package eth import ( + "fmt" "math" "math/big" "math/rand" @@ -19,37 +20,45 @@ var poolLogger = ethlogger.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 *peerInfo + suicideC chan bool + blockChainC chan bool + forkC chan chan bool + offC chan 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,50 +136,116 @@ func (self *BlockPool) Stop() { return } self.running = false + self.lock.Unlock() - poolLogger.Infoln("Stopping") + 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: + poolLogger.Infoln("Processes complete") + case <-time.After(t): + poolLogger.Warnf("Timeout") + } + self.flushC = make(chan bool) +} + // AddPeer is called by the eth protocol instance running on the peer after // 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 +256,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,12 +274,12 @@ 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.Debugf("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td) } else { - poolLogger.Warnln("no peers left") + poolLogger.Warnln("no peers") } } } @@ -200,97 +292,132 @@ func (self *BlockPool) RemovePeer(peerId string) { // 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 } // peer is still the best + poolLogger.Debugf("adding hashes for best peer %s", peerId) - var child *poolNode - var depth int + var size, n int + var hash []byte + var ok bool + var section, child, parent *section + var entry *poolEntry + var nodes []*poolNode +LOOP: // iterate using next (rlp stream lazy decoder) feeding hashesC - self.wg.Add(1) - go func() { - for { - select { - case <-self.quit: - return - case <-peer.quitC: - // if the peer is demoted, no more hashes taken - break - default: - hash, ok := next() - if !ok { - // message consumed chain skeleton built - break + for hash, ok = next(); ok; hash, ok = next() { + n++ + select { + case <-self.quit: + return + case <-peer.quitC: + // if the peer is demoted, no more hashes taken + peer = nil + break LOOP + default: + } + if self.hasBlock(hash) { + // check if known block connecting the downloaded chain to our blockchain + poolLogger.DebugDetailf("[%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) } - // 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 } + break LOOP } - 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() + // look up node in pool + entry = self.get(hash) + if entry != nil { + // 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 + child = entry.section + continue LOOP + } + 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 + node := &poolNode{ + hash: hash, + peer: peerId, + } + if size == 0 { + section = newSection() + } + nodes = append(nodes, node) + size++ + } //for + + self.chainLock.Lock() + + poolLogger.DebugDetailf("added %v hashes sent by %s", n, peerId) + + if parent != nil && entry != nil && entry.node != parent.top { + poolLogger.DebugDetailf("[%s] split section at fork", sectionName(parent)) + parent.controlC <- nil + 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 <- nil + close(waiter) + } + + if size > 0 { + self.processSection(section, nodes) + 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.DebugDetailf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child)) + self.link(parent, child) + } + + self.chainLock.Unlock() + + if parent != nil && peer != nil { + self.activateChain(parent, peer) + poolLogger.Debugf("[%s] activate parent section [%s]", name(parent.top.hash), sectionName(parent)) + } + + if section != nil { + peer.addSection(section.top.hash, section) + section.controlC <- peer + poolLogger.Debugf("[%s] activate new section", sectionName(section)) + } +} + +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 @@ -299,67 +426,114 @@ func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId 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() - node := self.get(hash) - node.RLock() - b := node.block - node.RUnlock() - if b != nil { + if self.hasBlock(hash) { + poolLogger.DebugDetailf("block [%s] already known", name(hash)) return } - if node == nil && !self.hasBlock(hash) { + entry := self.get(hash) + if entry == nil { + poolLogger.Warnf("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() + + // check if block already present + if node.block != nil { + poolLogger.DebugDetailf("block [%x] already sent by %s", name(hash), node.blockBy) + return + } + // validate block for PoW if !self.verifyPoW(block) { + poolLogger.Warnf("invalid pow on block [%x] by peer %s", hash, peerId) self.peerError(peerId, ErrInvalidPoW, "%x", hash) + return } - node.Lock() + + poolLogger.Debugf("added block [%s] sent 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) { + select { + case <-section.offC: + self.addSectionToBlockChain(section) + case <-section.blockChainC: + default: + close(section.blockChainC) + } +} - 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.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.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() + } + return +} + +func (self *BlockPool) activateChain(section *section, peer *peerInfo) { + 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.DebugDetailf("[%s] register section with peer %s", sectionName(section), peer.id) + if peer.addSection(section.top.hash, section) == section { + return + } + poolLogger.DebugDetailf("[%s] activate section process", sectionName(section)) + select { + case section.controlC <- peer: + case <-section.offC: + } + i++ + section = self.getParent(section) + select { + case <-peer.quitC: + break LOOP + case <-self.quit: + break LOOP + default: + } + } } // main worker thread on each section in the poolchain @@ -370,261 +544,315 @@ 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.DebugDetailf("[%s] setup section process", sectionName(section)) + + self.wg.Add(1) go func() { - self.wg.Add(1) - for { - node.sectionRLock() - controlC = node.section.controlC - node.sectionRUnlock() - if init { - // missing blocks read from nodeC - // initialized section - if depth == 0 { - break + // 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 + 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, missing, lastMissing, depth int + var idle int + var init, done, same, ready bool + var insertChain bool + var quitC chan bool + + var blockChainC = section.blockChainC + + LOOP: + for { + + 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.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 { - 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++ + if len(hashes) > 0 { + // send block requests to peers + self.requestBlocks(blocksRequests, hashes) + hashes = nil + } + if missing == lastMissing { + // idle round + if same { + // more than once + idle++ + // too many idle rounds + if idle >= blocksRequestMaxIdleRounds { + 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 { + idle = 0 + } + same = true + } else { + same = false } - same = false } 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 } - 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.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 { + 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.DebugDetailf("[%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) + peer.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 + } - alarm = false + select { + 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) + + case <-suicideTimer: + close(section.suicideC) + 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)) + + // 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: + poolLogger.DebugDetailf("[%s] block request time", sectionName(section)) + blocksRequestTime = true + + case <-blockHashesRequestTimer: + poolLogger.DebugDetailf("[%s] hash request time", sectionName(section)) + blockHashesRequestTime = true + + case newPeer = <-section.controlC: + + // active -> idle + if peer != nil && newPeer == nil { + self.procWg.Done() + if init { + poolLogger.Debugf("[%s] idle mode (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) + } + 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]) - 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) + // idle -> active + if peer == nil && newPeer != nil { + self.procWg.Add(1) + + poolLogger.Debugf("[%s] active mode", sectionName(section)) + if !blocksRequestsComplete { + 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 { + blockHashesRequestTime = true } - alarm = true - processC = missingC - if !once { + if !init { + processC = make(chan *poolNode, blockHashesBatchSize) + missingC = make(chan *poolNode, blockHashesBatchSize) + i = 0 + missing = 0 + self.wg.Add(1) + self.procWg.Add(1) + depth = len(section.nodes) + lastMissing = depth // if not run at least once fully, launch iterator - processC = make(chan *poolNode) - missingC = make(chan *poolNode) - self.foldUp(orignode, processC) - once = true + 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 + // 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 + <-waiter 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) + poolLogger.DebugDetailf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, lastMissing, depth) + continue LOOP + } + if ready { + i = 0 + missing = 0 + ready = false } 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 { 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 { + 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, lastMissing, depth) + if i == lastMissing && init { + 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] section complete: %v block hashes requests - %v block requests - missing %v/%v/%v", sectionName(section), blockHashesRequests, blocksRequests, missing, lastMissing, depth) + + close(section.offC) self.wg.Done() - node.sectionLock() - node.section.controlC = nil - node.sectionUnlock() - // this signals that controller not available + if peer != nil { + self.procWg.Done() + } }() - return section - + return } func (self *BlockPool) peerError(peerId string, code int, format string, params ...interface{}) { @@ -636,39 +864,39 @@ 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 { - self.peer.requestBlockHashes(hash) - } -} - func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { - // distribute block request among known peers - self.peersLock.Lock() - defer self.peersLock.Unlock() - peerCount := len(self.peers) - // on first attempt use the best peer - if attempts == 0 { - 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)] - sort.Ints(indexes) - for _, peer := range self.peers { - if i == indexes[0] { - 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 + 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++ - } + 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 peer %s", len(hashes), peer.id) + peer.requestBlocks(hashes) + indexes = indexes[1:] + if len(indexes) == 0 { + break + } + } + i++ + } + self.wg.Done() + self.procWg.Done() + }() } func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { @@ -679,337 +907,100 @@ func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { } info, ok := self.peers[peerId] if !ok { - panic("unknown peer") + return nil, false } 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() - self.sections[string(hash)] = section + key := string(hash) + found = self.sections[key] + poolLogger.DebugDetailf("[%s] section process %s registered", sectionName(section), self.id) + self.sections[key] = section + return } -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 - 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) - 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 peer.sections { - if section.done() { - delete(self.sections, hash) - } - _, exists := peer.sections[hash] - if on || peer == nil || exists { - if on { - // self is best peer - section.start() - } else { - // (re)starts process without requests, only suicide timer - section.stop() - } - } - } -} - -// 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 - - if child == child.section.bottom { - bottom = true - } - 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() - } +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 { - // child is on top of a chain need to close section - child.section.bottom = child + 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) } - // adopt new child + 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.DebugDetailf("[%s][%x] section process complete - remove", newPeer.id, hash[:4]) + delete(newPeer.sections, hash) + case section.controlC <- newPeer: + poolLogger.DebugDetailf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) + } + } + newPeer.quitC = make(chan bool) + } + if oldPeer != nil { + close(oldPeer.quitC) + } +} + +func (self *BlockPool) getParent(sec *section) *section { + self.chainLock.RLock() + defer self.chainLock.RUnlock() + return sec.parent +} + +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 *peerInfo), + suicideC: make(chan bool), + blockChainC: make(chan bool), + offC: 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 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] chain 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] chain reverse 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..b50a314ead 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/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 = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugLevel)) -func (self *testChainManager) KnownBlock(hash []byte) bool { - if self.knownBlock != nil { - return self.knownBlock(hash) +var ini = false + +func logInit() { + if !ini { + ethlogger.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(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 + 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) + +} 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 { diff --git a/eth/protocol.go b/eth/protocol.go index 7c5d09489b..b67e5aaeab 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -3,7 +3,7 @@ package eth import ( "bytes" "fmt" - "math" + "io" "math/big" "github.com/ethereum/go-ethereum/core/types" @@ -95,14 +95,13 @@ 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()[:8]), } err = self.handleStatus() if err == nil { for { err = self.handle() if err != nil { - fmt.Println(err) self.blockPool.RemovePeer(self.id) break } @@ -117,7 +116,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,76 +124,87 @@ 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, "msg %v: %v", msg, 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, "->msg %v: %v", msg, err) } hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) 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)) + msgStream := rlp.NewStream(msg.Payload) 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 != io.EOF { + 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 ProtocolError(ErrDecode, "%v", err) - } case GetBlocksMsg: - var blockHashes [][]byte - if err := msg.Decode(&blockHashes); err != nil { - return ProtocolError(ErrDecode, "%v", err) - } - max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) + msgStream := rlp.NewStream(msg.Payload) 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 == io.EOF { + break + } else { + return self.protoError(ErrDecode, "msg %v: %v", msg, err) + } } block := self.chainManager.GetBlock(hash) if block != nil { - blocks = append(blocks, block.RlpData()) + blocks = append(blocks, block) + } + 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) for { - var block *types.Block + var block types.Block if err := msgStream.Decode(&block); err != nil { - if err == rlp.EOL { + if err == io.EOF { break } else { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } } - self.blockPool.AddBlock(block, self.id) + self.blockPool.AddBlock(&block, self.id) } case NewBlockMsg: var request newBlockMsgData if err := msg.Decode(&request); err != nil { - return ProtocolError(ErrDecode, "%v", err) + return self.protoError(ErrDecode, "msg %v: %v", msg, err) } hash := request.Block.Hash() // to simplify backend interface adding a new block @@ -202,12 +212,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) @@ -215,14 +225,14 @@ func (self *ethProtocol) handle() error { } default: - return ProtocolError(ErrInvalidMsgCode, "%v", msg.Code) + return self.protoError(ErrInvalidMsgCode, "%v", msg.Code) } return nil } type statusMsgData struct { - ProtocolVersion uint - NetworkId uint + ProtocolVersion uint32 + NetworkId uint32 TD *big.Int CurrentBlock []byte GenesisBlock []byte @@ -253,56 +263,56 @@ 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, "msg %v: %v", msg, 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 } 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 { 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) { 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 +320,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) } } diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 322aec7b70..ab2aa289f0 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -1,35 +1,48 @@ package eth import ( + "bytes" "io" + "log" "math/big" + "os" "testing" + "time" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethutil" + ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" ) +var sys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel)) + type testMsgReadWriter struct { in chan p2p.Msg - out 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 } 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) { @@ -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) { + logInit() + eth := newEth(t) + td := ethutil.Big1 + currentBlock := []byte{1} + genesis := []byte{2} + eth.chainManager.status = func() (*big.Int, []byte, []byte) { return td, currentBlock, genesis } + go eth.run() + statusMsg := p2p.NewMsg(4) + eth.In(statusMsg) + delay := 1 * time.Second + eth.checkError(ErrNoStatusMsg, delay) + var status statusMsgData + eth.checkMsg(0, StatusMsg, &status) // first outgoing msg should be StatusMsg + if status.TD.Cmp(td) != 0 || + status.ProtocolVersion != ProtocolVersion || + status.NetworkId != NetworkId || + status.TD.Cmp(td) != 0 || + bytes.Compare(status.CurrentBlock, currentBlock) != 0 || + bytes.Compare(status.GenesisBlock, genesis) != 0 { + t.Errorf("incorrect outgoing status") + } + + eth.reset() + go eth.run() + statusMsg = p2p.NewMsg(0, uint32(48), uint32(0), td, currentBlock, genesis) + eth.In(statusMsg) + eth.checkError(ErrProtocolVersionMismatch, delay) + + eth.reset() + go eth.run() + statusMsg = p2p.NewMsg(0, uint32(49), uint32(1), td, currentBlock, genesis) + eth.In(statusMsg) + eth.checkError(ErrNetworkIdMismatch, delay) + + eth.reset() + go eth.run() + statusMsg = p2p.NewMsg(0, uint32(49), uint32(0), td, currentBlock, []byte{3}) + eth.In(statusMsg) + eth.checkError(ErrGenesisBlockMismatch, delay) + } 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 + diff --git a/eth/test/bootstrap.sh b/eth/test/bootstrap.sh new file mode 100644 index 0000000000..3da038be8b --- /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 10 +bash ./mine.sh 01 5 00 +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 new file mode 100755 index 0000000000000000000000000000000000000000..ad3c05b24af70b3e02afbde9aa0c4911f56432ee GIT binary patch literal 9726 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
Gm=vJ7a&95t7OOcj%!t-TGvCba{^a2} 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 literal 0 HcmV?d00001 diff --git a/eth/test/chains/02.chain b/eth/test/chains/02.chain new file mode 100755 index 0000000000000000000000000000000000000000..440c92d658508658cb7044fa26bdea741ef99a8b GIT binary patch literal 14989 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 /tmp/eth.test/mine.tmp & +PID=$! +sleep 1 +kill $PID +cat /tmp/eth.test/mine.tmp | grep 'exporting' diff --git a/eth/test/run.sh b/eth/test/run.sh new file mode 100644 index 0000000000..5229af0353 --- /dev/null +++ b/eth/test/run.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# bash run.sh (testid0 testid1 ...) +# runs tests tests/testid0.sh tests/testid1.sh ... +# without arguments, it runs all tests + +. tests/common.sh + +TESTS= + +if [ "$#" -eq 0 ]; then + for NAME in tests/??.sh; do + i=`basename $NAME .sh` + TESTS="$TESTS $i" + done +else + TESTS=$@ +fi + +ETH=../../ethereum +DIR="/tmp/eth.test/nodes" +TIMEOUT=10 + +mkdir -p $DIR/js + +echo "running tests $TESTS" +for NAME in $TESTS; do + PIDS= + CHAIN="tests/$NAME.chain" + JSFILE="$DIR/js/$NAME.js" + CHAIN_TEST="$DIR/$NAME/chain" + + echo "RUN: test $NAME" + cat tests/common.js > $JSFILE + . tests/$NAME.sh + sleep $TIMEOUT + echo "timeout after $TIMEOUT seconds: killing $PIDS" + kill $PIDS + if [ -r "$CHAIN" ]; then + if diff $CHAIN $CHAIN_TEST >/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 \ No newline at end of file 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..9c5077164b --- /dev/null +++ b/eth/test/tests/00.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +TIMEOUT=4 + +cat >> $JSFILE <> $JSFILE <> $JSFILE <> $JSFILE <> $JSFILE <> $JSFILE < 0 { - return bp.rw.EncodeMsg(peersMsg, peers) + return bp.rw.EncodeMsg(peersMsg, peers...) } case peersMsg: @@ -193,14 +196,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 } @@ -210,12 +208,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", @@ -238,9 +234,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) @@ -270,25 +264,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 -} diff --git a/p2p/protocol_test.go b/p2p/protocol_test.go index 65f26fb12d..ce25b3e1b5 100644 --- a/p2p/protocol_test.go +++ b/p2p/protocol_test.go @@ -2,12 +2,89 @@ package p2p import ( "fmt" + "net" + "reflect" "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 newTestPeer() (peer *Peer) { + peer = NewPeer(&peerId{}, []Cap{}) + peer.pubkeyHook = func(*peerAddr) error { return nil } + peer.ourID = &peerId{} + peer.listenAddr = &peerAddr{} + peer.otherPeers = func() []*Peer { return nil } + return +} + +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{}}, + } + 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)) + go func() { + 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) + } + } + close(addrChan) + var own []*peerAddr + var got *peerAddr + for got = range addrChan { + own = append(own, got) + } + 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 := newTestPeer() + peer1.ourListenAddr = ownAddr + 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 := 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) + } +} + 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() @@ -32,6 +109,7 @@ func TestBaseProtocolDisconnect(t *testing.T) { if err := rw2.EncodeMsg(discMsg, DiscQuitting); err != nil { t.Error(err) } + close(done) }() diff --git a/p2p/server.go b/p2p/server.go index 3267812343..cfff442f71 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) } } @@ -258,6 +260,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 @@ -330,6 +333,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 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, 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)},