From e5aa38cb0f846cde3e0d70e751cfa6d53a889e99 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 5 Dec 2014 21:14:55 +0000 Subject: initial commit for eth-p2p integration --- eth/error.go | 73 +++++++++++++ eth/protocol.go | 294 +++++++++++++++++++++++++++++++++++++++++++++++++++ eth/protocol_test.go | 133 +++++++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 eth/error.go create mode 100644 eth/protocol.go create mode 100644 eth/protocol_test.go (limited to 'eth') diff --git a/eth/error.go b/eth/error.go new file mode 100644 index 000000000..cb4459435 --- /dev/null +++ b/eth/error.go @@ -0,0 +1,73 @@ +package eth + +import ( + "fmt" + // "github.com/ethereum/go-ethereum/logger" +) + +const ( + ErrMsgTooLarge = iota + ErrDecode + ErrInvalidMsgCode + ErrProtocolVersionMismatch + ErrNetworkIdMismatch + ErrGenesisBlockMismatch + ErrNoStatusMsg + ErrExtraStatusMsg + ErrInvalidBlock +) + +var errorToString = map[int]string{ + ErrMsgTooLarge: "Message too long", + ErrDecode: "Invalid message", + ErrInvalidMsgCode: "Invalid message code", + ErrProtocolVersionMismatch: "Protocol version mismatch", + ErrNetworkIdMismatch: "NetworkId mismatch", + ErrGenesisBlockMismatch: "Genesis block mismatch", + ErrNoStatusMsg: "No status message", + ErrExtraStatusMsg: "Extra status message", + ErrInvalidBlock: "Invalid block", +} + +type protocolError struct { + Code int + fatal bool + message string + format string + params []interface{} + // size int +} + +func newProtocolError(code int, format string, params ...interface{}) *protocolError { + return &protocolError{Code: code, format: format, params: params} +} + +func ProtocolError(code int, format string, params ...interface{}) (err *protocolError) { + err = newProtocolError(code, format, params...) + // report(err) + if err.Fatal() { + logger.Errorln(err) + } else { + logger.Debugln(err) + } + return +} + +func (self protocolError) Error() (message string) { + message = self.message + if message == "" { + message, ok := errorToString[self.Code] + if !ok { + panic("invalid error code") + } + if self.format != "" { + message += ": " + fmt.Sprintf(self.format, self.params...) + } + self.message = message + } + return +} + +func (self *protocolError) Fatal() bool { + return self.fatal +} diff --git a/eth/protocol.go b/eth/protocol.go new file mode 100644 index 000000000..992fc7550 --- /dev/null +++ b/eth/protocol.go @@ -0,0 +1,294 @@ +package eth + +import ( + "bytes" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethutil" + ethlogger "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/p2p" +) + +var logger = ethlogger.NewLogger("SERV") + +// ethProtocol represents the ethereum wire protocol +// instance is running on each peer +type ethProtocol struct { + eth backend + td *big.Int + peer *p2p.Peer + rw p2p.MsgReadWriter +} + +// backend is the interface the ethereum protocol backend should implement +// used as an argument to EthProtocol +type backend interface { + GetTransactions() (txs []*types.Transaction) + AddTransactions(txs []*types.Transaction) + GetBlockHashes(hash []byte, amount uint32) (hashes [][]byte) + AddHash(hash []byte, peer *p2p.Peer) (more bool) + GetBlock(hash []byte) (block *types.Block) + AddBlock(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) + AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) + Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) +} + +const ( + ProtocolVersion = 43 + // 0x00 // PoC-1 + // 0x01 // PoC-2 + // 0x07 // PoC-3 + // 0x09 // PoC-4 + // 0x17 // PoC-5 + // 0x1c // PoC-6 + NetworkId = 0 + ProtocolLength = uint64(8) + ProtocolMaxMsgSize = 10 * 1024 * 1024 + + blockHashesBatchSize = 256 +) + +// eth protocol message codes +const ( + StatusMsg = iota + GetTxMsg // unused + TxMsg + GetBlockHashesMsg + BlockHashesMsg + GetBlocksMsg + BlocksMsg + NewBlockMsg +) + +// message structs used for rlp decoding +type newBlockMsgData struct { + Block *types.Block + TD *big.Int +} + +type getBlockHashesMsgData struct { + Hash []byte + Amount uint32 +} + +// main entrypoint, wrappers starting a server running the eth protocol +// use this constructor to attach the protocol (class) to server caps +func EthProtocol(eth backend) *p2p.Protocol { + return &p2p.Protocol{ + Name: "eth", + Version: ProtocolVersion, + Length: ProtocolLength, + Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { + return runEthProtocol(eth, peer, rw) + }, + } +} + +func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { + self := ðProtocol{ + eth: eth, + rw: rw, + peer: peer, + } + err = self.handleStatus() + if err == nil { + go func() { + for { + err = self.handle() + if err != nil { + break + } + } + }() + } + return +} + +func (self *ethProtocol) handle() error { + msg, err := self.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > ProtocolMaxMsgSize { + return ProtocolError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) + } + // make sure that the payload has been fully consumed + defer msg.Discard() + + switch msg.Code { + + case StatusMsg: + return ProtocolError(ErrExtraStatusMsg, "") + + case GetTxMsg: + txs := self.eth.GetTransactions() + // TODO: rewrite using rlp flat + txsInterface := make([]interface{}, len(txs)) + for i, tx := range txs { + txsInterface[i] = tx.RlpData() + } + return self.rw.EncodeMsg(TxMsg, txsInterface...) + + case TxMsg: + var txs []*types.Transaction + if err := msg.Decode(&txs); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + self.eth.AddTransactions(txs) + + case GetBlockHashesMsg: + var request getBlockHashesMsgData + if err := msg.Decode(&request); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + hashes := self.eth.GetBlockHashes(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 + // s := rlp.NewListStream(msg.Payload, uint64(msg.Size)) + var blockHashes [][]byte + if err := msg.Decode(&blockHashes); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + fetchMore := true + for _, hash := range blockHashes { + fetchMore = self.eth.AddHash(hash, self.peer) + if !fetchMore { + break + } + } + if fetchMore { + return self.FetchHashes(blockHashes[len(blockHashes)-1]) + } + + case GetBlocksMsg: + // Limit to max 300 blocks + var blockHashes [][]byte + if err := msg.Decode(&blockHashes); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + max := int(math.Min(float64(len(blockHashes)), 300.0)) + var blocks []interface{} + for i, hash := range blockHashes { + if i >= max { + break + } + block := self.eth.GetBlock(hash) + if block != nil { + blocks = append(blocks, block.Value().Raw()) + } + } + return self.rw.EncodeMsg(BlocksMsg, blocks...) + + case BlocksMsg: + var blocks []*types.Block + if err := msg.Decode(&blocks); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + for _, block := range blocks { + fetchHashes, err := self.eth.AddBlock(nil, block, self.peer) + if err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } + if fetchHashes { + if err := self.FetchHashes(block.Hash()); err != nil { + return err + } + } + } + + case NewBlockMsg: + var request newBlockMsgData + if err := msg.Decode(&request); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + var fetchHashes bool + // this should reset td and offer blockpool as candidate new peer? + if fetchHashes, err = self.eth.AddBlock(request.TD, request.Block, self.peer); err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } + if fetchHashes { + return self.FetchHashes(request.Block.Hash()) + } + + default: + return ProtocolError(ErrInvalidMsgCode, "%v", msg.Code) + } + return nil +} + +type statusMsgData struct { + ProtocolVersion uint + NetworkId uint + TD *big.Int + CurrentBlock []byte + GenesisBlock []byte +} + +func (self *ethProtocol) statusMsg() p2p.Msg { + td, currentBlock, genesisBlock := self.eth.Status() + + return p2p.NewMsg(StatusMsg, + uint32(ProtocolVersion), + uint32(NetworkId), + td, + currentBlock, + genesisBlock, + ) +} + +func (self *ethProtocol) handleStatus() error { + // send precanned status message + if err := self.rw.WriteMsg(self.statusMsg()); err != nil { + return err + } + + // read and handle remote status + msg, err := self.rw.ReadMsg() + if err != nil { + return err + } + + if msg.Code != StatusMsg { + return ProtocolError(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) + } + + if msg.Size > ProtocolMaxMsgSize { + return ProtocolError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) + } + + var status statusMsgData + if err := msg.Decode(&status); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + + _, _, genesisBlock := self.eth.Status() + + if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { + return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) + } + + if status.NetworkId != NetworkId { + return ProtocolError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, NetworkId) + } + + if ProtocolVersion != status.ProtocolVersion { + return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) + } + + logger.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) + + if self.eth.AddPeer(status.TD, status.CurrentBlock, self.peer) { + return self.FetchHashes(status.CurrentBlock) + } + + return nil +} + +func (self *ethProtocol) FetchHashes(from []byte) error { + logger.Debugf("Fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) + return self.rw.EncodeMsg(GetBlockHashesMsg, from, blockHashesBatchSize) +} diff --git a/eth/protocol_test.go b/eth/protocol_test.go new file mode 100644 index 000000000..757e87fa4 --- /dev/null +++ b/eth/protocol_test.go @@ -0,0 +1,133 @@ +package eth + +import ( + "io" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" +) + +type testMsgReadWriter struct { + in chan p2p.Msg + out chan 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) WriteMsg(msg p2p.Msg) error { + self.out <- msg + return nil +} + +func (self *testMsgReadWriter) EncodeMsg(code uint64, data ...interface{}) error { + return self.WriteMsg(p2p.NewMsg(code, data)) +} + +func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { + msg, ok := <-self.in + if !ok { + return msg, io.EOF + } + 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 { + getTransactions func() []*types.Transaction + addTransactions func(txs []*types.Transaction) + getBlockHashes func(hash []byte, amount uint32) (hashes [][]byte) + addHash func(hash []byte, peer *p2p.Peer) (more bool) + getBlock func(hash []byte) *types.Block + addBlock func(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) + addPeer func(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) + status func() (td *big.Int, currentBlock []byte, genesisBlock []byte) +} + +func (self *TestBackend) GetTransactions() (txs []*types.Transaction) { + if self.getTransactions != nil { + txs = self.getTransactions() + } + return +} + +func (self *TestBackend) AddTransactions(txs []*types.Transaction) { + if self.addTransactions != nil { + self.addTransactions(txs) + } +} + +func (self *TestBackend) GetBlockHashes(hash []byte, amount uint32) (hashes [][]byte) { + if self.getBlockHashes != nil { + hashes = self.getBlockHashes(hash, amount) + } + return +} + +func (self *TestBackend) AddHash(hash []byte, peer *p2p.Peer) (more bool) { + if self.addHash != nil { + more = self.addHash(hash, peer) + } + return +} +func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { + if self.getBlock != nil { + block = self.getBlock(hash) + } + return +} + +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) + } + return +} + +func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) { + if self.addPeer != nil { + fetchHashes = self.addPeer(td, currentBlock, peer) + } + return +} + +func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { + if self.status != nil { + td, currentBlock, genesisBlock = self.status() + } + return +} + +func TestEth(t *testing.T) { + quit := make(chan bool) + rw := &testMsgReadWriter{make(chan p2p.Msg, 10), make(chan p2p.Msg, 10)} + testBackend := &TestBackend{} + var err error + go func() { + err = runEthProtocol(testBackend, nil, rw) + close(quit) + }() + statusMsg := p2p.NewMsg(4) + rw.In(statusMsg) + <-quit + errorCheck(t, ErrNoStatusMsg, err) + // read(t, remote, []byte("hello, world"), nil) +} -- cgit v1.2.3 From eb5cb04aa991d59a6597479fbddf5dec51093ed6 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 9 Dec 2014 23:54:02 +0000 Subject: no logging in error (to be refactored into p2p) --- eth/error.go | 6 ------ 1 file changed, 6 deletions(-) (limited to 'eth') diff --git a/eth/error.go b/eth/error.go index cb4459435..9355d6457 100644 --- a/eth/error.go +++ b/eth/error.go @@ -2,7 +2,6 @@ package eth import ( "fmt" - // "github.com/ethereum/go-ethereum/logger" ) const ( @@ -45,11 +44,6 @@ func newProtocolError(code int, format string, params ...interface{}) *protocolE func ProtocolError(code int, format string, params ...interface{}) (err *protocolError) { err = newProtocolError(code, format, params...) // report(err) - if err.Fatal() { - logger.Errorln(err) - } else { - logger.Debugln(err) - } return } -- cgit v1.2.3 From d957dd2c9f5adefdedf702223de39634c1f4a32b Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 9 Dec 2014 23:55:50 +0000 Subject: eth protocol changes - changed backend interface - using callbacks for blockPool - use rlp stream for lazy decoding - use peer as logger - add id (peer pubkey) to ethProtocol fields - add testPeer to protocol test (temporary) --- eth/protocol.go | 136 ++++++++++++++++++++++++++++++--------------------- eth/protocol_test.go | 56 ++++++++++++++++----- 2 files changed, 122 insertions(+), 70 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 992fc7550..380bcc8d2 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -7,18 +7,16 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" ) -var logger = ethlogger.NewLogger("SERV") - // ethProtocol represents the ethereum wire protocol // instance is running on each peer type ethProtocol struct { eth backend - td *big.Int peer *p2p.Peer + id string rw p2p.MsgReadWriter } @@ -26,28 +24,21 @@ type ethProtocol struct { // used as an argument to EthProtocol type backend interface { GetTransactions() (txs []*types.Transaction) - AddTransactions(txs []*types.Transaction) + AddTransactions([]*types.Transaction) GetBlockHashes(hash []byte, amount uint32) (hashes [][]byte) - AddHash(hash []byte, peer *p2p.Peer) (more bool) + AddBlockHashes(next func() ([]byte, bool), peerId string) GetBlock(hash []byte) (block *types.Block) - AddBlock(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) - AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) + AddBlock(block *types.Block, peerId string) (err error) + AddPeer(td *big.Int, currentBlock []byte, peerId string, requestHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) + RemovePeer(peerId string) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) } const ( - ProtocolVersion = 43 - // 0x00 // PoC-1 - // 0x01 // PoC-2 - // 0x07 // PoC-3 - // 0x09 // PoC-4 - // 0x17 // PoC-5 - // 0x1c // PoC-6 + ProtocolVersion = 43 NetworkId = 0 ProtocolLength = uint64(8) ProtocolMaxMsgSize = 10 * 1024 * 1024 - - blockHashesBatchSize = 256 ) // eth protocol message codes @@ -74,7 +65,8 @@ type getBlockHashesMsgData struct { } // main entrypoint, wrappers starting a server running the eth protocol -// use this constructor to attach the protocol (class) to server caps +// use this constructor to attach the protocol ("class") to server caps +// the Dev p2p layer then runs the protocol instance on each peer func EthProtocol(eth backend) *p2p.Protocol { return &p2p.Protocol{ Name: "eth", @@ -86,11 +78,14 @@ func EthProtocol(eth backend) *p2p.Protocol { } } +// the main loop that handles incoming messages +// note RemovePeer in the post-disconnect hook func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { self := ðProtocol{ eth: eth, rw: rw, peer: peer, + id: (string)(peer.Identity().Pubkey()), } err = self.handleStatus() if err == nil { @@ -98,6 +93,7 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro for { err = self.handle() if err != nil { + self.eth.RemovePeer(self.id) break } } @@ -132,6 +128,7 @@ func (self *ethProtocol) handle() error { return self.rw.EncodeMsg(TxMsg, txsInterface...) case TxMsg: + // TODO: rework using lazy RLP stream var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { return ProtocolError(ErrDecode, "%v", err) @@ -148,29 +145,26 @@ func (self *ethProtocol) handle() error { case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains - // s := rlp.NewListStream(msg.Payload, uint64(msg.Size)) - var blockHashes [][]byte - if err := msg.Decode(&blockHashes); err != nil { - return ProtocolError(ErrDecode, "%v", err) - } - fetchMore := true - for _, hash := range blockHashes { - fetchMore = self.eth.AddHash(hash, self.peer) - if !fetchMore { - break + msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) + var err error + iter := func() (hash []byte, ok bool) { + hash, err = msgStream.Bytes() + if err == nil { + ok = true } + return } - if fetchMore { - return self.FetchHashes(blockHashes[len(blockHashes)-1]) + self.eth.AddBlockHashes(iter, self.id) + if err != nil && err != rlp.EOL { + return ProtocolError(ErrDecode, "%v", err) } case GetBlocksMsg: - // Limit to max 300 blocks var blockHashes [][]byte if err := msg.Decode(&blockHashes); err != nil { return ProtocolError(ErrDecode, "%v", err) } - max := int(math.Min(float64(len(blockHashes)), 300.0)) + max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) var blocks []interface{} for i, hash := range blockHashes { if i >= max { @@ -184,20 +178,19 @@ func (self *ethProtocol) handle() error { return self.rw.EncodeMsg(BlocksMsg, blocks...) case BlocksMsg: - var blocks []*types.Block - if err := msg.Decode(&blocks); err != nil { - return ProtocolError(ErrDecode, "%v", err) - } - for _, block := range blocks { - fetchHashes, err := self.eth.AddBlock(nil, block, self.peer) - if err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } - if fetchHashes { - if err := self.FetchHashes(block.Hash()); err != nil { - return err + msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) + for { + var block *types.Block + if err := msgStream.Decode(&block); err != nil { + if err == rlp.EOL { + break + } else { + return ProtocolError(ErrDecode, "%v", err) } } + if err := self.eth.AddBlock(block, self.id); err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } } case NewBlockMsg: @@ -205,13 +198,24 @@ func (self *ethProtocol) handle() error { if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } - var fetchHashes bool - // this should reset td and offer blockpool as candidate new peer? - if fetchHashes, err = self.eth.AddBlock(request.TD, request.Block, self.peer); err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } - if fetchHashes { - return self.FetchHashes(request.Block.Hash()) + hash := request.Block.Hash() + // to simplify backend interface adding a new block + // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer + // (or selected as new best peer) + if self.eth.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) { + called := true + iter := func() (hash []byte, ok bool) { + if called { + called = false + return hash, true + } else { + return + } + } + self.eth.AddBlockHashes(iter, self.id) + if err := self.eth.AddBlock(request.Block, self.id); err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } } default: @@ -279,16 +283,34 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) } - logger.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) + self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) - if self.eth.AddPeer(status.TD, status.CurrentBlock, self.peer) { - return self.FetchHashes(status.CurrentBlock) - } + self.eth.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) return nil } -func (self *ethProtocol) FetchHashes(from []byte) error { - logger.Debugf("Fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) +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) } + +func (self *ethProtocol) requestBlocks(hashes [][]byte) error { + self.peer.Debugf("fetching %v blocks", len(hashes)) + return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)) +} + +func (self *ethProtocol) invalidBlock(err error) { + ProtocolError(ErrInvalidBlock, "%v", err) + self.peer.Disconnect(p2p.DiscSubprotocolError) +} + +func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { + err = ProtocolError(code, format, params...) + if err.Fatal() { + self.peer.Errorln(err) + } else { + self.peer.Debugln(err) + } + return +} diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 757e87fa4..a166ea6cd 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" ) @@ -55,10 +56,11 @@ type TestBackend struct { getTransactions func() []*types.Transaction addTransactions func(txs []*types.Transaction) getBlockHashes func(hash []byte, amount uint32) (hashes [][]byte) - addHash func(hash []byte, peer *p2p.Peer) (more bool) + addBlockHashes func(next func() ([]byte, bool), peerId string) getBlock func(hash []byte) *types.Block - addBlock func(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) - addPeer func(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) + 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) } @@ -82,12 +84,12 @@ func (self *TestBackend) GetBlockHashes(hash []byte, amount uint32) (hashes [][] return } -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) } - return } + func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { if self.getBlock != nil { block = self.getBlock(hash) @@ -95,20 +97,26 @@ func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { return } -func (self *TestBackend) AddBlock(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) { +func (self *TestBackend) AddBlock(block *types.Block, peerId string) (err error) { if self.addBlock != nil { - fetchHashes, err = self.addBlock(td, block, peer) + err = self.addBlock(block, peerId) } return } -func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) { +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 { - fetchHashes = self.addPeer(td, currentBlock, peer) + best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, invalidBlock) } return } +func (self *TestBackend) RemovePeer(peerId string) { + if self.removePeer != nil { + self.removePeer(peerId) + } +} + func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { if self.status != nil { td, currentBlock, genesisBlock = self.status() @@ -116,13 +124,35 @@ func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBloc return } -func TestEth(t *testing.T) { +// TODO: refactor this into p2p/client_identity +type peerId struct { + pubkey []byte +} + +func (self *peerId) String() string { + return "test peer" +} + +func (self *peerId) Pubkey() (pubkey []byte) { + pubkey = self.pubkey + if len(pubkey) == 0 { + pubkey = crypto.GenerateNewKeyPair().PublicKey + self.pubkey = pubkey + } + return +} + +func testPeer() *p2p.Peer { + return p2p.NewPeer(&peerId{}, []p2p.Cap{}) +} + +func TestErrNoStatusMsg(t *testing.T) { quit := make(chan bool) rw := &testMsgReadWriter{make(chan p2p.Msg, 10), make(chan p2p.Msg, 10)} testBackend := &TestBackend{} var err error go func() { - err = runEthProtocol(testBackend, nil, rw) + err = runEthProtocol(testBackend, testPeer(), rw) close(quit) }() statusMsg := p2p.NewMsg(4) -- cgit v1.2.3 From 4366fdfc13d68357c1f94cd3355ef50e35980920 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 10 Dec 2014 04:12:25 +0000 Subject: initial commit for eth blockpool + test --- eth/block_pool.go | 514 +++++++++++++++++++++++++++++++++++++++++++++++++ eth/block_pool_test.go | 198 +++++++++++++++++++ 2 files changed, 712 insertions(+) create mode 100644 eth/block_pool.go create mode 100644 eth/block_pool_test.go (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go new file mode 100644 index 000000000..2c5ebb1e3 --- /dev/null +++ b/eth/block_pool.go @@ -0,0 +1,514 @@ +package eth + +import ( + "bytes" + "fmt" + "math" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/event" + ethlogger "github.com/ethereum/go-ethereum/logger" +) + +var poolLogger = ethlogger.NewLogger("Blockpool") + +const ( + blockHashesBatchSize = 256 + blockBatchSize = 64 + blockRequestInterval = 10 // seconds + blockRequestRepetition = 1 + cacheTimeout = 3 // minutes + blockTimeout = 5 // minutes +) + +type poolNode struct { + hash []byte + block *types.Block + child *poolNode + parent *poolNode + root *nodePointer + knownParent bool + suicide chan bool + peer string + source string + blockRequestRoot bool + blockRequestControl *bool + blockRequestQuit *(chan bool) +} + +// the minimal interface for chain manager +type chainManager interface { + KnownBlock(hash []byte) bool + AddBlock(*types.Block) error + CheckPoW(*types.Block) bool +} + +type BlockPool struct { + chainManager chainManager + eventer event.TypeMux + + // pool Pool + lock sync.Mutex + pool map[string]*poolNode + + peersLock sync.Mutex + peers map[string]*peerInfo + peer *peerInfo + + quit chan bool + wg sync.WaitGroup + running bool +} + +type peerInfo struct { + td *big.Int + currentBlock []byte + id string + requestBlockHashes func([]byte) error + requestBlocks func([][]byte) error + invalidBlock func(error) +} + +type nodePointer struct { + hash []byte +} + +type peerChangeEvent struct { + *peerInfo +} + +func NewBlockPool(chMgr chainManager) *BlockPool { + return &BlockPool{ + chainManager: chMgr, + pool: make(map[string]*poolNode), + peers: make(map[string]*peerInfo), + quit: make(chan bool), + running: true, + } +} + +func (self *BlockPool) Stop() { + self.lock.Lock() + if !self.running { + self.lock.Unlock() + return + } + self.running = false + self.lock.Unlock() + + poolLogger.Infoln("Stopping") + + close(self.quit) + self.wg.Wait() + poolLogger.Infoln("Stopped") + +} + +// Entry point for eth protocol to add block hashes received via BlockHashesMsg +// only hashes from the best peer is handled +// this method is always responsible to initiate further hash requests until +// a known parent is reached unless cancelled by a peerChange event +func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) { + // subscribe to peerChangeEvent before we check for best peer + peerChange := self.eventer.Subscribe(peerChangeEvent{}) + defer peerChange.Unsubscribe() + // check if this peer is the best + peer, best := self.getPeer(peerId) + if !best { + return + } + root := &nodePointer{} + // peer is still the best + hashes := make(chan []byte) + var lastPoolNode *poolNode + + // using a for select loop so that peer change (new best peer) can abort the parallel thread that processes hashes of the earlier best peer + for { + hash, ok := next() + if ok { + hashes <- hash + } else { + break + } + select { + case <-self.quit: + return + case <-peerChange.Chan(): + // remember where we left off with this peer + if lastPoolNode != nil { + root.hash = lastPoolNode.hash + go self.killChain(lastPoolNode) + } + case hash := <-hashes: + self.lock.Lock() + defer self.lock.Unlock() + // check if known block connecting the downloaded chain to our blockchain + if self.chainManager.KnownBlock(hash) { + poolLogger.Infof("known block (%x...)\n", hash[0:4]) + if lastPoolNode != nil { + lastPoolNode.knownParent = true + go self.requestBlocksLoop(lastPoolNode) + } else { + // all hashes known if topmost one is in blockchain + } + return + } + // + var currentPoolNode *poolNode + // check if lastPoolNode has the correct parent node (hash matching), + // then just assign to currentPoolNode + if lastPoolNode != nil && lastPoolNode.parent != nil && bytes.Compare(lastPoolNode.parent.hash, hash) == 0 { + currentPoolNode = lastPoolNode.parent + } else { + // otherwise look up in pool + currentPoolNode = self.pool[string(hash)] + // if node does not exist, create it and index in the pool + if currentPoolNode == nil { + currentPoolNode = &poolNode{ + hash: hash, + } + self.pool[string(hash)] = currentPoolNode + } + } + // set up parent-child nodes (doubly linked list) + self.link(currentPoolNode, lastPoolNode) + // ! we trust the node iff + // (1) node marked as by the same peer or + // (2) it has a PoW valid block retrieved + if currentPoolNode.peer == peer.id || currentPoolNode.block != nil { + // the trusted checkpoint from which we request hashes down to known head + lastPoolNode = self.pool[string(currentPoolNode.root.hash)] + break + } + currentPoolNode.peer = peer.id + currentPoolNode.root = root + lastPoolNode = currentPoolNode + } + } + // lastPoolNode is nil if and only if the node with stored root hash is already cleaned up + // after valid block insertion, therefore in this case the blockpool active chain is connected to the blockchain, so no need to request further hashes or request blocks + if lastPoolNode != nil { + root.hash = lastPoolNode.hash + peer.requestBlockHashes(lastPoolNode.hash) + go self.requestBlocksLoop(lastPoolNode) + } + return +} + +func (self *BlockPool) requestBlocksLoop(node *poolNode) { + suicide := time.After(blockTimeout * time.Minute) + requestTimer := time.After(0) + var controlChan chan bool + closedChan := make(chan bool) + quit := make(chan bool) + close(closedChan) + requestBlocks := true + origNode := node + self.lock.Lock() + node.blockRequestRoot = true + b := false + control := &b + node.blockRequestControl = control + node.blockRequestQuit = &quit + self.lock.Unlock() + blocks := 0 + self.wg.Add(1) +loop: + for { + if requestBlocks { + controlChan = closedChan + } else { + self.lock.Lock() + if *node.blockRequestControl { + controlChan = closedChan + *node.blockRequestControl = false + } + self.lock.Unlock() + } + select { + case <-quit: + break loop + case <-suicide: + go self.killChain(origNode) + break loop + + case <-requestTimer: + requestBlocks = true + + case <-controlChan: + controlChan = nil + // this iteration takes care of requesting blocks only starting from the first node with a missing block (moving target), + // max up to the next checkpoint (n.blockRequestRoot true) + nodes := []*poolNode{} + n := node + next := node + self.lock.Lock() + for n != nil && (n == node || !n.blockRequestRoot) && (requestBlocks || n.block != nil) { + if n.block != nil { + if len(nodes) == 0 { + // nil control indicates that node is not needed anymore + // block can be inserted to blockchain and deleted if knownParent + n.blockRequestControl = nil + blocks++ + next = next.child + } else { + // this is needed to indicate that when a new chain forks from an existing one + // triggering a reorg will ? renew the blockTimeout period ??? + // if there is a block but control == nil should start fetching blocks, see link function + n.blockRequestControl = control + } + } else { + nodes = append(nodes, n) + n.blockRequestControl = control + } + n = n.child + } + // if node is connected to the blockchain, we can immediately start inserting + // blocks to the blockchain and delete nodes + if node.knownParent { + go self.insertChainFrom(node) + } + if next.blockRequestRoot && next != node { + // no more missing blocks till the checkpoint, quitting + poolLogger.Debugf("fetched %v blocks on active chain, batch %v-%v", blocks, origNode, n) + break loop + } + self.lock.Unlock() + + // reset starting node to the first descendant node with missing block + node = next + if !requestBlocks { + continue + } + go self.requestBlocks(nodes) + requestTimer = time.After(blockRequestInterval * time.Second) + } + } + self.wg.Done() + return +} + +func (self *BlockPool) requestBlocks(nodes []*poolNode) { + // distribute block request among known peers + self.peersLock.Lock() + peerCount := len(self.peers) + poolLogger.Debugf("requesting %v missing blocks from %v peers", len(nodes), peerCount) + blockHashes := make([][][]byte, peerCount) + repetitions := int(math.Max(float64(peerCount)/2.0, float64(blockRequestRepetition))) + for n, node := range nodes { + for i := 0; i < repetitions; i++ { + blockHashes[n%peerCount] = append(blockHashes[n%peerCount], node.hash) + n++ + } + } + i := 0 + for _, peer := range self.peers { + peer.requestBlocks(blockHashes[i]) + i++ + } + self.peersLock.Unlock() +} + +func (self *BlockPool) insertChainFrom(node *poolNode) { + self.lock.Lock() + defer self.lock.Unlock() + for node != nil && node.blockRequestControl == nil { + err := self.chainManager.AddBlock(node.block) + if err != nil { + poolLogger.Debugf("invalid block %v", node.hash) + poolLogger.Debugf("penalise peers %v (hash), %v (block)", node.peer, node.source) + // penalise peer in node.source + go self.killChain(node) + return + } + poolLogger.Debugf("insert block %v into blockchain", node.hash) + node = node.child + } + // if block insertion succeeds, mark the child as knownParent + // trigger request blocks reorg + if node != nil { + node.knownParent = true + *(node.blockRequestControl) = true + } +} + +// 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, invalidBlock func(error)) bool { + self.peersLock.Lock() + defer self.peersLock.Unlock() + if self.peers[peerId] != nil { + panic("peer already added") + } + info := &peerInfo{ + td: td, + currentBlock: currentBlock, + id: peerId, //peer.Identity().Pubkey() + requestBlockHashes: requestBlockHashes, + requestBlocks: requestBlocks, + invalidBlock: invalidBlock, + } + self.peers[peerId] = info + poolLogger.Debugf("add new peer %v with td %v", peerId, td) + currentTD := ethutil.Big0 + if self.peer != nil { + currentTD = self.peer.td + } + if td.Cmp(currentTD) > 0 { + self.peer = info + self.eventer.Post(peerChangeEvent{info}) + poolLogger.Debugf("peer %v promoted to best peer", peerId) + requestBlockHashes(currentBlock) + return true + } + return false +} + +// RemovePeer is called by the eth protocol when the peer disconnects +func (self *BlockPool) RemovePeer(peerId string) { + self.peersLock.Lock() + defer self.peersLock.Unlock() + if self.peers[peerId] != nil { + panic("peer already removed") + } + self.peers[peerId] = nil + poolLogger.Debugf("remove peer %v", peerId[0:4]) + + // if current best peer is removed, need find a better one + if peerId == self.peer.id { + var newPeer *peerInfo + max := ethutil.Big0 + // peer with the highest self-acclaimed TD is chosen + for _, info := range self.peers { + if info.td.Cmp(max) > 0 { + max = info.td + newPeer = info + } + } + self.peer = newPeer + self.eventer.Post(peerChangeEvent{newPeer}) + if newPeer != nil { + poolLogger.Debugf("peer %v with td %v spromoted to best peer", newPeer.id[0:4], newPeer.td) + newPeer.requestBlockHashes(newPeer.currentBlock) + } else { + poolLogger.Warnln("no peers left") + } + } +} + +func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { + self.peersLock.Lock() + defer self.peersLock.Unlock() + if self.peer.id == peerId { + return self.peer, true + } + info, ok := self.peers[peerId] + if !ok { + panic("unknown peer") + } + return info, false +} + +// if same peer gave different chain before, this will overwrite it +// if currentPoolNode existed as a non-leaf node the earlier fork is delinked +// if same parent hash is found, we can abort, we do not allow the same peer to change minds about parent of same hash, if errored first time round, will get penalized. +// if lastPoolNode had a different parent the earlier parent (with entire subtree) is delinked, this situation cannot normally arise though +// just in case reset lastPoolNode as non-root (unlikely) + +func (self *BlockPool) link(parent, child *poolNode) { + // reactivate node scheduled for suicide + if parent.suicide != nil { + close(parent.suicide) + parent.suicide = nil + } + if parent.child != child { + orphan := parent.child + orphan.parent = nil + go self.killChain(orphan) + parent.child = child + } + if child != nil { + if child.parent != parent { + orphan := child.parent + orphan.child = nil + go func() { + // if it is a aberrant reverse fork, zip down to bottom + for orphan.parent != nil { + orphan = orphan.parent + } + self.killChain(orphan) + }() + child.parent = parent + } + child.knownParent = false + } +} + +func (self *BlockPool) killChain(node *poolNode) { + if node == nil { + return + } + poolLogger.Debugf("suicide scheduled on node %v", node) + suicide := make(chan bool) + self.lock.Lock() + node.suicide = suicide + self.lock.Unlock() + timer := time.After(cacheTimeout * time.Minute) + self.wg.Add(1) + select { + case <-self.quit: + case <-suicide: + // cancel suicide = close node.suicide to reactivate node + case <-timer: + poolLogger.Debugf("suicide on node %v", node) + self.lock.Lock() + defer self.lock.Unlock() + // proceed up via child links until another suicide root found or chain ends + // abort request blocks loops that start above + // and delete nodes from pool then quit the suicide process + okToAbort := node.blockRequestRoot + for node != nil && (node.suicide == suicide || node.suicide == nil) { + self.pool[string(node.hash)] = nil + if okToAbort && node.blockRequestQuit != nil { + quit := *(node.blockRequestQuit) + if quit != nil { // not yet closed + *(node.blockRequestQuit) = nil + close(quit) + } + } else { + okToAbort = true + } + node = node.child + } + } + self.wg.Done() +} + +// AddBlock is the entry point for the eth protocol when blockmsg is received upon requests +// It has a strict interpretation of the protocol in that if the block received has not been requested, it results in an error (which can be ignored) +// block is checked for PoW +// only the first PoW-valid block for a hash is considered legit +func (self *BlockPool) AddBlock(block *types.Block, peerId string) (err error) { + hash := block.Hash() + self.lock.Lock() + defer self.lock.Unlock() + node, ok := self.pool[string(hash)] + if !ok && !self.chainManager.KnownBlock(hash) { + return fmt.Errorf("unrequested block %x", hash) + } + if node.block != nil { + return + } + // validate block for PoW + if !self.chainManager.CheckPoW(block) { + return fmt.Errorf("invalid pow on block %x", hash) + } + node.block = block + node.source = peerId + return nil +} diff --git a/eth/block_pool_test.go b/eth/block_pool_test.go new file mode 100644 index 000000000..315cc748d --- /dev/null +++ b/eth/block_pool_test.go @@ -0,0 +1,198 @@ +package eth + +import ( + "bytes" + "fmt" + "log" + "os" + "sync" + "testing" + + "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" +) + +var sys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel)) + +type testChainManager struct { + knownBlock func(hash []byte) bool + addBlock func(*types.Block) error + checkPoW func(*types.Block) bool +} + +func (self *testChainManager) KnownBlock(hash []byte) bool { + if self.knownBlock != nil { + return self.knownBlock(hash) + } + 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 + } + } + 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") + } + } + 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...), + } +} + +type intToHash map[int][]byte + +type hashToInt map[string]int + +type testHashPool struct { + intToHash + hashToInt +} + +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) { + for _, i := range indexes { + hash, found := self.intToHash[i] + if !found { + hash = newHash(i) + self.intToHash[i] = hash + self.hashToInt[string(hash)] = i + } + hashes = append(hashes, hash) + } + return +} + +func (self *testHashPool) hashesToIndexes(hashes [][]byte) (indexes []int) { + for _, hash := range hashes { + i, found := self.hashToInt[string(hash)] + if !found { + i = -1 + } + indexes = append(indexes, i) + } + return +} + +type protocolChecker struct { + blockHashesRequests []int + blocksRequests [][]int + invalidBlocks []error + hashPool *testHashPool + lock sync.Mutex +} + +// -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 +} + +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 + } + return +} + +func (self *protocolChecker) invalidBlockCallBack() (invalidBlockCallBack func(error)) { + invalidBlockCallBack = func(err error) { + self.invalidBlocks = append(self.invalidBlocks, err) + } + return +} + +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(), + ) + if !best { + t.Errorf("peer not accepted as best") + } + blockPool.Stop() + +} -- cgit v1.2.3 From 02017ed0e019ea2692c1e184873d09f41551a0ec Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 18:03:24 +0000 Subject: initial commit for new backend (eth.Ethereum) --- eth/backend.go | 357 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 eth/backend.go (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go new file mode 100644 index 000000000..9154ca0f5 --- /dev/null +++ b/eth/backend.go @@ -0,0 +1,357 @@ +package eth + +import ( + "encoding/json" + "net" + "path" + "sync" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/event" + ethlogger "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/state" +) + +const ( + seedNodeAddress = "poc-7.ethdev.com:30300" +) + +var logger = ethlogger.NewLogger("SERV") + +type Ethereum struct { + // Channel for shutting down the ethereum + shutdownChan chan bool + quit chan bool + + // DB interface + db ethutil.Database + // State manager for processing new blocks and managing the over all states + blockManager *core.BlockManager + + // The transaction pool. Transaction can be pushed on this pool + // for later including in the blocks + txPool *core.TxPool + // The canonical chain + chainManager *core.ChainManager + // The block pool + blockPool *BlockPool + // Event + eventMux *event.TypeMux + + // Nonce + Nonce uint64 + + ListenAddr string + + blacklist p2p.Blacklist + server *p2p.Server + txSub event.Subscription + blockSub event.Subscription + + // Capabilities for outgoing peers + // serverCaps Caps + peersFile string + + Mining bool + + RpcServer *rpc.JsonRpcServer + + keyManager *crypto.KeyManager + + clientIdentity p2p.ClientIdentity + + synclock sync.Mutex + syncGroup sync.WaitGroup + + filterMu sync.RWMutex + filterId int + filters map[int]*core.Filter +} + +func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, port string, maxPeers int) (*Ethereum, error) { + + saveProtocolVersion(db) + ethutil.Config.Db = db + + // FIXME: + blacklist := p2p.NewBlacklist() + // Sorry Py person. I must blacklist. you perform badly + blacklist.Put(ethutil.Hex2Bytes("64656330303561383532336435376331616537643864663236623336313863373537353163636634333530626263396330346237336262623931383064393031")) + + peersFile := path.Join(ethutil.Config.ExecPath, "known_peers.json") + + nonce, _ := ethutil.RandomUint64() + + listenAddr := ":" + port + + eth := &Ethereum{ + shutdownChan: make(chan bool), + quit: make(chan bool), + db: db, + Nonce: nonce, + // serverCaps: caps, + peersFile: peersFile, + ListenAddr: listenAddr, + keyManager: keyManager, + clientIdentity: identity, + blacklist: blacklist, + eventMux: &event.TypeMux{}, + filters: make(map[int]*core.Filter), + } + + eth.txPool = core.NewTxPool(eth) + eth.chainManager = core.NewChainManager(eth.EventMux()) + eth.blockManager = core.NewBlockManager(eth) + eth.chainManager.SetProcessor(eth.blockManager) + + hasBlock := eth.chainManager.HasBlock + insertChain := eth.chainManager.InsertChain + // pow := ezp.New() + // verifyPoW := pow.Verify + verifyPoW := func(*types.Block) bool { return true } + eth.blockPool = NewBlockPool(hasBlock, insertChain, verifyPoW) + + // Start the tx pool + eth.txPool.Start() + + ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) + protocols := []p2p.Protocol{ethProto} + + server := &p2p.Server{ + Identity: identity, + MaxPeers: maxPeers, + Protocols: protocols, + ListenAddr: listenAddr, + Blacklist: blacklist, + NAT: nat, + } + + eth.server = server + + return eth, nil +} + +func (s *Ethereum) KeyManager() *crypto.KeyManager { + return s.keyManager +} + +func (s *Ethereum) ClientIdentity() p2p.ClientIdentity { + return s.clientIdentity +} + +func (s *Ethereum) ChainManager() *core.ChainManager { + return s.chainManager +} + +func (s *Ethereum) BlockManager() *core.BlockManager { + return s.blockManager +} + +func (s *Ethereum) TxPool() *core.TxPool { + return s.txPool +} + +func (s *Ethereum) BlockPool() *BlockPool { + return s.blockPool +} + +func (s *Ethereum) EventMux() *event.TypeMux { + return s.eventMux +} +func (self *Ethereum) Db() ethutil.Database { + return self.db +} + +func (s *Ethereum) IsMining() bool { + return s.Mining +} + +func (s *Ethereum) IsListening() bool { + if s.ListenAddr == "" { + return false + } else { + return true + } +} + +func (s *Ethereum) PeerCount() int { + return s.server.PeerCount() +} + +func (s *Ethereum) Peers() []*p2p.Peer { + return s.server.Peers() +} + +// Start the ethereum +func (s *Ethereum) Start(seed bool) error { + err := s.server.Start() + if err != nil { + return err + } + s.blockPool.Start() + s.blockManager.Start() + + go s.filterLoop() + + // broadcast transactions + s.txSub = s.eventMux.Subscribe(core.TxPreEvent{}) + go s.txBroadcastLoop() + + // broadcast mined blocks + s.blockSub = s.eventMux.Subscribe(core.NewMinedBlockEvent{}) + go s.blockBroadcastLoop() + + // TODO: read peers here + if seed { + logger.Infof("Connect to seed node %v", seedNodeAddress) + if err := s.SuggestPeer(seedNodeAddress); err != nil { + return err + } + } + + logger.Infoln("Server started") + return nil +} + +func (self *Ethereum) SuggestPeer(addr string) error { + netaddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + logger.Errorf("couldn't resolve %s:", addr, err) + return err + } + + self.server.SuggestPeer(netaddr.IP, netaddr.Port, nil) + return nil +} + +func (s *Ethereum) Stop() { + // Close the database + defer s.db.Close() + + // + // WritePeers(s.peersFile, s.server.PeerAddresses()) + close(s.quit) + + s.txSub.Unsubscribe() // quits txBroadcastLoop + s.blockSub.Unsubscribe() // quits blockBroadcastLoop + + if s.RpcServer != nil { + s.RpcServer.Stop() + } + s.txPool.Stop() + s.blockManager.Stop() + s.eventMux.Stop() + s.blockPool.Stop() + + logger.Infoln("Server stopped") + close(s.shutdownChan) +} + +// This function will wait for a shutdown and resumes main thread execution +func (s *Ethereum) WaitForShutdown() { + <-s.shutdownChan +} + +func WritePeers(path string, addresses []string) { + if len(addresses) > 0 { + data, _ := json.MarshalIndent(addresses, "", " ") + ethutil.WriteFile(path, data) + } +} + +func ReadPeers(path string) (ips []string, err error) { + var data string + data, err = ethutil.ReadAllFile(path) + if err != nil { + json.Unmarshal([]byte(data), &ips) + } + return +} + +// now tx broadcasting is taken out of txPool +// handled here via subscription, efficiency? +func (self *Ethereum) txBroadcastLoop() { + // automatically stops if unsubscribe + for obj := range self.txSub.Chan() { + event := obj.(core.TxPreEvent) + self.server.Broadcast("eth", TxMsg, []interface{}{event.Tx.RlpData()}) + } +} + +func (self *Ethereum) blockBroadcastLoop() { + // automatically stops if unsubscribe + for obj := range self.txSub.Chan() { + event := obj.(core.NewMinedBlockEvent) + self.server.Broadcast("eth", NewBlockMsg, event.Block.Value().Val) + } +} + +func saveProtocolVersion(db ethutil.Database) { + d, _ := db.Get([]byte("ProtocolVersion")) + protocolVersion := ethutil.NewValue(d).Uint() + + if protocolVersion == 0 { + db.Put([]byte("ProtocolVersion"), ethutil.NewValue(ProtocolVersion).Bytes()) + } +} + +// InstallFilter adds filter for blockchain events. +// The filter's callbacks will run for matching blocks and messages. +// The filter should not be modified after it has been installed. +func (self *Ethereum) InstallFilter(filter *core.Filter) (id int) { + self.filterMu.Lock() + id = self.filterId + self.filters[id] = filter + self.filterId++ + self.filterMu.Unlock() + return id +} + +func (self *Ethereum) UninstallFilter(id int) { + self.filterMu.Lock() + delete(self.filters, id) + self.filterMu.Unlock() +} + +// GetFilter retrieves a filter installed using InstallFilter. +// The filter may not be modified. +func (self *Ethereum) GetFilter(id int) *core.Filter { + self.filterMu.RLock() + defer self.filterMu.RUnlock() + return self.filters[id] +} + +func (self *Ethereum) filterLoop() { + // Subscribe to events + events := self.eventMux.Subscribe(core.NewBlockEvent{}, state.Messages(nil)) + for event := range events.Chan() { + switch event.(type) { + case core.NewBlockEvent: + self.filterMu.RLock() + for _, filter := range self.filters { + if filter.BlockCallback != nil { + e := event.(core.NewBlockEvent) + filter.BlockCallback(e.Block) + } + } + self.filterMu.RUnlock() + case state.Messages: + self.filterMu.RLock() + for _, filter := range self.filters { + if filter.MessageCallback != nil { + e := event.(state.Messages) + msgs := filter.FilterMessages(e) + if len(msgs) > 0 { + filter.MessageCallback(msgs) + } + } + } + self.filterMu.RUnlock() + } + } +} -- cgit v1.2.3 From c44e02589877722af7ef61738ff7ec05a7a14279 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 18:04:50 +0000 Subject: protocol - new interface explicit backend components txPool, chainManager, blockPool - added protoErrorDisconnect for blockpool callback (FIXME: handling peer disconnects) --- eth/protocol.go | 102 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 50 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 380bcc8d2..3b5b49696 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -14,26 +14,33 @@ import ( // ethProtocol represents the ethereum wire protocol // instance is running on each peer type ethProtocol struct { - eth backend - peer *p2p.Peer - id string - rw p2p.MsgReadWriter + txPool txPool + chainManager chainManager + blockPool blockPool + peer *p2p.Peer + id string + rw p2p.MsgReadWriter } // backend is the interface the ethereum protocol backend should implement // used as an argument to EthProtocol -type backend interface { - GetTransactions() (txs []*types.Transaction) +type txPool interface { AddTransactions([]*types.Transaction) - GetBlockHashes(hash []byte, amount uint32) (hashes [][]byte) - AddBlockHashes(next func() ([]byte, bool), peerId string) +} + +type chainManager interface { + GetBlockHashesFromHash(hash []byte, amount uint64) (hashes [][]byte) GetBlock(hash []byte) (block *types.Block) - AddBlock(block *types.Block, peerId string) (err error) - AddPeer(td *big.Int, currentBlock []byte, peerId string, requestHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) - RemovePeer(peerId string) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) } +type blockPool interface { + AddBlockHashes(next func() ([]byte, bool), peerId string) + AddBlock(block *types.Block, peerId string) + AddPeer(td *big.Int, currentBlock []byte, peerId string, requestHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) (best bool) + RemovePeer(peerId string) +} + const ( ProtocolVersion = 43 NetworkId = 0 @@ -61,31 +68,33 @@ type newBlockMsgData struct { type getBlockHashesMsgData struct { Hash []byte - Amount uint32 + Amount uint64 } // main entrypoint, wrappers starting a server running the eth protocol // use this constructor to attach the protocol ("class") to server caps // the Dev p2p layer then runs the protocol instance on each peer -func EthProtocol(eth backend) *p2p.Protocol { - return &p2p.Protocol{ +func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) p2p.Protocol { + return p2p.Protocol{ Name: "eth", Version: ProtocolVersion, Length: ProtocolLength, Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - return runEthProtocol(eth, peer, rw) + return runEthProtocol(txPool, chainManager, blockPool, peer, rw) }, } } // the main loop that handles incoming messages // note RemovePeer in the post-disconnect hook -func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { +func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { self := ðProtocol{ - eth: eth, - rw: rw, - peer: peer, - id: (string)(peer.Identity().Pubkey()), + txPool: txPool, + chainManager: chainManager, + blockPool: blockPool, + rw: rw, + peer: peer, + id: (string)(peer.Identity().Pubkey()), } err = self.handleStatus() if err == nil { @@ -93,7 +102,7 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro for { err = self.handle() if err != nil { - self.eth.RemovePeer(self.id) + self.blockPool.RemovePeer(self.id) break } } @@ -118,29 +127,20 @@ func (self *ethProtocol) handle() error { case StatusMsg: return ProtocolError(ErrExtraStatusMsg, "") - case GetTxMsg: - txs := self.eth.GetTransactions() - // TODO: rewrite using rlp flat - txsInterface := make([]interface{}, len(txs)) - for i, tx := range txs { - txsInterface[i] = tx.RlpData() - } - return self.rw.EncodeMsg(TxMsg, txsInterface...) - case TxMsg: // TODO: rework using lazy RLP stream var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { return ProtocolError(ErrDecode, "%v", err) } - self.eth.AddTransactions(txs) + self.txPool.AddTransactions(txs) case GetBlockHashesMsg: var request getBlockHashesMsgData if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } - hashes := self.eth.GetBlockHashes(request.Hash, request.Amount) + hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: @@ -154,7 +154,7 @@ func (self *ethProtocol) handle() error { } return } - self.eth.AddBlockHashes(iter, self.id) + self.blockPool.AddBlockHashes(iter, self.id) if err != nil && err != rlp.EOL { return ProtocolError(ErrDecode, "%v", err) } @@ -170,7 +170,7 @@ func (self *ethProtocol) handle() error { if i >= max { break } - block := self.eth.GetBlock(hash) + block := self.chainManager.GetBlock(hash) if block != nil { blocks = append(blocks, block.Value().Raw()) } @@ -188,9 +188,7 @@ func (self *ethProtocol) handle() error { return ProtocolError(ErrDecode, "%v", err) } } - if err := self.eth.AddBlock(block, self.id); err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } + self.blockPool.AddBlock(block, self.id) } case NewBlockMsg: @@ -202,7 +200,7 @@ func (self *ethProtocol) handle() error { // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) - if self.eth.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) { + if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { called := true iter := func() (hash []byte, ok bool) { if called { @@ -212,10 +210,8 @@ func (self *ethProtocol) handle() error { return } } - self.eth.AddBlockHashes(iter, self.id) - if err := self.eth.AddBlock(request.Block, self.id); err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } + self.blockPool.AddBlockHashes(iter, self.id) + self.blockPool.AddBlock(request.Block, self.id) } default: @@ -233,7 +229,7 @@ type statusMsgData struct { } func (self *ethProtocol) statusMsg() p2p.Msg { - td, currentBlock, genesisBlock := self.eth.Status() + td, currentBlock, genesisBlock := self.chainManager.Status() return p2p.NewMsg(StatusMsg, uint32(ProtocolVersion), @@ -269,7 +265,7 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrDecode, "%v", err) } - _, _, genesisBlock := self.eth.Status() + _, _, genesisBlock := self.chainManager.Status() if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) @@ -285,7 +281,7 @@ func (self *ethProtocol) handleStatus() error { self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) - self.eth.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) + self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) return nil } @@ -300,11 +296,6 @@ func (self *ethProtocol) requestBlocks(hashes [][]byte) error { return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)) } -func (self *ethProtocol) invalidBlock(err error) { - ProtocolError(ErrInvalidBlock, "%v", err) - self.peer.Disconnect(p2p.DiscSubprotocolError) -} - func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { err = ProtocolError(code, format, params...) if err.Fatal() { @@ -314,3 +305,14 @@ func (self *ethProtocol) protoError(code int, format string, params ...interface } return } + +func (self *ethProtocol) protoErrorDisconnect(code int, format string, params ...interface{}) { + err := ProtocolError(code, format, params...) + if err.Fatal() { + self.peer.Errorln(err) + // disconnect + } else { + self.peer.Debugln(err) + } + +} -- cgit v1.2.3 From 3308d82b234f93dbba80d332e495dcf157aacbe8 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 18:07:05 +0000 Subject: add protocol error types specific to blockpool --- eth/error.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'eth') diff --git a/eth/error.go b/eth/error.go index 9355d6457..d1daad575 100644 --- a/eth/error.go +++ b/eth/error.go @@ -14,6 +14,8 @@ const ( ErrNoStatusMsg ErrExtraStatusMsg ErrInvalidBlock + ErrInvalidPoW + ErrUnrequestedBlock ) var errorToString = map[int]string{ @@ -26,6 +28,8 @@ var errorToString = map[int]string{ ErrNoStatusMsg: "No status message", ErrExtraStatusMsg: "Extra status message", ErrInvalidBlock: "Invalid block", + ErrInvalidPoW: "Invalid PoW", + ErrUnrequestedBlock: "Unrequested block", } type protocolError struct { -- cgit v1.2.3 From 76070b46742338f354ab47a2c5a202e808daae23 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 18:08:18 +0000 Subject: blockpool rewritten , tests broken FIXME --- eth/block_pool.go | 1226 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 863 insertions(+), 363 deletions(-) (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go index 2c5ebb1e3..64d1e73fa 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -1,369 +1,159 @@ package eth import ( - "bytes" - "fmt" "math" "math/big" + "math/rand" + "sort" "sync" "time" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/event" ethlogger "github.com/ethereum/go-ethereum/logger" ) var poolLogger = ethlogger.NewLogger("Blockpool") const ( - blockHashesBatchSize = 256 - blockBatchSize = 64 - blockRequestInterval = 10 // seconds - blockRequestRepetition = 1 - cacheTimeout = 3 // minutes - blockTimeout = 5 // minutes + blockHashesBatchSize = 256 + blockBatchSize = 64 + blocksRequestInterval = 10 // seconds + blocksRequestRepetition = 1 + blockHashesRequestInterval = 10 // seconds + blocksRequestMaxIdleRounds = 10 + cacheTimeout = 3 // minutes + blockTimeout = 5 // minutes ) type poolNode struct { - hash []byte - block *types.Block - child *poolNode - parent *poolNode - root *nodePointer - knownParent bool - suicide chan bool - peer string - source string - blockRequestRoot bool - blockRequestControl *bool - blockRequestQuit *(chan bool) -} - -// the minimal interface for chain manager -type chainManager interface { - KnownBlock(hash []byte) bool - AddBlock(*types.Block) error - CheckPoW(*types.Block) bool + lock sync.RWMutex + hash []byte + block *types.Block + child *poolNode + parent *poolNode + section *section + knownParent bool + peer string + source string + complete bool } type BlockPool struct { - chainManager chainManager - eventer event.TypeMux - - // pool Pool - lock sync.Mutex + lock sync.RWMutex pool map[string]*poolNode - peersLock sync.Mutex + peersLock sync.RWMutex peers map[string]*peerInfo peer *peerInfo quit chan bool wg sync.WaitGroup running bool + + // the minimal interface with blockchain + hasBlock func(hash []byte) bool + insertChain func(types.Blocks) error + verifyPoW func(*types.Block) bool } type peerInfo struct { - td *big.Int - currentBlock []byte - id string + lock sync.RWMutex + + td *big.Int + currentBlock []byte + id string + requestBlockHashes func([]byte) error requestBlocks func([][]byte) error - invalidBlock func(error) -} + peerError func(int, string, ...interface{}) -type nodePointer struct { - hash []byte + sections map[string]*section + roots []*poolNode + quitC chan bool } -type peerChangeEvent struct { - *peerInfo -} - -func NewBlockPool(chMgr chainManager) *BlockPool { +func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(*types.Block) bool, +) *BlockPool { return &BlockPool{ - chainManager: chMgr, - pool: make(map[string]*poolNode), - peers: make(map[string]*peerInfo), - quit: make(chan bool), - running: true, + hasBlock: hasBlock, + insertChain: insertChain, + verifyPoW: verifyPoW, } } -func (self *BlockPool) Stop() { +// allows restart +func (self *BlockPool) Start() { self.lock.Lock() - if !self.running { + if self.running { self.lock.Unlock() return } - self.running = false + self.running = true + self.quit = make(chan bool) + self.pool = make(map[string]*poolNode) self.lock.Unlock() - poolLogger.Infoln("Stopping") + self.peersLock.Lock() + self.peers = make(map[string]*peerInfo) + self.peersLock.Unlock() - close(self.quit) - self.wg.Wait() - poolLogger.Infoln("Stopped") + poolLogger.Infoln("Started") } -// Entry point for eth protocol to add block hashes received via BlockHashesMsg -// only hashes from the best peer is handled -// this method is always responsible to initiate further hash requests until -// a known parent is reached unless cancelled by a peerChange event -func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) { - // subscribe to peerChangeEvent before we check for best peer - peerChange := self.eventer.Subscribe(peerChangeEvent{}) - defer peerChange.Unsubscribe() - // check if this peer is the best - peer, best := self.getPeer(peerId) - if !best { +func (self *BlockPool) Stop() { + self.lock.Lock() + if !self.running { + self.lock.Unlock() return } - root := &nodePointer{} - // peer is still the best - hashes := make(chan []byte) - var lastPoolNode *poolNode - - // using a for select loop so that peer change (new best peer) can abort the parallel thread that processes hashes of the earlier best peer - for { - hash, ok := next() - if ok { - hashes <- hash - } else { - break - } - select { - case <-self.quit: - return - case <-peerChange.Chan(): - // remember where we left off with this peer - if lastPoolNode != nil { - root.hash = lastPoolNode.hash - go self.killChain(lastPoolNode) - } - case hash := <-hashes: - self.lock.Lock() - defer self.lock.Unlock() - // check if known block connecting the downloaded chain to our blockchain - if self.chainManager.KnownBlock(hash) { - poolLogger.Infof("known block (%x...)\n", hash[0:4]) - if lastPoolNode != nil { - lastPoolNode.knownParent = true - go self.requestBlocksLoop(lastPoolNode) - } else { - // all hashes known if topmost one is in blockchain - } - return - } - // - var currentPoolNode *poolNode - // check if lastPoolNode has the correct parent node (hash matching), - // then just assign to currentPoolNode - if lastPoolNode != nil && lastPoolNode.parent != nil && bytes.Compare(lastPoolNode.parent.hash, hash) == 0 { - currentPoolNode = lastPoolNode.parent - } else { - // otherwise look up in pool - currentPoolNode = self.pool[string(hash)] - // if node does not exist, create it and index in the pool - if currentPoolNode == nil { - currentPoolNode = &poolNode{ - hash: hash, - } - self.pool[string(hash)] = currentPoolNode - } - } - // set up parent-child nodes (doubly linked list) - self.link(currentPoolNode, lastPoolNode) - // ! we trust the node iff - // (1) node marked as by the same peer or - // (2) it has a PoW valid block retrieved - if currentPoolNode.peer == peer.id || currentPoolNode.block != nil { - // the trusted checkpoint from which we request hashes down to known head - lastPoolNode = self.pool[string(currentPoolNode.root.hash)] - break - } - currentPoolNode.peer = peer.id - currentPoolNode.root = root - lastPoolNode = currentPoolNode - } - } - // lastPoolNode is nil if and only if the node with stored root hash is already cleaned up - // after valid block insertion, therefore in this case the blockpool active chain is connected to the blockchain, so no need to request further hashes or request blocks - if lastPoolNode != nil { - root.hash = lastPoolNode.hash - peer.requestBlockHashes(lastPoolNode.hash) - go self.requestBlocksLoop(lastPoolNode) - } - return -} - -func (self *BlockPool) requestBlocksLoop(node *poolNode) { - suicide := time.After(blockTimeout * time.Minute) - requestTimer := time.After(0) - var controlChan chan bool - closedChan := make(chan bool) - quit := make(chan bool) - close(closedChan) - requestBlocks := true - origNode := node - self.lock.Lock() - node.blockRequestRoot = true - b := false - control := &b - node.blockRequestControl = control - node.blockRequestQuit = &quit + self.running = false self.lock.Unlock() - blocks := 0 - self.wg.Add(1) -loop: - for { - if requestBlocks { - controlChan = closedChan - } else { - self.lock.Lock() - if *node.blockRequestControl { - controlChan = closedChan - *node.blockRequestControl = false - } - self.lock.Unlock() - } - select { - case <-quit: - break loop - case <-suicide: - go self.killChain(origNode) - break loop - - case <-requestTimer: - requestBlocks = true - - case <-controlChan: - controlChan = nil - // this iteration takes care of requesting blocks only starting from the first node with a missing block (moving target), - // max up to the next checkpoint (n.blockRequestRoot true) - nodes := []*poolNode{} - n := node - next := node - self.lock.Lock() - for n != nil && (n == node || !n.blockRequestRoot) && (requestBlocks || n.block != nil) { - if n.block != nil { - if len(nodes) == 0 { - // nil control indicates that node is not needed anymore - // block can be inserted to blockchain and deleted if knownParent - n.blockRequestControl = nil - blocks++ - next = next.child - } else { - // this is needed to indicate that when a new chain forks from an existing one - // triggering a reorg will ? renew the blockTimeout period ??? - // if there is a block but control == nil should start fetching blocks, see link function - n.blockRequestControl = control - } - } else { - nodes = append(nodes, n) - n.blockRequestControl = control - } - n = n.child - } - // if node is connected to the blockchain, we can immediately start inserting - // blocks to the blockchain and delete nodes - if node.knownParent { - go self.insertChainFrom(node) - } - if next.blockRequestRoot && next != node { - // no more missing blocks till the checkpoint, quitting - poolLogger.Debugf("fetched %v blocks on active chain, batch %v-%v", blocks, origNode, n) - break loop - } - self.lock.Unlock() - // reset starting node to the first descendant node with missing block - node = next - if !requestBlocks { - continue - } - go self.requestBlocks(nodes) - requestTimer = time.After(blockRequestInterval * time.Second) - } - } - self.wg.Done() - return -} + poolLogger.Infoln("Stopping") -func (self *BlockPool) requestBlocks(nodes []*poolNode) { - // distribute block request among known peers + close(self.quit) + self.lock.Lock() self.peersLock.Lock() - peerCount := len(self.peers) - poolLogger.Debugf("requesting %v missing blocks from %v peers", len(nodes), peerCount) - blockHashes := make([][][]byte, peerCount) - repetitions := int(math.Max(float64(peerCount)/2.0, float64(blockRequestRepetition))) - for n, node := range nodes { - for i := 0; i < repetitions; i++ { - blockHashes[n%peerCount] = append(blockHashes[n%peerCount], node.hash) - n++ - } - } - i := 0 - for _, peer := range self.peers { - peer.requestBlocks(blockHashes[i]) - i++ - } + self.peers = nil + self.pool = nil + self.peer = nil + self.wg.Wait() + self.lock.Unlock() self.peersLock.Unlock() -} + poolLogger.Infoln("Stopped") -func (self *BlockPool) insertChainFrom(node *poolNode) { - self.lock.Lock() - defer self.lock.Unlock() - for node != nil && node.blockRequestControl == nil { - err := self.chainManager.AddBlock(node.block) - if err != nil { - poolLogger.Debugf("invalid block %v", node.hash) - poolLogger.Debugf("penalise peers %v (hash), %v (block)", node.peer, node.source) - // penalise peer in node.source - go self.killChain(node) - return - } - poolLogger.Debugf("insert block %v into blockchain", node.hash) - node = node.child - } - // if block insertion succeeds, mark the child as knownParent - // trigger request blocks reorg - if node != nil { - node.knownParent = true - *(node.blockRequestControl) = true - } } // 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, invalidBlock func(error)) bool { +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") } - info := &peerInfo{ + peer := &peerInfo{ td: td, currentBlock: currentBlock, id: peerId, //peer.Identity().Pubkey() requestBlockHashes: requestBlockHashes, requestBlocks: requestBlocks, - invalidBlock: invalidBlock, + peerError: peerError, } - self.peers[peerId] = info + self.peers[peerId] = peer poolLogger.Debugf("add new peer %v with td %v", peerId, td) currentTD := ethutil.Big0 if self.peer != nil { currentTD = self.peer.td } if td.Cmp(currentTD) > 0 { - self.peer = info - self.eventer.Post(peerChangeEvent{info}) + self.peer.stop(peer) + peer.start(self.peer) poolLogger.Debugf("peer %v promoted to best peer", peerId) - requestBlockHashes(currentBlock) + self.peer = peer return true } return false @@ -373,14 +163,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() - if self.peers[peerId] != nil { - panic("peer already removed") + peer := self.peers[peerId] + if peer == nil { + return } self.peers[peerId] = nil poolLogger.Debugf("remove peer %v", peerId[0:4]) // if current best peer is removed, need find a better one - if peerId == self.peer.id { + if self.peer != nil && peerId == self.peer.id { var newPeer *peerInfo max := ethutil.Big0 // peer with the highest self-acclaimed TD is chosen @@ -390,21 +181,499 @@ func (self *BlockPool) RemovePeer(peerId string) { newPeer = info } } - self.peer = newPeer - self.eventer.Post(peerChangeEvent{newPeer}) + self.peer.stop(peer) + peer.start(self.peer) if newPeer != nil { - poolLogger.Debugf("peer %v with td %v spromoted to best peer", newPeer.id[0:4], newPeer.td) - newPeer.requestBlockHashes(newPeer.currentBlock) + poolLogger.Debugf("peer %v with td %v promoted to best peer", newPeer.id[0:4], newPeer.td) } else { poolLogger.Warnln("no peers left") } } } -func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { +// Entry point for eth protocol to add block hashes received via BlockHashesMsg +// only hashes from the best peer is handled +// this method is always responsible to initiate further hash requests until +// a known parent is reached unless cancelled by a peerChange event +// this process also launches all request processes on each chain section +// 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 + peer, best := self.getPeer(peerId) + if !best { + return + } + // peer is still the best + + var child *poolNode + var depth int + + // 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 + } + // check if known block connecting the downloaded chain to our blockchain + if self.hasBlock(hash) { + poolLogger.Infof("known block (%x...)\n", hash[0:4]) + if child != nil { + child.Lock() + // mark child as absolute pool root with parent known to blockchain + child.knownParent = true + child.Unlock() + } + break + } + // + var parent *poolNode + // look up node in pool + parent = self.get(hash) + if parent != nil { + // reached a known chain in the pool + // request blocks on the newly added part of the chain + if child != nil { + self.link(parent, child) + + // activate the current chain + self.activateChain(parent, peer, true) + poolLogger.Debugf("potential chain of %v blocks added, reached blockpool, activate chain", depth) + break + } + // if this is the first hash, we expect to find it + parent.RLock() + grandParent := parent.parent + parent.RUnlock() + if grandParent != nil { + // activate the current chain + self.activateChain(parent, peer, true) + poolLogger.Debugf("block hash found, activate chain") + break + } + // the first node is the root of a chain in the pool, rejoice and continue + } + // if node does not exist, create it and index in the pool + section := §ion{} + if child == nil { + section.top = parent + } + parent = &poolNode{ + hash: hash, + child: child, + section: section, + peer: peerId, + } + self.set(hash, parent) + poolLogger.Debugf("create potential block for %x...", hash[0:4]) + + depth++ + child = parent + } + } + if 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() + } + }() +} + +// AddBlock is the entry point for the eth protocol when blockmsg is received upon requests +// It has a strict interpretation of the protocol in that if the block received has not been requested, it results in an error (which can be ignored) +// block is checked for PoW +// only the first PoW-valid block for a hash is considered legit +func (self *BlockPool) AddBlock(block *types.Block, peerId string) { + hash := block.Hash() + node := self.get(hash) + node.RLock() + b := node.block + node.RUnlock() + if b != nil { + return + } + if node == nil && !self.hasBlock(hash) { + self.peerError(peerId, ErrUnrequestedBlock, "%x", hash) + return + } + // validate block for PoW + if !self.verifyPoW(block) { + self.peerError(peerId, ErrInvalidPoW, "%x", hash) + } + node.Lock() + node.block = block + node.source = peerId + node.Unlock() +} + +// 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: + } + + node = bottom.parent + bottom.sectionRUnlock() + } + // remember root for this peer + peer.addRoot(node) + self.wg.Done() + }() +} + +// main worker thread on each section in the poolchain +// - kills the section if there are blocks missing after an absolute time +// - kills the section if there are maxIdleRounds of idle rounds of block requests with no response +// - periodically polls the chain section for missing blocks which are then requested from peers +// - registers the process controller on the peer so that if the peer is promoted as best peer the second time (after a disconnect of a better one), all active processes are switched back on unless they expire and killed () +// - 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 + + node.sectionLock() + defer node.sectionUnlock() + section := §ion{controlC: controlC, resetC: resetC} + node.section = section + + 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 + } + // 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 + } + } + + // 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 + } 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 + } + 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 + done = false + // processC supposed to be empty and never closed so just swap, no need to allocate + tempC := processC + processC = missingC + missingC = tempC + } + 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 not root of chain, switch off + poolLogger.Debugf("[%x] parent found, hash requests deactivated (after %v total attempts)\n", hash[0:4], blockHashesRequests) + blockHashesRequestTimer = nil + } 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) + } + 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) + + alarm = false + blocksRequestTimer = nil + blockHashesRequestTimer = nil + 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) + } + 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 + } + alarm = true + processC = missingC + if !once { + // if not run at least once fully, launch iterator + processC = make(chan *poolNode) + missingC = make(chan *poolNode) + self.foldUp(orignode, processC) + once = true + } + } + total = lastMissing + case <-resetC: + once = false + init = false + done = false + case node, ok := <-processC: + if !ok { + // channel closed, first iteration finished + init = true + once = true + continue + } + i++ + // if node has no block + node.RLock() + block := node.block + nhash := node.hash + knownParent := node.knownParent + node.RUnlock() + if !init { + depth++ + } + if block == nil { + missing++ + if !init { + total++ + } + hashes = append(hashes, nhash) + if len(hashes) == blockBatchSize { + 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) + } + } + } + } + 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) + + self.wg.Done() + node.sectionLock() + node.section.controlC = nil + node.sectionUnlock() + // this signals that controller not available + }() + return section + +} + +func (self *BlockPool) peerError(peerId string, code int, format string, params ...interface{}) { + self.peersLock.RLock() + defer self.peersLock.RUnlock() + peer, ok := self.peers[peerId] + if ok { + peer.peerError(code, format, 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() - if self.peer.id == peerId { + 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 + } + } + i++ + } +} + +func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { + self.peersLock.RLock() + defer self.peersLock.RUnlock() + if self.peer != nil && self.peer.id == peerId { return self.peer, true } info, ok := self.peers[peerId] @@ -414,101 +683,332 @@ func (self *BlockPool) getPeer(peerId string) (*peerInfo, bool) { return info, false } -// if same peer gave different chain before, this will overwrite it -// if currentPoolNode existed as a non-leaf node the earlier fork is delinked -// if same parent hash is found, we can abort, we do not allow the same peer to change minds about parent of same hash, if errored first time round, will get penalized. -// if lastPoolNode had a different parent the earlier parent (with entire subtree) is delinked, this situation cannot normally arise though -// just in case reset lastPoolNode as non-root (unlikely) +func (self *peerInfo) addSection(hash []byte, section *section) { + self.lock.Lock() + defer self.lock.Unlock() + self.sections[string(hash)] = section +} + +func (self *peerInfo) addRoot(node *poolNode) { + self.lock.Lock() + defer self.lock.Unlock() + self.roots = append(self.roots, node) +} +// (re)starts processes registered for this peer (self) +func (self *peerInfo) start(peer *peerInfo) { + self.lock.Lock() + defer self.lock.Unlock() + self.quitC = make(chan bool) + for _, root := range self.roots { + root.sectionRLock() + if root.section.bottom != nil { + if root.parent == nil { + self.requestBlockHashes(root.hash) + } + } + root.sectionRUnlock() + } + self.roots = nil + 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) { - // reactivate node scheduled for suicide - if parent.suicide != nil { - close(parent.suicide) - parent.suicide = nil + 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 - orphan.parent = nil - go self.killChain(orphan) + if orphan != nil { + // got a fork in the chain + if top { + orphan.lock.Lock() + // make old child orphan + orphan.parent = nil + orphan.lock.Unlock() + } else { // we are under section lock + // make old child orphan + orphan.parent = nil + // reset section objects above the fork + nchild := orphan.child + node := orphan + section := §ion{bottom: orphan} + for node.section == nchild.section { + node = nchild + node.section = section + nchild = node.child + } + section.top = node + // set up a suicide + self.processSection(orphan).stop() + } + } else { + // child is on top of a chain need to close section + child.section.bottom = child + } + // adopt new child parent.child = child + if !top { + parent.section.top = parent + // restart section process so that shorter section is scanned for blocks + parent.section.reset() + } } + if child != nil { if child.parent != parent { - orphan := child.parent - orphan.child = nil - go func() { - // if it is a aberrant reverse fork, zip down to bottom - for orphan.parent != nil { - orphan = orphan.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 + } } - self.killChain(orphan) - }() - child.parent = parent + } else { + // linking to a root node, ie. parent is under the root of a chain + parent.section.top = parent + } } - child.knownParent = false + child.parent = parent + child.section.bottom = child } -} + // this needed if someone lied about the parent before + child.knownParent = false -func (self *BlockPool) killChain(node *poolNode) { - if node == nil { - return + parent.sectionUnlock() + if child != nil { + child.sectionUnlock() } - poolLogger.Debugf("suicide scheduled on node %v", node) - suicide := make(chan bool) - self.lock.Lock() - node.suicide = suicide - self.lock.Unlock() - timer := time.After(cacheTimeout * time.Minute) +} + +// 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) - select { - case <-self.quit: - case <-suicide: - // cancel suicide = close node.suicide to reactivate node - case <-timer: - poolLogger.Debugf("suicide on node %v", node) - self.lock.Lock() - defer self.lock.Unlock() - // proceed up via child links until another suicide root found or chain ends - // abort request blocks loops that start above - // and delete nodes from pool then quit the suicide process - okToAbort := node.blockRequestRoot - for node != nil && (node.suicide == suicide || node.suicide == nil) { - self.pool[string(node.hash)] = nil - if okToAbort && node.blockRequestQuit != nil { - quit := *(node.blockRequestQuit) - if quit != nil { // not yet closed - *(node.blockRequestQuit) = nil - close(quit) + 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 { - okToAbort = true + if child != nil { + // delink rest of this section if ended midsection + child.section.bottom = child + child.parent = nil + } } - node = node.child } + 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() { + self.lock.RLock() + defer self.lock.RUnlock() + if self.controlC != nil { + self.controlC <- true } - self.wg.Done() } -// AddBlock is the entry point for the eth protocol when blockmsg is received upon requests -// It has a strict interpretation of the protocol in that if the block received has not been requested, it results in an error (which can be ignored) -// block is checked for PoW -// only the first PoW-valid block for a hash is considered legit -func (self *BlockPool) AddBlock(block *types.Block, peerId string) (err error) { - hash := block.Hash() +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() - node, ok := self.pool[string(hash)] - if !ok && !self.chainManager.KnownBlock(hash) { - return fmt.Errorf("unrequested block %x", hash) + if self.controlC != nil { + close(self.controlC) + self.controlC = nil } - if node.block != nil { - return - } - // validate block for PoW - if !self.chainManager.CheckPoW(block) { - return fmt.Errorf("invalid pow on block %x", hash) +} + +func (self *section) done() bool { + self.lock.Lock() + defer self.lock.Unlock() + if self.controlC != nil { + return true } - node.block = block - node.source = peerId - return nil + 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) { + 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() } -- cgit v1.2.3 From 0add0c400f0dda50c443352a255af704e71bdd3c Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 5 Dec 2014 21:14:55 +0000 Subject: initial commit for eth-p2p integration --- eth/protocol.go | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++- eth/protocol_test.go | 39 ++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 3b5b49696..985b42901 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -20,7 +20,7 @@ type ethProtocol struct { peer *p2p.Peer id string rw p2p.MsgReadWriter -} + } // backend is the interface the ethereum protocol backend should implement // used as an argument to EthProtocol @@ -68,6 +68,7 @@ type newBlockMsgData struct { type getBlockHashesMsgData struct { Hash []byte +<<<<<<< HEAD Amount uint64 } @@ -76,15 +77,29 @@ type getBlockHashesMsgData struct { // the Dev p2p layer then runs the protocol instance on each peer func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) p2p.Protocol { return p2p.Protocol{ +======= + Amount uint32 +} + +// main entrypoint, wrappers starting a server running the eth protocol +// use this constructor to attach the protocol (class) to server caps +func EthProtocol(eth backend) *p2p.Protocol { + return &p2p.Protocol{ +>>>>>>> initial commit for eth-p2p integration Name: "eth", Version: ProtocolVersion, Length: ProtocolLength, Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { +<<<<<<< HEAD return runEthProtocol(txPool, chainManager, blockPool, peer, rw) +======= + return runEthProtocol(eth, peer, rw) +>>>>>>> initial commit for eth-p2p integration }, } } +<<<<<<< HEAD // the main loop that handles incoming messages // note RemovePeer in the post-disconnect hook func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { @@ -95,6 +110,13 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo rw: rw, peer: peer, id: (string)(peer.Identity().Pubkey()), +======= +func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { + self := ðProtocol{ + eth: eth, + rw: rw, + peer: peer, +>>>>>>> initial commit for eth-p2p integration } err = self.handleStatus() if err == nil { @@ -102,7 +124,10 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo for { err = self.handle() if err != nil { +<<<<<<< HEAD self.blockPool.RemovePeer(self.id) +======= +>>>>>>> initial commit for eth-p2p integration break } } @@ -127,24 +152,46 @@ func (self *ethProtocol) handle() error { case StatusMsg: return ProtocolError(ErrExtraStatusMsg, "") +<<<<<<< HEAD case TxMsg: // TODO: rework using lazy RLP stream +======= + case GetTxMsg: + txs := self.eth.GetTransactions() + // TODO: rewrite using rlp flat + txsInterface := make([]interface{}, len(txs)) + for i, tx := range txs { + txsInterface[i] = tx.RlpData() + } + return self.rw.EncodeMsg(TxMsg, txsInterface...) + + case TxMsg: +>>>>>>> initial commit for eth-p2p integration var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD self.txPool.AddTransactions(txs) +======= + self.eth.AddTransactions(txs) +>>>>>>> initial commit for eth-p2p integration case GetBlockHashesMsg: var request getBlockHashesMsgData if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) +======= + hashes := self.eth.GetBlockHashes(request.Hash, request.Amount) +>>>>>>> initial commit for eth-p2p integration return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains +<<<<<<< HEAD msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) var err error iter := func() (hash []byte, ok bool) { @@ -160,17 +207,45 @@ func (self *ethProtocol) handle() error { } case GetBlocksMsg: +======= + // s := rlp.NewListStream(msg.Payload, uint64(msg.Size)) var blockHashes [][]byte if err := msg.Decode(&blockHashes); err != nil { return ProtocolError(ErrDecode, "%v", err) } + fetchMore := true + for _, hash := range blockHashes { + fetchMore = self.eth.AddHash(hash, self.peer) + if !fetchMore { + break + } + } + if fetchMore { + return self.FetchHashes(blockHashes[len(blockHashes)-1]) + } + + case GetBlocksMsg: + // Limit to max 300 blocks +>>>>>>> initial commit for eth-p2p integration + var blockHashes [][]byte + if err := msg.Decode(&blockHashes); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } +<<<<<<< HEAD max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) +======= + max := int(math.Min(float64(len(blockHashes)), 300.0)) +>>>>>>> initial commit for eth-p2p integration var blocks []interface{} for i, hash := range blockHashes { if i >= max { break } +<<<<<<< HEAD block := self.chainManager.GetBlock(hash) +======= + block := self.eth.GetBlock(hash) +>>>>>>> initial commit for eth-p2p integration if block != nil { blocks = append(blocks, block.Value().Raw()) } @@ -178,6 +253,7 @@ func (self *ethProtocol) handle() error { return self.rw.EncodeMsg(BlocksMsg, blocks...) case BlocksMsg: +<<<<<<< HEAD msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) for { var block *types.Block @@ -189,6 +265,22 @@ func (self *ethProtocol) handle() error { } } self.blockPool.AddBlock(block, self.id) +======= + var blocks []*types.Block + if err := msg.Decode(&blocks); err != nil { + return ProtocolError(ErrDecode, "%v", err) + } + for _, block := range blocks { + fetchHashes, err := self.eth.AddBlock(nil, block, self.peer) + if err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } + if fetchHashes { + if err := self.FetchHashes(block.Hash()); err != nil { + return err + } + } +>>>>>>> initial commit for eth-p2p integration } case NewBlockMsg: @@ -196,6 +288,7 @@ func (self *ethProtocol) handle() error { if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD hash := request.Block.Hash() // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer @@ -212,6 +305,15 @@ func (self *ethProtocol) handle() error { } self.blockPool.AddBlockHashes(iter, self.id) self.blockPool.AddBlock(request.Block, self.id) +======= + var fetchHashes bool + // this should reset td and offer blockpool as candidate new peer? + if fetchHashes, err = self.eth.AddBlock(request.TD, request.Block, self.peer); err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } + if fetchHashes { + return self.FetchHashes(request.Block.Hash()) +>>>>>>> initial commit for eth-p2p integration } default: @@ -229,7 +331,11 @@ type statusMsgData struct { } func (self *ethProtocol) statusMsg() p2p.Msg { +<<<<<<< HEAD td, currentBlock, genesisBlock := self.chainManager.Status() +======= + td, currentBlock, genesisBlock := self.eth.Status() +>>>>>>> initial commit for eth-p2p integration return p2p.NewMsg(StatusMsg, uint32(ProtocolVersion), @@ -265,7 +371,11 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD _, _, genesisBlock := self.chainManager.Status() +======= + _, _, genesisBlock := self.eth.Status() +>>>>>>> initial commit for eth-p2p integration if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) @@ -279,13 +389,22 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) } +<<<<<<< HEAD self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) +======= + logger.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) + + if self.eth.AddPeer(status.TD, status.CurrentBlock, self.peer) { + return self.FetchHashes(status.CurrentBlock) + } +>>>>>>> initial commit for eth-p2p integration return nil } +<<<<<<< HEAD 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) @@ -316,3 +435,9 @@ func (self *ethProtocol) protoErrorDisconnect(code int, format string, params .. } } +======= +func (self *ethProtocol) FetchHashes(from []byte) error { + logger.Debugf("Fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) + return self.rw.EncodeMsg(GetBlockHashesMsg, from, blockHashesBatchSize) +} +>>>>>>> initial commit for eth-p2p integration diff --git a/eth/protocol_test.go b/eth/protocol_test.go index a166ea6cd..93696213a 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -56,11 +56,18 @@ type TestBackend struct { getTransactions func() []*types.Transaction addTransactions func(txs []*types.Transaction) getBlockHashes func(hash []byte, amount uint32) (hashes [][]byte) +<<<<<<< HEAD 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) +======= + addHash func(hash []byte, peer *p2p.Peer) (more bool) + getBlock func(hash []byte) *types.Block + addBlock func(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) + addPeer func(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) +>>>>>>> initial commit for eth-p2p integration status func() (td *big.Int, currentBlock []byte, genesisBlock []byte) } @@ -84,12 +91,21 @@ func (self *TestBackend) GetBlockHashes(hash []byte, amount uint32) (hashes [][] return } +<<<<<<< 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) + } + return +} +>>>>>>> initial commit for eth-p2p integration func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { if self.getBlock != nil { block = self.getBlock(hash) @@ -97,26 +113,41 @@ func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { return } +<<<<<<< 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 } return } +<<<<<<< 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 } return } +<<<<<<< HEAD func (self *TestBackend) RemovePeer(peerId string) { if self.removePeer != nil { self.removePeer(peerId) } } +======= +>>>>>>> initial commit for eth-p2p integration func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { if self.status != nil { td, currentBlock, genesisBlock = self.status() @@ -124,6 +155,7 @@ func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBloc return } +<<<<<<< HEAD // TODO: refactor this into p2p/client_identity type peerId struct { pubkey []byte @@ -147,12 +179,19 @@ func testPeer() *p2p.Peer { } func TestErrNoStatusMsg(t *testing.T) { +======= +func TestEth(t *testing.T) { +>>>>>>> initial commit for eth-p2p integration quit := make(chan bool) rw := &testMsgReadWriter{make(chan p2p.Msg, 10), make(chan p2p.Msg, 10)} testBackend := &TestBackend{} var err error go func() { +<<<<<<< HEAD err = runEthProtocol(testBackend, testPeer(), rw) +======= + err = runEthProtocol(testBackend, nil, rw) +>>>>>>> initial commit for eth-p2p integration close(quit) }() statusMsg := p2p.NewMsg(4) -- cgit v1.2.3 From 2dd8f411473f23e61983506aaa1c37efec043ac0 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 9 Dec 2014 23:55:50 +0000 Subject: eth protocol changes - changed backend interface - using callbacks for blockPool - use rlp stream for lazy decoding - use peer as logger - add id (peer pubkey) to ethProtocol fields - add testPeer to protocol test (temporary) --- eth/protocol.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++-- eth/protocol_test.go | 46 ++++++++++++++++++++----- 2 files changed, 132 insertions(+), 11 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 985b42901..fbc4610ec 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -82,7 +82,8 @@ func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) } // main entrypoint, wrappers starting a server running the eth protocol -// use this constructor to attach the protocol (class) to server caps +// use this constructor to attach the protocol ("class") to server caps +// the Dev p2p layer then runs the protocol instance on each peer func EthProtocol(eth backend) *p2p.Protocol { return &p2p.Protocol{ >>>>>>> initial commit for eth-p2p integration @@ -99,6 +100,7 @@ func EthProtocol(eth backend) *p2p.Protocol { } } +<<<<<<< HEAD <<<<<<< HEAD // the main loop that handles incoming messages // note RemovePeer in the post-disconnect hook @@ -111,12 +113,20 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo peer: peer, id: (string)(peer.Identity().Pubkey()), ======= +======= +// the main loop that handles incoming messages +// note RemovePeer in the post-disconnect hook +>>>>>>> eth protocol changes func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { self := ðProtocol{ eth: eth, rw: rw, peer: peer, +<<<<<<< HEAD >>>>>>> initial commit for eth-p2p integration +======= + id: (string)(peer.Identity().Pubkey()), +>>>>>>> eth protocol changes } err = self.handleStatus() if err == nil { @@ -124,10 +134,14 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro for { err = self.handle() if err != nil { +<<<<<<< HEAD <<<<<<< HEAD self.blockPool.RemovePeer(self.id) ======= >>>>>>> initial commit for eth-p2p integration +======= + self.eth.RemovePeer(self.id) +>>>>>>> eth protocol changes break } } @@ -166,7 +180,11 @@ func (self *ethProtocol) handle() error { return self.rw.EncodeMsg(TxMsg, txsInterface...) case TxMsg: +<<<<<<< HEAD >>>>>>> initial commit for eth-p2p integration +======= + // TODO: rework using lazy RLP stream +>>>>>>> eth protocol changes var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { return ProtocolError(ErrDecode, "%v", err) @@ -192,12 +210,16 @@ func (self *ethProtocol) handle() error { case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> eth protocol changes msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) var err error iter := func() (hash []byte, ok bool) { hash, err = msgStream.Bytes() if err == nil { ok = true +<<<<<<< HEAD } return } @@ -218,24 +240,35 @@ func (self *ethProtocol) handle() error { fetchMore = self.eth.AddHash(hash, self.peer) if !fetchMore { break +======= +>>>>>>> eth protocol changes } + return } - if fetchMore { - return self.FetchHashes(blockHashes[len(blockHashes)-1]) + self.eth.AddBlockHashes(iter, self.id) + if err != nil && err != rlp.EOL { + return ProtocolError(ErrDecode, "%v", err) } case GetBlocksMsg: +<<<<<<< HEAD // Limit to max 300 blocks >>>>>>> initial commit for eth-p2p integration +======= +>>>>>>> eth protocol changes var blockHashes [][]byte if err := msg.Decode(&blockHashes); err != nil { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD <<<<<<< HEAD max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) ======= max := int(math.Min(float64(len(blockHashes)), 300.0)) >>>>>>> initial commit for eth-p2p integration +======= + max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) +>>>>>>> eth protocol changes var blocks []interface{} for i, hash := range blockHashes { if i >= max { @@ -254,6 +287,9 @@ func (self *ethProtocol) handle() error { case BlocksMsg: <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> eth protocol changes msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) for { var block *types.Block @@ -262,6 +298,7 @@ func (self *ethProtocol) handle() error { break } else { return ProtocolError(ErrDecode, "%v", err) +<<<<<<< HEAD } } self.blockPool.AddBlock(block, self.id) @@ -281,6 +318,13 @@ func (self *ethProtocol) handle() error { } } >>>>>>> initial commit for eth-p2p integration +======= + } + } + if err := self.eth.AddBlock(block, self.id); err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } +>>>>>>> eth protocol changes } case NewBlockMsg: @@ -289,11 +333,18 @@ func (self *ethProtocol) handle() error { return ProtocolError(ErrDecode, "%v", err) } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> eth protocol changes hash := request.Block.Hash() // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) +<<<<<<< HEAD if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { +======= + if self.eth.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) { +>>>>>>> eth protocol changes called := true iter := func() (hash []byte, ok bool) { if called { @@ -303,6 +354,7 @@ func (self *ethProtocol) handle() error { return } } +<<<<<<< HEAD self.blockPool.AddBlockHashes(iter, self.id) self.blockPool.AddBlock(request.Block, self.id) ======= @@ -314,6 +366,12 @@ func (self *ethProtocol) handle() error { if fetchHashes { return self.FetchHashes(request.Block.Hash()) >>>>>>> initial commit for eth-p2p integration +======= + self.eth.AddBlockHashes(iter, self.id) + if err := self.eth.AddBlock(request.Block, self.id); err != nil { + return ProtocolError(ErrInvalidBlock, "%v", err) + } +>>>>>>> eth protocol changes } default: @@ -389,6 +447,7 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) } +<<<<<<< HEAD <<<<<<< HEAD self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) @@ -400,10 +459,16 @@ func (self *ethProtocol) handleStatus() error { return self.FetchHashes(status.CurrentBlock) } >>>>>>> initial commit for eth-p2p integration +======= + self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) + + self.eth.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) +>>>>>>> eth protocol changes return nil } +<<<<<<< HEAD <<<<<<< HEAD func (self *ethProtocol) requestBlockHashes(from []byte) error { self.peer.Debugf("fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) @@ -441,3 +506,29 @@ func (self *ethProtocol) FetchHashes(from []byte) error { return self.rw.EncodeMsg(GetBlockHashesMsg, from, blockHashesBatchSize) } >>>>>>> initial commit for eth-p2p integration +======= +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) +} + +func (self *ethProtocol) requestBlocks(hashes [][]byte) error { + self.peer.Debugf("fetching %v blocks", len(hashes)) + return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)) +} + +func (self *ethProtocol) invalidBlock(err error) { + ProtocolError(ErrInvalidBlock, "%v", err) + self.peer.Disconnect(p2p.DiscSubprotocolError) +} + +func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { + err = ProtocolError(code, format, params...) + if err.Fatal() { + self.peer.Errorln(err) + } else { + self.peer.Debugln(err) + } + return +} +>>>>>>> eth protocol changes diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 93696213a..322aec7b7 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -56,18 +56,11 @@ type TestBackend struct { getTransactions func() []*types.Transaction addTransactions func(txs []*types.Transaction) getBlockHashes func(hash []byte, amount uint32) (hashes [][]byte) -<<<<<<< HEAD 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) -======= - addHash func(hash []byte, peer *p2p.Peer) (more bool) - getBlock func(hash []byte) *types.Block - addBlock func(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) - addPeer func(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) ->>>>>>> initial commit for eth-p2p integration status func() (td *big.Int, currentBlock []byte, genesisBlock []byte) } @@ -91,6 +84,7 @@ func (self *TestBackend) GetBlockHashes(hash []byte, amount uint32) (hashes [][] return } +<<<<<<< HEAD <<<<<<< HEAD func (self *TestBackend) AddBlockHashes(next func() ([]byte, bool), peerId string) { if self.addBlockHashes != nil { @@ -102,10 +96,18 @@ func (self *TestBackend) AddBlockHashes(next func() ([]byte, bool), peerId strin 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 } - return } +<<<<<<< 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) @@ -113,6 +115,7 @@ func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { return } +<<<<<<< HEAD <<<<<<< HEAD func (self *TestBackend) AddBlock(block *types.Block, peerId string) (err error) { if self.addBlock != nil { @@ -122,10 +125,16 @@ func (self *TestBackend) AddBlock(td *big.Int, block *types.Block, peer *p2p.Pee 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 { @@ -135,19 +144,30 @@ func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Pee 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) { if self.status != nil { td, currentBlock, genesisBlock = self.status() @@ -156,6 +176,9 @@ func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBloc } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> eth protocol changes // TODO: refactor this into p2p/client_identity type peerId struct { pubkey []byte @@ -179,19 +202,26 @@ func testPeer() *p2p.Peer { } 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) -- cgit v1.2.3 From 5e4d77b2b8020a106a13762c49ef40acac619a9c Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 10 Dec 2014 04:12:25 +0000 Subject: initial commit for eth blockpool + test --- eth/block_pool.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go index 64d1e73fa..7cfbc63f8 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" ethlogger "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/pow" ) var poolLogger = ethlogger.NewLogger("Blockpool") @@ -54,7 +55,7 @@ type BlockPool struct { // the minimal interface with blockchain hasBlock func(hash []byte) bool insertChain func(types.Blocks) error - verifyPoW func(*types.Block) bool + verifyPoW func(pow.Block) bool } type peerInfo struct { @@ -73,7 +74,7 @@ type peerInfo struct { quitC chan bool } -func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(*types.Block) bool, +func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(pow.Block) bool, ) *BlockPool { return &BlockPool{ hasBlock: hasBlock, -- cgit v1.2.3 From 4dfce4624dcc89302ec0b1f22cf853a8382fb7c7 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 18:04:50 +0000 Subject: protocol - new interface explicit backend components txPool, chainManager, blockPool - added protoErrorDisconnect for blockpool callback (FIXME: handling peer disconnects) --- eth/protocol.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 7 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index fbc4610ec..37e642fd0 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -20,7 +20,7 @@ type ethProtocol struct { peer *p2p.Peer id string rw p2p.MsgReadWriter - } +} // backend is the interface the ethereum protocol backend should implement // used as an argument to EthProtocol @@ -68,6 +68,7 @@ type newBlockMsgData struct { type getBlockHashesMsgData struct { Hash []byte +<<<<<<< HEAD <<<<<<< HEAD Amount uint64 } @@ -79,23 +80,35 @@ func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) return p2p.Protocol{ ======= Amount uint32 +======= + Amount uint64 +>>>>>>> protocol } // main entrypoint, wrappers starting a server running the eth protocol // use this constructor to attach the protocol ("class") to server caps // the Dev p2p layer then runs the protocol instance on each peer +<<<<<<< HEAD func EthProtocol(eth backend) *p2p.Protocol { return &p2p.Protocol{ >>>>>>> initial commit for eth-p2p integration +======= +func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) p2p.Protocol { + return p2p.Protocol{ +>>>>>>> protocol Name: "eth", Version: ProtocolVersion, Length: ProtocolLength, Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { +<<<<<<< HEAD <<<<<<< HEAD return runEthProtocol(txPool, chainManager, blockPool, peer, rw) ======= return runEthProtocol(eth, peer, rw) >>>>>>> initial commit for eth-p2p integration +======= + return runEthProtocol(txPool, chainManager, blockPool, peer, rw) +>>>>>>> protocol }, } } @@ -105,6 +118,7 @@ func EthProtocol(eth backend) *p2p.Protocol { // the main loop that handles incoming messages // note RemovePeer in the post-disconnect hook func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { +<<<<<<< HEAD self := ðProtocol{ txPool: txPool, chainManager: chainManager, @@ -127,6 +141,15 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro ======= id: (string)(peer.Identity().Pubkey()), >>>>>>> eth protocol changes +======= + self := ðProtocol{ + txPool: txPool, + chainManager: chainManager, + blockPool: blockPool, + rw: rw, + peer: peer, + id: (string)(peer.Identity().Pubkey()), +>>>>>>> protocol } err = self.handleStatus() if err == nil { @@ -135,6 +158,7 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro err = self.handle() if err != nil { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD self.blockPool.RemovePeer(self.id) ======= @@ -142,6 +166,9 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro ======= self.eth.RemovePeer(self.id) >>>>>>> eth protocol changes +======= + self.blockPool.RemovePeer(self.id) +>>>>>>> protocol break } } @@ -166,6 +193,7 @@ func (self *ethProtocol) handle() error { case StatusMsg: return ProtocolError(ErrExtraStatusMsg, "") +<<<<<<< HEAD <<<<<<< HEAD case TxMsg: // TODO: rework using lazy RLP stream @@ -179,6 +207,8 @@ func (self *ethProtocol) handle() error { } return self.rw.EncodeMsg(TxMsg, txsInterface...) +======= +>>>>>>> protocol case TxMsg: <<<<<<< HEAD >>>>>>> initial commit for eth-p2p integration @@ -189,22 +219,30 @@ func (self *ethProtocol) handle() error { if err := msg.Decode(&txs); err != nil { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD <<<<<<< HEAD self.txPool.AddTransactions(txs) ======= self.eth.AddTransactions(txs) >>>>>>> initial commit for eth-p2p integration +======= + self.txPool.AddTransactions(txs) +>>>>>>> protocol case GetBlockHashesMsg: var request getBlockHashesMsgData if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD <<<<<<< HEAD hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) ======= hashes := self.eth.GetBlockHashes(request.Hash, request.Amount) >>>>>>> initial commit for eth-p2p integration +======= + hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) +>>>>>>> protocol return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: @@ -245,7 +283,7 @@ func (self *ethProtocol) handle() error { } return } - self.eth.AddBlockHashes(iter, self.id) + self.blockPool.AddBlockHashes(iter, self.id) if err != nil && err != rlp.EOL { return ProtocolError(ErrDecode, "%v", err) } @@ -274,11 +312,15 @@ func (self *ethProtocol) handle() error { if i >= max { break } +<<<<<<< HEAD <<<<<<< HEAD block := self.chainManager.GetBlock(hash) ======= block := self.eth.GetBlock(hash) >>>>>>> initial commit for eth-p2p integration +======= + block := self.chainManager.GetBlock(hash) +>>>>>>> protocol if block != nil { blocks = append(blocks, block.Value().Raw()) } @@ -321,10 +363,14 @@ func (self *ethProtocol) handle() error { ======= } } +<<<<<<< HEAD if err := self.eth.AddBlock(block, self.id); err != nil { return ProtocolError(ErrInvalidBlock, "%v", err) } >>>>>>> eth protocol changes +======= + self.blockPool.AddBlock(block, self.id) +>>>>>>> protocol } case NewBlockMsg: @@ -340,11 +386,15 @@ func (self *ethProtocol) handle() error { // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) +<<<<<<< HEAD <<<<<<< HEAD if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { ======= if self.eth.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) { >>>>>>> eth protocol changes +======= + if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { +>>>>>>> protocol called := true iter := func() (hash []byte, ok bool) { if called { @@ -354,6 +404,7 @@ func (self *ethProtocol) handle() error { return } } +<<<<<<< HEAD <<<<<<< HEAD self.blockPool.AddBlockHashes(iter, self.id) self.blockPool.AddBlock(request.Block, self.id) @@ -372,6 +423,10 @@ func (self *ethProtocol) handle() error { return ProtocolError(ErrInvalidBlock, "%v", err) } >>>>>>> eth protocol changes +======= + self.blockPool.AddBlockHashes(iter, self.id) + self.blockPool.AddBlock(request.Block, self.id) +>>>>>>> protocol } default: @@ -389,11 +444,15 @@ type statusMsgData struct { } func (self *ethProtocol) statusMsg() p2p.Msg { +<<<<<<< HEAD <<<<<<< HEAD td, currentBlock, genesisBlock := self.chainManager.Status() ======= td, currentBlock, genesisBlock := self.eth.Status() >>>>>>> initial commit for eth-p2p integration +======= + td, currentBlock, genesisBlock := self.chainManager.Status() +>>>>>>> protocol return p2p.NewMsg(StatusMsg, uint32(ProtocolVersion), @@ -429,11 +488,15 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrDecode, "%v", err) } +<<<<<<< HEAD <<<<<<< HEAD _, _, genesisBlock := self.chainManager.Status() ======= _, _, genesisBlock := self.eth.Status() >>>>>>> initial commit for eth-p2p integration +======= + _, _, genesisBlock := self.chainManager.Status() +>>>>>>> protocol if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) @@ -462,8 +525,12 @@ func (self *ethProtocol) handleStatus() error { ======= self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) +<<<<<<< HEAD self.eth.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) >>>>>>> eth protocol changes +======= + self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) +>>>>>>> protocol return nil } @@ -517,11 +584,6 @@ func (self *ethProtocol) requestBlocks(hashes [][]byte) error { return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)) } -func (self *ethProtocol) invalidBlock(err error) { - ProtocolError(ErrInvalidBlock, "%v", err) - self.peer.Disconnect(p2p.DiscSubprotocolError) -} - func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { err = ProtocolError(code, format, params...) if err.Fatal() { @@ -531,4 +593,18 @@ func (self *ethProtocol) protoError(code int, format string, params ...interface } return } +<<<<<<< HEAD >>>>>>> eth protocol changes +======= + +func (self *ethProtocol) protoErrorDisconnect(code int, format string, params ...interface{}) { + err := ProtocolError(code, format, params...) + if err.Fatal() { + self.peer.Errorln(err) + // disconnect + } else { + self.peer.Debugln(err) + } + +} +>>>>>>> protocol -- cgit v1.2.3 From 01dc1c13942867d0579f5010a560da4073ece05e Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 18:08:18 +0000 Subject: blockpool rewritten , tests broken FIXME --- eth/block_pool.go | 8 ++ eth/protocol.go | 292 ------------------------------------------------------ 2 files changed, 8 insertions(+), 292 deletions(-) (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go index 7cfbc63f8..a5cda7b58 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -55,7 +55,11 @@ type BlockPool struct { // the minimal interface with blockchain hasBlock func(hash []byte) bool insertChain func(types.Blocks) error +<<<<<<< HEAD verifyPoW func(pow.Block) bool +======= + verifyPoW func(*types.Block) bool +>>>>>>> blockpool rewritten , tests broken FIXME } type peerInfo struct { @@ -74,7 +78,11 @@ type peerInfo struct { quitC chan bool } +<<<<<<< HEAD func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(pow.Block) bool, +======= +func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(*types.Block) bool, +>>>>>>> blockpool rewritten , tests broken FIXME ) *BlockPool { return &BlockPool{ hasBlock: hasBlock, diff --git a/eth/protocol.go b/eth/protocol.go index 37e642fd0..3b5b49696 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -68,8 +68,6 @@ type newBlockMsgData struct { type getBlockHashesMsgData struct { Hash []byte -<<<<<<< HEAD -<<<<<<< HEAD Amount uint64 } @@ -78,70 +76,18 @@ type getBlockHashesMsgData struct { // the Dev p2p layer then runs the protocol instance on each peer func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) p2p.Protocol { return p2p.Protocol{ -======= - Amount uint32 -======= - Amount uint64 ->>>>>>> protocol -} - -// main entrypoint, wrappers starting a server running the eth protocol -// use this constructor to attach the protocol ("class") to server caps -// the Dev p2p layer then runs the protocol instance on each peer -<<<<<<< HEAD -func EthProtocol(eth backend) *p2p.Protocol { - return &p2p.Protocol{ ->>>>>>> initial commit for eth-p2p integration -======= -func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) p2p.Protocol { - return p2p.Protocol{ ->>>>>>> protocol Name: "eth", Version: ProtocolVersion, Length: ProtocolLength, Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { -<<<<<<< HEAD -<<<<<<< HEAD - return runEthProtocol(txPool, chainManager, blockPool, peer, rw) -======= - return runEthProtocol(eth, peer, rw) ->>>>>>> initial commit for eth-p2p integration -======= return runEthProtocol(txPool, chainManager, blockPool, peer, rw) ->>>>>>> protocol }, } } -<<<<<<< HEAD -<<<<<<< HEAD // the main loop that handles incoming messages // note RemovePeer in the post-disconnect hook func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { -<<<<<<< HEAD - self := ðProtocol{ - txPool: txPool, - chainManager: chainManager, - blockPool: blockPool, - rw: rw, - peer: peer, - id: (string)(peer.Identity().Pubkey()), -======= -======= -// the main loop that handles incoming messages -// note RemovePeer in the post-disconnect hook ->>>>>>> eth protocol changes -func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { - self := ðProtocol{ - eth: eth, - rw: rw, - peer: peer, -<<<<<<< HEAD ->>>>>>> initial commit for eth-p2p integration -======= - id: (string)(peer.Identity().Pubkey()), ->>>>>>> eth protocol changes -======= self := ðProtocol{ txPool: txPool, chainManager: chainManager, @@ -149,7 +95,6 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro rw: rw, peer: peer, id: (string)(peer.Identity().Pubkey()), ->>>>>>> protocol } err = self.handleStatus() if err == nil { @@ -157,18 +102,7 @@ func runEthProtocol(eth backend, peer *p2p.Peer, rw p2p.MsgReadWriter) (err erro for { err = self.handle() if err != nil { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - self.blockPool.RemovePeer(self.id) -======= ->>>>>>> initial commit for eth-p2p integration -======= - self.eth.RemovePeer(self.id) ->>>>>>> eth protocol changes -======= self.blockPool.RemovePeer(self.id) ->>>>>>> protocol break } } @@ -193,93 +127,30 @@ func (self *ethProtocol) handle() error { case StatusMsg: return ProtocolError(ErrExtraStatusMsg, "") -<<<<<<< HEAD -<<<<<<< HEAD - case TxMsg: - // TODO: rework using lazy RLP stream -======= - case GetTxMsg: - txs := self.eth.GetTransactions() - // TODO: rewrite using rlp flat - txsInterface := make([]interface{}, len(txs)) - for i, tx := range txs { - txsInterface[i] = tx.RlpData() - } - return self.rw.EncodeMsg(TxMsg, txsInterface...) - -======= ->>>>>>> protocol case TxMsg: -<<<<<<< HEAD ->>>>>>> initial commit for eth-p2p integration -======= // TODO: rework using lazy RLP stream ->>>>>>> eth protocol changes var txs []*types.Transaction if err := msg.Decode(&txs); err != nil { return ProtocolError(ErrDecode, "%v", err) } -<<<<<<< HEAD -<<<<<<< HEAD self.txPool.AddTransactions(txs) -======= - self.eth.AddTransactions(txs) ->>>>>>> initial commit for eth-p2p integration -======= - self.txPool.AddTransactions(txs) ->>>>>>> protocol case GetBlockHashesMsg: var request getBlockHashesMsgData if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } -<<<<<<< HEAD -<<<<<<< HEAD - hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) -======= - hashes := self.eth.GetBlockHashes(request.Hash, request.Amount) ->>>>>>> initial commit for eth-p2p integration -======= hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) ->>>>>>> protocol return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) var err error iter := func() (hash []byte, ok bool) { hash, err = msgStream.Bytes() if err == nil { ok = true -<<<<<<< HEAD - } - return - } - self.blockPool.AddBlockHashes(iter, self.id) - if err != nil && err != rlp.EOL { - return ProtocolError(ErrDecode, "%v", err) - } - - case GetBlocksMsg: -======= - // s := rlp.NewListStream(msg.Payload, uint64(msg.Size)) - var blockHashes [][]byte - if err := msg.Decode(&blockHashes); err != nil { - return ProtocolError(ErrDecode, "%v", err) - } - fetchMore := true - for _, hash := range blockHashes { - fetchMore = self.eth.AddHash(hash, self.peer) - if !fetchMore { - break -======= ->>>>>>> eth protocol changes } return } @@ -289,38 +160,17 @@ func (self *ethProtocol) handle() error { } case GetBlocksMsg: -<<<<<<< HEAD - // Limit to max 300 blocks ->>>>>>> initial commit for eth-p2p integration -======= ->>>>>>> eth protocol changes var blockHashes [][]byte if err := msg.Decode(&blockHashes); err != nil { return ProtocolError(ErrDecode, "%v", err) } -<<<<<<< HEAD -<<<<<<< HEAD - max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) -======= - max := int(math.Min(float64(len(blockHashes)), 300.0)) ->>>>>>> initial commit for eth-p2p integration -======= max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) ->>>>>>> eth protocol changes var blocks []interface{} for i, hash := range blockHashes { if i >= max { break } -<<<<<<< HEAD -<<<<<<< HEAD - block := self.chainManager.GetBlock(hash) -======= - block := self.eth.GetBlock(hash) ->>>>>>> initial commit for eth-p2p integration -======= block := self.chainManager.GetBlock(hash) ->>>>>>> protocol if block != nil { blocks = append(blocks, block.Value().Raw()) } @@ -328,10 +178,6 @@ func (self *ethProtocol) handle() error { return self.rw.EncodeMsg(BlocksMsg, blocks...) case BlocksMsg: -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) for { var block *types.Block @@ -340,37 +186,9 @@ func (self *ethProtocol) handle() error { break } else { return ProtocolError(ErrDecode, "%v", err) -<<<<<<< HEAD } } self.blockPool.AddBlock(block, self.id) -======= - var blocks []*types.Block - if err := msg.Decode(&blocks); err != nil { - return ProtocolError(ErrDecode, "%v", err) - } - for _, block := range blocks { - fetchHashes, err := self.eth.AddBlock(nil, block, self.peer) - if err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } - if fetchHashes { - if err := self.FetchHashes(block.Hash()); err != nil { - return err - } - } ->>>>>>> initial commit for eth-p2p integration -======= - } - } -<<<<<<< HEAD - if err := self.eth.AddBlock(block, self.id); err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } ->>>>>>> eth protocol changes -======= - self.blockPool.AddBlock(block, self.id) ->>>>>>> protocol } case NewBlockMsg: @@ -378,23 +196,11 @@ func (self *ethProtocol) handle() error { if err := msg.Decode(&request); err != nil { return ProtocolError(ErrDecode, "%v", err) } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes hash := request.Block.Hash() // to simplify backend interface adding a new block // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) -<<<<<<< HEAD -<<<<<<< HEAD if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { -======= - if self.eth.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) { ->>>>>>> eth protocol changes -======= - if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { ->>>>>>> protocol called := true iter := func() (hash []byte, ok bool) { if called { @@ -404,29 +210,8 @@ func (self *ethProtocol) handle() error { return } } -<<<<<<< HEAD -<<<<<<< HEAD - self.blockPool.AddBlockHashes(iter, self.id) - self.blockPool.AddBlock(request.Block, self.id) -======= - var fetchHashes bool - // this should reset td and offer blockpool as candidate new peer? - if fetchHashes, err = self.eth.AddBlock(request.TD, request.Block, self.peer); err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } - if fetchHashes { - return self.FetchHashes(request.Block.Hash()) ->>>>>>> initial commit for eth-p2p integration -======= - self.eth.AddBlockHashes(iter, self.id) - if err := self.eth.AddBlock(request.Block, self.id); err != nil { - return ProtocolError(ErrInvalidBlock, "%v", err) - } ->>>>>>> eth protocol changes -======= self.blockPool.AddBlockHashes(iter, self.id) self.blockPool.AddBlock(request.Block, self.id) ->>>>>>> protocol } default: @@ -444,15 +229,7 @@ type statusMsgData struct { } func (self *ethProtocol) statusMsg() p2p.Msg { -<<<<<<< HEAD -<<<<<<< HEAD - td, currentBlock, genesisBlock := self.chainManager.Status() -======= - td, currentBlock, genesisBlock := self.eth.Status() ->>>>>>> initial commit for eth-p2p integration -======= td, currentBlock, genesisBlock := self.chainManager.Status() ->>>>>>> protocol return p2p.NewMsg(StatusMsg, uint32(ProtocolVersion), @@ -488,15 +265,7 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrDecode, "%v", err) } -<<<<<<< HEAD -<<<<<<< HEAD _, _, genesisBlock := self.chainManager.Status() -======= - _, _, genesisBlock := self.eth.Status() ->>>>>>> initial commit for eth-p2p integration -======= - _, _, genesisBlock := self.chainManager.Status() ->>>>>>> protocol if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) @@ -510,70 +279,13 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) } -<<<<<<< HEAD -<<<<<<< HEAD - self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) - - self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) -======= - logger.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) - - if self.eth.AddPeer(status.TD, status.CurrentBlock, self.peer) { - return self.FetchHashes(status.CurrentBlock) - } ->>>>>>> initial commit for eth-p2p integration -======= self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) -<<<<<<< HEAD - self.eth.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.invalidBlock) ->>>>>>> eth protocol changes -======= self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) ->>>>>>> protocol return nil } -<<<<<<< HEAD -<<<<<<< HEAD -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) -} - -func (self *ethProtocol) requestBlocks(hashes [][]byte) error { - self.peer.Debugf("fetching %v blocks", len(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) - } else { - self.peer.Debugln(err) - } - return -} - -func (self *ethProtocol) protoErrorDisconnect(code int, format string, params ...interface{}) { - err := ProtocolError(code, format, params...) - if err.Fatal() { - self.peer.Errorln(err) - // disconnect - } else { - self.peer.Debugln(err) - } - -} -======= -func (self *ethProtocol) FetchHashes(from []byte) error { - logger.Debugf("Fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) - return self.rw.EncodeMsg(GetBlockHashesMsg, from, blockHashesBatchSize) -} ->>>>>>> initial commit for eth-p2p integration -======= 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) @@ -593,9 +305,6 @@ func (self *ethProtocol) protoError(code int, format string, params ...interface } return } -<<<<<<< HEAD ->>>>>>> eth protocol changes -======= func (self *ethProtocol) protoErrorDisconnect(code int, format string, params ...interface{}) { err := ProtocolError(code, format, params...) @@ -607,4 +316,3 @@ func (self *ethProtocol) protoErrorDisconnect(code int, format string, params .. } } ->>>>>>> protocol -- cgit v1.2.3 From e77b720ead2e8f91f6f98664a66953c3826269c6 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 19:35:54 +0000 Subject: blockmanager start/stop obsolete --- eth/backend.go | 2 -- 1 file changed, 2 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 9154ca0f5..a7824e5d7 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -194,7 +194,6 @@ func (s *Ethereum) Start(seed bool) error { return err } s.blockPool.Start() - s.blockManager.Start() go s.filterLoop() @@ -244,7 +243,6 @@ func (s *Ethereum) Stop() { s.RpcServer.Stop() } s.txPool.Stop() - s.blockManager.Stop() s.eventMux.Stop() s.blockPool.Stop() -- cgit v1.2.3 From 4c89d5331f41fa93e6840893c6727b05eabe5f99 Mon Sep 17 00:00:00 2001 From: zelig Date: Sun, 14 Dec 2014 20:08:24 +0000 Subject: adapt blockpool/backend to use pow/ezp with pow.Block for VerifyPoW func --- eth/backend.go | 7 +++---- eth/block_pool.go | 8 -------- 2 files changed, 3 insertions(+), 12 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index a7824e5d7..6235fc824 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -7,12 +7,12 @@ import ( "sync" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/state" ) @@ -111,9 +111,8 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain - // pow := ezp.New() - // verifyPoW := pow.Verify - verifyPoW := func(*types.Block) bool { return true } + pow := ezp.New() + verifyPoW := pow.Verify eth.blockPool = NewBlockPool(hasBlock, insertChain, verifyPoW) // Start the tx pool diff --git a/eth/block_pool.go b/eth/block_pool.go index a5cda7b58..7cfbc63f8 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -55,11 +55,7 @@ type BlockPool struct { // the minimal interface with blockchain hasBlock func(hash []byte) bool insertChain func(types.Blocks) error -<<<<<<< HEAD verifyPoW func(pow.Block) bool -======= - verifyPoW func(*types.Block) bool ->>>>>>> blockpool rewritten , tests broken FIXME } type peerInfo struct { @@ -78,11 +74,7 @@ type peerInfo struct { quitC chan bool } -<<<<<<< HEAD func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(pow.Block) bool, -======= -func NewBlockPool(hasBlock func(hash []byte) bool, insertChain func(types.Blocks) error, verifyPoW func(*types.Block) bool, ->>>>>>> blockpool rewritten , tests broken FIXME ) *BlockPool { return &BlockPool{ hasBlock: hasBlock, -- cgit v1.2.3 From f111fc060884d69fbe46066b9ccae4c9aa5da890 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 11:37:23 +0100 Subject: WIP --- eth/backend.go | 53 ++++++++++------------------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 6235fc824..bdd5956a3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -3,7 +3,6 @@ package eth import ( "encoding/json" "net" - "path" "sync" "github.com/ethereum/go-ethereum/core" @@ -32,7 +31,6 @@ type Ethereum struct { db ethutil.Database // State manager for processing new blocks and managing the over all states blockManager *core.BlockManager - // The transaction pool. Transaction can be pushed on this pool // for later including in the blocks txPool *core.TxPool @@ -43,22 +41,11 @@ type Ethereum struct { // Event eventMux *event.TypeMux - // Nonce - Nonce uint64 - - ListenAddr string - blacklist p2p.Blacklist server *p2p.Server txSub event.Subscription blockSub event.Subscription - // Capabilities for outgoing peers - // serverCaps Caps - peersFile string - - Mining bool - RpcServer *rpc.JsonRpcServer keyManager *crypto.KeyManager @@ -71,6 +58,8 @@ type Ethereum struct { filterMu sync.RWMutex filterId int filters map[int]*core.Filter + + Mining bool } func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, port string, maxPeers int) (*Ethereum, error) { @@ -78,28 +67,13 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke saveProtocolVersion(db) ethutil.Config.Db = db - // FIXME: - blacklist := p2p.NewBlacklist() - // Sorry Py person. I must blacklist. you perform badly - blacklist.Put(ethutil.Hex2Bytes("64656330303561383532336435376331616537643864663236623336313863373537353163636634333530626263396330346237336262623931383064393031")) - - peersFile := path.Join(ethutil.Config.ExecPath, "known_peers.json") - - nonce, _ := ethutil.RandomUint64() - - listenAddr := ":" + port - eth := &Ethereum{ - shutdownChan: make(chan bool), - quit: make(chan bool), - db: db, - Nonce: nonce, - // serverCaps: caps, - peersFile: peersFile, - ListenAddr: listenAddr, + shutdownChan: make(chan bool), + quit: make(chan bool), + db: db, keyManager: keyManager, clientIdentity: identity, - blacklist: blacklist, + blacklist: p2p.NewBlocklist(), eventMux: &event.TypeMux{}, filters: make(map[int]*core.Filter), } @@ -111,9 +85,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain - pow := ezp.New() - verifyPoW := pow.Verify - eth.blockPool = NewBlockPool(hasBlock, insertChain, verifyPoW) + eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify) // Start the tx pool eth.txPool.Start() @@ -125,7 +97,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke Identity: identity, MaxPeers: maxPeers, Protocols: protocols, - ListenAddr: listenAddr, + ListenAddr: ":" + port, Blacklist: blacklist, NAT: nat, } @@ -171,11 +143,8 @@ func (s *Ethereum) IsMining() bool { } func (s *Ethereum) IsListening() bool { - if s.ListenAddr == "" { - return false - } else { - return true - } + // XXX TODO + return false } func (s *Ethereum) PeerCount() int { @@ -231,8 +200,6 @@ func (s *Ethereum) Stop() { // Close the database defer s.db.Close() - // - // WritePeers(s.peersFile, s.server.PeerAddresses()) close(s.quit) s.txSub.Unsubscribe() // quits txBroadcastLoop -- cgit v1.2.3 From afc8b887abfbfeaec5040a39f0f20d3071902abe Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 12:01:55 +0100 Subject: added whisper --- eth/backend.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index bdd5956a3..0bdeeea2a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/state" + "github.com/ethereum/go-ethereum/whisper" ) const ( @@ -28,26 +29,23 @@ type Ethereum struct { quit chan bool // DB interface - db ethutil.Database + db ethutil.Database + blacklist p2p.Blacklist + + //*** SERVICES *** // State manager for processing new blocks and managing the over all states blockManager *core.BlockManager - // The transaction pool. Transaction can be pushed on this pool - // for later including in the blocks - txPool *core.TxPool - // The canonical chain + txPool *core.TxPool chainManager *core.ChainManager - // The block pool - blockPool *BlockPool - // Event - eventMux *event.TypeMux + blockPool *BlockPool + whisper *whisper.Whisper - blacklist p2p.Blacklist - server *p2p.Server - txSub event.Subscription - blockSub event.Subscription - - RpcServer *rpc.JsonRpcServer + server *p2p.Server + eventMux *event.TypeMux + txSub event.Subscription + blockSub event.Subscription + RpcServer *rpc.JsonRpcServer keyManager *crypto.KeyManager clientIdentity p2p.ClientIdentity @@ -73,7 +71,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke db: db, keyManager: keyManager, clientIdentity: identity, - blacklist: p2p.NewBlocklist(), + blacklist: p2p.NewBlacklist(), eventMux: &event.TypeMux{}, filters: make(map[int]*core.Filter), } @@ -82,12 +80,13 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eth.chainManager = core.NewChainManager(eth.EventMux()) eth.blockManager = core.NewBlockManager(eth) eth.chainManager.SetProcessor(eth.blockManager) + eth.whisper = whisper.New() hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify) - // Start the tx pool + // Start services eth.txPool.Start() ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) @@ -98,7 +97,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke MaxPeers: maxPeers, Protocols: protocols, ListenAddr: ":" + port, - Blacklist: blacklist, + Blacklist: eth.blacklist, NAT: nat, } @@ -162,6 +161,7 @@ func (s *Ethereum) Start(seed bool) error { return err } s.blockPool.Start() + s.whisper.Start() go s.filterLoop() @@ -211,6 +211,7 @@ func (s *Ethereum) Stop() { s.txPool.Stop() s.eventMux.Stop() s.blockPool.Stop() + s.whisper.Stop() logger.Infoln("Server stopped") close(s.shutdownChan) -- cgit v1.2.3 From 6c168c8f2220df0cfd9f9e97ba5cfd6e351bf44e Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 12:02:24 +0100 Subject: added protocol handler --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 0bdeeea2a..f3f25d7b5 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -90,7 +90,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eth.txPool.Start() ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) - protocols := []p2p.Protocol{ethProto} + protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} server := &p2p.Server{ Identity: identity, -- cgit v1.2.3 From 4b5ad31b3ab8e1370233be6479e7476bc0019080 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 12:07:46 +0100 Subject: WIP --- eth/backend.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index f3f25d7b5..5b7dc6435 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/event/filter" ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/pow/ezp" @@ -53,9 +54,10 @@ type Ethereum struct { synclock sync.Mutex syncGroup sync.WaitGroup - filterMu sync.RWMutex - filterId int - filters map[int]*core.Filter + filterManager *filter.FilterManager + //filterMu sync.RWMutex + //filterId int + //filters map[int]*core.Filter Mining bool } -- cgit v1.2.3 From cdb2ebbdfa510294b8443e33c32f9e0ec414f78e Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 12:08:10 +0100 Subject: Added old filter. Needs some refactoring --- eth/backend.go | 63 ++++++---------------------------------------------------- 1 file changed, 6 insertions(+), 57 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 5b7dc6435..fb401a68d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/whisper" ) @@ -75,7 +74,6 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke clientIdentity: identity, blacklist: p2p.NewBlacklist(), eventMux: &event.TypeMux{}, - filters: make(map[int]*core.Filter), } eth.txPool = core.NewTxPool(eth) @@ -83,6 +81,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eth.blockManager = core.NewBlockManager(eth) eth.chainManager.SetProcessor(eth.blockManager) eth.whisper = whisper.New() + eth.filterManager = filter.NewFilterManager(eth.EventMux()) hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain @@ -164,8 +163,7 @@ func (s *Ethereum) Start(seed bool) error { } s.blockPool.Start() s.whisper.Start() - - go s.filterLoop() + s.filterManager.Start() // broadcast transactions s.txSub = s.eventMux.Subscribe(core.TxPreEvent{}) @@ -267,58 +265,9 @@ func saveProtocolVersion(db ethutil.Database) { } } -// InstallFilter adds filter for blockchain events. -// The filter's callbacks will run for matching blocks and messages. -// The filter should not be modified after it has been installed. +// XXX Refactor me & MOVE func (self *Ethereum) InstallFilter(filter *core.Filter) (id int) { - self.filterMu.Lock() - id = self.filterId - self.filters[id] = filter - self.filterId++ - self.filterMu.Unlock() - return id -} - -func (self *Ethereum) UninstallFilter(id int) { - self.filterMu.Lock() - delete(self.filters, id) - self.filterMu.Unlock() -} - -// GetFilter retrieves a filter installed using InstallFilter. -// The filter may not be modified. -func (self *Ethereum) GetFilter(id int) *core.Filter { - self.filterMu.RLock() - defer self.filterMu.RUnlock() - return self.filters[id] -} - -func (self *Ethereum) filterLoop() { - // Subscribe to events - events := self.eventMux.Subscribe(core.NewBlockEvent{}, state.Messages(nil)) - for event := range events.Chan() { - switch event.(type) { - case core.NewBlockEvent: - self.filterMu.RLock() - for _, filter := range self.filters { - if filter.BlockCallback != nil { - e := event.(core.NewBlockEvent) - filter.BlockCallback(e.Block) - } - } - self.filterMu.RUnlock() - case state.Messages: - self.filterMu.RLock() - for _, filter := range self.filters { - if filter.MessageCallback != nil { - e := event.(state.Messages) - msgs := filter.FilterMessages(e) - if len(msgs) > 0 { - filter.MessageCallback(msgs) - } - } - } - self.filterMu.RUnlock() - } - } + return self.filterManager.InstallFilter(filter) } +func (self *Ethereum) UninstallFilter(id int) { self.filterManager.UninstallFilter(id) } +func (self *Ethereum) GetFilter(id int) *core.Filter { return self.filterManager.GetFilter(id) } -- cgit v1.2.3 From 96272e19a6b7a3163ec53f45e04407e9d2ff8414 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 12:34:06 +0100 Subject: removed filter manager from base --- eth/backend.go | 15 --------------- eth/protocol.go | 43 ++++++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 36 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index fb401a68d..3ec36d3fc 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/event/filter" ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/pow/ezp" @@ -53,11 +52,6 @@ type Ethereum struct { synclock sync.Mutex syncGroup sync.WaitGroup - filterManager *filter.FilterManager - //filterMu sync.RWMutex - //filterId int - //filters map[int]*core.Filter - Mining bool } @@ -81,7 +75,6 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eth.blockManager = core.NewBlockManager(eth) eth.chainManager.SetProcessor(eth.blockManager) eth.whisper = whisper.New() - eth.filterManager = filter.NewFilterManager(eth.EventMux()) hasBlock := eth.chainManager.HasBlock insertChain := eth.chainManager.InsertChain @@ -163,7 +156,6 @@ func (s *Ethereum) Start(seed bool) error { } s.blockPool.Start() s.whisper.Start() - s.filterManager.Start() // broadcast transactions s.txSub = s.eventMux.Subscribe(core.TxPreEvent{}) @@ -264,10 +256,3 @@ func saveProtocolVersion(db ethutil.Database) { db.Put([]byte("ProtocolVersion"), ethutil.NewValue(ProtocolVersion).Bytes()) } } - -// XXX Refactor me & MOVE -func (self *Ethereum) InstallFilter(filter *core.Filter) (id int) { - return self.filterManager.InstallFilter(filter) -} -func (self *Ethereum) UninstallFilter(id int) { self.filterManager.UninstallFilter(id) } -func (self *Ethereum) GetFilter(id int) *core.Filter { return self.filterManager.GetFilter(id) } diff --git a/eth/protocol.go b/eth/protocol.go index 3b5b49696..8cbf6d309 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -11,6 +11,25 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +const ( + ProtocolVersion = 49 + NetworkId = 0 + ProtocolLength = uint64(8) + ProtocolMaxMsgSize = 10 * 1024 * 1024 +) + +// eth protocol message codes +const ( + StatusMsg = iota + GetTxMsg // unused + TxMsg + GetBlockHashesMsg + BlockHashesMsg + GetBlocksMsg + BlocksMsg + NewBlockMsg +) + // ethProtocol represents the ethereum wire protocol // instance is running on each peer type ethProtocol struct { @@ -41,25 +60,6 @@ type blockPool interface { RemovePeer(peerId string) } -const ( - ProtocolVersion = 43 - NetworkId = 0 - ProtocolLength = uint64(8) - ProtocolMaxMsgSize = 10 * 1024 * 1024 -) - -// eth protocol message codes -const ( - StatusMsg = iota - GetTxMsg // unused - TxMsg - GetBlockHashesMsg - BlockHashesMsg - GetBlocksMsg - BlocksMsg - NewBlockMsg -) - // message structs used for rlp decoding type newBlockMsgData struct { Block *types.Block @@ -279,9 +279,10 @@ func (self *ethProtocol) handleStatus() error { return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) } - self.peer.Infof("Peer is [eth] capable (%d/%d). TD = %v ~ %x", status.ProtocolVersion, status.NetworkId, status.CurrentBlock) + 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.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) + self.peer.Infoln("AddPeer(IGNORED)") return nil } -- cgit v1.2.3 From c7bc684909ab3d135d25a718d5ff2c725816e0fc Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 12:35:21 +0100 Subject: Moved peer helper metheds --- eth/backend.go | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 3ec36d3fc..1d991b9ef 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -1,7 +1,6 @@ package eth import ( - "encoding/json" "net" "sync" @@ -214,22 +213,6 @@ func (s *Ethereum) WaitForShutdown() { <-s.shutdownChan } -func WritePeers(path string, addresses []string) { - if len(addresses) > 0 { - data, _ := json.MarshalIndent(addresses, "", " ") - ethutil.WriteFile(path, data) - } -} - -func ReadPeers(path string) (ips []string, err error) { - var data string - data, err = ethutil.ReadAllFile(path) - if err != nil { - json.Unmarshal([]byte(data), &ips) - } - return -} - // now tx broadcasting is taken out of txPool // handled here via subscription, efficiency? func (self *Ethereum) txBroadcastLoop() { -- cgit v1.2.3 From 56dac74f71d4d1fffa3e240c7d3cee803dec788d Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 13:00:09 +0100 Subject: made mist in a compilable, workable state using the new refactored packages --- eth/backend.go | 4 ++++ eth/peer_util.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 eth/peer_util.go (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 1d991b9ef..e275238bd 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -147,6 +147,10 @@ func (s *Ethereum) Peers() []*p2p.Peer { return s.server.Peers() } +func (s *Ethereum) MaxPeers() int { + return s.server.MaxPeers +} + // Start the ethereum func (s *Ethereum) Start(seed bool) error { err := s.server.Start() diff --git a/eth/peer_util.go b/eth/peer_util.go new file mode 100644 index 000000000..6cf80cde2 --- /dev/null +++ b/eth/peer_util.go @@ -0,0 +1,23 @@ +package eth + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/ethutil" +) + +func WritePeers(path string, addresses []string) { + if len(addresses) > 0 { + data, _ := json.MarshalIndent(addresses, "", " ") + ethutil.WriteFile(path, data) + } +} + +func ReadPeers(path string) (ips []string, err error) { + var data string + data, err = ethutil.ReadAllFile(path) + if err != nil { + json.Unmarshal([]byte(data), &ips) + } + return +} -- cgit v1.2.3 From 455241debb6fa11053bd3a5429cdd9890bb607dc Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 17:09:06 +0100 Subject: Removed goroutine from "Run" --- eth/protocol.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 8cbf6d309..3b6f95d44 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -2,6 +2,7 @@ package eth import ( "bytes" + "fmt" "math" "math/big" @@ -98,15 +99,14 @@ func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPoo } err = self.handleStatus() if err == nil { - go func() { - for { - err = self.handle() - if err != nil { - self.blockPool.RemovePeer(self.id) - break - } + for { + err = self.handle() + if err != nil { + fmt.Println(err) + self.blockPool.RemovePeer(self.id) + break } - }() + } } return } -- cgit v1.2.3 From 6ff9d5770b332da6d767bc315a54c5a815143aff Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 15 Dec 2014 17:09:14 +0100 Subject: Added whisper handler --- eth/backend.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index e275238bd..ef82a5bfc 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -123,6 +123,10 @@ func (s *Ethereum) BlockPool() *BlockPool { return s.blockPool } +func (s *Ethereum) Whisper() *whisper.Whisper { + return s.whisper +} + func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -- cgit v1.2.3 From 0dc566124aee24cf3f01b4e586bde7752da89824 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 18 Dec 2014 13:22:59 +0100 Subject: Merge fixes --- eth/backend.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index ef82a5bfc..0aad6a514 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -69,9 +69,9 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke eventMux: &event.TypeMux{}, } - eth.txPool = core.NewTxPool(eth) eth.chainManager = core.NewChainManager(eth.EventMux()) - eth.blockManager = core.NewBlockManager(eth) + eth.txPool = core.NewTxPool(eth.chainManager, eth.EventMux()) + eth.blockManager = core.NewBlockManager(eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockManager) eth.whisper = whisper.New() -- cgit v1.2.3 From 4cd79d8ddd7608d60344b13fe4bda7315429d1d9 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 23 Dec 2014 13:48:44 +0100 Subject: Refactored block & Transaction * Includes new rlp decoder --- eth/backend.go | 2 +- eth/protocol.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 0aad6a514..383cda46f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -235,7 +235,7 @@ func (self *Ethereum) blockBroadcastLoop() { // automatically stops if unsubscribe for obj := range self.txSub.Chan() { event := obj.(core.NewMinedBlockEvent) - self.server.Broadcast("eth", NewBlockMsg, event.Block.Value().Val) + self.server.Broadcast("eth", NewBlockMsg, event.Block.RlpData()) } } diff --git a/eth/protocol.go b/eth/protocol.go index 3b6f95d44..963d41794 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -172,7 +172,7 @@ func (self *ethProtocol) handle() error { } block := self.chainManager.GetBlock(hash) if block != nil { - blocks = append(blocks, block.Value().Raw()) + blocks = append(blocks, block.RlpData()) } } return self.rw.EncodeMsg(BlocksMsg, blocks...) -- cgit v1.2.3 From d336e24dcec2bb2cb89fff76302882aa82124dc8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 Jan 2015 12:26:55 +0100 Subject: Removed the need of having a backend for the tx pool --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 383cda46f..78c2159c0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -70,7 +70,7 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke } eth.chainManager = core.NewChainManager(eth.EventMux()) - eth.txPool = core.NewTxPool(eth.chainManager, eth.EventMux()) + eth.txPool = core.NewTxPool(eth.EventMux()) eth.blockManager = core.NewBlockManager(eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockManager) eth.whisper = whisper.New() -- cgit v1.2.3 From 530953050ad0cf99d2a354c165b431d111baa1e3 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 Jan 2015 17:35:00 +0100 Subject: Updated protocol version --- eth/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 963d41794..7c5d09489 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -13,7 +13,7 @@ import ( ) const ( - ProtocolVersion = 49 + ProtocolVersion = 51 NetworkId = 0 ProtocolLength = uint64(8) ProtocolMaxMsgSize = 10 * 1024 * 1024 -- cgit v1.2.3 From 16f417f5af16de8f1c2c140f8b249bd989200bd3 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 Jan 2015 22:19:58 +0100 Subject: Fixed bug where logging could crash client during tx adding --- eth/backend.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 78c2159c0..36c1ac30f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -234,8 +234,10 @@ func (self *Ethereum) txBroadcastLoop() { func (self *Ethereum) blockBroadcastLoop() { // automatically stops if unsubscribe for obj := range self.txSub.Chan() { - event := obj.(core.NewMinedBlockEvent) - self.server.Broadcast("eth", NewBlockMsg, event.Block.RlpData()) + switch ev := obj.(type) { + case core.NewMinedBlockEvent: + self.server.Broadcast("eth", NewBlockMsg, ev.Block.RlpData()) + } } } -- cgit v1.2.3 From 09841b1c9b2553a4572590128580df37c8fa83ad Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 4 Jan 2015 14:20:16 +0100 Subject: Cleaned up some of that util --- eth/backend.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 16 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 36c1ac30f..bf6c91282 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -1,11 +1,13 @@ package eth import ( + "fmt" "net" "sync" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/event" ethlogger "github.com/ethereum/go-ethereum/logger" @@ -19,6 +21,24 @@ const ( seedNodeAddress = "poc-7.ethdev.com:30300" ) +type Config struct { + Name string + Version string + Identifier string + KeyStore string + DataDir string + LogFile string + LogLevel int + KeyRing string + + MaxPeers int + Port string + NATType string + PMPGateway string + + KeyManager *crypto.KeyManager +} + var logger = ethlogger.NewLogger("SERV") type Ethereum struct { @@ -38,7 +58,7 @@ type Ethereum struct { blockPool *BlockPool whisper *whisper.Whisper - server *p2p.Server + net *p2p.Server eventMux *event.TypeMux txSub event.Subscription blockSub event.Subscription @@ -47,6 +67,7 @@ type Ethereum struct { keyManager *crypto.KeyManager clientIdentity p2p.ClientIdentity + logger ethlogger.LogSystem synclock sync.Mutex syncGroup sync.WaitGroup @@ -54,7 +75,36 @@ type Ethereum struct { Mining bool } -func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, port string, maxPeers int) (*Ethereum, error) { +func New(config *Config) (*Ethereum, error) { + // Boostrap database + logger := ethlogger.New(config.DataDir, config.LogFile, config.LogLevel) + db, err := ethdb.NewLDBDatabase("database") + if err != nil { + return nil, err + } + + // Perform database sanity checks + d, _ := db.Get([]byte("ProtocolVersion")) + protov := ethutil.NewValue(d).Uint() + if protov != ProtocolVersion && protov != 0 { + return nil, fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, ProtocolVersion, ethutil.Config.ExecPath+"/database") + } + + // Create new keymanager + var keyManager *crypto.KeyManager + switch config.KeyStore { + case "db": + keyManager = crypto.NewDBKeyManager(db) + case "file": + keyManager = crypto.NewFileKeyManager(config.DataDir) + default: + return nil, fmt.Errorf("unknown keystore type: %s", config.KeyStore) + } + // Initialise the keyring + keyManager.Init(config.KeyRing, 0, false) + + // Create a new client id for this instance. This will help identifying the node on the network + clientId := p2p.NewSimpleClientIdentity(config.Name, config.Version, config.Identifier, keyManager.PublicKey()) saveProtocolVersion(db) ethutil.Config.Db = db @@ -64,9 +114,10 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke quit: make(chan bool), db: db, keyManager: keyManager, - clientIdentity: identity, + clientIdentity: clientId, blacklist: p2p.NewBlacklist(), eventMux: &event.TypeMux{}, + logger: logger, } eth.chainManager = core.NewChainManager(eth.EventMux()) @@ -85,17 +136,20 @@ func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.Ke ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} - server := &p2p.Server{ - Identity: identity, - MaxPeers: maxPeers, + nat, err := p2p.ParseNAT(config.NATType, config.PMPGateway) + if err != nil { + return nil, err + } + + eth.net = &p2p.Server{ + Identity: clientId, + MaxPeers: config.MaxPeers, Protocols: protocols, - ListenAddr: ":" + port, + ListenAddr: ":" + config.Port, Blacklist: eth.blacklist, NAT: nat, } - eth.server = server - return eth, nil } @@ -103,6 +157,10 @@ func (s *Ethereum) KeyManager() *crypto.KeyManager { return s.keyManager } +func (s *Ethereum) Logger() ethlogger.LogSystem { + return s.logger +} + func (s *Ethereum) ClientIdentity() p2p.ClientIdentity { return s.clientIdentity } @@ -144,20 +202,20 @@ func (s *Ethereum) IsListening() bool { } func (s *Ethereum) PeerCount() int { - return s.server.PeerCount() + return s.net.PeerCount() } func (s *Ethereum) Peers() []*p2p.Peer { - return s.server.Peers() + return s.net.Peers() } func (s *Ethereum) MaxPeers() int { - return s.server.MaxPeers + return s.net.MaxPeers } // Start the ethereum func (s *Ethereum) Start(seed bool) error { - err := s.server.Start() + err := s.net.Start() if err != nil { return err } @@ -191,7 +249,7 @@ func (self *Ethereum) SuggestPeer(addr string) error { return err } - self.server.SuggestPeer(netaddr.IP, netaddr.Port, nil) + self.net.SuggestPeer(netaddr.IP, netaddr.Port, nil) return nil } @@ -227,7 +285,7 @@ func (self *Ethereum) txBroadcastLoop() { // automatically stops if unsubscribe for obj := range self.txSub.Chan() { event := obj.(core.TxPreEvent) - self.server.Broadcast("eth", TxMsg, []interface{}{event.Tx.RlpData()}) + self.net.Broadcast("eth", TxMsg, []interface{}{event.Tx.RlpData()}) } } @@ -236,7 +294,7 @@ func (self *Ethereum) blockBroadcastLoop() { for obj := range self.txSub.Chan() { switch ev := obj.(type) { case core.NewMinedBlockEvent: - self.server.Broadcast("eth", NewBlockMsg, ev.Block.RlpData()) + self.net.Broadcast("eth", NewBlockMsg, ev.Block.RlpData()) } } } -- cgit v1.2.3 From c1dee151445d1d9700e0c623916299370868490c Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 5 Jan 2015 00:18:44 +0100 Subject: BlockManager => BlockProcessor --- eth/backend.go | 18 ++++++------- eth/wallet.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 eth/wallet.go (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index bf6c91282..db8e8e029 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -52,11 +52,11 @@ type Ethereum struct { //*** SERVICES *** // State manager for processing new blocks and managing the over all states - blockManager *core.BlockManager - txPool *core.TxPool - chainManager *core.ChainManager - blockPool *BlockPool - whisper *whisper.Whisper + blockProcessor *core.BlockProcessor + txPool *core.TxPool + chainManager *core.ChainManager + blockPool *BlockPool + whisper *whisper.Whisper net *p2p.Server eventMux *event.TypeMux @@ -122,8 +122,8 @@ func New(config *Config) (*Ethereum, error) { eth.chainManager = core.NewChainManager(eth.EventMux()) eth.txPool = core.NewTxPool(eth.EventMux()) - eth.blockManager = core.NewBlockManager(eth.txPool, eth.chainManager, eth.EventMux()) - eth.chainManager.SetProcessor(eth.blockManager) + eth.blockProcessor = core.NewBlockProcessor(eth.txPool, eth.chainManager, eth.EventMux()) + eth.chainManager.SetProcessor(eth.blockProcessor) eth.whisper = whisper.New() hasBlock := eth.chainManager.HasBlock @@ -169,8 +169,8 @@ func (s *Ethereum) ChainManager() *core.ChainManager { return s.chainManager } -func (s *Ethereum) BlockManager() *core.BlockManager { - return s.blockManager +func (s *Ethereum) BlockProcessor() *core.BlockProcessor { + return s.blockProcessor } func (s *Ethereum) TxPool() *core.TxPool { diff --git a/eth/wallet.go b/eth/wallet.go new file mode 100644 index 000000000..9ec834309 --- /dev/null +++ b/eth/wallet.go @@ -0,0 +1,80 @@ +package eth + +/* +import ( + "crypto/ecdsa" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" +) + +type Account struct { + w *Wallet +} + +func (self *Account) Transact(to *Account, value, gas, price *big.Int, data []byte) error { + return self.w.transact(self, to, value, gas, price, data) +} + +func (self *Account) Address() []byte { + return nil +} + +func (self *Account) PrivateKey() *ecdsa.PrivateKey { + return nil +} + +type Wallet struct{} + +func NewWallet() *Wallet { + return &Wallet{} +} + +func (self *Wallet) GetAccount(i int) *Account { +} + +func (self *Wallet) transact(from, to *Account, value, gas, price *big.Int, data []byte) error { + if from.PrivateKey() == nil { + return errors.New("accounts is not owned (no private key available)") + } + + var createsContract bool + if to == nil { + createsContract = true + } + + var msg *types.Transaction + if contractCreation { + msg = types.NewContractCreationTx(value, gas, price, data) + } else { + msg = types.NewTransactionMessage(to.Address(), value, gas, price, data) + } + + state := self.chainManager.TransState() + nonce := state.GetNonce(key.Address()) + + msg.SetNonce(nonce) + msg.SignECDSA(from.PriateKey()) + + // Do some pre processing for our "pre" events and hooks + block := self.chainManager.NewBlock(from.Address()) + coinbase := state.GetOrNewStateObject(from.Address()) + coinbase.SetGasPool(block.GasLimit()) + self.blockManager.ApplyTransactions(coinbase, state, block, types.Transactions{tx}, true) + + err := self.obj.TxPool().Add(tx) + if err != nil { + return nil, err + } + state.SetNonce(key.Address(), nonce+1) + + if contractCreation { + addr := core.AddressFromMessage(tx) + pipelogger.Infof("Contract addr %x\n", addr) + } + + return tx, nil +} +*/ -- cgit v1.2.3 From 6abf8ef78f1474fdeb7a6a6ce084bf994cc055f2 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 5 Jan 2015 17:10:42 +0100 Subject: Merge --- eth/backend.go | 47 +- eth/block_pool.go | 1407 +++++++++++++++++++++++----------------------- eth/block_pool_test.go | 926 ++++++++++++++++++++++++++---- eth/error.go | 11 +- eth/protocol.go | 100 ++-- eth/protocol_test.go | 300 +++++----- 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 + 27 files changed, 2015 insertions(+), 1019 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 (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index db8e8e029..065a4f7d8 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 7cfbc63f8..519c9fc13 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 := &peerInfo{ - td: td, - currentBlock: currentBlock, - id: peerId, //peer.Identity().Pubkey() - requestBlockHashes: requestBlockHashes, - requestBlocks: requestBlocks, - peerError: peerError, - } - self.peers[peerId] = peer - poolLogger.Debugf("add new peer %v with td %v", peerId, td) + 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]) + } + // check peer current head + if self.hasBlock(currentBlock) { + // peer not ahead + return false + } + + 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 - } - // 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, + 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) } - 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 - - node.sectionLock() - defer node.sectionUnlock() - section := §ion{controlC: controlC, resetC: resetC} - node.section = section +func (self *BlockPool) processSection(section *section, nodes []*poolNode) { + + 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) + + // 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 { - node.sectionRLock() - controlC = node.section.controlC - node.sectionRUnlock() - - if init { - // missing blocks read from nodeC - // initialized section - if depth == 0 { - break + + if insertChain { + insertChain = false + rest, err := self.addSectionToBlockChain(section) + if err != nil { + close(section.suicideC) + continue LOOP } - // enable select case to read missing block when ready - processC = missingC - missingC = make(chan *poolNode, lastMissing) - nodeC = nil - // only do once - init = false - } else { - if !once { - missingC = nil - processC = nil - i = 0 - total = 0 - lastMissing = 0 + if rest == 0 { + blocksRequestsComplete = true + child := self.getChild(section) + if child != nil { + self.connectToBlockChain(child) + } } } - // went through all blocks in section - if i != 0 && i == lastMissing { - if len(hashes) > 0 { - // send block requests to peers - self.requestBlocks(blocksRequests, hashes) - } - blocksRequests++ - poolLogger.Debugf("[%x] block request attempt %v: missing %v/%v/%v", hash[0:4], blocksRequests, missing, total, depth) - if missing == lastMissing { - // idle round - if same { - // more than once - idle++ - // too many idle rounds - if idle > blocksRequestMaxIdleRounds { - poolLogger.Debugf("[%x] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", hash[0:4], idle, blocksRequests, missing, total, depth) - self.killChain(node, nil) - break + 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 { + // 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 { - idle = 0 + same = false } - same = true - } 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 - } - 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 + blockHashesRequestTime = 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)) } - if running && !r { - poolLogger.Debugf("process on section %x... (%v total attempts): missing %v/%v/%v", hash[0:4], blocksRequests, missing, total, depth) + 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: - alarm = false + // 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 + } + 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++ } - 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) { - self.lock.Lock() - defer self.lock.Unlock() - self.sections[string(hash)] = section -} - -func (self *peerInfo) addRoot(node *poolNode) { +func (self *peerInfo) addSection(hash []byte, section *section) (found *section) { 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) + 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 *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { + if newPeer != nil { + entry := self.get(newPeer.currentBlock) + if entry == nil { + poolLogger.Debugf("[%s] head block [%s] not found, requesting hashes", newPeer.id, name(newPeer.currentBlock)) + newPeer.requestBlockHashes(newPeer.currentBlock) + } else { + poolLogger.Debugf("[%s] head block [%s] found, activate chain at section [%s]", newPeer.id, name(newPeer.currentBlock), sectionName(entry.section)) + self.activateChain(entry.section, newPeer) + } + 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]) } } - root.sectionRUnlock() + newPeer.quitC = make(chan bool) + } + if oldPeer != nil { + close(oldPeer.quitC) } - 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 *BlockPool) getParent(sec *section) *section { + self.chainLock.RLock() + defer self.chainLock.RUnlock() + return sec.parent } -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() - } - } +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 } -// 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() - } - } else { - // child is on top of a chain need to close section - child.section.bottom = child - } - // adopt new child +// 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() { - 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() { +func (self *BlockPool) get(hash []byte) (node *poolEntry) { 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 315cc748d..b50a314ea 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 -} - -func (self *testChainManager) KnownBlock(hash []byte) bool { - if self.knownBlock != nil { - return self.knownBlock(hash) - } - return false -} +var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugLevel)) -func (self *testChainManager) AddBlock(block *types.Block) error { - if self.addBlock != nil { - return self.addBlock(block) - } - return nil -} +var ini = false -func (self *testChainManager) CheckPoW(block *types.Block) bool { - if self.checkPoW != nil { - return self.checkPoW(block) +func logInit() { + if !ini { + ethlogger.AddLogSystem(logsys) + ini = true } - 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") - } - } - 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 - } + for i := range a { + if a[i] != b[i] { + 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 } -// -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 +// 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), } - return } -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 +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) + } + } } - return } -func (self *protocolChecker) invalidBlockCallBack() (invalidBlockCallBack func(error)) { - invalidBlockCallBack = func(err error) { - self.invalidBlocks = append(self.invalidBlocks, err) +// 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: + } } - return } +// 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) +// 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 +} + +// 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 nil +} + +// 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("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("peer not accepted as 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 d1daad575..1d9f80638 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 7c5d09489..b67e5aaea 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 322aec7b7..ab2aa289f 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 +func (self *testChainManager) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { + if self.status != nil { + td, currentBlock, genesisBlock = self.status() } + return } -<<<<<<< HEAD ->>>>>>> initial commit for eth-p2p integration -======= ->>>>>>> eth protocol changes -func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { +func (self *testChainManager) 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) { +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 { - err = self.addBlock(block, peerId) ->>>>>>> eth protocol changes + self.addBlock(block, peerId) } - 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) { +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 { - 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 + best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, peerError) } return } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes -func (self *TestBackend) RemovePeer(peerId string) { +func (self *testBlockPool) 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) { - if self.status != nil { - td, currentBlock, genesisBlock = self.status() - } - return -} - -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> eth protocol changes // 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) - }() +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) - rw.In(statusMsg) - <-quit - errorCheck(t, ErrNoStatusMsg, err) - // read(t, remote, []byte("hello, world"), nil) + 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 000000000..65728efa5 --- /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 000000000..3da038be8 --- /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 000000000..ad3c05b24 Binary files /dev/null and b/eth/test/chains/00.chain differ diff --git a/eth/test/chains/01.chain b/eth/test/chains/01.chain new file mode 100755 index 000000000..56c9aef65 Binary files /dev/null and b/eth/test/chains/01.chain differ diff --git a/eth/test/chains/02.chain b/eth/test/chains/02.chain new file mode 100755 index 000000000..440c92d65 Binary files /dev/null and b/eth/test/chains/02.chain differ diff --git a/eth/test/chains/03.chain b/eth/test/chains/03.chain new file mode 100755 index 000000000..1cc7570ab Binary files /dev/null and b/eth/test/chains/03.chain differ diff --git a/eth/test/chains/04.chain b/eth/test/chains/04.chain new file mode 100755 index 000000000..d4e5b1aa8 Binary files /dev/null and b/eth/test/chains/04.chain differ diff --git a/eth/test/mine.sh b/eth/test/mine.sh new file mode 100644 index 000000000..0d95db1e4 --- /dev/null +++ b/eth/test/mine.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# bash ./mine.sh node_id timeout(sec) [basechain] +ETH=../../ethereum +MINE="$ETH -datadir tmp/nodes/$1 -seed=false -port '' -shh=false -id test$1" +rm -rf tmp/nodes/$1 +echo "Creating chain $1..." +if [[ "" != "$3" ]]; then + CHAIN="chains/$3.chain" + CHAINARG="-chain $CHAIN" + $MINE -mine $CHAINARG -loglevel 3 | grep 'importing' +fi +$MINE -mine -loglevel 0 & +PID=$! +sleep $2 +kill $PID +$MINE -loglevel 3 <(echo "eth.export(\"chains/$1.chain\")") > /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 000000000..5229af035 --- /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 000000000..9655cb3df --- /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 000000000..9c5077164 --- /dev/null +++ b/eth/test/tests/00.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +TIMEOUT=4 + +cat >> $JSFILE <> $JSFILE <> $JSFILE <> $JSFILE <> $JSFILE <> $JSFILE < Date: Mon, 5 Jan 2015 17:15:25 +0100 Subject: unclean shutdown for now --- eth/block_pool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go index 519c9fc13..2334330e0 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -142,7 +142,7 @@ func (self *BlockPool) Stop() { poolLogger.Infoln("Stopping...") close(self.quit) - self.wg.Wait() + //self.wg.Wait() self.peersLock.Lock() self.peers = nil -- cgit v1.2.3 From eb0e7b1b8120852a1d56aa0ebd3a98e652965635 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 6 Jan 2015 11:35:09 +0100 Subject: eth, p2p: remove EncodeMsg from p2p.MsgWriter ...and make it a top-level function instead. The original idea behind having EncodeMsg in the interface was that implementations might be able to encode RLP data to their underlying writer directly instead of buffering the encoded data. The encoder will buffer anyway, so that doesn't matter anymore. Given the recent problems with EncodeMsg (copy-pasted implementation bug) I'd rather implement once, correctly. --- eth/protocol.go | 8 ++++---- eth/protocol_test.go | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index b67e5aaea..723ab5502 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -140,7 +140,7 @@ func (self *ethProtocol) handle() error { 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)...) + return p2p.EncodeMsg(self.rw, BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) case BlockHashesMsg: // TODO: redo using lazy decode , this way very inefficient on known chains @@ -185,7 +185,7 @@ func (self *ethProtocol) handle() error { break } } - return self.rw.EncodeMsg(BlocksMsg, blocks...) + return p2p.EncodeMsg(self.rw, BlocksMsg, blocks...) case BlocksMsg: msgStream := rlp.NewStream(msg.Payload) @@ -298,12 +298,12 @@ func (self *ethProtocol) handleStatus() error { func (self *ethProtocol) requestBlockHashes(from []byte) error { self.peer.Debugf("fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) - return self.rw.EncodeMsg(GetBlockHashesMsg, interface{}(from), uint64(blockHashesBatchSize)) + return p2p.EncodeMsg(self.rw, 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 p2p.EncodeMsg(self.rw, GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)...) } func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { diff --git a/eth/protocol_test.go b/eth/protocol_test.go index ab2aa289f..224b59abd 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -41,10 +41,6 @@ func (self *testMsgReadWriter) WriteMsg(msg p2p.Msg) error { return nil } -func (self *testMsgReadWriter) EncodeMsg(code uint64, data ...interface{}) error { - return self.WriteMsg(p2p.NewMsg(code, data...)) -} - func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { msg, ok := <-self.in if !ok { -- cgit v1.2.3 From 4e7f53adf00d25ada6fe6c52c2795a78cce7e795 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 6 Jan 2015 13:31:08 +0100 Subject: Changed to poc-8 & removed GetTxs --- eth/backend.go | 2 +- eth/protocol.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 065a4f7d8..2971df422 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -18,7 +18,7 @@ import ( ) const ( - seedNodeAddress = "poc-7.ethdev.com:30300" + seedNodeAddress = "poc-8.ethdev.com:30300" ) type Config struct { diff --git a/eth/protocol.go b/eth/protocol.go index 723ab5502..f52d935e8 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -22,7 +22,6 @@ const ( // eth protocol message codes const ( StatusMsg = iota - GetTxMsg // unused TxMsg GetBlockHashesMsg BlockHashesMsg -- cgit v1.2.3 From 1b903767e0418c4e11221f271107a825c2a23933 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 6 Jan 2015 13:31:52 +0100 Subject: Fixed port num --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 2971df422..a6ff52748 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -18,7 +18,7 @@ import ( ) const ( - seedNodeAddress = "poc-8.ethdev.com:30300" + seedNodeAddress = "poc-8.ethdev.com:30303" ) type Config struct { -- cgit v1.2.3 From a76b7dadaee6eddf64cba8ad8dd6ce71c785a7ee Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 6 Jan 2015 13:39:01 +0100 Subject: Don't auto push jeff ... --- eth/protocol.go | 1 + 1 file changed, 1 insertion(+) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index f52d935e8..723ab5502 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -22,6 +22,7 @@ const ( // eth protocol message codes const ( StatusMsg = iota + GetTxMsg // unused TxMsg GetBlockHashesMsg BlockHashesMsg -- cgit v1.2.3 From 25e6c4eff8364770cfd2908db9c54a012b9e4ec4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 6 Jan 2015 14:02:47 +0100 Subject: Adjusted difficulty and skip get tx messages --- eth/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 723ab5502..736bcd94b 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -122,7 +122,7 @@ func (self *ethProtocol) handle() error { defer msg.Discard() switch msg.Code { - + case GetTxMsg: // ignore case StatusMsg: return self.protoError(ErrExtraStatusMsg, "") -- cgit v1.2.3 From fed3e6a808921fb8274b50043c5c39a24a1bbccf Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 7 Jan 2015 13:17:48 +0100 Subject: Refactored ethutil.Config.Db out --- eth/backend.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index a6ff52748..02d3dc942 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -81,7 +81,7 @@ type Ethereum struct { func New(config *Config) (*Ethereum, error) { // Boostrap database logger := ethlogger.New(config.DataDir, config.LogFile, config.LogLevel) - db, err := ethdb.NewLDBDatabase("database") + db, err := ethdb.NewLDBDatabase("blockchain") if err != nil { return nil, err } @@ -110,7 +110,7 @@ func New(config *Config) (*Ethereum, error) { clientId := p2p.NewSimpleClientIdentity(config.Name, config.Version, config.Identifier, keyManager.PublicKey()) saveProtocolVersion(db) - ethutil.Config.Db = db + //ethutil.Config.Db = db eth := &Ethereum{ shutdownChan: make(chan bool), @@ -123,9 +123,9 @@ func New(config *Config) (*Ethereum, error) { logger: logger, } - eth.chainManager = core.NewChainManager(eth.EventMux()) + eth.chainManager = core.NewChainManager(db, eth.EventMux()) eth.txPool = core.NewTxPool(eth.EventMux()) - eth.blockProcessor = core.NewBlockProcessor(eth.txPool, eth.chainManager, eth.EventMux()) + eth.blockProcessor = core.NewBlockProcessor(db, eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockProcessor) eth.whisper = whisper.New() -- cgit v1.2.3 From 26f066f0c7570bcca43299721c2b7a1a70186ee3 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 8 Jan 2015 22:18:23 +0100 Subject: just enable by default --- eth/backend.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index 02d3dc942..ad0486309 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -134,24 +134,20 @@ func New(config *Config) (*Ethereum, error) { eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify) ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) - protocols := []p2p.Protocol{ethProto} - - if config.Shh { - eth.whisper = whisper.New() - protocols = append(protocols, eth.whisper.Protocol()) - } + protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} nat, err := p2p.ParseNAT(config.NATType, config.PMPGateway) if err != nil { return nil, err } + fmt.Println(nat) eth.net = &p2p.Server{ Identity: clientId, MaxPeers: config.MaxPeers, Protocols: protocols, Blacklist: eth.blacklist, - NAT: nat, + NAT: p2p.UPNP(), NoDial: !config.Dial, } -- cgit v1.2.3 From 69dfca2feb5c94f7a28a0b24c28181b6da4b9da3 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 9 Jan 2015 05:04:32 +0000 Subject: minor changes in integration tests --- eth/test/tests/02.sh | 6 +++--- eth/test/tests/03.sh | 4 ++-- eth/test/tests/common.sh | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'eth') diff --git a/eth/test/tests/02.sh b/eth/test/tests/02.sh index 5231dbd78..619217ed8 100644 --- a/eth/test/tests/02.sh +++ b/eth/test/tests/02.sh @@ -12,8 +12,8 @@ EOF peer 11 01 peer 12 02 -P13ID=$PID +P12ID=$PID test_node $NAME "" -loglevel 5 $JSFILE -sleep 0.5 -kill $P13ID +sleep 0.3 +kill $P12ID diff --git a/eth/test/tests/03.sh b/eth/test/tests/03.sh index 8c9d6565e..d7dba737f 100644 --- a/eth/test/tests/03.sh +++ b/eth/test/tests/03.sh @@ -1,10 +1,10 @@ #!/bin/bash -TIMEOUT=35 +TIMEOUT=12 cat >> $JSFILE < Date: Fri, 9 Jan 2015 05:06:04 +0000 Subject: no need to call AddBlockHashes when receiving new block --- eth/protocol.go | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index 736bcd94b..c9a5dea8d 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -211,16 +211,6 @@ func (self *ethProtocol) handle() error { // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // (or selected as new best peer) if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { - called := true - iter := func() ([]byte, bool) { - if called { - called = false - return hash, true - } else { - return nil, false - } - } - self.blockPool.AddBlockHashes(iter, self.id) self.blockPool.AddBlock(request.Block, self.id) } -- cgit v1.2.3 From f72cb28b0f688d99d7936900474e7d10d06ffb3a Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 9 Jan 2015 05:57:09 +0000 Subject: adapt unit tests to spec - AddBlockHashes ignores the first hash (just used to match getBlockHashes query) sends the rest as blocksMsg - new test TestPeerWithKnownParentBlock - new test TestChainConnectingWithParentHash - adapt all other tests to the new scheme --- eth/block_pool_test.go | 192 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 139 insertions(+), 53 deletions(-) (limited to 'eth') diff --git a/eth/block_pool_test.go b/eth/block_pool_test.go index b50a314ea..94c3b43d2 100644 --- a/eth/block_pool_test.go +++ b/eth/block_pool_test.go @@ -18,7 +18,7 @@ import ( const waitTimeout = 60 // seconds -var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugLevel)) +var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel)) var ini = false @@ -336,12 +336,12 @@ func (self *peerTester) AddPeer() bool { // 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) + i := 1 next := func() (hash []byte, ok bool) { if i < len(hashes) { hash = hashes[i] @@ -415,7 +415,7 @@ func TestAddPeer(t *testing.T) { if blockPool.peer.id != "peer0" { t.Errorf("peer0 (TD=1) not set as best") } - peer0.checkBlockHashesRequests(0) + // peer0.checkBlockHashesRequests(0) best = peer2.AddPeer() if !best { @@ -424,7 +424,7 @@ func TestAddPeer(t *testing.T) { if blockPool.peer.id != "peer2" { t.Errorf("peer2 (TD=3) not set as best") } - peer2.checkBlockHashesRequests(2) + peer2.waitBlocksRequests(2) best = peer1.AddPeer() if best { @@ -449,7 +449,7 @@ func TestAddPeer(t *testing.T) { if blockPool.peer.td.Cmp(big.NewInt(int64(4))) != 0 { t.Errorf("peer2 TD not updated") } - peer2.checkBlockHashesRequests(2, 3) + peer2.waitBlocksRequests(3) peer1.td = 3 peer1.currentBlock = 2 @@ -474,7 +474,7 @@ func TestAddPeer(t *testing.T) { if blockPool.peer.id != "peer1" { t.Errorf("existing peer1 (TD=3) should be set as best peer") } - peer1.checkBlockHashesRequests(2) + peer1.waitBlocksRequests(2) blockPool.RemovePeer("peer1") peer, best = blockPool.getPeer("peer1") @@ -485,6 +485,7 @@ func TestAddPeer(t *testing.T) { if blockPool.peer.id != "peer0" { t.Errorf("existing peer0 (TD=1) should be set as best peer") } + peer0.waitBlocksRequests(0) blockPool.RemovePeer("peer0") peer, best = blockPool.getPeer("peer0") @@ -502,7 +503,7 @@ func TestAddPeer(t *testing.T) { if blockPool.peer.id != "peer0" { t.Errorf("peer0 (TD=1) should be set as best") } - peer0.checkBlockHashesRequests(0, 0, 3) + peer0.waitBlocksRequests(3) blockPool.Stop() @@ -513,17 +514,36 @@ func TestPeerWithKnownBlock(t *testing.T) { _, 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.Wait(waitTimeout * time.Second) blockPool.Stop() // no request on known block peer0.checkBlockHashesRequests() } +func TestPeerWithKnownParentBlock(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.initRefBlockChain(1) + blockPoolTester.blockChain[0] = nil + blockPool.Start() + + peer0 := blockPoolTester.newPeer("0", 1, 1) + peer0.AddPeer() + peer0.AddBlocks(0, 1) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + peer0.checkBlocksRequests([]int{1}) + peer0.checkBlockHashesRequests() + blockPoolTester.refBlockChain[1] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + func TestSimpleChain(t *testing.T) { logInit() _, blockPool, blockPoolTester := newTestBlockPool(t) @@ -534,8 +554,9 @@ func TestSimpleChain(t *testing.T) { peer1 := blockPoolTester.newPeer("peer1", 1, 2) peer1.AddPeer() + peer1.AddBlocks(1, 2) go peer1.AddBlockHashes(2, 1, 0) - peer1.AddBlocks(0, 1, 2) + peer1.AddBlocks(0, 1) blockPool.Wait(waitTimeout * time.Second) blockPool.Stop() @@ -543,6 +564,26 @@ func TestSimpleChain(t *testing.T) { blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) } +func TestChainConnectingWithParentHash(t *testing.T) { + logInit() + _, blockPool, blockPoolTester := newTestBlockPool(t) + blockPoolTester.blockChain[0] = nil + blockPoolTester.initRefBlockChain(3) + + blockPool.Start() + + peer1 := blockPoolTester.newPeer("peer1", 1, 3) + peer1.AddPeer() + go peer1.AddBlocks(2, 3) + go peer1.AddBlockHashes(3, 2, 1) + peer1.AddBlocks(0, 1, 2) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[3] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + func TestInvalidBlock(t *testing.T) { logInit() _, blockPool, blockPoolTester := newTestBlockPool(t) @@ -554,8 +595,9 @@ func TestInvalidBlock(t *testing.T) { peer1 := blockPoolTester.newPeer("peer1", 1, 3) peer1.AddPeer() + go peer1.AddBlocks(2, 3) go peer1.AddBlockHashes(3, 2, 1, 0) - peer1.AddBlocks(0, 1, 2, 3) + peer1.AddBlocks(0, 1, 2) blockPool.Wait(waitTimeout * time.Second) blockPool.Stop() @@ -566,7 +608,7 @@ func TestInvalidBlock(t *testing.T) { t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidBlock) } } else { - t.Errorf("expected invalid block error, got nothing") + t.Errorf("expected invalid block error, got nothing %v", peer1.peerErrors) } } @@ -579,7 +621,7 @@ func TestVerifyPoW(t *testing.T) { blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool { bb, _ := b.(*types.Block) indexes := blockPoolTester.hashPool.hashesToIndexes([][]byte{bb.Hash()}) - if indexes[0] == 1 && !first { + if indexes[0] == 2 && !first { first = true return false } else { @@ -590,15 +632,17 @@ func TestVerifyPoW(t *testing.T) { blockPool.Start() - peer1 := blockPoolTester.newPeer("peer1", 1, 2) + peer1 := blockPoolTester.newPeer("peer1", 1, 3) peer1.AddPeer() - go peer1.AddBlockHashes(2, 1, 0) + go peer1.AddBlocks(2, 3) + go peer1.AddBlockHashes(3, 2, 1, 0) peer1.AddBlocks(0, 1, 2) - peer1.AddBlocks(0, 1) - blockPool.Wait(waitTimeout * time.Second) + // blockPool.Wait(waitTimeout * time.Second) + time.Sleep(1 * time.Second) blockPool.Stop() - blockPoolTester.refBlockChain[2] = []int{} + blockPoolTester.refBlockChain[1] = []int{} + delete(blockPoolTester.refBlockChain, 2) blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) if len(peer1.peerErrors) == 1 { if peer1.peerErrors[0] != ErrInvalidPoW { @@ -620,8 +664,9 @@ func TestMultiSectionChain(t *testing.T) { peer1 := blockPoolTester.newPeer("peer1", 1, 5) peer1.AddPeer() + go peer1.AddBlocks(4, 5) go peer1.AddBlockHashes(5, 4, 3) - go peer1.AddBlocks(2, 3, 4, 5) + go peer1.AddBlocks(2, 3, 4) go peer1.AddBlockHashes(3, 2, 1, 0) peer1.AddBlocks(0, 1, 2) @@ -641,14 +686,17 @@ func TestNewBlocksOnPartialChain(t *testing.T) { peer1 := blockPoolTester.newPeer("peer1", 1, 5) peer1.AddPeer() + go peer1.AddBlocks(4, 5) // partially complete section go peer1.AddBlockHashes(5, 4, 3) - peer1.AddBlocks(2, 3) // partially complete section + peer1.AddBlocks(3, 4) // partially complete section // peer1 found new blocks peer1.td = 2 peer1.currentBlock = 7 peer1.AddPeer() + go peer1.AddBlocks(6, 7) go peer1.AddBlockHashes(7, 6, 5) - go peer1.AddBlocks(3, 4, 5, 6, 7) + go peer1.AddBlocks(2, 3) + go peer1.AddBlocks(5, 6) go peer1.AddBlockHashes(3, 2, 1, 0) // tests that hash request from known chain root is remembered peer1.AddBlocks(0, 1, 2) @@ -658,35 +706,37 @@ func TestNewBlocksOnPartialChain(t *testing.T) { blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) } -func TestPeerSwitch(t *testing.T) { +func TestPeerSwitchUp(t *testing.T) { logInit() _, blockPool, blockPoolTester := newTestBlockPool(t) blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(6) + blockPoolTester.initRefBlockChain(7) blockPool.Start() - peer1 := blockPoolTester.newPeer("peer1", 1, 5) - peer2 := blockPoolTester.newPeer("peer2", 2, 6) + peer1 := blockPoolTester.newPeer("peer1", 1, 6) + peer2 := blockPoolTester.newPeer("peer2", 2, 7) peer2.blocksRequestsMap = peer1.blocksRequestsMap peer1.AddPeer() - go peer1.AddBlockHashes(5, 4, 3) + go peer1.AddBlocks(5, 6) + go peer1.AddBlockHashes(6, 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 peer2.AddBlocks(6, 7) + go peer2.AddBlockHashes(7, 6) // + go peer2.AddBlocks(4, 5) // 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.refBlockChain[7] = []int{} blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) } -func TestPeerDownSwitch(t *testing.T) { +func TestPeerSwitchDown(t *testing.T) { logInit() _, blockPool, blockPoolTester := newTestBlockPool(t) blockPoolTester.blockChain[0] = nil @@ -698,12 +748,39 @@ func TestPeerDownSwitch(t *testing.T) { peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.AddPeer() - go peer2.AddBlockHashes(6, 5, 4) peer2.AddBlocks(5, 6) // partially complete, section will be preserved + go peer2.AddBlockHashes(6, 5, 4) // + peer2.AddBlocks(4, 5) // 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 + go peer1.AddBlocks(3, 4) // tests that section set by demoted peer is remembered and blocks are accepted , this connects the chain sections together + peer1.AddBlocks(0, 1, 2, 3) + + blockPool.Wait(waitTimeout * time.Second) + blockPool.Stop() + blockPoolTester.refBlockChain[6] = []int{} + blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) +} + +func TestPeerCompleteSectionSwitchDown(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() + peer2.AddBlocks(5, 6) // partially complete, section will be preserved + go peer2.AddBlockHashes(6, 5, 4) // + peer2.AddBlocks(3, 4, 5) // complete section + blockPool.RemovePeer("peer2") // peer2 disconnects + peer1.AddPeer() // inferior peer1 is promoted as best peer + peer1.AddBlockHashes(4, 3, 2, 1, 0) // tests that hash request are directly connecting if the head block exists peer1.AddBlocks(0, 1, 2, 3) blockPool.Wait(waitTimeout * time.Second) @@ -725,11 +802,13 @@ func TestPeerSwitchBack(t *testing.T) { peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.AddPeer() + go peer2.AddBlocks(7, 8) 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 + go peer1.AddBlocks(10, 11) // + 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 @@ -756,11 +835,13 @@ func TestForkSimple(t *testing.T) { peer2.blocksRequestsMap = peer1.blocksRequestsMap peer1.AddPeer() + go peer1.AddBlocks(8, 9) go peer1.AddBlockHashes(9, 8, 7, 3, 2) - peer1.AddBlocks(1, 2, 3, 7, 8, 9) + peer1.AddBlocks(1, 2, 3, 7, 8) peer2.AddPeer() // peer2 is promoted as best peer + go peer2.AddBlocks(5, 6) // 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.AddBlocks(1, 2, 3, 4, 5) go peer2.AddBlockHashes(2, 1, 0) peer2.AddBlocks(0, 1, 2) @@ -790,23 +871,24 @@ func TestForkSwitchBackByNewBlocks(t *testing.T) { peer2.blocksRequestsMap = peer1.blocksRequestsMap peer1.AddPeer() - go peer1.AddBlockHashes(9, 8, 7, 3, 2) - peer1.AddBlocks(8, 9) // partial section + peer1.AddBlocks(8, 9) // + go peer1.AddBlockHashes(9, 8, 7, 3, 2) // + peer1.AddBlocks(7, 8) // partial section peer2.AddPeer() // + peer2.AddBlocks(5, 6) // go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 - peer2.AddBlocks(1, 2, 3, 4, 5, 6) // + peer2.AddBlocks(1, 2, 3, 4, 5) // // peer1 finds new blocks peer1.td = 3 peer1.currentBlock = 11 peer1.AddPeer() + go peer1.AddBlocks(10, 11) 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 + peer1.AddBlocks(9, 10) + go peer1.AddBlocks(3, 7) // tests that block requests on earlier fork are 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) + peer1.AddBlocks(0, 1) blockPool.Wait(waitTimeout * time.Second) blockPool.Stop() @@ -834,16 +916,18 @@ func TestForkSwitchBackByPeerSwitchBack(t *testing.T) { peer2.blocksRequestsMap = peer1.blocksRequestsMap peer1.AddPeer() + go peer1.AddBlocks(8, 9) go peer1.AddBlockHashes(9, 8, 7, 3, 2) - peer1.AddBlocks(8, 9) - peer2.AddPeer() // + peer1.AddBlocks(7, 8) + peer2.AddPeer() + go peer2.AddBlocks(5, 6) // go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 - peer2.AddBlocks(2, 3, 4, 5, 6) // + peer2.AddBlocks(2, 3, 4, 5) // 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.AddBlocks(3, 7) // tests that block requests on earlier fork are remembered and orphan section relinks to existing parent block + go peer1.AddBlocks(1, 2) // go peer1.AddBlockHashes(2, 1, 0) // - peer1.AddBlocks(0, 1, 2, 3) + peer1.AddBlocks(0, 1) blockPool.Wait(waitTimeout * time.Second) blockPool.Stop() @@ -871,17 +955,19 @@ func TestForkCompleteSectionSwitchBackByPeerSwitchBack(t *testing.T) { peer2.blocksRequestsMap = peer1.blocksRequestsMap peer1.AddPeer() + go peer1.AddBlocks(8, 9) go peer1.AddBlockHashes(9, 8, 7) - peer1.AddBlocks(3, 7, 8, 9) // make sure this section is complete + peer1.AddBlocks(3, 7, 8) // 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 + peer1.AddBlocks(2, 3) // partially complete sections block 2 missing peer2.AddPeer() // + go peer2.AddBlocks(5, 6) // go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 - peer2.AddBlocks(2, 3, 4, 5, 6) // block 2 still missing. + peer2.AddBlocks(2, 3, 4, 5) // 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.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) -- cgit v1.2.3 From 8ecc9509b3ac490fe8ac9d91e24e8271963ee443 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 9 Jan 2015 06:03:32 +0000 Subject: add ErrInsufficientChainInfo error --- eth/error.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'eth') diff --git a/eth/error.go b/eth/error.go index 1d9f80638..9c4a68481 100644 --- a/eth/error.go +++ b/eth/error.go @@ -16,6 +16,7 @@ const ( ErrInvalidBlock ErrInvalidPoW ErrUnrequestedBlock + ErrInsufficientChainInfo ) var errorToString = map[int]string{ @@ -30,6 +31,7 @@ var errorToString = map[int]string{ ErrInvalidBlock: "Invalid block", ErrInvalidPoW: "Invalid PoW", ErrUnrequestedBlock: "Unrequested block", + ErrInsufficientChainInfo: "Insufficient chain info", } type protocolError struct { -- cgit v1.2.3 From 5a9952c7b47f20451feea1158286450e08b85551 Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 9 Jan 2015 06:03:45 +0000 Subject: major blockpool change - the spec says response to getBlockHashes(from, max) should return all hashes starting from PARENT of from. This required major changes and results in much hackier code. - Introduced a first round block request after peer introduces with current head, so that hashes can be linked to the head - peerInfo records currentBlockHash, currentBlock, parentHash and headSection - AddBlockHashes checks header section and creates the top node from the peerInfo of the best peer - AddBlock checks peerInfo and updates the block there rather than in a node - request further hashes once a section is created but then no more until the root block is found (so that we know when to stop asking) - in processSection, when root node is checked and receives a block, we need to check if the section has a parent known to blockchain or blockPool - when peers are switched, new peer launches a new requestHeadSection loop or activates its actual head section, i.e., the section for it currentBlockHash - all tests pass --- eth/block_pool.go | 467 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 350 insertions(+), 117 deletions(-) (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go index 2334330e0..b624d064a 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -1,6 +1,7 @@ package eth import ( + "bytes" "fmt" "math" "math/big" @@ -24,8 +25,8 @@ const ( blocksRequestRepetition = 1 blockHashesRequestInterval = 500 // ms blocksRequestMaxIdleRounds = 100 - cacheTimeout = 3 // minutes - blockTimeout = 5 // minutes + blockHashesTimeout = 60 // seconds + blocksTimeout = 120 // seconds ) type poolNode struct { @@ -70,9 +71,14 @@ type BlockPool struct { type peerInfo struct { lock sync.RWMutex - td *big.Int - currentBlock []byte - id string + td *big.Int + currentBlockHash []byte + currentBlock *types.Block + currentBlockC chan *types.Block + parentHash []byte + headSection *section + headSectionC chan *section + id string requestBlockHashes func([]byte) error requestBlocks func([][]byte) error @@ -203,30 +209,39 @@ func (self *BlockPool) Wait(t time.Duration) { // 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 { +func (self *BlockPool) AddPeer(td *big.Int, currentBlockHash []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) (best bool) { self.peersLock.Lock() defer self.peersLock.Unlock() peer, ok := self.peers[peerId] if ok { - poolLogger.Debugf("Update peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) - peer.td = td - peer.currentBlock = currentBlock + if bytes.Compare(peer.currentBlockHash, currentBlockHash) != 0 { + poolLogger.Debugf("Update peer %v with td %v and current block %s", peerId, td, name(currentBlockHash)) + peer.lock.Lock() + peer.td = td + peer.currentBlockHash = currentBlockHash + peer.currentBlock = nil + peer.parentHash = nil + peer.headSection = nil + peer.lock.Unlock() + } } else { peer = &peerInfo{ td: td, - currentBlock: currentBlock, + currentBlockHash: currentBlockHash, id: peerId, //peer.Identity().Pubkey() requestBlockHashes: requestBlockHashes, requestBlocks: requestBlocks, peerError: peerError, sections: make(map[string]*section), + currentBlockC: make(chan *types.Block), + headSectionC: make(chan *section), } self.peers[peerId] = peer - poolLogger.Debugf("add new peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) + poolLogger.Debugf("add new peer %v with td %v and current block %x", peerId, td, currentBlockHash[:4]) } // check peer current head - if self.hasBlock(currentBlock) { + if self.hasBlock(currentBlockHash) { // peer not ahead return false } @@ -234,22 +249,135 @@ func (self *BlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string, 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 + poolLogger.Debugf("[%s] already the best peer. Request new head section info from %s", peerId, name(currentBlockHash)) + peer.headSectionC <- nil + best = true + } else { + currentTD := ethutil.Big0 + if self.peer != nil { + currentTD = self.peer.td + } + if td.Cmp(currentTD) > 0 { + poolLogger.Debugf("peer %v promoted best peer", peerId) + self.switchPeer(self.peer, peer) + self.peer = peer + best = true + } } + return +} - currentTD := ethutil.Big0 - if self.peer != nil { - currentTD = self.peer.td - } - if td.Cmp(currentTD) > 0 { - poolLogger.Debugf("peer %v promoted best peer", peerId) - self.switchPeer(self.peer, peer) - self.peer = peer - return true - } - return false +func (self *BlockPool) requestHeadSection(peer *peerInfo) { + self.wg.Add(1) + self.procWg.Add(1) + poolLogger.Debugf("[%s] head section at [%s] requesting info", peer.id, name(peer.currentBlockHash)) + + go func() { + var idle bool + peer.lock.RLock() + quitC := peer.quitC + currentBlockHash := peer.currentBlockHash + peer.lock.RUnlock() + blockHashesRequestTimer := time.NewTimer(0) + blocksRequestTimer := time.NewTimer(0) + suicide := time.NewTimer(blockHashesTimeout * time.Second) + blockHashesRequestTimer.Stop() + defer blockHashesRequestTimer.Stop() + defer blocksRequestTimer.Stop() + + entry := self.get(currentBlockHash) + if entry != nil { + entry.node.lock.RLock() + currentBlock := entry.node.block + entry.node.lock.RUnlock() + if currentBlock != nil { + peer.lock.Lock() + peer.currentBlock = currentBlock + peer.parentHash = currentBlock.ParentHash() + poolLogger.Debugf("[%s] head block [%s] found", peer.id, name(currentBlockHash)) + peer.lock.Unlock() + blockHashesRequestTimer.Reset(0) + blocksRequestTimer.Stop() + } + } + + LOOP: + for { + + select { + case <-self.quit: + break LOOP + + case <-quitC: + poolLogger.Debugf("[%s] head section at [%s] incomplete - quit request loop", peer.id, name(currentBlockHash)) + break LOOP + + case headSection := <-peer.headSectionC: + peer.lock.Lock() + peer.headSection = headSection + if headSection == nil { + oldBlockHash := currentBlockHash + currentBlockHash = peer.currentBlockHash + poolLogger.Debugf("[%s] head section changed [%s] -> [%s]", peer.id, name(oldBlockHash), name(currentBlockHash)) + if idle { + idle = false + suicide.Reset(blockHashesTimeout * time.Second) + self.procWg.Add(1) + } + blocksRequestTimer.Reset(blocksRequestInterval * time.Millisecond) + } else { + poolLogger.DebugDetailf("[%s] head section at [%s] created", peer.id, name(currentBlockHash)) + if !idle { + idle = true + suicide.Stop() + self.procWg.Done() + } + } + peer.lock.Unlock() + blockHashesRequestTimer.Stop() + + case <-blockHashesRequestTimer.C: + poolLogger.DebugDetailf("[%s] head section at [%s] not found, requesting block hashes", peer.id, name(currentBlockHash)) + peer.requestBlockHashes(currentBlockHash) + blockHashesRequestTimer.Reset(blockHashesRequestInterval * time.Millisecond) + + case currentBlock := <-peer.currentBlockC: + peer.lock.Lock() + peer.currentBlock = currentBlock + peer.parentHash = currentBlock.ParentHash() + poolLogger.DebugDetailf("[%s] head block [%s] found", peer.id, name(currentBlockHash)) + peer.lock.Unlock() + if self.hasBlock(currentBlock.ParentHash()) { + if err := self.insertChain(types.Blocks([]*types.Block{currentBlock})); err != nil { + peer.peerError(ErrInvalidBlock, "%v", err) + } + if !idle { + idle = true + suicide.Stop() + self.procWg.Done() + } + } else { + blockHashesRequestTimer.Reset(0) + } + blocksRequestTimer.Stop() + + case <-blocksRequestTimer.C: + peer.lock.RLock() + poolLogger.DebugDetailf("[%s] head block [%s] not found, requesting", peer.id, name(currentBlockHash)) + peer.requestBlocks([][]byte{peer.currentBlockHash}) + peer.lock.RUnlock() + blocksRequestTimer.Reset(blocksRequestInterval * time.Millisecond) + + case <-suicide.C: + peer.peerError(ErrInsufficientChainInfo, "peer failed to provide block hashes or head block for block hash %x", currentBlockHash) + break LOOP + } + } + self.wg.Done() + if !idle { + self.procWg.Done() + } + }() } // RemovePeer is called by the eth protocol when the peer disconnects @@ -274,13 +402,13 @@ func (self *BlockPool) RemovePeer(peerId string) { newPeer = info } } - self.peer = newPeer - self.switchPeer(peer, newPeer) if newPeer != nil { poolLogger.Debugf("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td) } else { poolLogger.Warnln("no peers") } + self.peer = newPeer + self.switchPeer(peer, newPeer) } } @@ -299,25 +427,56 @@ func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string) return } // peer is still the best - poolLogger.Debugf("adding hashes for best peer %s", peerId) var size, n int var hash []byte - var ok bool - var section, child, parent *section + var ok, headSection bool + var sec, child, parent *section var entry *poolEntry var nodes []*poolNode + bestPeer := peer + + hash, ok = next() + peer.lock.Lock() + if bytes.Compare(peer.parentHash, hash) == 0 { + if self.hasBlock(peer.currentBlockHash) { + return + } + poolLogger.Debugf("adding hashes at chain head for best peer %s starting from [%s]", peerId, name(peer.currentBlockHash)) + headSection = true + + if entry := self.get(peer.currentBlockHash); entry == nil { + node := &poolNode{ + hash: peer.currentBlockHash, + block: peer.currentBlock, + peer: peerId, + blockBy: peerId, + } + if size == 0 { + sec = newSection() + } + nodes = append(nodes, node) + size++ + n++ + } else { + child = entry.section + } + } else { + poolLogger.Debugf("adding hashes for best peer %s starting from [%s]", peerId, name(hash)) + } + quitC := peer.quitC + peer.lock.Unlock() LOOP: // iterate using next (rlp stream lazy decoder) feeding hashesC - for hash, ok = next(); ok; hash, ok = next() { + for ; ok; hash, ok = next() { n++ select { case <-self.quit: return - case <-peer.quitC: + case <-quitC: // if the peer is demoted, no more hashes taken - peer = nil + bestPeer = nil break LOOP default: } @@ -325,8 +484,8 @@ LOOP: // 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) + if sec != nil { + self.connectToBlockChain(sec) } else { if child != nil { self.connectToBlockChain(child) @@ -340,6 +499,7 @@ LOOP: // reached a known chain in the pool if entry.node == entry.section.bottom && n == 1 { // the first block hash received is an orphan in the pool, so rejoice and continue + poolLogger.DebugDetailf("[%s] connecting child section", sectionName(entry.section)) child = entry.section continue LOOP } @@ -353,7 +513,7 @@ LOOP: peer: peerId, } if size == 0 { - section = newSection() + sec = newSection() } nodes = append(nodes, node) size++ @@ -379,10 +539,10 @@ LOOP: } 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) + self.processSection(sec, nodes) + poolLogger.DebugDetailf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(sec), size, sectionName(child)) + self.link(parent, sec) + self.link(sec, child) } else { poolLogger.DebugDetailf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child)) self.link(parent, child) @@ -390,15 +550,31 @@ LOOP: self.chainLock.Unlock() - if parent != nil && peer != nil { + if parent != nil && bestPeer != 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)) + if sec != nil { + peer.addSection(sec.top.hash, sec) + // request next section here once, only repeat if bottom block arrives, + // otherwise no way to check if it arrived + peer.requestBlockHashes(sec.bottom.hash) + sec.controlC <- bestPeer + poolLogger.Debugf("[%s] activate new section", sectionName(sec)) + } + + if headSection { + var headSec *section + switch { + case sec != nil: + headSec = sec + case child != nil: + headSec = child + default: + headSec = parent + } + peer.headSectionC <- headSec } } @@ -426,14 +602,21 @@ func sectionName(section *section) (name string) { // only the first PoW-valid block for a hash is considered legit func (self *BlockPool) AddBlock(block *types.Block, peerId string) { hash := block.Hash() - if self.hasBlock(hash) { - poolLogger.DebugDetailf("block [%s] already known", name(hash)) - return - } + self.peersLock.Lock() + peer := self.peer + self.peersLock.Unlock() + entry := self.get(hash) + if bytes.Compare(hash, peer.currentBlockHash) == 0 { + poolLogger.Debugf("add head block [%s] for peer %s", name(hash), peerId) + peer.currentBlockC <- block + } else { + if entry == nil { + poolLogger.Warnf("unrequested block [%s] by peer %s", name(hash), peerId) + self.peerError(peerId, ErrUnrequestedBlock, "%x", hash) + } + } if entry == nil { - poolLogger.Warnf("unrequested block [%x] by peer %s", hash, peerId) - self.peerError(peerId, ErrUnrequestedBlock, "%x", hash) return } @@ -443,17 +626,21 @@ func (self *BlockPool) AddBlock(block *types.Block, peerId string) { // check if block already present if node.block != nil { - poolLogger.DebugDetailf("block [%x] already sent by %s", name(hash), node.blockBy) + poolLogger.DebugDetailf("block [%s] 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 - } + if self.hasBlock(hash) { + poolLogger.DebugDetailf("block [%s] already known", name(hash)) + } else { + // validate block for PoW + if !self.verifyPoW(block) { + poolLogger.Warnf("invalid pow on block [%s] by peer %s", name(hash), peerId) + self.peerError(peerId, ErrInvalidPoW, "%x", hash) + return + } + } poolLogger.Debugf("added block [%s] sent by peer %s", name(hash), peerId) node.block = block node.blockBy = peerId @@ -544,23 +731,23 @@ LOOP: // - 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(section *section, nodes []*poolNode) { +func (self *BlockPool) processSection(sec *section, nodes []*poolNode) { for i, node := range nodes { - entry := &poolEntry{node: node, section: section, index: i} + entry := &poolEntry{node: node, section: sec, 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)) + sec.bottom = nodes[len(nodes)-1] + sec.top = nodes[0] + sec.nodes = nodes + poolLogger.DebugDetailf("[%s] setup section process", sectionName(sec)) self.wg.Add(1) go func() { // absolute time after which sub-chain is killed if not complete (some blocks are missing) - suicideTimer := time.After(blockTimeout * time.Minute) + suicideTimer := time.After(blocksTimeout * time.Second) var peer, newPeer *peerInfo @@ -580,21 +767,23 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { var insertChain bool var quitC chan bool - var blockChainC = section.blockChainC + var blockChainC = sec.blockChainC + + var parentHash []byte LOOP: for { if insertChain { insertChain = false - rest, err := self.addSectionToBlockChain(section) + rest, err := self.addSectionToBlockChain(sec) if err != nil { - close(section.suicideC) + close(sec.suicideC) continue LOOP } if rest == 0 { blocksRequestsComplete = true - child := self.getChild(section) + child := self.getChild(sec) if child != nil { self.connectToBlockChain(child) } @@ -603,7 +792,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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) + poolLogger.Debugf("[%s] section complete %v blocks retrieved (%v attempts), hash requests complete on root (%v attempts)", sectionName(sec), depth, blocksRequests, blockHashesRequests) break LOOP } // otherwise suicide if no hashes coming @@ -611,11 +800,12 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // 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) + poolLogger.DebugDetailf("[%s] got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth) blocksRequestsComplete = true blocksRequestTimer = nil blocksRequestTime = false } else { + poolLogger.DebugDetailf("[%s] section checked: missing %v/%v/%v", sectionName(sec), missing, lastMissing, depth) // some missing blocks blocksRequests++ if len(hashes) > 0 { @@ -630,8 +820,8 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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) + poolLogger.DebugDetailf("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionName(sec), idle, blocksRequests, missing, lastMissing, depth) + close(sec.suicideC) } } else { idle = 0 @@ -653,22 +843,39 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // if ready && blocksRequestTime && !blocksRequestsComplete { - poolLogger.DebugDetailf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) + poolLogger.DebugDetailf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth) blocksRequestTimer = time.After(blocksRequestInterval * time.Millisecond) blocksRequestTime = false processC = offC } if blockHashesRequestTime { - if self.getParent(section) != nil { + var parentSection = self.getParent(sec) + if parentSection == nil { + if parent := self.get(parentHash); parent != nil { + parentSection = parent.section + self.chainLock.Lock() + self.link(parentSection, sec) + self.chainLock.Unlock() + } else { + if self.hasBlock(parentHash) { + insertChain = true + blockHashesRequestTime = false + blockHashesRequestTimer = nil + blockHashesRequestsComplete = true + continue LOOP + } + } + } + if parentSection != nil { // if not root of chain, switch off - poolLogger.DebugDetailf("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(section), blockHashesRequests) + poolLogger.DebugDetailf("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(sec), blockHashesRequests) blockHashesRequestTimer = nil blockHashesRequestsComplete = true } else { blockHashesRequests++ - poolLogger.Debugf("[%s] hash request on root (%v total attempts)\n", sectionName(section), blockHashesRequests) - peer.requestBlockHashes(section.bottom.hash) + poolLogger.Debugf("[%s] hash request on root (%v total attempts)\n", sectionName(sec), blockHashesRequests) + peer.requestBlockHashes(sec.bottom.hash) blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Millisecond) } blockHashesRequestTime = false @@ -682,27 +889,27 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { // peer quit or demoted, put section in idle mode quitC = nil go func() { - section.controlC <- nil + sec.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) + close(sec.suicideC) + poolLogger.Debugf("[%s] timeout. (%v total attempts): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth) - case <-section.suicideC: - poolLogger.Debugf("[%s] suicide", sectionName(section)) + case <-sec.suicideC: + poolLogger.Debugf("[%s] suicide", sectionName(sec)) // first delink from child and parent under chainlock self.chainLock.Lock() - self.link(nil, section) - self.link(section, nil) + self.link(nil, sec) + self.link(sec, nil) self.chainLock.Unlock() // delete node entries from pool index under pool lock self.lock.Lock() - for _, node := range section.nodes { + for _, node := range sec.nodes { delete(self.pool, string(node.hash)) } self.lock.Unlock() @@ -710,20 +917,20 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { break LOOP case <-blocksRequestTimer: - poolLogger.DebugDetailf("[%s] block request time", sectionName(section)) + poolLogger.DebugDetailf("[%s] block request time", sectionName(sec)) blocksRequestTime = true case <-blockHashesRequestTimer: - poolLogger.DebugDetailf("[%s] hash request time", sectionName(section)) + poolLogger.DebugDetailf("[%s] hash request time", sectionName(sec)) blockHashesRequestTime = true - case newPeer = <-section.controlC: + case newPeer = <-sec.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) + poolLogger.Debugf("[%s] idle mode (%v total attempts): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth) } blocksRequestTime = false blocksRequestTimer = nil @@ -739,11 +946,11 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { if peer == nil && newPeer != nil { self.procWg.Add(1) - poolLogger.Debugf("[%s] active mode", sectionName(section)) + poolLogger.Debugf("[%s] active mode", sectionName(sec)) if !blocksRequestsComplete { blocksRequestTime = true } - if !blockHashesRequestsComplete { + if !blockHashesRequestsComplete && parentHash != nil { blockHashesRequestTime = true } if !init { @@ -753,13 +960,13 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { missing = 0 self.wg.Add(1) self.procWg.Add(1) - depth = len(section.nodes) + depth = len(sec.nodes) lastMissing = depth // if not run at least once fully, launch iterator go func() { var node *poolNode IT: - for _, node = range section.nodes { + for _, node = range sec.nodes { select { case processC <- node: case <-self.quit: @@ -771,7 +978,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { self.procWg.Done() }() } else { - poolLogger.Debugf("[%s] restore earlier state", sectionName(section)) + poolLogger.Debugf("[%s] restore earlier state", sectionName(sec)) processC = offC } } @@ -781,7 +988,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { } peer = newPeer - case waiter := <-section.forkC: + case waiter := <-sec.forkC: // this case just blocks the process until section is split at the fork <-waiter init = false @@ -794,7 +1001,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { init = true done = true processC = make(chan *poolNode, missing) - poolLogger.DebugDetailf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, lastMissing, depth) + poolLogger.DebugDetailf("[%s] section initalised: missing %v/%v/%v", sectionName(sec), missing, lastMissing, depth) continue LOOP } if ready { @@ -811,17 +1018,24 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { missing++ hashes = append(hashes, node.hash) if len(hashes) == blockBatchSize { - poolLogger.Debugf("[%s] request %v missing blocks", sectionName(section), len(hashes)) + poolLogger.Debugf("[%s] request %v missing blocks", sectionName(sec), len(hashes)) self.requestBlocks(blocksRequests, hashes) hashes = nil } missingC <- node } else { - if blockChainC == nil && i == lastMissing { - insertChain = true + if i == lastMissing { + if blockChainC == nil { + insertChain = true + } else { + if parentHash == nil { + parentHash = block.ParentHash() + poolLogger.Debugf("[%s] found root block [%s]", sectionName(sec), name(parentHash)) + blockHashesRequestTime = true + } + } } } - poolLogger.Debugf("[%s] %v/%v/%v/%v", sectionName(section), i, missing, lastMissing, depth) if i == lastMissing && init { done = true } @@ -829,23 +1043,22 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) { 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)) + poolLogger.Debugf("[%s] reached blockchain", sectionName(sec)) 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 { + if len(sec.nodes) > 0 && sec.nodes[len(sec.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) + close(sec.offC) self.wg.Done() if peer != nil { @@ -917,22 +1130,28 @@ func (self *peerInfo) addSection(hash []byte, section *section) (found *section) defer self.lock.Unlock() key := string(hash) found = self.sections[key] - poolLogger.DebugDetailf("[%s] section process %s registered", sectionName(section), self.id) + poolLogger.DebugDetailf("[%s] section process stored for %s", sectionName(section), self.id) self.sections[key] = section return } func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { if newPeer != nil { - entry := self.get(newPeer.currentBlock) - if entry == nil { - poolLogger.Debugf("[%s] head block [%s] not found, requesting hashes", newPeer.id, name(newPeer.currentBlock)) - newPeer.requestBlockHashes(newPeer.currentBlock) - } else { - poolLogger.Debugf("[%s] head block [%s] found, activate chain at section [%s]", newPeer.id, name(newPeer.currentBlock), sectionName(entry.section)) - self.activateChain(entry.section, newPeer) - } + newPeer.quitC = make(chan bool) poolLogger.DebugDetailf("[%s] activate section processes", newPeer.id) + var addSections []*section + for hash, section := range newPeer.sections { + // split sections get reorganised here + if string(section.top.hash) != hash { + addSections = append(addSections, section) + if entry := self.get([]byte(hash)); entry != nil { + addSections = append(addSections, entry.section) + } + } + } + for _, section := range addSections { + newPeer.sections[string(section.top.hash)] = section + } for hash, section := range newPeer.sections { // this will block if section process is waiting for peer lock select { @@ -940,12 +1159,26 @@ func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { 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]) + poolLogger.DebugDetailf("[%s][%x] activates section [%s]", newPeer.id, hash[:4], sectionName(section)) } } - newPeer.quitC = make(chan bool) + newPeer.lock.Lock() + headSection := newPeer.headSection + currentBlockHash := newPeer.currentBlockHash + newPeer.lock.Unlock() + if headSection == nil { + poolLogger.DebugDetailf("[%s] head section for [%s] not created, requesting info", newPeer.id, name(currentBlockHash)) + self.requestHeadSection(newPeer) + } else { + if entry := self.get(currentBlockHash); entry != nil { + headSection = entry.section + } + poolLogger.DebugDetailf("[%s] activate chain at head section [%s] for current head [%s]", newPeer.id, sectionName(headSection), name(currentBlockHash)) + self.activateChain(headSection, newPeer) + } } if oldPeer != nil { + poolLogger.DebugDetailf("[%s] quit section processes", oldPeer.id) close(oldPeer.quitC) } } -- cgit v1.2.3 From 35f4bb96f313f4f04e70d1cf135be3a4759a8179 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 9 Jan 2015 16:44:09 +0100 Subject: Limit hashes. Closes #249 --- eth/protocol.go | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'eth') diff --git a/eth/protocol.go b/eth/protocol.go index c9a5dea8d..24a0f0a8e 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -67,6 +67,8 @@ type newBlockMsgData struct { TD *big.Int } +const maxHashes = 255 + type getBlockHashesMsgData struct { Hash []byte Amount uint64 @@ -139,6 +141,11 @@ func (self *ethProtocol) handle() error { if err := msg.Decode(&request); err != nil { return self.protoError(ErrDecode, "->msg %v: %v", msg, err) } + + //request.Amount = uint64(math.Min(float64(maxHashes), float64(request.Amount))) + if request.Amount > maxHashes { + request.Amount = maxHashes + } hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) return p2p.EncodeMsg(self.rw, BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) -- cgit v1.2.3 From 9b509f64781a5bac37ed5b125ba4214cd7250b59 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 19 Jan 2015 11:20:12 +0100 Subject: Print error instead of returning for seed node err Returning an error would indicate a complete failure initialising the Ethereum backend. Instead we should print the message and continue. --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index ad0486309..c3c7d1287 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -245,7 +245,7 @@ func (s *Ethereum) Start(seed bool) error { if seed { logger.Infof("Connect to seed node %v", seedNodeAddress) if err := s.SuggestPeer(seedNodeAddress); err != nil { - return err + logger.Infoln(err) } } -- cgit v1.2.3 From d790229a33b1f3e6256ca6916be17a0cc3663076 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Sun, 25 Jan 2015 14:50:43 -0600 Subject: Move HTTP transport to sub package of RPC --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index c3c7d1287..fbe080618 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -66,7 +66,7 @@ type Ethereum struct { txSub event.Subscription blockSub event.Subscription - RpcServer *rpc.JsonRpcServer + RpcServer rpc.RpcServer keyManager *crypto.KeyManager clientIdentity p2p.ClientIdentity -- cgit v1.2.3 From 5f50fe7a4a6218bedf78333d751b57166932464a Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Tue, 27 Jan 2015 12:28:58 -0600 Subject: Update CLI to use new Websocket RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use “wsport” flag to change default port --- eth/backend.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index fbe080618..ab30a1b38 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -67,6 +67,7 @@ type Ethereum struct { blockSub event.Subscription RpcServer rpc.RpcServer + WsServer rpc.RpcServer keyManager *crypto.KeyManager clientIdentity p2p.ClientIdentity @@ -276,6 +277,9 @@ func (s *Ethereum) Stop() { if s.RpcServer != nil { s.RpcServer.Stop() } + if s.WsServer != nil { + s.WsServer.Stop() + } s.txPool.Stop() s.eventMux.Stop() s.blockPool.Stop() -- cgit v1.2.3 From 7f638f0b2d8d989be25e660178d79df3278e4c84 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 28 Jan 2015 18:14:28 +0100 Subject: moving to a better xeth --- eth/backend.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'eth') diff --git a/eth/backend.go b/eth/backend.go index ab30a1b38..b7b5c5f85 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -219,6 +219,10 @@ func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } +func (s *Ethereum) Coinbase() []byte { + return nil // TODO +} + // Start the ethereum func (s *Ethereum) Start(seed bool) error { err := s.net.Start() -- cgit v1.2.3 From 1337a8dfb11160d67db410b71af92a02d6b232f5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 28 Jan 2015 21:34:08 +0100 Subject: upped pv --- eth/block_pool.go | 2 +- eth/protocol.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'eth') diff --git a/eth/block_pool.go b/eth/block_pool.go index b624d064a..97ae683c1 100644 --- a/eth/block_pool.go +++ b/eth/block_pool.go @@ -1098,7 +1098,7 @@ func (self *BlockPool) requestBlocks(attempts int, hashes [][]byte) { poolLogger.Debugf("request %v missing blocks from %v/%v peers: chosen %v", len(hashes), repetitions, peerCount, indexes) for _, peer := range self.peers { if i == indexes[0] { - poolLogger.Debugf("request %v missing blocks from peer %s", len(hashes), peer.id) + poolLogger.Debugf("request %v missing blocks [%x/%x] from peer %s", len(hashes), hashes[0][:4], hashes[len(hashes)-1][:4], peer.id) peer.requestBlocks(hashes) indexes = indexes[1:] if len(indexes) == 0 { diff --git a/eth/protocol.go b/eth/protocol.go index 24a0f0a8e..68c52b7ce 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -13,7 +13,7 @@ import ( ) const ( - ProtocolVersion = 51 + ProtocolVersion = 52 NetworkId = 0 ProtocolLength = uint64(8) ProtocolMaxMsgSize = 10 * 1024 * 1024 -- cgit v1.2.3