From 868017cf1ae44ab01f6cff654bb65e0573c743d4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 4 Apr 2017 19:28:28 +0400 Subject: [PATCH 01/47] import go-common as cmn --- connection.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/connection.go b/connection.go index 4428e0da7..02cfb45d4 100644 --- a/connection.go +++ b/connection.go @@ -10,10 +10,10 @@ import ( "sync/atomic" "time" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" flow "github.com/tendermint/go-flowrate/flowrate" - "github.com/tendermint/go-wire" //"github.com/tendermint/log15" + wire "github.com/tendermint/go-wire" ) const ( @@ -60,7 +60,7 @@ queue is full. Inbound message bytes are handled with an onReceive callback function. */ type MConnection struct { - BaseService + cmn.BaseService conn net.Conn bufReader *bufio.Reader @@ -78,9 +78,9 @@ type MConnection struct { errored uint32 quit chan struct{} - flushTimer *ThrottleTimer // flush writes as necessary but throttled. - pingTimer *RepeatTimer // send pings periodically - chStatsTimer *RepeatTimer // update channel stats periodically + flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. + pingTimer *cmn.RepeatTimer // send pings periodically + chStatsTimer *cmn.RepeatTimer // update channel stats periodically LocalAddress *NetAddress RemoteAddress *NetAddress @@ -123,7 +123,7 @@ func NewMConnection(config cfg.Config, conn net.Conn, chDescs []*ChannelDescript mconn.channels = channels mconn.channelsIdx = channelsIdx - mconn.BaseService = *NewBaseService(log, "MConnection", mconn) + mconn.BaseService = *cmn.NewBaseService(log, "MConnection", mconn) return mconn } @@ -131,9 +131,9 @@ func NewMConnection(config cfg.Config, conn net.Conn, chDescs []*ChannelDescript func (c *MConnection) OnStart() error { c.BaseService.OnStart() c.quit = make(chan struct{}) - c.flushTimer = NewThrottleTimer("flush", flushThrottleMS*time.Millisecond) - c.pingTimer = NewRepeatTimer("ping", pingTimeoutSeconds*time.Second) - c.chStatsTimer = NewRepeatTimer("chStats", updateStatsSeconds*time.Second) + c.flushTimer = cmn.NewThrottleTimer("flush", flushThrottleMS*time.Millisecond) + c.pingTimer = cmn.NewRepeatTimer("ping", pingTimeoutSeconds*time.Second) + c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateStatsSeconds*time.Second) go c.sendRoutine() go c.recvRoutine() return nil @@ -171,7 +171,7 @@ func (c *MConnection) flush() { func (c *MConnection) _recover() { if r := recover(); r != nil { stack := debug.Stack() - err := StackError{r, stack} + err := cmn.StackError{r, stack} c.stopForError(err) } } @@ -196,7 +196,7 @@ func (c *MConnection) Send(chID byte, msg interface{}) bool { // Send message to channel. channel, ok := c.channelsIdx[chID] if !ok { - log.Error(Fmt("Cannot send bytes, unknown channel %X", chID)) + log.Error(cmn.Fmt("Cannot send bytes, unknown channel %X", chID)) return false } @@ -225,7 +225,7 @@ func (c *MConnection) TrySend(chID byte, msg interface{}) bool { // Send message to channel. channel, ok := c.channelsIdx[chID] if !ok { - log.Error(Fmt("Cannot send bytes, unknown channel %X", chID)) + log.Error(cmn.Fmt("Cannot send bytes, unknown channel %X", chID)) return false } @@ -248,7 +248,7 @@ func (c *MConnection) CanSend(chID byte) bool { channel, ok := c.channelsIdx[chID] if !ok { - log.Error(Fmt("Unknown channel %X", chID)) + log.Error(cmn.Fmt("Unknown channel %X", chID)) return false } return channel.canSend() @@ -424,7 +424,7 @@ FOR_LOOP: } channel, ok := c.channelsIdx[pkt.ChannelID] if !ok || channel == nil { - PanicQ(Fmt("Unknown channel %X", pkt.ChannelID)) + cmn.PanicQ(cmn.Fmt("Unknown channel %X", pkt.ChannelID)) } msgBytes, err := channel.recvMsgPacket(pkt) if err != nil { @@ -439,7 +439,7 @@ FOR_LOOP: c.onReceive(pkt.ChannelID, msgBytes) } default: - PanicSanity(Fmt("Unknown message type %X", pktType)) + cmn.PanicSanity(cmn.Fmt("Unknown message type %X", pktType)) } // TODO: shouldn't this go in the sendRoutine? @@ -524,7 +524,7 @@ type Channel struct { func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel { desc.FillDefaults() if desc.Priority <= 0 { - PanicSanity("Channel default priority must be a postive integer") + cmn.PanicSanity("Channel default priority must be a postive integer") } return &Channel{ conn: conn, @@ -593,14 +593,14 @@ func (ch *Channel) isSendPending() bool { func (ch *Channel) nextMsgPacket() msgPacket { packet := msgPacket{} packet.ChannelID = byte(ch.id) - packet.Bytes = ch.sending[:MinInt(maxMsgPacketPayloadSize, len(ch.sending))] + packet.Bytes = ch.sending[:cmn.MinInt(maxMsgPacketPayloadSize, len(ch.sending))] if len(ch.sending) <= maxMsgPacketPayloadSize { packet.EOF = byte(0x01) ch.sending = nil atomic.AddInt32(&ch.sendQueueSize, -1) // decrement sendQueueSize } else { packet.EOF = byte(0x00) - ch.sending = ch.sending[MinInt(maxMsgPacketPayloadSize, len(ch.sending)):] + ch.sending = ch.sending[cmn.MinInt(maxMsgPacketPayloadSize, len(ch.sending)):] } return packet } From 549d3bd09a5476aa0ea1660c4689178ec05fec26 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 4 Apr 2017 23:13:09 +0400 Subject: [PATCH 02/47] tests for MConnection --- connection_test.go | 105 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 connection_test.go diff --git a/connection_test.go b/connection_test.go new file mode 100644 index 000000000..9d489f655 --- /dev/null +++ b/connection_test.go @@ -0,0 +1,105 @@ +package p2p_test + +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + cfg "github.com/tendermint/go-config" + p2p "github.com/tendermint/go-p2p" +) + +func createMConnection(conn net.Conn) *p2p.MConnection { + onReceive := func(chID byte, msgBytes []byte) { + } + onError := func(r interface{}) { + } + return createMConnectionWithCallbacks(conn, onReceive, onError) +} + +func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msgBytes []byte), onError func(r interface{})) *p2p.MConnection { + config := cfg.NewMapConfig(map[string]interface{}{"send_rate": 512000, "recv_rate": 512000}) + chDescs := []*p2p.ChannelDescriptor{&p2p.ChannelDescriptor{ID: 0x01, Priority: 1}} + + return p2p.NewMConnection(config, conn, chDescs, onReceive, onError) +} + +func TestMConnectionSend(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + server, client := net.Pipe() + defer server.Close() + defer client.Close() + + mconn := createMConnection(client) + _, err := mconn.Start() + require.Nil(err) + defer mconn.Stop() + + msg := "Ant-Man" + assert.True(mconn.Send(0x01, msg)) + assert.False(mconn.CanSend(0x01)) + server.Read(make([]byte, len(msg))) + assert.True(mconn.CanSend(0x01)) + + msg = "Spider-Man" + assert.True(mconn.TrySend(0x01, msg)) + server.Read(make([]byte, len(msg))) +} + +func TestMConnectionReceive(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + server, client := net.Pipe() + defer server.Close() + defer client.Close() + + receivedCh := make(chan []byte) + errorsCh := make(chan interface{}) + onReceive := func(chID byte, msgBytes []byte) { + receivedCh <- msgBytes + } + onError := func(r interface{}) { + errorsCh <- r + } + mconn1 := createMConnectionWithCallbacks(client, onReceive, onError) + _, err := mconn1.Start() + require.Nil(err) + defer mconn1.Stop() + + mconn2 := createMConnection(server) + _, err = mconn2.Start() + require.Nil(err) + defer mconn2.Stop() + + msg := "Cyclops" + assert.True(mconn2.Send(0x01, msg)) + + select { + case receivedBytes := <-receivedCh: + assert.Equal([]byte(msg), receivedBytes[2:]) // first 3 bytes are internal + case err := <-errorsCh: + t.Fatalf("Expected %s, got %+v", msg, err) + case <-time.After(500 * time.Millisecond): + t.Fatalf("Did not receive %s message in 500ms", msg) + } +} + +func TestMConnectionStatus(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + server, client := net.Pipe() + defer server.Close() + defer client.Close() + + mconn := createMConnection(client) + _, err := mconn.Start() + require.Nil(err) + defer mconn.Stop() + + status := mconn.Status() + assert.NotNil(status) + assert.Zero(status.Channels[0].SendQueueSize) +} From 5be72672fea7184eb82a1fb29d35e37d7cd6b6ab Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 5 Apr 2017 00:14:24 +0400 Subject: [PATCH 03/47] use golang time datatype instead of time units in name --- connection.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/connection.go b/connection.go index 02cfb45d4..ddb096a11 100644 --- a/connection.go +++ b/connection.go @@ -21,14 +21,14 @@ const ( minReadBufferSize = 1024 minWriteBufferSize = 65536 idleTimeoutMinutes = 5 - updateStatsSeconds = 2 - pingTimeoutSeconds = 40 - flushThrottleMS = 100 + updateState = 2 * time.Second + pingTimeout = 40 * time.Second + flushThrottle = 100 * time.Millisecond defaultSendQueueCapacity = 1 defaultRecvBufferCapacity = 4096 defaultRecvMessageCapacity = 22020096 // 21MB - defaultSendTimeoutSeconds = 10 + defaultSendTimeout = 10 * time.Second ) type receiveCbFunc func(chID byte, msgBytes []byte) @@ -131,9 +131,9 @@ func NewMConnection(config cfg.Config, conn net.Conn, chDescs []*ChannelDescript func (c *MConnection) OnStart() error { c.BaseService.OnStart() c.quit = make(chan struct{}) - c.flushTimer = cmn.NewThrottleTimer("flush", flushThrottleMS*time.Millisecond) - c.pingTimer = cmn.NewRepeatTimer("ping", pingTimeoutSeconds*time.Second) - c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateStatsSeconds*time.Second) + c.flushTimer = cmn.NewThrottleTimer("flush", flushThrottle) + c.pingTimer = cmn.NewRepeatTimer("ping", pingTimeout) + c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateState) go c.sendRoutine() go c.recvRoutine() return nil @@ -538,9 +538,9 @@ func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel { // Queues message to send to this channel. // Goroutine-safe -// Times out (and returns false) after defaultSendTimeoutSeconds +// Times out (and returns false) after defaultSendTimeout func (ch *Channel) sendBytes(bytes []byte) bool { - timeout := time.NewTimer(defaultSendTimeoutSeconds * time.Second) + timeout := time.NewTimer(defaultSendTimeout) select { case <-timeout.C: // timeout From 2b02843453aa9499cb62e4afa1390fd14897e08f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 5 Apr 2017 00:17:58 +0400 Subject: [PATCH 04/47] remove unused const --- connection.go | 1 - 1 file changed, 1 deletion(-) diff --git a/connection.go b/connection.go index ddb096a11..8afb3f9e9 100644 --- a/connection.go +++ b/connection.go @@ -20,7 +20,6 @@ const ( numBatchMsgPackets = 10 minReadBufferSize = 1024 minWriteBufferSize = 65536 - idleTimeoutMinutes = 5 updateState = 2 * time.Second pingTimeout = 40 * time.Second flushThrottle = 100 * time.Millisecond From b8a939a894b25aa6ff40391b1fd0af5dacad2655 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 5 Apr 2017 01:49:01 +0400 Subject: [PATCH 05/47] test non persistent mconnection --- connection_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/connection_test.go b/connection_test.go index 9d489f655..79a7227a2 100644 --- a/connection_test.go +++ b/connection_test.go @@ -103,3 +103,36 @@ func TestMConnectionStatus(t *testing.T) { assert.NotNil(status) assert.Zero(status.Channels[0].SendQueueSize) } + +func TestMConnectionNonPersistent(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + server, client := net.Pipe() + defer server.Close() + defer client.Close() + + receivedCh := make(chan []byte) + errorsCh := make(chan interface{}) + onReceive := func(chID byte, msgBytes []byte) { + receivedCh <- msgBytes + } + onError := func(r interface{}) { + errorsCh <- r + } + mconn := createMConnectionWithCallbacks(client, onReceive, onError) + _, err := mconn.Start() + require.Nil(err) + defer mconn.Stop() + + client.Close() + + select { + case receivedBytes := <-receivedCh: + t.Fatalf("Expected error, got %v", receivedBytes) + case err := <-errorsCh: + assert.NotNil(err) + assert.False(mconn.IsRunning()) + case <-time.After(500 * time.Millisecond): + t.Fatal("Did not receive error in 500ms") + } +} From 5b0489cdb4121b3d4486a530e428f1665d100b3e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 6 Apr 2017 16:43:45 +0400 Subject: [PATCH 06/47] use plain struct instead of go-config --- connection.go | 43 ++++++++++++++++++++++++++++--------------- connection_test.go | 7 ++----- peer.go | 6 +++++- switch.go | 2 +- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/connection.go b/connection.go index 8afb3f9e9..e61608896 100644 --- a/connection.go +++ b/connection.go @@ -11,7 +11,6 @@ import ( "time" cmn "github.com/tendermint/go-common" - cfg "github.com/tendermint/go-config" flow "github.com/tendermint/go-flowrate/flowrate" wire "github.com/tendermint/go-wire" ) @@ -25,8 +24,10 @@ const ( flushThrottle = 100 * time.Millisecond defaultSendQueueCapacity = 1 + defaultSendRate = int64(512000) // 500KB/s defaultRecvBufferCapacity = 4096 - defaultRecvMessageCapacity = 22020096 // 21MB + defaultRecvMessageCapacity = 22020096 // 21MB + defaultRecvRate = int64(512000) // 500KB/s defaultSendTimeout = 10 * time.Second ) @@ -66,8 +67,6 @@ type MConnection struct { bufWriter *bufio.Writer sendMonitor *flow.Monitor recvMonitor *flow.Monitor - sendRate int64 - recvRate int64 send chan struct{} pong chan struct{} channels []*Channel @@ -75,6 +74,7 @@ type MConnection struct { onReceive receiveCbFunc onError errorCbFunc errored uint32 + config *MConnectionConfig quit chan struct{} flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. @@ -85,25 +85,38 @@ type MConnection struct { RemoteAddress *NetAddress } -func NewMConnection(config cfg.Config, conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection { +// MConnectionConfig is a MConnection configuration +type MConnectionConfig struct { + SendRate int64 + RecvRate int64 +} + +// NewMConnection wraps net.Conn and creates multiplex connection +func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection { + return NewMConnectionWithConfig( + conn, + chDescs, + onReceive, + onError, + &MConnectionConfig{ + SendRate: defaultSendRate, + RecvRate: defaultRecvRate, + }) +} + +// NewMConnectionWithConfig wraps net.Conn and creates multiplex connection with a config +func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc, config *MConnectionConfig) *MConnection { mconn := &MConnection{ conn: conn, bufReader: bufio.NewReaderSize(conn, minReadBufferSize), bufWriter: bufio.NewWriterSize(conn, minWriteBufferSize), sendMonitor: flow.New(0, 0), recvMonitor: flow.New(0, 0), - sendRate: int64(config.GetInt(configKeySendRate)), - recvRate: int64(config.GetInt(configKeyRecvRate)), send: make(chan struct{}, 1), pong: make(chan struct{}), onReceive: onReceive, onError: onError, - - // Initialized in Start() - quit: nil, - flushTimer: nil, - pingTimer: nil, - chStatsTimer: nil, + config: config, LocalAddress: NewNetAddress(conn.LocalAddr()), RemoteAddress: NewNetAddress(conn.RemoteAddr()), @@ -313,7 +326,7 @@ func (c *MConnection) sendSomeMsgPackets() bool { // Block until .sendMonitor says we can write. // Once we're ready we send more than we asked for, // but amortized it should even out. - c.sendMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.sendRate), true) + c.sendMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.config.SendRate), true) // Now send some msgPackets. for i := 0; i < numBatchMsgPackets; i++ { @@ -371,7 +384,7 @@ func (c *MConnection) recvRoutine() { FOR_LOOP: for { // Block until .recvMonitor says we can read. - c.recvMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.recvRate), true) + c.recvMonitor.Limit(maxMsgPacketTotalSize, atomic.LoadInt64(&c.config.RecvRate), true) /* // Peek into bufReader for debugging diff --git a/connection_test.go b/connection_test.go index 79a7227a2..cd6bb4a8f 100644 --- a/connection_test.go +++ b/connection_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cfg "github.com/tendermint/go-config" p2p "github.com/tendermint/go-p2p" ) @@ -20,10 +19,8 @@ func createMConnection(conn net.Conn) *p2p.MConnection { } func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msgBytes []byte), onError func(r interface{})) *p2p.MConnection { - config := cfg.NewMapConfig(map[string]interface{}{"send_rate": 512000, "recv_rate": 512000}) chDescs := []*p2p.ChannelDescriptor{&p2p.ChannelDescriptor{ID: 0x01, Priority: 1}} - - return p2p.NewMConnection(config, conn, chDescs, onReceive, onError) + return p2p.NewMConnection(conn, chDescs, onReceive, onError) } func TestMConnectionSend(t *testing.T) { @@ -104,7 +101,7 @@ func TestMConnectionStatus(t *testing.T) { assert.Zero(status.Channels[0].SendQueueSize) } -func TestMConnectionNonPersistent(t *testing.T) { +func TestMConnectionStopsAndReturnsError(t *testing.T) { assert, require := assert.New(t), require.New(t) server, client := net.Pipe() diff --git a/peer.go b/peer.go index 7a2d647f8..6b1b47a1f 100644 --- a/peer.go +++ b/peer.go @@ -61,7 +61,11 @@ func newPeer(config cfg.Config, conn net.Conn, peerNodeInfo *NodeInfo, outbound p.Stop() onPeerError(p, r) } - mconn := NewMConnection(config, conn, chDescs, onReceive, onError) + mconnConfig := &MConnectionConfig{ + SendRate: int64(config.GetInt(configKeySendRate)), + RecvRate: int64(config.GetInt(configKeyRecvRate)), + } + mconn := NewMConnectionWithConfig(conn, chDescs, onReceive, onError, mconnConfig) p = &Peer{ outbound: outbound, mconn: mconn, diff --git a/switch.go b/switch.go index eed8ceea5..78a3020ed 100644 --- a/switch.go +++ b/switch.go @@ -193,7 +193,7 @@ func (sw *Switch) OnStop() { } // NOTE: This performs a blocking handshake before the peer is added. -// CONTRACT: Iff error is returned, peer is nil, and conn is immediately closed. +// CONTRACT: If error is returned, peer is nil, and conn is immediately closed. func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) (*Peer, error) { // Filter by addr (ie. ip:port) From f88d56b2f832020bc4ce7bc7e732bb5a985cba4b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 10 Apr 2017 15:17:49 +0400 Subject: [PATCH 07/47] add glide --- .gitignore | 5 +--- glide.lock | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ glide.yaml | 23 ++++++++++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 glide.lock create mode 100644 glide.yaml diff --git a/.gitignore b/.gitignore index c765108df..62f28681c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ -*.swp -*.swo -*.bak -.DS_Store vendor +.glide diff --git a/glide.lock b/glide.lock new file mode 100644 index 000000000..797c86031 --- /dev/null +++ b/glide.lock @@ -0,0 +1,71 @@ +hash: 92a49cbcf88a339e4d29559fe291c30e61eacda1020fd04dfcd97de834e18b3e +updated: 2017-04-10T11:17:14.66226896Z +imports: +- name: github.com/btcsuite/btcd + version: 4b348c1d33373d672edd83fc576892d0e46686d2 + subpackages: + - btcec +- name: github.com/BurntSushi/toml + version: b26d9c308763d68093482582cea63d69be07a0f0 +- name: github.com/go-stack/stack + version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 +- name: github.com/mattn/go-colorable + version: ded68f7a9561c023e790de24279db7ebf473ea80 +- name: github.com/mattn/go-isatty + version: fc9e8d8ef48496124e79ae0df75490096eccf6fe +- name: github.com/pkg/errors + version: ff09b135c25aae272398c51a07235b90a75aa4f0 +- name: github.com/tendermint/ed25519 + version: 1f52c6f8b8a5c7908aff4497c186af344b428925 + subpackages: + - edwards25519 + - extra25519 +- name: github.com/tendermint/go-common + version: dcb015dff6c7af21e65c8e2f3b450df19d38c777 +- name: github.com/tendermint/go-config + version: 620dcbbd7d587cf3599dedbf329b64311b0c307a +- name: github.com/tendermint/go-crypto + version: 3f47cfac5fcd9e0f1727c7db980b3559913b3e3a +- name: github.com/tendermint/go-data + version: c955b191240568440ea902e14dad2ce19727543a +- name: github.com/tendermint/go-flowrate + version: a20c98e61957faa93b4014fbd902f20ab9317a6a + subpackages: + - flowrate +- name: github.com/tendermint/go-logger + version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 +- name: github.com/tendermint/go-wire + version: f530b7af7a8b06e612c2063bff6ace49060a085e +- name: github.com/tendermint/log15 + version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6 + subpackages: + - term +- name: golang.org/x/crypto + version: 9ef620b9ca2f82b55030ffd4f41327fa9e77a92c + subpackages: + - curve25519 + - nacl/box + - nacl/secretbox + - openpgp/armor + - openpgp/errors + - poly1305 + - ripemd160 + - salsa20/salsa +- name: golang.org/x/sys + version: f3918c30c5c2cb527c0b071a27c35120a6c0719a + subpackages: + - unix +testImports: +- name: github.com/davecgh/go-spew + version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: 4d4bfba8f1d1027c4fdbe371823030df51419987 + subpackages: + - assert + - require diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 000000000..cf71cc670 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,23 @@ +package: github.com/tendermint/go-p2p +import: +- package: github.com/tendermint/go-common +- package: github.com/tendermint/go-config +- package: github.com/tendermint/go-crypto +- package: github.com/tendermint/go-data + version: c955b191240568440ea902e14dad2ce19727543a +- package: github.com/tendermint/go-flowrate + subpackages: + - flowrate +- package: github.com/tendermint/go-logger +- package: github.com/tendermint/go-wire +- package: github.com/tendermint/log15 +- package: golang.org/x/crypto + subpackages: + - nacl/box + - nacl/secretbox + - ripemd160 +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert + - require From 8bb3a2e1d7b1152f5d6043a5ccca7c3fbdd9a993 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 7 Apr 2017 14:57:03 +0400 Subject: [PATCH 08/47] persistent peers (Refs #13) --- peer.go | 204 +++++++++++++++++++++++++++++++++++++++---------- pex_reactor.go | 4 +- switch.go | 173 +++++++++++++++++++++-------------------- switch_test.go | 131 ++++++++++++++++++++++++++++--- 4 files changed, 377 insertions(+), 135 deletions(-) diff --git a/peer.go b/peer.go index 6b1b47a1f..d94ce5838 100644 --- a/peer.go +++ b/peer.go @@ -4,105 +4,187 @@ import ( "fmt" "io" "net" + "time" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" - "github.com/tendermint/go-wire" + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" ) +// Peer could be marked as persistent, in which case you can use +// Redial function to reconnect. Note that inbound peers can't be +// made persistent. They should be made persistent on the other end. +// +// Before using a peer, you will need to perform a handshake on connection. type Peer struct { - BaseService + cmn.BaseService outbound bool - mconn *MConnection + + conn net.Conn // source connection + mconn *MConnection // multiplex connection + + authEnc bool // authenticated encryption + persistent bool + config cfg.Config *NodeInfo Key string - Data *CMap // User data. + Data *cmn.CMap // User data. +} + +func newPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) (*Peer, error) { + conn, err := dial(addr, config) + if err != nil { + return nil, err + } + + // outbound = true + return newPeerFromExistingConn(conn, true, reactorsByCh, chDescs, onPeerError, config, privKey) +} + +func newPeerFromExistingConn(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) (*Peer, error) { + // Encrypt connection + if config.GetBool(configKeyAuthEnc) { + var err error + conn, err = MakeSecretConnection(conn, privKey) + if err != nil { + return nil, err + } + } + + p := &Peer{ + outbound: outbound, + authEnc: config.GetBool(configKeyAuthEnc), + conn: conn, + config: config, + Data: cmn.NewCMap(), + } + + p.mconn = createMConnection(conn, p, reactorsByCh, chDescs, onPeerError, config) + + p.BaseService = *cmn.NewBaseService(log, "Peer", p) + + return p, nil +} + +// CloseConn should be used when the peer was created, but never started. +func (p *Peer) CloseConn() { + p.conn.Close() +} + +// MakePersistent marks the peer as persistent. +func (p *Peer) MakePersistent() { + if !p.outbound { + panic("inbound peers can't be made persistent") + } + + p.persistent = true } +// IsPersistent returns true if the peer is persitent, false otherwise. +func (p *Peer) IsPersistent() bool { + return p.persistent +} + +// HandshakeTimeout performs a handshake between a given node and the peer. // NOTE: blocking -// Before creating a peer with newPeer(), perform a handshake on connection. -func peerHandshake(conn net.Conn, ourNodeInfo *NodeInfo) (*NodeInfo, error) { +func (p *Peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) error { + // Set deadline for handshake so we don't block forever on conn.ReadFull + p.conn.SetDeadline(time.Now().Add(timeout)) + var peerNodeInfo = new(NodeInfo) var err1 error var err2 error - Parallel( + cmn.Parallel( func() { var n int - wire.WriteBinary(ourNodeInfo, conn, &n, &err1) + wire.WriteBinary(ourNodeInfo, p.conn, &n, &err1) }, func() { var n int - wire.ReadBinary(peerNodeInfo, conn, maxNodeInfoSize, &n, &err2) + wire.ReadBinary(peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2) log.Notice("Peer handshake", "peerNodeInfo", peerNodeInfo) }) if err1 != nil { - return nil, err1 + return err1 } if err2 != nil { - return nil, err2 + return err2 } - peerNodeInfo.RemoteAddr = conn.RemoteAddr().String() - return peerNodeInfo, nil -} -// NOTE: call peerHandshake on conn before calling newPeer(). -func newPeer(config cfg.Config, conn net.Conn, peerNodeInfo *NodeInfo, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{})) *Peer { - var p *Peer - onReceive := func(chID byte, msgBytes []byte) { - reactor := reactorsByCh[chID] - if reactor == nil { - PanicSanity(Fmt("Unknown channel %X", chID)) + if p.authEnc { + // Check that the professed PubKey matches the sconn's. + if !peerNodeInfo.PubKey.Equals(p.PubKey()) { + return fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v", + peerNodeInfo.PubKey, p.PubKey()) } - reactor.Receive(chID, p, msgBytes) } - onError := func(r interface{}) { - p.Stop() - onPeerError(p, r) - } - mconnConfig := &MConnectionConfig{ - SendRate: int64(config.GetInt(configKeySendRate)), - RecvRate: int64(config.GetInt(configKeyRecvRate)), + + // Remove deadline + p.conn.SetDeadline(time.Time{}) + + peerNodeInfo.RemoteAddr = p.RemoteAddr().String() + + p.NodeInfo = peerNodeInfo + p.Key = peerNodeInfo.PubKey.KeyString() + + return nil +} + +// RemoteAddr returns the remote network address. +func (p *Peer) RemoteAddr() net.Addr { + return p.conn.RemoteAddr() +} + +// PubKey returns the remote public key. +func (p *Peer) PubKey() crypto.PubKeyEd25519 { + if p.authEnc { + return p.conn.(*SecretConnection).RemotePubKey() } - mconn := NewMConnectionWithConfig(conn, chDescs, onReceive, onError, mconnConfig) - p = &Peer{ - outbound: outbound, - mconn: mconn, - NodeInfo: peerNodeInfo, - Key: peerNodeInfo.PubKey.KeyString(), - Data: NewCMap(), + if p.NodeInfo == nil { + panic("Attempt to get peer's PubKey before calling Handshake") } - p.BaseService = *NewBaseService(log, "Peer", p) - return p + return p.PubKey() } +// OnStart implements BaseService. func (p *Peer) OnStart() error { p.BaseService.OnStart() _, err := p.mconn.Start() return err } +// OnStop implements BaseService. func (p *Peer) OnStop() { p.BaseService.OnStop() p.mconn.Stop() } +// Connection returns underlying MConnection. func (p *Peer) Connection() *MConnection { return p.mconn } +// IsOutbound returns true if the connection is outbound, false otherwise. func (p *Peer) IsOutbound() bool { return p.outbound } +// Send msg to the channel identified by chID byte. Returns false if the send +// queue is full after timeout, specified by MConnection. func (p *Peer) Send(chID byte, msg interface{}) bool { if !p.IsRunning() { + // see Switch#Broadcast, where we fetch the list of peers and loop over + // them - while we're looping, one peer may be removed and stopped. return false } return p.mconn.Send(chID, msg) } +// TrySend msg to the channel identified by chID byte. Immediately returns +// false if the send queue is full. func (p *Peer) TrySend(chID byte, msg interface{}) bool { if !p.IsRunning() { return false @@ -110,6 +192,7 @@ func (p *Peer) TrySend(chID byte, msg interface{}) bool { return p.mconn.TrySend(chID, msg) } +// CanSend returns true if the send queue is not full, false otherwise. func (p *Peer) CanSend(chID byte) bool { if !p.IsRunning() { return false @@ -117,6 +200,7 @@ func (p *Peer) CanSend(chID byte) bool { return p.mconn.CanSend(chID) } +// WriteTo writes the peer's public key to w. func (p *Peer) WriteTo(w io.Writer) (n int64, err error) { var n_ int wire.WriteString(p.Key, w, &n_, &err) @@ -124,18 +208,56 @@ func (p *Peer) WriteTo(w io.Writer) (n int64, err error) { return } +// String representation. func (p *Peer) String() string { if p.outbound { return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.Key[:12]) - } else { - return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.Key[:12]) } + + return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.Key[:12]) } +// Equals reports whenever 2 peers are actually represent the same node. func (p *Peer) Equals(other *Peer) bool { return p.Key == other.Key } +// Get the data for a given key. func (p *Peer) Get(key string) interface{} { return p.Data.Get(key) } + +func dial(addr *NetAddress, config cfg.Config) (net.Conn, error) { + log.Info("Dialing address", "address", addr) + conn, err := addr.DialTimeout(time.Duration( + config.GetInt(configKeyDialTimeoutSeconds)) * time.Second) + if err != nil { + log.Info("Failed dialing address", "address", addr, "error", err) + return nil, err + } + if config.GetBool(configFuzzEnable) { + conn = FuzzConn(config, conn) + } + return conn, nil +} + +func createMConnection(conn net.Conn, p *Peer, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config) *MConnection { + onReceive := func(chID byte, msgBytes []byte) { + reactor := reactorsByCh[chID] + if reactor == nil { + cmn.PanicSanity(cmn.Fmt("Unknown channel %X", chID)) + } + reactor.Receive(chID, p, msgBytes) + } + + onError := func(r interface{}) { + onPeerError(p, r) + } + + mconnConfig := &MConnectionConfig{ + SendRate: int64(config.GetInt(configKeySendRate)), + RecvRate: int64(config.GetInt(configKeyRecvRate)), + } + + return NewMConnectionWithConfig(conn, chDescs, onReceive, onError, mconnConfig) +} diff --git a/pex_reactor.go b/pex_reactor.go index 45c4c96d8..4ac9306cf 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -9,7 +9,7 @@ import ( "time" . "github.com/tendermint/go-common" - "github.com/tendermint/go-wire" + wire "github.com/tendermint/go-wire" ) var pexErrInvalidMessage = errors.New("Invalid PEX message") @@ -201,7 +201,7 @@ func (pexR *PEXReactor) ensurePeers() { // Dial picked addresses for _, item := range toDial.Values() { go func(picked *NetAddress) { - _, err := pexR.Switch.DialPeerWithAddress(picked) + _, err := pexR.Switch.DialPeerWithAddress(picked, false) if err != nil { pexR.book.MarkAttempt(picked) } diff --git a/switch.go b/switch.go index 78a3020ed..b0551a4f8 100644 --- a/switch.go +++ b/switch.go @@ -9,10 +9,15 @@ import ( . "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" - "github.com/tendermint/go-crypto" + crypto "github.com/tendermint/go-crypto" "github.com/tendermint/log15" ) +const ( + reconnectAttempts = 30 + reconnectInterval = 3 * time.Second +) + type Reactor interface { Service // Start, Stop @@ -194,78 +199,43 @@ func (sw *Switch) OnStop() { // NOTE: This performs a blocking handshake before the peer is added. // CONTRACT: If error is returned, peer is nil, and conn is immediately closed. -func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) (*Peer, error) { - - // Filter by addr (ie. ip:port) - if err := sw.FilterConnByAddr(conn.RemoteAddr()); err != nil { - conn.Close() - return nil, err +func (sw *Switch) AddPeer(peer *Peer) error { + if err := sw.FilterConnByAddr(peer.RemoteAddr()); err != nil { + return err } - // Set deadline for handshake so we don't block forever on conn.ReadFull - conn.SetDeadline(time.Now().Add( - time.Duration(sw.config.GetInt(configKeyHandshakeTimeoutSeconds)) * time.Second)) - - // First, encrypt the connection. - var sconn net.Conn = conn - if sw.config.GetBool(configKeyAuthEnc) { - var err error - sconn, err = MakeSecretConnection(conn, sw.nodePrivKey) - if err != nil { - conn.Close() - return nil, err - } + if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { + return err } - // Filter by p2p-key - if err := sw.FilterConnByPubKey(sconn.(*SecretConnection).RemotePubKey()); err != nil { - sconn.Close() - return nil, err + if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.config.GetInt(configKeyHandshakeTimeoutSeconds))*time.Second); err != nil { + return err } - // Then, perform node handshake - peerNodeInfo, err := peerHandshake(sconn, sw.nodeInfo) - if err != nil { - sconn.Close() - return nil, err - } - if sw.config.GetBool(configKeyAuthEnc) { - // Check that the professed PubKey matches the sconn's. - if !peerNodeInfo.PubKey.Equals(sconn.(*SecretConnection).RemotePubKey()) { - sconn.Close() - return nil, fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v", - peerNodeInfo.PubKey, sconn.(*SecretConnection).RemotePubKey()) - } - } // Avoid self - if peerNodeInfo.PubKey.Equals(sw.nodeInfo.PubKey) { - sconn.Close() - return nil, fmt.Errorf("Ignoring connection from self") + if sw.nodeInfo.PubKey.Equals(peer.PubKey()) { + return errors.New("Ignoring connection from self") } + // Check version, chain id - if err := sw.nodeInfo.CompatibleWith(peerNodeInfo); err != nil { - sconn.Close() - return nil, err + if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo); err != nil { + return err } - peer := newPeer(sw.config, sconn, peerNodeInfo, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError) - // Add the peer to .peers // ignore if duplicate or if we already have too many for that IP range if err := sw.peers.Add(peer); err != nil { log.Notice("Ignoring peer", "error", err, "peer", peer) - peer.Stop() - return nil, err + return err } - // remove deadline and start peer - conn.SetDeadline(time.Time{}) + // Start peer if sw.IsRunning() { sw.startInitPeer(peer) } log.Notice("Added peer", "peer", peer) - return peer, nil + return nil } func (sw *Switch) FilterConnByAddr(addr net.Addr) error { @@ -292,8 +262,10 @@ func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKeyEd25519) error) { } func (sw *Switch) startInitPeer(peer *Peer) { - peer.Start() // spawn send/recv routines - sw.addPeerToReactors(peer) // run AddPeer on each reactor + peer.Start() // spawn send/recv routines + for _, reactor := range sw.reactors { + reactor.AddPeer(peer) + } } // Dial a list of seeds asynchronously in random order @@ -331,7 +303,7 @@ func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { } func (sw *Switch) dialSeed(addr *NetAddress) { - peer, err := sw.DialPeerWithAddress(addr) + peer, err := sw.DialPeerWithAddress(addr, true) if err != nil { log.Error("Error dialing seed", "error", err) return @@ -340,22 +312,23 @@ func (sw *Switch) dialSeed(addr *NetAddress) { } } -func (sw *Switch) DialPeerWithAddress(addr *NetAddress) (*Peer, error) { - log.Info("Dialing address", "address", addr) +func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, error) { sw.dialing.Set(addr.IP.String(), addr) - conn, err := addr.DialTimeout(time.Duration( - sw.config.GetInt(configKeyDialTimeoutSeconds)) * time.Second) - sw.dialing.Delete(addr.IP.String()) + defer sw.dialing.Delete(addr.IP.String()) + + peer, err := newPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + if persistent { + peer.MakePersistent() + } if err != nil { - log.Info("Failed dialing address", "address", addr, "error", err) + log.Info("Failed dialing peer", "address", addr, "error", err) + peer.CloseConn() return nil, err } - if sw.config.GetBool(configFuzzEnable) { - conn = FuzzConn(sw.config, conn) - } - peer, err := sw.AddPeerWithConnection(conn, true) + err = sw.AddPeer(peer) if err != nil { - log.Info("Failed adding peer", "address", addr, "conn", conn, "error", err) + log.Info("Failed adding peer", "address", addr, "error", err) + peer.CloseConn() return nil, err } log.Notice("Dialed and added peer", "address", addr, "peer", peer) @@ -400,31 +373,49 @@ func (sw *Switch) Peers() IPeerSet { return sw.peers } -// Disconnect from a peer due to external error. +// Disconnect from a peer due to external error, retry if it is a persistent peer. // TODO: make record depending on reason. func (sw *Switch) StopPeerForError(peer *Peer, reason interface{}) { + addr := NewNetAddress(peer.RemoteAddr()) log.Notice("Stopping peer for error", "peer", peer, "error", reason) - sw.peers.Remove(peer) - peer.Stop() - sw.removePeerFromReactors(peer, reason) + sw.stopAndRemovePeer(peer, reason) + + if peer.IsPersistent() { + go func() { + log.Notice("Reconnecting to peer", "peer", peer) + for i := 1; i < reconnectAttempts; i++ { + if !sw.IsRunning() { + return + } + + peer, err := sw.DialPeerWithAddress(addr, true) + if err != nil { + if i == reconnectAttempts { + log.Notice("Error reconnecting to peer. Giving up", "tries", i, "error", err) + return + } + log.Notice("Error reconnecting to peer. Trying again", "tries", i, "error", err) + time.Sleep(reconnectInterval) + continue + } + + log.Notice("Reconnected to peer", "peer", peer) + return + } + }() + } } // Disconnect from a peer gracefully. // TODO: handle graceful disconnects. func (sw *Switch) StopPeerGracefully(peer *Peer) { log.Notice("Stopping peer gracefully") - sw.peers.Remove(peer) - peer.Stop() - sw.removePeerFromReactors(peer, nil) + sw.stopAndRemovePeer(peer, nil) } -func (sw *Switch) addPeerToReactors(peer *Peer) { - for _, reactor := range sw.reactors { - reactor.AddPeer(peer) - } -} - -func (sw *Switch) removePeerFromReactors(peer *Peer, reason interface{}) { +func (sw *Switch) stopAndRemovePeer(peer *Peer, reason interface{}) { + sw.peers.Remove(peer) + peer.Stop() for _, reactor := range sw.reactors { reactor.RemovePeer(peer, reason) } @@ -449,9 +440,9 @@ func (sw *Switch) listenerRoutine(l Listener) { } // New inbound connection! - _, err := sw.AddPeerWithConnection(inConn, false) + err := sw.AddPeerWithConnection(inConn, false, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) if err != nil { - log.Notice("Ignoring inbound connection: error on AddPeerWithConnection", "address", inConn.RemoteAddr().String(), "error", err) + log.Notice("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "error", err) continue } @@ -511,14 +502,14 @@ func Connect2Switches(switches []*Switch, i, j int) { c1, c2 := net.Pipe() doneCh := make(chan struct{}) go func() { - _, err := switchI.AddPeerWithConnection(c1, false) // AddPeer is blocking, requires handshake. + err := switchI.AddPeerWithConnection(c1, false, switchI.reactorsByCh, switchI.chDescs, switchI.StopPeerForError, switchI.config, switchI.nodePrivKey) if PanicOnAddPeerErr && err != nil { panic(err) } doneCh <- struct{}{} }() go func() { - _, err := switchJ.AddPeerWithConnection(c2, true) + err := switchJ.AddPeerWithConnection(c2, false, switchJ.reactorsByCh, switchJ.chDescs, switchJ.StopPeerForError, switchJ.config, switchJ.nodePrivKey) if PanicOnAddPeerErr && err != nil { panic(err) } @@ -552,3 +543,19 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S s.SetNodePrivKey(privKey) return s } + +// AddPeerWithConnection is a helper function for testing. +func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) error { + peer, err := newPeerFromExistingConn(conn, outbound, reactorsByCh, chDescs, onPeerError, config, privKey) + if err != nil { + peer.CloseConn() + return err + } + + if err = sw.AddPeer(peer); err != nil { + peer.CloseConn() + return err + } + + return nil +} diff --git a/switch_test.go b/switch_test.go index 1b2ccd743..727bd2a1f 100644 --- a/switch_test.go +++ b/switch_test.go @@ -3,15 +3,19 @@ package p2p import ( "bytes" "fmt" + golog "log" "net" "sync" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" ) var ( @@ -21,7 +25,6 @@ var ( func init() { config = cfg.NewMapConfig(nil) setConfigDefaults(config) - } type PeerMessage struct { @@ -174,8 +177,12 @@ func TestConnAddrFilter(t *testing.T) { }) // connect to good peer - go s1.AddPeerWithConnection(c1, false) // AddPeer is blocking, requires handshake. - go s2.AddPeerWithConnection(c2, true) + go func() { + s1.AddPeerWithConnection(c1, false, s1.reactorsByCh, s1.chDescs, s1.StopPeerForError, s1.config, s1.nodePrivKey) + }() + go func() { + s2.AddPeerWithConnection(c2, true, s2.reactorsByCh, s2.chDescs, s2.StopPeerForError, s2.config, s2.nodePrivKey) + }() // Wait for things to happen, peers to get added... time.Sleep(100 * time.Millisecond * time.Duration(4)) @@ -205,8 +212,12 @@ func TestConnPubKeyFilter(t *testing.T) { }) // connect to good peer - go s1.AddPeerWithConnection(c1, false) // AddPeer is blocking, requires handshake. - go s2.AddPeerWithConnection(c2, true) + go func() { + s1.AddPeerWithConnection(c1, false, s1.reactorsByCh, s1.chDescs, s1.StopPeerForError, s1.config, s1.nodePrivKey) + }() + go func() { + s2.AddPeerWithConnection(c2, true, s2.reactorsByCh, s2.chDescs, s2.StopPeerForError, s2.config, s2.nodePrivKey) + }() // Wait for things to happen, peers to get added... time.Sleep(100 * time.Millisecond * time.Duration(4)) @@ -221,6 +232,63 @@ func TestConnPubKeyFilter(t *testing.T) { } } +func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + sw := makeSwitch(1, "testing", "123.123.123", initSwitchFunc) + sw.Start() + defer sw.Stop() + + sw2 := makeSwitch(2, "testing", "123.123.123", initSwitchFunc) + defer sw2.Stop() + l, serverAddr := listenTCP() + done := make(chan struct{}) + go accept(l, done, sw2) + defer close(done) + + peer, err := newPeer(NewNetAddress(serverAddr), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + require.Nil(err) + err = sw.AddPeer(peer) + require.Nil(err) + + // simulate failure by closing connection + peer.CloseConn() + + time.Sleep(100 * time.Millisecond) + + assert.Zero(sw.Peers().Size()) + assert.False(peer.IsRunning()) +} + +func TestSwitchReconnectsToPeerIfItIsPersistent(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + sw := makeSwitch(1, "testing", "123.123.123", initSwitchFunc) + sw.Start() + defer sw.Stop() + + sw2 := makeSwitch(2, "testing", "123.123.123", initSwitchFunc) + defer sw2.Stop() + l, serverAddr := listenTCP() + done := make(chan struct{}) + go accept(l, done, sw2) + defer close(done) + + peer, err := newPeer(NewNetAddress(serverAddr), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + peer.MakePersistent() + require.Nil(err) + err = sw.AddPeer(peer) + require.Nil(err) + + // simulate failure by closing connection + peer.CloseConn() + + time.Sleep(100 * time.Millisecond) + + assert.NotZero(sw.Peers().Size()) + assert.False(peer.IsRunning()) +} + func BenchmarkSwitches(b *testing.B) { b.StopTimer() @@ -252,9 +320,9 @@ func BenchmarkSwitches(b *testing.B) { successChan := s1.Broadcast(chID, "test data") for s := range successChan { if s { - numSuccess += 1 + numSuccess++ } else { - numFailure += 1 + numFailure++ } } } @@ -266,3 +334,48 @@ func BenchmarkSwitches(b *testing.B) { time.Sleep(1000 * time.Millisecond) } + +func listenTCP() (net.Listener, net.Addr) { + l, e := net.Listen("tcp", "127.0.0.1:0") // any available address + if e != nil { + golog.Fatalf("net.Listen tcp :0: %+v", e) + } + return l, l.Addr() +} + +// simulate remote peer +func accept(l net.Listener, done <-chan struct{}, sw *Switch) { + for { + conn, err := l.Accept() + if err != nil { + golog.Fatalf("Failed to accept conn: %+v", err) + } + conn, err = MakeSecretConnection(conn, sw.nodePrivKey) + if err != nil { + golog.Fatalf("Failed to make secret conn: %+v", err) + } + var err1, err2 error + nodeInfo := new(NodeInfo) + cmn.Parallel( + func() { + var n int + wire.WriteBinary(sw.nodeInfo, conn, &n, &err1) + }, + func() { + var n int + wire.ReadBinary(nodeInfo, conn, maxNodeInfoSize, &n, &err2) + }) + if err1 != nil { + golog.Fatalf("Failed to do handshake: %+v", err1) + } + if err2 != nil { + golog.Fatalf("Failed to do handshake: %+v", err2) + } + select { + case <-done: + conn.Close() + return + default: + } + } +} From a9bb6734e7f739be52b76575c82ed9091363df08 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 10 Apr 2017 15:59:32 -0400 Subject: [PATCH 09/47] SetDeadline for authEnc. Stop peer if Add fails --- peer.go | 6 ++++++ switch.go | 3 ++- switch_test.go | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/peer.go b/peer.go index d94ce5838..657162de9 100644 --- a/peer.go +++ b/peer.go @@ -48,12 +48,18 @@ func newPeerFromExistingConn(conn net.Conn, outbound bool, reactorsByCh map[byte // Encrypt connection if config.GetBool(configKeyAuthEnc) { var err error + // Set deadline for handshake so we don't block forever on conn.ReadFull + timeout := time.Duration(config.GetInt(configKeyHandshakeTimeoutSeconds)) * time.Second + conn.SetDeadline(time.Now().Add(timeout)) conn, err = MakeSecretConnection(conn, privKey) if err != nil { return nil, err } + // remove deadline + conn.SetDeadline(time.Time{}) } + // Key and NodeInfo are set after Handshake p := &Peer{ outbound: outbound, authEnc: config.GetBool(configKeyAuthEnc), diff --git a/switch.go b/switch.go index b0551a4f8..655c24f97 100644 --- a/switch.go +++ b/switch.go @@ -226,6 +226,7 @@ func (sw *Switch) AddPeer(peer *Peer) error { // ignore if duplicate or if we already have too many for that IP range if err := sw.peers.Add(peer); err != nil { log.Notice("Ignoring peer", "error", err, "peer", peer) + peer.Stop() return err } @@ -544,7 +545,7 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S return s } -// AddPeerWithConnection is a helper function for testing. +// AddPeerWithConnection creates a newPeer from the connection, performs the handshake, and adds it to the switch. func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) error { peer, err := newPeerFromExistingConn(conn, outbound, reactorsByCh, chDescs, onPeerError, config, privKey) if err != nil { diff --git a/switch_test.go b/switch_test.go index 727bd2a1f..e3fa6876f 100644 --- a/switch_test.go +++ b/switch_test.go @@ -260,7 +260,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { assert.False(peer.IsRunning()) } -func TestSwitchReconnectsToPeerIfItIsPersistent(t *testing.T) { +func TestSwitchReconnectsToPersistentPeer(t *testing.T) { assert, require := assert.New(t), require.New(t) sw := makeSwitch(1, "testing", "123.123.123", initSwitchFunc) From b6f744c732d55c34b7f25cfbd0b409805fd89150 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 10 Apr 2017 16:03:14 -0400 Subject: [PATCH 10/47] fix AddPeerWithConnection --- switch.go | 10 +++++----- switch_test.go | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/switch.go b/switch.go index 655c24f97..dff9256f3 100644 --- a/switch.go +++ b/switch.go @@ -441,7 +441,7 @@ func (sw *Switch) listenerRoutine(l Listener) { } // New inbound connection! - err := sw.AddPeerWithConnection(inConn, false, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + err := sw.AddPeerWithConnection(inConn, false) if err != nil { log.Notice("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "error", err) continue @@ -503,14 +503,14 @@ func Connect2Switches(switches []*Switch, i, j int) { c1, c2 := net.Pipe() doneCh := make(chan struct{}) go func() { - err := switchI.AddPeerWithConnection(c1, false, switchI.reactorsByCh, switchI.chDescs, switchI.StopPeerForError, switchI.config, switchI.nodePrivKey) + err := switchI.AddPeerWithConnection(c1, false) if PanicOnAddPeerErr && err != nil { panic(err) } doneCh <- struct{}{} }() go func() { - err := switchJ.AddPeerWithConnection(c2, false, switchJ.reactorsByCh, switchJ.chDescs, switchJ.StopPeerForError, switchJ.config, switchJ.nodePrivKey) + err := switchJ.AddPeerWithConnection(c2, false) if PanicOnAddPeerErr && err != nil { panic(err) } @@ -546,8 +546,8 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S } // AddPeerWithConnection creates a newPeer from the connection, performs the handshake, and adds it to the switch. -func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) error { - peer, err := newPeerFromExistingConn(conn, outbound, reactorsByCh, chDescs, onPeerError, config, privKey) +func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { + peer, err := newPeerFromExistingConn(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) if err != nil { peer.CloseConn() return err diff --git a/switch_test.go b/switch_test.go index e3fa6876f..8cfcb8c6e 100644 --- a/switch_test.go +++ b/switch_test.go @@ -178,10 +178,10 @@ func TestConnAddrFilter(t *testing.T) { // connect to good peer go func() { - s1.AddPeerWithConnection(c1, false, s1.reactorsByCh, s1.chDescs, s1.StopPeerForError, s1.config, s1.nodePrivKey) + s1.AddPeerWithConnection(c1, false) }() go func() { - s2.AddPeerWithConnection(c2, true, s2.reactorsByCh, s2.chDescs, s2.StopPeerForError, s2.config, s2.nodePrivKey) + s2.AddPeerWithConnection(c2, true) }() // Wait for things to happen, peers to get added... @@ -213,10 +213,10 @@ func TestConnPubKeyFilter(t *testing.T) { // connect to good peer go func() { - s1.AddPeerWithConnection(c1, false, s1.reactorsByCh, s1.chDescs, s1.StopPeerForError, s1.config, s1.nodePrivKey) + s1.AddPeerWithConnection(c1, false) }() go func() { - s2.AddPeerWithConnection(c2, true, s2.reactorsByCh, s2.chDescs, s2.StopPeerForError, s2.config, s2.nodePrivKey) + s2.AddPeerWithConnection(c2, true) }() // Wait for things to happen, peers to get added... From 9a1a6c56b402e44a8ef51283112fa1886e0c5aa5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 10 Apr 2017 16:04:55 -0400 Subject: [PATCH 11/47] dont expose makePersistent --- peer.go | 4 ++-- switch.go | 2 +- switch_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/peer.go b/peer.go index 657162de9..92d4973d8 100644 --- a/peer.go +++ b/peer.go @@ -80,8 +80,8 @@ func (p *Peer) CloseConn() { p.conn.Close() } -// MakePersistent marks the peer as persistent. -func (p *Peer) MakePersistent() { +// makePersistent marks the peer as persistent. +func (p *Peer) makePersistent() { if !p.outbound { panic("inbound peers can't be made persistent") } diff --git a/switch.go b/switch.go index dff9256f3..fcde27449 100644 --- a/switch.go +++ b/switch.go @@ -319,7 +319,7 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, peer, err := newPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) if persistent { - peer.MakePersistent() + peer.makePersistent() } if err != nil { log.Info("Failed dialing peer", "address", addr, "error", err) diff --git a/switch_test.go b/switch_test.go index 8cfcb8c6e..a9abfa0a5 100644 --- a/switch_test.go +++ b/switch_test.go @@ -275,7 +275,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { defer close(done) peer, err := newPeer(NewNetAddress(serverAddr), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) - peer.MakePersistent() + peer.makePersistent() require.Nil(err) err = sw.AddPeer(peer) require.Nil(err) From 8067cdb5f240ebb288394ef03c65414ba9e4a08c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 11 Apr 2017 12:42:11 -0400 Subject: [PATCH 12/47] fix closing conn --- peer.go | 7 ++++++- switch.go | 9 ++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/peer.go b/peer.go index 92d4973d8..1be50aff1 100644 --- a/peer.go +++ b/peer.go @@ -41,7 +41,12 @@ func newPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*Channel } // outbound = true - return newPeerFromExistingConn(conn, true, reactorsByCh, chDescs, onPeerError, config, privKey) + peer, err := newPeerFromExistingConn(conn, true, reactorsByCh, chDescs, onPeerError, config, privKey) + if err != nil { + conn.Close() + return nil, err + } + return peer, nil } func newPeerFromExistingConn(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) (*Peer, error) { diff --git a/switch.go b/switch.go index fcde27449..f38d4feb1 100644 --- a/switch.go +++ b/switch.go @@ -318,14 +318,13 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, defer sw.dialing.Delete(addr.IP.String()) peer, err := newPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) - if persistent { - peer.makePersistent() - } if err != nil { log.Info("Failed dialing peer", "address", addr, "error", err) - peer.CloseConn() return nil, err } + if persistent { + peer.makePersistent() + } err = sw.AddPeer(peer) if err != nil { log.Info("Failed adding peer", "address", addr, "error", err) @@ -549,7 +548,7 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { peer, err := newPeerFromExistingConn(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) if err != nil { - peer.CloseConn() + conn.Close() return err } From 7dcc3dbcd14a284e8150df8ac1bae96d774499cc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 11 Apr 2017 19:47:05 +0400 Subject: [PATCH 13/47] test peer --- config.go | 2 - connection.go | 20 ++++--- fuzz.go | 143 +++++++++++++++++++++++++++++++++---------------- peer.go | 85 ++++++++++++++++++----------- peer_test.go | 120 +++++++++++++++++++++++++++++++++++++++++ switch.go | 45 +++++++++++++--- switch_test.go | 71 ++++-------------------- 7 files changed, 331 insertions(+), 155 deletions(-) create mode 100644 peer_test.go diff --git a/config.go b/config.go index 60b69e367..a8b7e343b 100644 --- a/config.go +++ b/config.go @@ -17,7 +17,6 @@ const ( // Fuzz params configFuzzEnable = "fuzz_enable" // use the fuzz wrapped conn - configFuzzActive = "fuzz_active" // toggle fuzzing configFuzzMode = "fuzz_mode" // eg. drop, delay configFuzzMaxDelayMilliseconds = "fuzz_max_delay_milliseconds" configFuzzProbDropRW = "fuzz_prob_drop_rw" @@ -38,7 +37,6 @@ func setConfigDefaults(config cfg.Config) { // Fuzz defaults config.SetDefault(configFuzzEnable, false) - config.SetDefault(configFuzzActive, false) config.SetDefault(configFuzzMode, FuzzModeDrop) config.SetDefault(configFuzzMaxDelayMilliseconds, 3000) config.SetDefault(configFuzzProbDropRW, 0.2) diff --git a/connection.go b/connection.go index e61608896..3448ae085 100644 --- a/connection.go +++ b/connection.go @@ -74,7 +74,7 @@ type MConnection struct { onReceive receiveCbFunc onError errorCbFunc errored uint32 - config *MConnectionConfig + config *MConnConfig quit chan struct{} flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. @@ -85,12 +85,19 @@ type MConnection struct { RemoteAddress *NetAddress } -// MConnectionConfig is a MConnection configuration -type MConnectionConfig struct { +// MConnConfig is a MConnection configuration +type MConnConfig struct { SendRate int64 RecvRate int64 } +func defaultMConnectionConfig() *MConnConfig { + return &MConnConfig{ + SendRate: defaultSendRate, + RecvRate: defaultRecvRate, + } +} + // NewMConnection wraps net.Conn and creates multiplex connection func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection { return NewMConnectionWithConfig( @@ -98,14 +105,11 @@ func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive recei chDescs, onReceive, onError, - &MConnectionConfig{ - SendRate: defaultSendRate, - RecvRate: defaultRecvRate, - }) + defaultMConnectionConfig()) } // NewMConnectionWithConfig wraps net.Conn and creates multiplex connection with a config -func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc, config *MConnectionConfig) *MConnection { +func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc, config *MConnConfig) *MConnection { mconn := &MConnection{ conn: conn, bufReader: bufio.NewReaderSize(conn, minReadBufferSize), diff --git a/fuzz.go b/fuzz.go index ee8f43ccf..822002b93 100644 --- a/fuzz.go +++ b/fuzz.go @@ -1,90 +1,139 @@ package p2p import ( + "fmt" "math/rand" "net" "sync" "time" - - cfg "github.com/tendermint/go-config" ) -//-------------------------------------------------------- -// delay reads/writes -// randomly drop reads/writes -// randomly drop connections - const ( - FuzzModeDrop = "drop" - FuzzModeDelay = "delay" + // FuzzModeDrop is a mode in which we randomly drop reads/writes, connections or sleep + FuzzModeDrop = iota + // FuzzModeDelay is a mode in which we randomly sleep + FuzzModeDelay ) -func FuzzConn(config cfg.Config, conn net.Conn) net.Conn { +type FuzzConnConfig struct { + Mode int + MaxDelay time.Duration + ProbDropRW float64 + ProbDropConn float64 + ProbSleep float64 +} + +func defaultFuzzConnConfig() *FuzzConnConfig { + return &FuzzConnConfig{ + Mode: FuzzModeDrop, + MaxDelay: 3 * time.Second, + ProbDropRW: 0.2, + ProbDropConn: 0.00, + ProbSleep: 0.00, + } +} + +func FuzzConn(conn net.Conn) net.Conn { + return FuzzConnFromConfig(conn, defaultFuzzConnConfig()) +} + +func FuzzConnFromConfig(conn net.Conn, config *FuzzConnConfig) net.Conn { + return &FuzzedConnection{ + conn: conn, + start: make(<-chan time.Time), + active: true, + mode: config.Mode, + maxDelay: config.MaxDelay, + probDropRW: config.ProbDropRW, + probDropConn: config.ProbDropConn, + probSleep: config.ProbSleep, + } +} + +func FuzzConnAfter(conn net.Conn, d time.Duration) net.Conn { + return FuzzConnAfterFromConfig(conn, d, defaultFuzzConnConfig()) +} + +func FuzzConnAfterFromConfig(conn net.Conn, d time.Duration, config *FuzzConnConfig) net.Conn { return &FuzzedConnection{ - conn: conn, - start: time.After(time.Second * 10), // so we have time to do peer handshakes and get set up - params: config, + conn: conn, + start: time.After(d), + active: false, + mode: config.Mode, + maxDelay: config.MaxDelay, + probDropRW: config.ProbDropRW, + probDropConn: config.ProbDropConn, + probSleep: config.ProbSleep, } } +// FuzzedConnection wraps any net.Conn and depending on the mode either delays +// reads/writes or randomly drops reads/writes/connections. type FuzzedConnection struct { conn net.Conn - mtx sync.Mutex - fuzz bool // we don't start fuzzing right away - start <-chan time.Time + mtx sync.Mutex + start <-chan time.Time + active bool - // fuzz params - params cfg.Config + mode int + maxDelay time.Duration + probDropRW float64 + probDropConn float64 + probSleep float64 } func (fc *FuzzedConnection) randomDuration() time.Duration { - return time.Millisecond * time.Duration(rand.Int()%fc.MaxDelayMilliseconds()) -} - -func (fc *FuzzedConnection) Active() bool { - return fc.params.GetBool(configFuzzActive) + maxDelayMillis := int(fc.maxDelay.Nanoseconds() / 1000) + return time.Millisecond * time.Duration(rand.Int()%maxDelayMillis) } -func (fc *FuzzedConnection) Mode() string { - return fc.params.GetString(configFuzzMode) +func (fc *FuzzedConnection) SetMode(mode int) { + switch mode { + case FuzzModeDrop: + fc.mode = FuzzModeDrop + case FuzzModeDelay: + fc.mode = FuzzModeDelay + default: + panic(fmt.Sprintf("Unknown mode %d", mode)) + } } -func (fc *FuzzedConnection) ProbDropRW() float64 { - return fc.params.GetFloat64(configFuzzProbDropRW) +func (fc *FuzzedConnection) SetProbDropRW(prob float64) { + fc.probDropRW = prob } -func (fc *FuzzedConnection) ProbDropConn() float64 { - return fc.params.GetFloat64(configFuzzProbDropConn) +func (fc *FuzzedConnection) SetProbDropConn(prob float64) { + fc.probDropConn = prob } -func (fc *FuzzedConnection) ProbSleep() float64 { - return fc.params.GetFloat64(configFuzzProbSleep) +func (fc *FuzzedConnection) SetProbSleep(prob float64) { + fc.probSleep = prob } -func (fc *FuzzedConnection) MaxDelayMilliseconds() int { - return fc.params.GetInt(configFuzzMaxDelayMilliseconds) +func (fc *FuzzedConnection) SetMaxDelay(d time.Duration) { + fc.maxDelay = d } // implements the fuzz (delay, kill conn) // and returns whether or not the read/write should be ignored -func (fc *FuzzedConnection) Fuzz() bool { +func (fc *FuzzedConnection) fuzz() bool { if !fc.shouldFuzz() { return false } - switch fc.Mode() { + switch fc.mode { case FuzzModeDrop: // randomly drop the r/w, drop the conn, or sleep r := rand.Float64() - if r <= fc.ProbDropRW() { + if r <= fc.probDropRW { return true - } else if r < fc.ProbDropRW()+fc.ProbDropConn() { + } else if r < fc.probDropRW+fc.probDropConn { // XXX: can't this fail because machine precision? // XXX: do we need an error? fc.Close() return true - } else if r < fc.ProbDropRW()+fc.ProbDropConn()+fc.ProbSleep() { + } else if r < fc.probDropRW+fc.probDropConn+fc.probSleep { time.Sleep(fc.randomDuration()) } case FuzzModeDelay: @@ -96,33 +145,33 @@ func (fc *FuzzedConnection) Fuzz() bool { // we don't fuzz until start chan fires func (fc *FuzzedConnection) shouldFuzz() bool { - if !fc.Active() { - return false + if fc.active { + return true } fc.mtx.Lock() defer fc.mtx.Unlock() - if fc.fuzz { - return true - } select { case <-fc.start: - fc.fuzz = true + fc.active = true + return true default: + return false } - return false } +// Read implements net.Conn func (fc *FuzzedConnection) Read(data []byte) (n int, err error) { - if fc.Fuzz() { + if fc.fuzz() { return 0, nil } return fc.conn.Read(data) } +// Write implements net.Conn func (fc *FuzzedConnection) Write(data []byte) (n int, err error) { - if fc.Fuzz() { + if fc.fuzz() { return 0, nil } return fc.conn.Write(data) diff --git a/peer.go b/peer.go index 1be50aff1..6ef317378 100644 --- a/peer.go +++ b/peer.go @@ -7,7 +7,6 @@ import ( "time" cmn "github.com/tendermint/go-common" - cfg "github.com/tendermint/go-config" crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" ) @@ -25,23 +24,50 @@ type Peer struct { conn net.Conn // source connection mconn *MConnection // multiplex connection - authEnc bool // authenticated encryption persistent bool - config cfg.Config + config *PeerConfig *NodeInfo Key string Data *cmn.CMap // User data. } -func newPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) (*Peer, error) { +// PeerConfig is a Peer configuration +type PeerConfig struct { + AuthEnc bool // authenticated encryption + + HandshakeTimeout time.Duration + DialTimeout time.Duration + + MConfig *MConnConfig + + Fuzz bool // fuzz connection (for testing) + FuzzConfig *FuzzConnConfig +} + +func defaultPeerConfig() *PeerConfig { + return &PeerConfig{ + AuthEnc: true, + Fuzz: false, + HandshakeTimeout: 20 * time.Second, + DialTimeout: 3 * time.Second, + MConfig: defaultMConnectionConfig(), + FuzzConfig: defaultFuzzConnConfig(), + } +} + +func newPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) { + return newPeerWithConfig(addr, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, defaultPeerConfig()) +} + +func newPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { conn, err := dial(addr, config) if err != nil { return nil, err } // outbound = true - peer, err := newPeerFromExistingConn(conn, true, reactorsByCh, chDescs, onPeerError, config, privKey) + peer, err := newPeerFromExistingConnAndConfig(conn, true, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) if err != nil { conn.Close() return nil, err @@ -49,31 +75,39 @@ func newPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*Channel return peer, nil } -func newPeerFromExistingConn(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config, privKey crypto.PrivKeyEd25519) (*Peer, error) { +func newPeerFromExistingConn(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) { + return newPeerFromExistingConnAndConfig(rawConn, outbound, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, defaultPeerConfig()) +} + +func newPeerFromExistingConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { + conn := rawConn + + // Fuzz connection + if config.Fuzz { + // so we have time to do peer handshakes and get set up + conn = FuzzConnAfterFromConfig(conn, 10*time.Second, config.FuzzConfig) + } + // Encrypt connection - if config.GetBool(configKeyAuthEnc) { + if config.AuthEnc { + conn.SetDeadline(time.Now().Add(config.HandshakeTimeout)) + var err error - // Set deadline for handshake so we don't block forever on conn.ReadFull - timeout := time.Duration(config.GetInt(configKeyHandshakeTimeoutSeconds)) * time.Second - conn.SetDeadline(time.Now().Add(timeout)) - conn, err = MakeSecretConnection(conn, privKey) + conn, err = MakeSecretConnection(conn, ourNodePrivKey) if err != nil { return nil, err } - // remove deadline - conn.SetDeadline(time.Time{}) } // Key and NodeInfo are set after Handshake p := &Peer{ outbound: outbound, - authEnc: config.GetBool(configKeyAuthEnc), conn: conn, config: config, Data: cmn.NewCMap(), } - p.mconn = createMConnection(conn, p, reactorsByCh, chDescs, onPeerError, config) + p.mconn = createMConnection(conn, p, reactorsByCh, chDescs, onPeerError, config.MConfig) p.BaseService = *cmn.NewBaseService(log, "Peer", p) @@ -125,7 +159,7 @@ func (p *Peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er return err2 } - if p.authEnc { + if p.config.AuthEnc { // Check that the professed PubKey matches the sconn's. if !peerNodeInfo.PubKey.Equals(p.PubKey()) { return fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v", @@ -151,7 +185,7 @@ func (p *Peer) RemoteAddr() net.Addr { // PubKey returns the remote public key. func (p *Peer) PubKey() crypto.PubKeyEd25519 { - if p.authEnc { + if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() } if p.NodeInfo == nil { @@ -238,21 +272,17 @@ func (p *Peer) Get(key string) interface{} { return p.Data.Get(key) } -func dial(addr *NetAddress, config cfg.Config) (net.Conn, error) { +func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { log.Info("Dialing address", "address", addr) - conn, err := addr.DialTimeout(time.Duration( - config.GetInt(configKeyDialTimeoutSeconds)) * time.Second) + conn, err := addr.DialTimeout(config.DialTimeout) if err != nil { log.Info("Failed dialing address", "address", addr, "error", err) return nil, err } - if config.GetBool(configFuzzEnable) { - conn = FuzzConn(config, conn) - } return conn, nil } -func createMConnection(conn net.Conn, p *Peer, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config cfg.Config) *MConnection { +func createMConnection(conn net.Conn, p *Peer, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), config *MConnConfig) *MConnection { onReceive := func(chID byte, msgBytes []byte) { reactor := reactorsByCh[chID] if reactor == nil { @@ -265,10 +295,5 @@ func createMConnection(conn net.Conn, p *Peer, reactorsByCh map[byte]Reactor, ch onPeerError(p, r) } - mconnConfig := &MConnectionConfig{ - SendRate: int64(config.GetInt(configKeySendRate)), - RecvRate: int64(config.GetInt(configKeyRecvRate)), - } - - return NewMConnectionWithConfig(conn, chDescs, onReceive, onError, mconnConfig) + return NewMConnectionWithConfig(conn, chDescs, onReceive, onError, config) } diff --git a/peer_test.go b/peer_test.go new file mode 100644 index 000000000..56d4c1da5 --- /dev/null +++ b/peer_test.go @@ -0,0 +1,120 @@ +package p2p + +import ( + golog "log" + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/go-common" + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" +) + +func TestPeerStartStop(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // simulate remote peer + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519()} + rp.Start() + defer rp.Stop() + + p, err := createPeerAndPerformHandshake(rp.RemoteAddr()) + require.Nil(err) + + p.Start() + defer p.Stop() + + assert.True(p.IsRunning()) +} + +func createPeerAndPerformHandshake(addr *NetAddress) (*Peer, error) { + chDescs := []*ChannelDescriptor{ + &ChannelDescriptor{ID: 0x01, Priority: 1}, + } + reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} + pk := crypto.GenPrivKeyEd25519() + p, err := newPeer(addr, reactorsByCh, chDescs, func(p *Peer, r interface{}) {}, pk) + if err != nil { + return nil, err + } + err = p.HandshakeTimeout(&NodeInfo{ + PubKey: pk.PubKey().(crypto.PubKeyEd25519), + Moniker: "remote_peer", + Network: "testing", + Version: "123.123.123", + }, 1*time.Second) + if err != nil { + return nil, err + } + return p, nil +} + +type remotePeer struct { + PrivKey crypto.PrivKeyEd25519 + addr *NetAddress + quit chan struct{} +} + +func (p *remotePeer) RemoteAddr() *NetAddress { + return p.addr +} + +func (p *remotePeer) Start() { + l, e := net.Listen("tcp", "127.0.0.1:0") // any available address + if e != nil { + golog.Fatalf("net.Listen tcp :0: %+v", e) + } + p.addr = NewNetAddress(l.Addr()) + p.quit = make(chan struct{}) + go p.accept(l) +} + +func (p *remotePeer) Stop() { + close(p.quit) +} + +func (p *remotePeer) accept(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + golog.Fatalf("Failed to accept conn: %+v", err) + } + conn, err = MakeSecretConnection(conn, p.PrivKey) + if err != nil { + golog.Fatalf("Failed to make secret conn: %+v", err) + } + var err1, err2 error + nodeInfo := new(NodeInfo) + cmn.Parallel( + func() { + var n int + ourNodeInfo := &NodeInfo{ + PubKey: p.PrivKey.PubKey().(crypto.PubKeyEd25519), + Moniker: "remote_peer", + Network: "testing", + Version: "123.123.123", + } + wire.WriteBinary(ourNodeInfo, conn, &n, &err1) + }, + func() { + var n int + wire.ReadBinary(nodeInfo, conn, maxNodeInfoSize, &n, &err2) + }) + if err1 != nil { + golog.Fatalf("Failed to do handshake: %+v", err1) + } + if err2 != nil { + golog.Fatalf("Failed to do handshake: %+v", err2) + } + select { + case <-p.quit: + conn.Close() + return + default: + } + } +} diff --git a/switch.go b/switch.go index f38d4feb1..b25384618 100644 --- a/switch.go +++ b/switch.go @@ -317,7 +317,7 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, sw.dialing.Set(addr.IP.String(), addr) defer sw.dialing.Delete(addr.IP.String()) - peer, err := newPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + peer, err := newPeerWithConfig(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, peerConfigFromGoConfig(sw.config)) if err != nil { log.Info("Failed dialing peer", "address", addr, "error", err) return nil, err @@ -435,12 +435,8 @@ func (sw *Switch) listenerRoutine(l Listener) { continue } - if sw.config.GetBool(configFuzzEnable) { - inConn = FuzzConn(sw.config, inConn) - } - // New inbound connection! - err := sw.AddPeerWithConnection(inConn, false) + err := sw.AddPeerWithConnectionAndConfig(inConn, false, peerConfigFromGoConfig(sw.config)) if err != nil { log.Notice("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "error", err) continue @@ -546,7 +542,7 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S // AddPeerWithConnection creates a newPeer from the connection, performs the handshake, and adds it to the switch. func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { - peer, err := newPeerFromExistingConn(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + peer, err := newPeerFromExistingConn(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) if err != nil { conn.Close() return err @@ -559,3 +555,38 @@ func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { return nil } + +func (sw *Switch) AddPeerWithConnectionAndConfig(conn net.Conn, outbound bool, config *PeerConfig) error { + peer, err := newPeerFromExistingConnAndConfig(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config) + if err != nil { + peer.CloseConn() + return err + } + + if err = sw.AddPeer(peer); err != nil { + peer.CloseConn() + return err + } + + return nil +} + +func peerConfigFromGoConfig(config cfg.Config) *PeerConfig { + return &PeerConfig{ + AuthEnc: config.GetBool(configKeyAuthEnc), + Fuzz: config.GetBool(configFuzzEnable), + HandshakeTimeout: time.Duration(config.GetInt(configKeyHandshakeTimeoutSeconds)) * time.Second, + DialTimeout: time.Duration(config.GetInt(configKeyDialTimeoutSeconds)) * time.Second, + MConfig: &MConnConfig{ + SendRate: int64(config.GetInt(configKeySendRate)), + RecvRate: int64(config.GetInt(configKeyRecvRate)), + }, + FuzzConfig: &FuzzConnConfig{ + Mode: config.GetInt(configFuzzMode), + MaxDelay: time.Duration(config.GetInt(configFuzzMaxDelayMilliseconds)) * time.Millisecond, + ProbDropRW: config.GetFloat64(configFuzzProbDropRW), + ProbDropConn: config.GetFloat64(configFuzzProbDropConn), + ProbSleep: config.GetFloat64(configFuzzProbSleep), + }, + } +} diff --git a/switch_test.go b/switch_test.go index a9abfa0a5..530a12b17 100644 --- a/switch_test.go +++ b/switch_test.go @@ -3,7 +3,6 @@ package p2p import ( "bytes" "fmt" - golog "log" "net" "sync" "testing" @@ -12,7 +11,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" . "github.com/tendermint/go-common" - cmn "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" @@ -239,14 +237,12 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { sw.Start() defer sw.Stop() - sw2 := makeSwitch(2, "testing", "123.123.123", initSwitchFunc) - defer sw2.Stop() - l, serverAddr := listenTCP() - done := make(chan struct{}) - go accept(l, done, sw2) - defer close(done) + // simulate remote peer + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519()} + rp.Start() + defer rp.Stop() - peer, err := newPeer(NewNetAddress(serverAddr), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + peer, err := newPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) require.Nil(err) err = sw.AddPeer(peer) require.Nil(err) @@ -267,14 +263,12 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { sw.Start() defer sw.Stop() - sw2 := makeSwitch(2, "testing", "123.123.123", initSwitchFunc) - defer sw2.Stop() - l, serverAddr := listenTCP() - done := make(chan struct{}) - go accept(l, done, sw2) - defer close(done) + // simulate remote peer + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519()} + rp.Start() + defer rp.Stop() - peer, err := newPeer(NewNetAddress(serverAddr), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.config, sw.nodePrivKey) + peer, err := newPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) peer.makePersistent() require.Nil(err) err = sw.AddPeer(peer) @@ -334,48 +328,3 @@ func BenchmarkSwitches(b *testing.B) { time.Sleep(1000 * time.Millisecond) } - -func listenTCP() (net.Listener, net.Addr) { - l, e := net.Listen("tcp", "127.0.0.1:0") // any available address - if e != nil { - golog.Fatalf("net.Listen tcp :0: %+v", e) - } - return l, l.Addr() -} - -// simulate remote peer -func accept(l net.Listener, done <-chan struct{}, sw *Switch) { - for { - conn, err := l.Accept() - if err != nil { - golog.Fatalf("Failed to accept conn: %+v", err) - } - conn, err = MakeSecretConnection(conn, sw.nodePrivKey) - if err != nil { - golog.Fatalf("Failed to make secret conn: %+v", err) - } - var err1, err2 error - nodeInfo := new(NodeInfo) - cmn.Parallel( - func() { - var n int - wire.WriteBinary(sw.nodeInfo, conn, &n, &err1) - }, - func() { - var n int - wire.ReadBinary(nodeInfo, conn, maxNodeInfoSize, &n, &err2) - }) - if err1 != nil { - golog.Fatalf("Failed to do handshake: %+v", err1) - } - if err2 != nil { - golog.Fatalf("Failed to do handshake: %+v", err2) - } - select { - case <-done: - conn.Close() - return - default: - } - } -} From 5965578c56678f6ac413fe02849f3ebaa116be2b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Apr 2017 11:55:14 +0400 Subject: [PATCH 14/47] [fuzz] only one way to set config variables --- fuzz.go | 157 +++++++++++++++++++++++++------------------------------- peer.go | 2 +- 2 files changed, 71 insertions(+), 88 deletions(-) diff --git a/fuzz.go b/fuzz.go index 822002b93..aefac986a 100644 --- a/fuzz.go +++ b/fuzz.go @@ -1,7 +1,6 @@ package p2p import ( - "fmt" "math/rand" "net" "sync" @@ -15,6 +14,19 @@ const ( FuzzModeDelay ) +// FuzzedConnection wraps any net.Conn and depending on the mode either delays +// reads/writes or randomly drops reads/writes/connections. +type FuzzedConnection struct { + conn net.Conn + + mtx sync.Mutex + start <-chan time.Time + active bool + + config *FuzzConnConfig +} + +// FuzzConnConfig is a FuzzedConnection configuration. type FuzzConnConfig struct { Mode int MaxDelay time.Duration @@ -23,7 +35,8 @@ type FuzzConnConfig struct { ProbSleep float64 } -func defaultFuzzConnConfig() *FuzzConnConfig { +// DefaultFuzzConnConfig returns the default config. +func DefaultFuzzConnConfig() *FuzzConnConfig { return &FuzzConnConfig{ Mode: FuzzModeDrop, MaxDelay: 3 * time.Second, @@ -33,86 +46,85 @@ func defaultFuzzConnConfig() *FuzzConnConfig { } } +// FuzzConn creates a new FuzzedConnection. Fuzzing starts immediately. func FuzzConn(conn net.Conn) net.Conn { - return FuzzConnFromConfig(conn, defaultFuzzConnConfig()) + return FuzzConnFromConfig(conn, DefaultFuzzConnConfig()) } +// FuzzConnFromConfig creates a new FuzzedConnection from a config. Fuzzing +// starts immediately. func FuzzConnFromConfig(conn net.Conn, config *FuzzConnConfig) net.Conn { return &FuzzedConnection{ - conn: conn, - start: make(<-chan time.Time), - active: true, - mode: config.Mode, - maxDelay: config.MaxDelay, - probDropRW: config.ProbDropRW, - probDropConn: config.ProbDropConn, - probSleep: config.ProbSleep, + conn: conn, + start: make(<-chan time.Time), + active: true, + config: config, } } +// FuzzConnAfter creates a new FuzzedConnection. Fuzzing starts when the +// duration elapses. func FuzzConnAfter(conn net.Conn, d time.Duration) net.Conn { - return FuzzConnAfterFromConfig(conn, d, defaultFuzzConnConfig()) + return FuzzConnAfterFromConfig(conn, d, DefaultFuzzConnConfig()) } +// FuzzConnAfterFromConfig creates a new FuzzedConnection from a config. +// Fuzzing starts when the duration elapses. func FuzzConnAfterFromConfig(conn net.Conn, d time.Duration, config *FuzzConnConfig) net.Conn { return &FuzzedConnection{ - conn: conn, - start: time.After(d), - active: false, - mode: config.Mode, - maxDelay: config.MaxDelay, - probDropRW: config.ProbDropRW, - probDropConn: config.ProbDropConn, - probSleep: config.ProbSleep, + conn: conn, + start: time.After(d), + active: false, + config: config, } } -// FuzzedConnection wraps any net.Conn and depending on the mode either delays -// reads/writes or randomly drops reads/writes/connections. -type FuzzedConnection struct { - conn net.Conn - - mtx sync.Mutex - start <-chan time.Time - active bool - - mode int - maxDelay time.Duration - probDropRW float64 - probDropConn float64 - probSleep float64 +// Config returns the connection's config. +func (fc *FuzzedConnection) Config() *FuzzConnConfig { + return fc.config } -func (fc *FuzzedConnection) randomDuration() time.Duration { - maxDelayMillis := int(fc.maxDelay.Nanoseconds() / 1000) - return time.Millisecond * time.Duration(rand.Int()%maxDelayMillis) +// Read implements net.Conn. +func (fc *FuzzedConnection) Read(data []byte) (n int, err error) { + if fc.fuzz() { + return 0, nil + } + return fc.conn.Read(data) } -func (fc *FuzzedConnection) SetMode(mode int) { - switch mode { - case FuzzModeDrop: - fc.mode = FuzzModeDrop - case FuzzModeDelay: - fc.mode = FuzzModeDelay - default: - panic(fmt.Sprintf("Unknown mode %d", mode)) +// Write implements net.Conn. +func (fc *FuzzedConnection) Write(data []byte) (n int, err error) { + if fc.fuzz() { + return 0, nil } + return fc.conn.Write(data) } -func (fc *FuzzedConnection) SetProbDropRW(prob float64) { - fc.probDropRW = prob -} +// Close implements net.Conn. +func (fc *FuzzedConnection) Close() error { return fc.conn.Close() } + +// LocalAddr implements net.Conn. +func (fc *FuzzedConnection) LocalAddr() net.Addr { return fc.conn.LocalAddr() } + +// RemoteAddr implements net.Conn. +func (fc *FuzzedConnection) RemoteAddr() net.Addr { return fc.conn.RemoteAddr() } + +// SetDeadline implements net.Conn. +func (fc *FuzzedConnection) SetDeadline(t time.Time) error { return fc.conn.SetDeadline(t) } -func (fc *FuzzedConnection) SetProbDropConn(prob float64) { - fc.probDropConn = prob +// SetReadDeadline implements net.Conn. +func (fc *FuzzedConnection) SetReadDeadline(t time.Time) error { + return fc.conn.SetReadDeadline(t) } -func (fc *FuzzedConnection) SetProbSleep(prob float64) { - fc.probSleep = prob +// SetWriteDeadline implements net.Conn. +func (fc *FuzzedConnection) SetWriteDeadline(t time.Time) error { + return fc.conn.SetWriteDeadline(t) } -func (fc *FuzzedConnection) SetMaxDelay(d time.Duration) { - fc.maxDelay = d +func (fc *FuzzedConnection) randomDuration() time.Duration { + maxDelayMillis := int(fc.config.MaxDelay.Nanoseconds() / 1000) + return time.Millisecond * time.Duration(rand.Int()%maxDelayMillis) } // implements the fuzz (delay, kill conn) @@ -122,18 +134,18 @@ func (fc *FuzzedConnection) fuzz() bool { return false } - switch fc.mode { + switch fc.config.Mode { case FuzzModeDrop: // randomly drop the r/w, drop the conn, or sleep r := rand.Float64() - if r <= fc.probDropRW { + if r <= fc.config.ProbDropRW { return true - } else if r < fc.probDropRW+fc.probDropConn { + } else if r < fc.config.ProbDropRW+fc.config.ProbDropConn { // XXX: can't this fail because machine precision? // XXX: do we need an error? fc.Close() return true - } else if r < fc.probDropRW+fc.probDropConn+fc.probSleep { + } else if r < fc.config.ProbDropRW+fc.config.ProbDropConn+fc.config.ProbSleep { time.Sleep(fc.randomDuration()) } case FuzzModeDelay: @@ -143,7 +155,6 @@ func (fc *FuzzedConnection) fuzz() bool { return false } -// we don't fuzz until start chan fires func (fc *FuzzedConnection) shouldFuzz() bool { if fc.active { return true @@ -160,31 +171,3 @@ func (fc *FuzzedConnection) shouldFuzz() bool { return false } } - -// Read implements net.Conn -func (fc *FuzzedConnection) Read(data []byte) (n int, err error) { - if fc.fuzz() { - return 0, nil - } - return fc.conn.Read(data) -} - -// Write implements net.Conn -func (fc *FuzzedConnection) Write(data []byte) (n int, err error) { - if fc.fuzz() { - return 0, nil - } - return fc.conn.Write(data) -} - -// Implements net.Conn -func (fc *FuzzedConnection) Close() error { return fc.conn.Close() } -func (fc *FuzzedConnection) LocalAddr() net.Addr { return fc.conn.LocalAddr() } -func (fc *FuzzedConnection) RemoteAddr() net.Addr { return fc.conn.RemoteAddr() } -func (fc *FuzzedConnection) SetDeadline(t time.Time) error { return fc.conn.SetDeadline(t) } -func (fc *FuzzedConnection) SetReadDeadline(t time.Time) error { - return fc.conn.SetReadDeadline(t) -} -func (fc *FuzzedConnection) SetWriteDeadline(t time.Time) error { - return fc.conn.SetWriteDeadline(t) -} diff --git a/peer.go b/peer.go index 6ef317378..3d2b4720d 100644 --- a/peer.go +++ b/peer.go @@ -52,7 +52,7 @@ func defaultPeerConfig() *PeerConfig { HandshakeTimeout: 20 * time.Second, DialTimeout: 3 * time.Second, MConfig: defaultMConnectionConfig(), - FuzzConfig: defaultFuzzConnConfig(), + FuzzConfig: DefaultFuzzConnConfig(), } } From a63e1bb2dc68735eab5bfe57b9a581e1959aabfe Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Apr 2017 12:08:57 +0400 Subject: [PATCH 15/47] fix possible panic --- switch.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/switch.go b/switch.go index b25384618..f4889a5f5 100644 --- a/switch.go +++ b/switch.go @@ -549,7 +549,7 @@ func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { } if err = sw.AddPeer(peer); err != nil { - peer.CloseConn() + conn.Close() return err } @@ -559,12 +559,12 @@ func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { func (sw *Switch) AddPeerWithConnectionAndConfig(conn net.Conn, outbound bool, config *PeerConfig) error { peer, err := newPeerFromExistingConnAndConfig(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config) if err != nil { - peer.CloseConn() + conn.Close() return err } if err = sw.AddPeer(peer); err != nil { - peer.CloseConn() + conn.Close() return err } From 715b8c629fb15fb741b86e7b3362acd6448c0ce9 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Apr 2017 12:09:43 +0400 Subject: [PATCH 16/47] use the peer struct to simulate remote peer --- peer_test.go | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/peer_test.go b/peer_test.go index 56d4c1da5..d564022ad 100644 --- a/peer_test.go +++ b/peer_test.go @@ -9,9 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cmn "github.com/tendermint/go-common" crypto "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" ) func TestPeerStartStop(t *testing.T) { @@ -43,7 +41,7 @@ func createPeerAndPerformHandshake(addr *NetAddress) (*Peer, error) { } err = p.HandshakeTimeout(&NodeInfo{ PubKey: pk.PubKey().(crypto.PubKeyEd25519), - Moniker: "remote_peer", + Moniker: "host_peer", Network: "testing", Version: "123.123.123", }, 1*time.Second) @@ -83,32 +81,18 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to accept conn: %+v", err) } - conn, err = MakeSecretConnection(conn, p.PrivKey) + peer, err := newPeerFromExistingConn(conn, false, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p *Peer, r interface{}) {}, p.PrivKey) if err != nil { - golog.Fatalf("Failed to make secret conn: %+v", err) + golog.Fatalf("Failed to create a peer: %+v", err) } - var err1, err2 error - nodeInfo := new(NodeInfo) - cmn.Parallel( - func() { - var n int - ourNodeInfo := &NodeInfo{ - PubKey: p.PrivKey.PubKey().(crypto.PubKeyEd25519), - Moniker: "remote_peer", - Network: "testing", - Version: "123.123.123", - } - wire.WriteBinary(ourNodeInfo, conn, &n, &err1) - }, - func() { - var n int - wire.ReadBinary(nodeInfo, conn, maxNodeInfoSize, &n, &err2) - }) - if err1 != nil { - golog.Fatalf("Failed to do handshake: %+v", err1) - } - if err2 != nil { - golog.Fatalf("Failed to do handshake: %+v", err2) + err = peer.HandshakeTimeout(&NodeInfo{ + PubKey: p.PrivKey.PubKey().(crypto.PubKeyEd25519), + Moniker: "remote_peer", + Network: "testing", + Version: "123.123.123", + }, 1*time.Second) + if err != nil { + golog.Fatalf("Failed to perform handshake: %+v", err) } select { case <-p.quit: From 1d01f6af9873b706cefdb1acd949bc40a8275a0b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 13 Apr 2017 12:36:16 +0400 Subject: [PATCH 17/47] 2 kinds of peers: outbound and inbound --- peer.go | 24 ++++++++++++++---------- peer_test.go | 4 ++-- switch.go | 17 ++++++++--------- switch_test.go | 12 ++++++------ 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/peer.go b/peer.go index 3d2b4720d..262569187 100644 --- a/peer.go +++ b/peer.go @@ -32,7 +32,7 @@ type Peer struct { Data *cmn.CMap // User data. } -// PeerConfig is a Peer configuration +// PeerConfig is a Peer configuration. type PeerConfig struct { AuthEnc bool // authenticated encryption @@ -45,7 +45,8 @@ type PeerConfig struct { FuzzConfig *FuzzConnConfig } -func defaultPeerConfig() *PeerConfig { +// DefaultPeerConfig returns the default config. +func DefaultPeerConfig() *PeerConfig { return &PeerConfig{ AuthEnc: true, Fuzz: false, @@ -56,18 +57,17 @@ func defaultPeerConfig() *PeerConfig { } } -func newPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) { - return newPeerWithConfig(addr, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, defaultPeerConfig()) +func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) { + return newOutboundPeerWithConfig(addr, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, DefaultPeerConfig()) } -func newPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { +func newOutboundPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { conn, err := dial(addr, config) if err != nil { return nil, err } - // outbound = true - peer, err := newPeerFromExistingConnAndConfig(conn, true, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) + peer, err := newPeerFromConnAndConfig(conn, true, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) if err != nil { conn.Close() return nil, err @@ -75,11 +75,15 @@ func newPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs return peer, nil } -func newPeerFromExistingConn(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) { - return newPeerFromExistingConnAndConfig(rawConn, outbound, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, defaultPeerConfig()) +func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519) (*Peer, error) { + return newInboundPeerWithConfig(conn, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, DefaultPeerConfig()) } -func newPeerFromExistingConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { +func newInboundPeerWithConfig(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { + return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) +} + +func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { conn := rawConn // Fuzz connection diff --git a/peer_test.go b/peer_test.go index d564022ad..199428af1 100644 --- a/peer_test.go +++ b/peer_test.go @@ -35,7 +35,7 @@ func createPeerAndPerformHandshake(addr *NetAddress) (*Peer, error) { } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} pk := crypto.GenPrivKeyEd25519() - p, err := newPeer(addr, reactorsByCh, chDescs, func(p *Peer, r interface{}) {}, pk) + p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p *Peer, r interface{}) {}, pk) if err != nil { return nil, err } @@ -81,7 +81,7 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to accept conn: %+v", err) } - peer, err := newPeerFromExistingConn(conn, false, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p *Peer, r interface{}) {}, p.PrivKey) + peer, err := newInboundPeer(conn, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p *Peer, r interface{}) {}, p.PrivKey) if err != nil { golog.Fatalf("Failed to create a peer: %+v", err) } diff --git a/switch.go b/switch.go index f4889a5f5..7505c0632 100644 --- a/switch.go +++ b/switch.go @@ -317,7 +317,7 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, sw.dialing.Set(addr.IP.String(), addr) defer sw.dialing.Delete(addr.IP.String()) - peer, err := newPeerWithConfig(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, peerConfigFromGoConfig(sw.config)) + peer, err := newOutboundPeerWithConfig(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, peerConfigFromGoConfig(sw.config)) if err != nil { log.Info("Failed dialing peer", "address", addr, "error", err) return nil, err @@ -436,7 +436,7 @@ func (sw *Switch) listenerRoutine(l Listener) { } // New inbound connection! - err := sw.AddPeerWithConnectionAndConfig(inConn, false, peerConfigFromGoConfig(sw.config)) + err := sw.addPeerWithConnectionAndConfig(inConn, peerConfigFromGoConfig(sw.config)) if err != nil { log.Notice("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "error", err) continue @@ -498,14 +498,14 @@ func Connect2Switches(switches []*Switch, i, j int) { c1, c2 := net.Pipe() doneCh := make(chan struct{}) go func() { - err := switchI.AddPeerWithConnection(c1, false) + err := switchI.addPeerWithConnection(c1) if PanicOnAddPeerErr && err != nil { panic(err) } doneCh <- struct{}{} }() go func() { - err := switchJ.AddPeerWithConnection(c2, false) + err := switchJ.addPeerWithConnection(c2) if PanicOnAddPeerErr && err != nil { panic(err) } @@ -540,9 +540,8 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S return s } -// AddPeerWithConnection creates a newPeer from the connection, performs the handshake, and adds it to the switch. -func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { - peer, err := newPeerFromExistingConn(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) +func (sw *Switch) addPeerWithConnection(conn net.Conn) error { + peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) if err != nil { conn.Close() return err @@ -556,8 +555,8 @@ func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) error { return nil } -func (sw *Switch) AddPeerWithConnectionAndConfig(conn net.Conn, outbound bool, config *PeerConfig) error { - peer, err := newPeerFromExistingConnAndConfig(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config) +func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error { + peer, err := newInboundPeerWithConfig(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config) if err != nil { conn.Close() return err diff --git a/switch_test.go b/switch_test.go index 530a12b17..6a9d6e858 100644 --- a/switch_test.go +++ b/switch_test.go @@ -176,10 +176,10 @@ func TestConnAddrFilter(t *testing.T) { // connect to good peer go func() { - s1.AddPeerWithConnection(c1, false) + s1.addPeerWithConnection(c1) }() go func() { - s2.AddPeerWithConnection(c2, true) + s2.addPeerWithConnection(c2) }() // Wait for things to happen, peers to get added... @@ -211,10 +211,10 @@ func TestConnPubKeyFilter(t *testing.T) { // connect to good peer go func() { - s1.AddPeerWithConnection(c1, false) + s1.addPeerWithConnection(c1) }() go func() { - s2.AddPeerWithConnection(c2, true) + s2.addPeerWithConnection(c2) }() // Wait for things to happen, peers to get added... @@ -242,7 +242,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) + peer, err := newOutboundPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) require.Nil(err) err = sw.AddPeer(peer) require.Nil(err) @@ -268,7 +268,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) + peer, err := newOutboundPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) peer.makePersistent() require.Nil(err) err = sw.AddPeer(peer) From 06d219db8eeb53a3d311eff0768f7d23df8ec12f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Apr 2017 12:43:28 +0400 Subject: [PATCH 18/47] test peer with no auth enc --- connection.go | 7 ++++--- glide.lock | 10 +++++----- glide.yaml | 4 +++- peer.go | 23 ++++++++++++----------- peer_test.go | 45 ++++++++++++++++++++++++++++++++++++++------- switch.go | 4 ++-- switch_test.go | 8 ++++---- 7 files changed, 68 insertions(+), 33 deletions(-) diff --git a/connection.go b/connection.go index 3448ae085..cf9bec4c5 100644 --- a/connection.go +++ b/connection.go @@ -85,13 +85,14 @@ type MConnection struct { RemoteAddress *NetAddress } -// MConnConfig is a MConnection configuration +// MConnConfig is a MConnection configuration. type MConnConfig struct { SendRate int64 RecvRate int64 } -func defaultMConnectionConfig() *MConnConfig { +// DefaultMConnConfig returns the default config. +func DefaultMConnConfig() *MConnConfig { return &MConnConfig{ SendRate: defaultSendRate, RecvRate: defaultRecvRate, @@ -105,7 +106,7 @@ func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive recei chDescs, onReceive, onError, - defaultMConnectionConfig()) + DefaultMConnConfig()) } // NewMConnectionWithConfig wraps net.Conn and creates multiplex connection with a config diff --git a/glide.lock b/glide.lock index 797c86031..423f18a09 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 92a49cbcf88a339e4d29559fe291c30e61eacda1020fd04dfcd97de834e18b3e -updated: 2017-04-10T11:17:14.66226896Z +hash: ef8ea7b02d9a133bfbfcf3f4615d43be0956ad2bc9eb0050e0721fca12d09308 +updated: 2017-04-14T08:28:07.579629532Z imports: - name: github.com/btcsuite/btcd version: 4b348c1d33373d672edd83fc576892d0e46686d2 @@ -25,9 +25,9 @@ imports: - name: github.com/tendermint/go-config version: 620dcbbd7d587cf3599dedbf329b64311b0c307a - name: github.com/tendermint/go-crypto - version: 3f47cfac5fcd9e0f1727c7db980b3559913b3e3a + version: 750b25c47a5782f5f2b773ed9e706cb82b3ccef4 - name: github.com/tendermint/go-data - version: c955b191240568440ea902e14dad2ce19727543a + version: e7fcc6d081ec8518912fcdc103188275f83a3ee5 - name: github.com/tendermint/go-flowrate version: a20c98e61957faa93b4014fbd902f20ab9317a6a subpackages: @@ -41,7 +41,7 @@ imports: subpackages: - term - name: golang.org/x/crypto - version: 9ef620b9ca2f82b55030ffd4f41327fa9e77a92c + version: cbc3d0884eac986df6e78a039b8792e869bff863 subpackages: - curve25519 - nacl/box diff --git a/glide.yaml b/glide.yaml index cf71cc670..2020b0424 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,8 +3,9 @@ import: - package: github.com/tendermint/go-common - package: github.com/tendermint/go-config - package: github.com/tendermint/go-crypto + version: develop - package: github.com/tendermint/go-data - version: c955b191240568440ea902e14dad2ce19727543a + version: develop - package: github.com/tendermint/go-flowrate subpackages: - flowrate @@ -16,6 +17,7 @@ import: - nacl/box - nacl/secretbox - ripemd160 +- package: github.com/pkg/errors testImport: - package: github.com/stretchr/testify subpackages: diff --git a/peer.go b/peer.go index 262569187..5461d7e8a 100644 --- a/peer.go +++ b/peer.go @@ -6,6 +6,7 @@ import ( "net" "time" + "github.com/pkg/errors" cmn "github.com/tendermint/go-common" crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" @@ -49,10 +50,10 @@ type PeerConfig struct { func DefaultPeerConfig() *PeerConfig { return &PeerConfig{ AuthEnc: true, - Fuzz: false, - HandshakeTimeout: 20 * time.Second, + HandshakeTimeout: 2 * time.Second, DialTimeout: 3 * time.Second, - MConfig: defaultMConnectionConfig(), + MConfig: DefaultMConnConfig(), + Fuzz: false, FuzzConfig: DefaultFuzzConnConfig(), } } @@ -64,7 +65,7 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] func newOutboundPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) { conn, err := dial(addr, config) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Error creating peer") } peer, err := newPeerFromConnAndConfig(conn, true, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) @@ -99,7 +100,7 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[ var err error conn, err = MakeSecretConnection(conn, ourNodePrivKey) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Error creating peer") } } @@ -157,10 +158,10 @@ func (p *Peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er log.Notice("Peer handshake", "peerNodeInfo", peerNodeInfo) }) if err1 != nil { - return err1 + return errors.Wrap(err1, "Error during handshake/write") } if err2 != nil { - return err2 + return errors.Wrap(err2, "Error during handshake/read") } if p.config.AuthEnc { @@ -174,7 +175,7 @@ func (p *Peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er // Remove deadline p.conn.SetDeadline(time.Time{}) - peerNodeInfo.RemoteAddr = p.RemoteAddr().String() + peerNodeInfo.RemoteAddr = p.Addr().String() p.NodeInfo = peerNodeInfo p.Key = peerNodeInfo.PubKey.KeyString() @@ -182,12 +183,12 @@ func (p *Peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er return nil } -// RemoteAddr returns the remote network address. -func (p *Peer) RemoteAddr() net.Addr { +// Addr returns peer's network address. +func (p *Peer) Addr() net.Addr { return p.conn.RemoteAddr() } -// PubKey returns the remote public key. +// PubKey returns peer's public key. func (p *Peer) PubKey() crypto.PubKeyEd25519 { if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() diff --git a/peer_test.go b/peer_test.go index 199428af1..5c23f3424 100644 --- a/peer_test.go +++ b/peer_test.go @@ -12,30 +12,56 @@ import ( crypto "github.com/tendermint/go-crypto" ) -func TestPeerStartStop(t *testing.T) { +func TestPeerBasic(t *testing.T) { assert, require := assert.New(t), require.New(t) // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() - p, err := createPeerAndPerformHandshake(rp.RemoteAddr()) + p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), DefaultPeerConfig()) require.Nil(err) p.Start() defer p.Stop() assert.True(p.IsRunning()) + assert.True(p.IsOutbound()) + assert.False(p.IsPersistent()) + p.makePersistent() + assert.True(p.IsPersistent()) + assert.Equal(rp.Addr().String(), p.Addr().String()) + assert.Equal(rp.PubKey(), p.PubKey()) } -func createPeerAndPerformHandshake(addr *NetAddress) (*Peer, error) { +func TestPeerWithoutAuthEnc(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + config := DefaultPeerConfig() + config.AuthEnc = false + + // simulate remote peer + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config} + rp.Start() + defer rp.Stop() + + p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config) + require.Nil(err) + + p.Start() + defer p.Stop() + + assert.True(p.IsRunning()) +} + +func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) (*Peer, error) { chDescs := []*ChannelDescriptor{ &ChannelDescriptor{ID: 0x01, Priority: 1}, } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} pk := crypto.GenPrivKeyEd25519() - p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p *Peer, r interface{}) {}, pk) + p, err := newOutboundPeerWithConfig(addr, reactorsByCh, chDescs, func(p *Peer, r interface{}) {}, pk, config) if err != nil { return nil, err } @@ -53,14 +79,19 @@ func createPeerAndPerformHandshake(addr *NetAddress) (*Peer, error) { type remotePeer struct { PrivKey crypto.PrivKeyEd25519 + Config *PeerConfig addr *NetAddress quit chan struct{} } -func (p *remotePeer) RemoteAddr() *NetAddress { +func (p *remotePeer) Addr() *NetAddress { return p.addr } +func (p *remotePeer) PubKey() crypto.PubKeyEd25519 { + return p.PrivKey.PubKey().(crypto.PubKeyEd25519) +} + func (p *remotePeer) Start() { l, e := net.Listen("tcp", "127.0.0.1:0") // any available address if e != nil { @@ -81,7 +112,7 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to accept conn: %+v", err) } - peer, err := newInboundPeer(conn, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p *Peer, r interface{}) {}, p.PrivKey) + peer, err := newInboundPeerWithConfig(conn, make(map[byte]Reactor), make([]*ChannelDescriptor, 0), func(p *Peer, r interface{}) {}, p.PrivKey, p.Config) if err != nil { golog.Fatalf("Failed to create a peer: %+v", err) } diff --git a/switch.go b/switch.go index 7505c0632..8ceb1ab70 100644 --- a/switch.go +++ b/switch.go @@ -200,7 +200,7 @@ func (sw *Switch) OnStop() { // NOTE: This performs a blocking handshake before the peer is added. // CONTRACT: If error is returned, peer is nil, and conn is immediately closed. func (sw *Switch) AddPeer(peer *Peer) error { - if err := sw.FilterConnByAddr(peer.RemoteAddr()); err != nil { + if err := sw.FilterConnByAddr(peer.Addr()); err != nil { return err } @@ -376,7 +376,7 @@ func (sw *Switch) Peers() IPeerSet { // Disconnect from a peer due to external error, retry if it is a persistent peer. // TODO: make record depending on reason. func (sw *Switch) StopPeerForError(peer *Peer, reason interface{}) { - addr := NewNetAddress(peer.RemoteAddr()) + addr := NewNetAddress(peer.Addr()) log.Notice("Stopping peer for error", "peer", peer, "error", reason) sw.stopAndRemovePeer(peer, reason) diff --git a/switch_test.go b/switch_test.go index 6a9d6e858..a81bb4ac0 100644 --- a/switch_test.go +++ b/switch_test.go @@ -238,11 +238,11 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { defer sw.Stop() // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) require.Nil(err) err = sw.AddPeer(peer) require.Nil(err) @@ -264,11 +264,11 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { defer sw.Stop() // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.RemoteAddr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey) peer.makePersistent() require.Nil(err) err = sw.AddPeer(peer) From ebe23f1379dc8610411e06ad797f16dc2a7f0635 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Apr 2017 14:21:58 +0400 Subject: [PATCH 19/47] refactor MConnection#sendBytes --- connection.go | 6 ++---- connection_test.go | 9 +++++++-- peer_test.go | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/connection.go b/connection.go index cf9bec4c5..6658722ab 100644 --- a/connection.go +++ b/connection.go @@ -557,14 +557,12 @@ func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel { // Goroutine-safe // Times out (and returns false) after defaultSendTimeout func (ch *Channel) sendBytes(bytes []byte) bool { - timeout := time.NewTimer(defaultSendTimeout) select { - case <-timeout.C: - // timeout - return false case ch.sendQueue <- bytes: atomic.AddInt32(&ch.sendQueueSize, 1) return true + case <-time.After(defaultSendTimeout): + return false } } diff --git a/connection_test.go b/connection_test.go index cd6bb4a8f..a7d6ebcf9 100644 --- a/connection_test.go +++ b/connection_test.go @@ -19,7 +19,7 @@ func createMConnection(conn net.Conn) *p2p.MConnection { } func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msgBytes []byte), onError func(r interface{})) *p2p.MConnection { - chDescs := []*p2p.ChannelDescriptor{&p2p.ChannelDescriptor{ID: 0x01, Priority: 1}} + chDescs := []*p2p.ChannelDescriptor{&p2p.ChannelDescriptor{ID: 0x01, Priority: 1, SendQueueCapacity: 1}} return p2p.NewMConnection(conn, chDescs, onReceive, onError) } @@ -37,13 +37,18 @@ func TestMConnectionSend(t *testing.T) { msg := "Ant-Man" assert.True(mconn.Send(0x01, msg)) - assert.False(mconn.CanSend(0x01)) + assert.False(mconn.CanSend(0x01), "CanSend should return false because queue is full") + // assert.False(mconn.Send(0x01, msg), "Send should return false because queue is full") + // assert.False(mconn.TrySend(0x01, msg), "TrySend should return false because queue is full") server.Read(make([]byte, len(msg))) assert.True(mconn.CanSend(0x01)) msg = "Spider-Man" assert.True(mconn.TrySend(0x01, msg)) server.Read(make([]byte, len(msg))) + + assert.False(mconn.CanSend(0x05), "CanSend should return false because channel is unknown") + assert.False(mconn.Send(0x05, "Absorbing Man"), "Send should return false because channel is unknown") } func TestMConnectionReceive(t *testing.T) { diff --git a/peer_test.go b/peer_test.go index 5c23f3424..5f3ed0e23 100644 --- a/peer_test.go +++ b/peer_test.go @@ -55,6 +55,27 @@ func TestPeerWithoutAuthEnc(t *testing.T) { assert.True(p.IsRunning()) } +func TestPeerSend(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + config := DefaultPeerConfig() + config.AuthEnc = false + + // simulate remote peer + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config} + rp.Start() + defer rp.Stop() + + p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config) + require.Nil(err) + + p.Start() + defer p.Stop() + + assert.True(p.CanSend(0x01)) + assert.True(p.Send(0x01, "Asylum")) +} + func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) (*Peer, error) { chDescs := []*ChannelDescriptor{ &ChannelDescriptor{ID: 0x01, Priority: 1}, From 6dc113aa808be0be85890b6a001011c29f1773e0 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Apr 2017 14:56:02 +0400 Subject: [PATCH 20/47] [netaddress] panic only when normal run --- netaddress.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/netaddress.go b/netaddress.go index 90fcf6a43..263ec9037 100644 --- a/netaddress.go +++ b/netaddress.go @@ -6,6 +6,7 @@ package p2p import ( "errors" + "flag" "net" "strconv" "time" @@ -13,28 +14,36 @@ import ( cmn "github.com/tendermint/go-common" ) +// NetAddress defines information about a peer on the network +// including its IP address, and port. type NetAddress struct { IP net.IP Port uint16 str string } +// NewNetAddress returns a new NetAddress using the provided TCP +// address. When testing, other net.Addr (except TCP) will result in +// using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will +// panic. // TODO: socks proxies? func NewNetAddress(addr net.Addr) *NetAddress { tcpAddr, ok := addr.(*net.TCPAddr) if !ok { - log.Warn(`Only TCPAddrs are supported. If used for anything but testing, - may result in undefined behaviour!`, "addr", addr) - return NewNetAddressIPPort(net.IP("0.0.0.0"), 0) - // NOTE: it would be nice to only not panic if we're in testing ... - // PanicSanity(Fmt("Only TCPAddrs are supported. Got: %v", addr)) + if flag.Lookup("test.v") == nil { // normal run + cmn.PanicSanity(cmn.Fmt("Only TCPAddrs are supported. Got: %v", addr)) + } else { // in testing + return NewNetAddressIPPort(net.IP("0.0.0.0"), 0) + } } ip := tcpAddr.IP port := uint16(tcpAddr.Port) return NewNetAddressIPPort(ip, port) } -// Also resolves the host if host is not an IP. +// NewNetAddressString returns a new NetAddress using the provided +// address in the form of "IP:Port". Also resolves the host if host +// is not an IP. func NewNetAddressString(addr string) (*NetAddress, error) { host, portStr, err := net.SplitHostPort(addr) @@ -62,6 +71,8 @@ func NewNetAddressString(addr string) (*NetAddress, error) { return na, nil } +// NewNetAddressStrings returns an array of NetAddress'es build using +// the provided strings. func NewNetAddressStrings(addrs []string) ([]*NetAddress, error) { netAddrs := make([]*NetAddress, len(addrs)) for i, addr := range addrs { @@ -74,6 +85,8 @@ func NewNetAddressStrings(addrs []string) ([]*NetAddress, error) { return netAddrs, nil } +// NewNetAddressIPPort returns a new NetAddress using the provided IP +// and port number. func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { na := &NetAddress{ IP: ip, @@ -86,23 +99,25 @@ func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { return na } +// Equals reports whether na and other are the same addresses. func (na *NetAddress) Equals(other interface{}) bool { if o, ok := other.(*NetAddress); ok { return na.String() == o.String() - } else { - return false } + + return false } func (na *NetAddress) Less(other interface{}) bool { if o, ok := other.(*NetAddress); ok { return na.String() < o.String() - } else { - cmn.PanicSanity("Cannot compare unequal types") - return false } + + cmn.PanicSanity("Cannot compare unequal types") + return false } +// String representation. func (na *NetAddress) String() string { if na.str == "" { na.str = net.JoinHostPort( @@ -113,6 +128,7 @@ func (na *NetAddress) String() string { return na.str } +// Dial calls net.Dial on the address. func (na *NetAddress) Dial() (net.Conn, error) { conn, err := net.Dial("tcp", na.String()) if err != nil { @@ -121,6 +137,7 @@ func (na *NetAddress) Dial() (net.Conn, error) { return conn, nil } +// DialTimeout calls net.DialTimeout on the address. func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { conn, err := net.DialTimeout("tcp", na.String(), timeout) if err != nil { @@ -129,6 +146,7 @@ func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { return conn, nil } +// Routable returns true if the address is routable. func (na *NetAddress) Routable() bool { // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? return na.Valid() && !(na.RFC1918() || na.RFC3927() || na.RFC4862() || @@ -142,10 +160,12 @@ func (na *NetAddress) Valid() bool { na.IP.Equal(net.IPv4bcast)) } +// Local returns true if it is a local address. func (na *NetAddress) Local() bool { return na.IP.IsLoopback() || zero4.Contains(na.IP) } +// ReachabilityTo checks whenever o can be reached from na. func (na *NetAddress) ReachabilityTo(o *NetAddress) int { const ( Unreachable = 0 From fbedb426ce2eb1cd82f474ceb6b38acffdf859ef Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Apr 2017 16:37:07 +0400 Subject: [PATCH 21/47] tests for NetAddress --- netaddress_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 netaddress_test.go diff --git a/netaddress_test.go b/netaddress_test.go new file mode 100644 index 000000000..db871fdec --- /dev/null +++ b/netaddress_test.go @@ -0,0 +1,113 @@ +package p2p + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewNetAddress(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") + require.Nil(err) + addr := NewNetAddress(tcpAddr) + + assert.Equal("127.0.0.1:8080", addr.String()) + + assert.NotPanics(func() { + NewNetAddress(&net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) + }, "Calling NewNetAddress with UDPAddr should not panic in testing") +} + +func TestNewNetAddressString(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + tests := []struct { + addr string + correct bool + }{ + {"127.0.0.1:8080", true}, + {"127.0.0:8080", false}, + {"a", false}, + {"127.0.0.1:a", false}, + {"a:8080", false}, + {"8082", false}, + {"127.0.0:8080000", false}, + } + + for _, t := range tests { + addr, err := NewNetAddressString(t.addr) + if t.correct { + require.Nil(err) + assert.Equal(t.addr, addr.String()) + } else { + require.NotNil(err) + } + } +} + +func TestNewNetAddressStrings(t *testing.T) { + assert, require := assert.New(t), require.New(t) + addrs, err := NewNetAddressStrings([]string{"127.0.0.1:8080", "127.0.0.2:8080"}) + require.Nil(err) + + assert.Equal(2, len(addrs)) +} + +func TestNewNetAddressIPPort(t *testing.T) { + assert := assert.New(t) + addr := NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8080) + + assert.Equal("127.0.0.1:8080", addr.String()) +} + +func TestNetAddressProperties(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // TODO add more test cases + tests := []struct { + addr string + valid bool + local bool + routable bool + }{ + {"127.0.0.1:8080", true, true, false}, + {"ya.ru:80", true, false, true}, + } + + for _, t := range tests { + addr, err := NewNetAddressString(t.addr) + require.Nil(err) + + assert.Equal(t.valid, addr.Valid()) + assert.Equal(t.local, addr.Local()) + assert.Equal(t.routable, addr.Routable()) + } +} + +func TestNetAddressReachabilityTo(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // TODO add more test cases + tests := []struct { + addr string + other string + reachability int + }{ + {"127.0.0.1:8080", "127.0.0.1:8081", 0}, + {"ya.ru:80", "127.0.0.1:8080", 1}, + } + + for _, t := range tests { + addr, err := NewNetAddressString(t.addr) + require.Nil(err) + + other, err := NewNetAddressString(t.other) + require.Nil(err) + + assert.Equal(t.reachability, addr.ReachabilityTo(other)) + } +} From 2ac69176e1f80329f08221f79a15199ef3f6a810 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 18 Apr 2017 12:11:48 +0400 Subject: [PATCH 22/47] add a comment for MConnection#CanSend also add a note to TestMConnectionSend --- connection.go | 2 ++ connection_test.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connection.go b/connection.go index 6658722ab..a14fa5ee4 100644 --- a/connection.go +++ b/connection.go @@ -258,6 +258,8 @@ func (c *MConnection) TrySend(chID byte, msg interface{}) bool { return ok } +// CanSend returns true if you can send more data onto the chID, false +// otherwise. Use only as a heuristic. func (c *MConnection) CanSend(chID byte) bool { if !c.IsRunning() { return false diff --git a/connection_test.go b/connection_test.go index a7d6ebcf9..84e20eee3 100644 --- a/connection_test.go +++ b/connection_test.go @@ -37,9 +37,9 @@ func TestMConnectionSend(t *testing.T) { msg := "Ant-Man" assert.True(mconn.Send(0x01, msg)) + // Note: subsequent Send/TrySend calls could pass because we are reading from + // the send queue in a separate goroutine. assert.False(mconn.CanSend(0x01), "CanSend should return false because queue is full") - // assert.False(mconn.Send(0x01, msg), "Send should return false because queue is full") - // assert.False(mconn.TrySend(0x01, msg), "TrySend should return false because queue is full") server.Read(make([]byte, len(msg))) assert.True(mconn.CanSend(0x01)) From 7d5b62b61f11d54d57113743fcca49f52775eddc Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 18 Apr 2017 23:58:24 -0400 Subject: [PATCH 23/47] CHANGELOG and version bump --- CHANGELOG.md | 21 +++++++++++++++++++++ version.go | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e30fcca..bba6dd77b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.5.0 (April 18, 2017) + +BREAKING CHANGES: + +- Remove or unexport methods from FuzzedConnection: Active, Mode, ProbDropRW, ProbDropConn, ProbSleep, MaxDelayMilliseconds, Fuzz +- switch.AddPeerWithConnection is unexported and replaced by switch.AddPeer +- switch.DialPeerWithAddress takes a bool, setting the peer as persistent or not + +FEATURES: + +- Persistent peers: any peer considered a "seed" will be reconnected to when the connection is dropped + + +IMPROVEMENTS: + +- Many more tests and comments +- Refactor configurations for less dependence on go-config. Introduces new structs PeerConfig, MConnConfig, FuzzConnConfig +- New methods on peer: CloseConn, HandshakeTimeout, IsPersistent, Addr, PubKey +- NewNetAddress supports a testing mode where the address defaults to 0.0.0.0:0 + + ## 0.4.0 (March 6, 2017) BREAKING CHANGES: diff --git a/version.go b/version.go index c13ec447b..9a4c7bbaf 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package p2p -const Version = "0.4.0" // DialSeeds returns an error +const Version = "0.5.0" From e05052b079fa407fe3aa74ebd0838c6a64b77835 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 19 Apr 2017 00:01:55 -0400 Subject: [PATCH 24/47] update glide --- glide.lock | 30 ++++++++++++++++-------------- glide.yaml | 4 ++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/glide.lock b/glide.lock index 423f18a09..71505ea05 100644 --- a/glide.lock +++ b/glide.lock @@ -1,31 +1,33 @@ -hash: ef8ea7b02d9a133bfbfcf3f4615d43be0956ad2bc9eb0050e0721fca12d09308 -updated: 2017-04-14T08:28:07.579629532Z +hash: f3d76bef9548cc37ad6038cb55f0812bac7e64735a99995c9da85010eef27f50 +updated: 2017-04-19T00:00:50.949249104-04:00 imports: - name: github.com/btcsuite/btcd - version: 4b348c1d33373d672edd83fc576892d0e46686d2 + version: b8df516b4b267acf2de46be593a9d948d1d2c420 subpackages: - btcec +- name: github.com/btcsuite/fastsha256 + version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/BurntSushi/toml - version: b26d9c308763d68093482582cea63d69be07a0f0 + version: 99064174e013895bbd9b025c31100bd1d9b590ca - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/mattn/go-colorable - version: ded68f7a9561c023e790de24279db7ebf473ea80 + version: 9fdad7c47650b7d2e1da50644c1f4ba7f172f252 - name: github.com/mattn/go-isatty - version: fc9e8d8ef48496124e79ae0df75490096eccf6fe + version: 56b76bdf51f7708750eac80fa38b952bb9f32639 - name: github.com/pkg/errors - version: ff09b135c25aae272398c51a07235b90a75aa4f0 + version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/tendermint/ed25519 version: 1f52c6f8b8a5c7908aff4497c186af344b428925 subpackages: - edwards25519 - extra25519 - name: github.com/tendermint/go-common - version: dcb015dff6c7af21e65c8e2f3b450df19d38c777 + version: f9e3db037330c8a8d61d3966de8473eaf01154fa - name: github.com/tendermint/go-config version: 620dcbbd7d587cf3599dedbf329b64311b0c307a - name: github.com/tendermint/go-crypto - version: 750b25c47a5782f5f2b773ed9e706cb82b3ccef4 + version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da - name: github.com/tendermint/go-data version: e7fcc6d081ec8518912fcdc103188275f83a3ee5 - name: github.com/tendermint/go-flowrate @@ -35,13 +37,13 @@ imports: - name: github.com/tendermint/go-logger version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 - name: github.com/tendermint/go-wire - version: f530b7af7a8b06e612c2063bff6ace49060a085e + version: c1c9a57ab8038448ddea1714c0698f8051e5748c - name: github.com/tendermint/log15 version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6 subpackages: - term - name: golang.org/x/crypto - version: cbc3d0884eac986df6e78a039b8792e869bff863 + version: 1f22c0103821b9390939b6776727195525381532 subpackages: - curve25519 - nacl/box @@ -52,12 +54,12 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/sys - version: f3918c30c5c2cb527c0b071a27c35120a6c0719a + version: 50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e subpackages: - unix testImports: - name: github.com/davecgh/go-spew - version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: - spew - name: github.com/pmezard/go-difflib @@ -65,7 +67,7 @@ testImports: subpackages: - difflib - name: github.com/stretchr/testify - version: 4d4bfba8f1d1027c4fdbe371823030df51419987 + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - assert - require diff --git a/glide.yaml b/glide.yaml index 2020b0424..e7edc80ae 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,7 +1,9 @@ package: github.com/tendermint/go-p2p import: - package: github.com/tendermint/go-common + version: develop - package: github.com/tendermint/go-config + version: develop - package: github.com/tendermint/go-crypto version: develop - package: github.com/tendermint/go-data @@ -10,7 +12,9 @@ import: subpackages: - flowrate - package: github.com/tendermint/go-logger + version: develop - package: github.com/tendermint/go-wire + version: develop - package: github.com/tendermint/log15 - package: golang.org/x/crypto subpackages: From 057cfb30f12137cfe65851be71beb50ae8ed617f Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Wed, 11 Jan 2017 15:03:14 +0400 Subject: [PATCH 25/47] remove unused error --- pex_reactor.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 4ac9306cf..2cffb529c 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -2,7 +2,6 @@ package p2p import ( "bytes" - "errors" "fmt" "math/rand" "reflect" @@ -12,8 +11,6 @@ import ( wire "github.com/tendermint/go-wire" ) -var pexErrInvalidMessage = errors.New("Invalid PEX message") - const ( PexChannel = byte(0x00) ensurePeersPeriodSeconds = 30 From 26f661a5dd0f60c140e6169d7ab90ecefaf51629 Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Wed, 11 Jan 2017 15:03:29 +0400 Subject: [PATCH 26/47] prefer short names --- pex_reactor.go | 112 ++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 2cffb529c..01d0cbd18 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -18,10 +18,8 @@ const ( maxPexMessageSize = 1048576 // 1MB ) -/* -PEXReactor handles PEX (peer exchange) and ensures that an -adequate number of peers are connected to the switch. -*/ +// PEXReactor handles PEX (peer exchange) and ensures that an +// adequate number of peers are connected to the switch. type PEXReactor struct { BaseReactor @@ -29,28 +27,28 @@ type PEXReactor struct { book *AddrBook } -func NewPEXReactor(book *AddrBook) *PEXReactor { - pexR := &PEXReactor{ - book: book, +func NewPEXReactor(b *AddrBook) *PEXReactor { + r := &PEXReactor{ + book: b, } - pexR.BaseReactor = *NewBaseReactor(log, "PEXReactor", pexR) - return pexR + r.BaseReactor = *NewBaseReactor(log, "PEXReactor", r) + return r } -func (pexR *PEXReactor) OnStart() error { - pexR.BaseReactor.OnStart() - pexR.book.Start() - go pexR.ensurePeersRoutine() +func (r *PEXReactor) OnStart() error { + r.BaseReactor.OnStart() + r.book.Start() + go r.ensurePeersRoutine() return nil } -func (pexR *PEXReactor) OnStop() { - pexR.BaseReactor.OnStop() - pexR.book.Stop() +func (r *PEXReactor) OnStop() { + r.BaseReactor.OnStop() + r.book.Stop() } -// Implements Reactor -func (pexR *PEXReactor) GetChannels() []*ChannelDescriptor { +// GetChannels implements Reactor +func (r *PEXReactor) GetChannels() []*ChannelDescriptor { return []*ChannelDescriptor{ &ChannelDescriptor{ ID: PexChannel, @@ -60,49 +58,45 @@ func (pexR *PEXReactor) GetChannels() []*ChannelDescriptor { } } -// Implements Reactor -func (pexR *PEXReactor) AddPeer(peer *Peer) { - // Add the peer to the address book - netAddr, err := NewNetAddressString(peer.ListenAddr) +// AddPeer implements Reactor by adding peer to the address book (if inbound) +// or by requesting more addresses (if outbound). +func (r *PEXReactor) AddPeer(p *Peer) { + netAddr, err := NewNetAddressString(p.ListenAddr) if err != nil { // this should never happen - log.Error("Error in AddPeer: invalid peer address", "addr", peer.ListenAddr, "error", err) + log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err) return } - if peer.IsOutbound() { - if pexR.book.NeedMoreAddrs() { - pexR.RequestPEX(peer) + if p.IsOutbound() { // For outbound peers, the address is already in the books + if r.book.NeedMoreAddrs() { + r.RequestPEX(p) } - } else { - // For inbound connections, the peer is its own source - // (For outbound peers, the address is already in the books) - pexR.book.AddAddress(netAddr, netAddr) + } else { // For inbound connections, the peer is its own source + r.book.AddAddress(netAddr, netAddr) } } -// Implements Reactor -func (pexR *PEXReactor) RemovePeer(peer *Peer, reason interface{}) { +// RemovePeer implements Reactor +func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { // TODO } -// Implements Reactor -// Handles incoming PEX messages. -func (pexR *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { - - // decode message +// Receive implements Reactor by handling incoming PEX messages. +func (r *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { _, msg, err := DecodeMessage(msgBytes) if err != nil { log.Warn("Error decoding message", "error", err) return } + log.Notice("Received message", "msg", msg) switch msg := msg.(type) { case *pexRequestMessage: // src requested some peers. // TODO: prevent abuse. - pexR.SendAddrs(src, pexR.book.GetSelection()) + r.SendAddrs(src, r.book.GetSelection()) case *pexAddrsMessage: // We received some peer addresses from src. // TODO: prevent abuse. @@ -110,7 +104,7 @@ func (pexR *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { srcAddr := src.Connection().RemoteAddress for _, addr := range msg.Addrs { if addr != nil { - pexR.book.AddAddress(addr, srcAddr) + r.book.AddAddress(addr, srcAddr) } } default: @@ -118,30 +112,32 @@ func (pexR *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { } } -// Asks peer for more addresses. -func (pexR *PEXReactor) RequestPEX(peer *Peer) { - peer.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}}) +// RequestPEX asks peer for more addresses. +func (r *PEXReactor) RequestPEX(p *Peer) { + p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}}) } -func (pexR *PEXReactor) SendAddrs(peer *Peer, addrs []*NetAddress) { - peer.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) +// SendAddrs sends addrs to the peer. +func (r *PEXReactor) SendAddrs(p *Peer, addrs []*NetAddress) { + p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) } // Ensures that sufficient peers are connected. (continuous) -func (pexR *PEXReactor) ensurePeersRoutine() { +func (r *PEXReactor) ensurePeersRoutine() { // Randomize when routine starts time.Sleep(time.Duration(rand.Int63n(500*ensurePeersPeriodSeconds)) * time.Millisecond) // fire once immediately. - pexR.ensurePeers() + r.ensurePeers() + // fire periodically timer := NewRepeatTimer("pex", ensurePeersPeriodSeconds*time.Second) FOR_LOOP: for { select { case <-timer.Ch: - pexR.ensurePeers() - case <-pexR.Quit: + r.ensurePeers() + case <-r.Quit: break FOR_LOOP } } @@ -151,8 +147,8 @@ FOR_LOOP: } // Ensures that sufficient peers are connected. (once) -func (pexR *PEXReactor) ensurePeers() { - numOutPeers, _, numDialing := pexR.Switch.NumPeers() +func (r *PEXReactor) ensurePeers() { + numOutPeers, _, numDialing := r.Switch.NumPeers() numToDial := minNumOutboundPeers - (numOutPeers + numDialing) log.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial) if numToDial <= 0 { @@ -168,13 +164,13 @@ func (pexR *PEXReactor) ensurePeers() { // Try to fetch a new peer 3 times. // This caps the maximum number of tries to 3 * numToDial. for j := 0; j < 3; j++ { - try := pexR.book.PickAddress(newBias) + try := r.book.PickAddress(newBias) if try == nil { break } alreadySelected := toDial.Has(try.IP.String()) - alreadyDialing := pexR.Switch.IsDialing(try) - alreadyConnected := pexR.Switch.Peers().Has(try.IP.String()) + alreadyDialing := r.Switch.IsDialing(try) + alreadyConnected := r.Switch.Peers().Has(try.IP.String()) if alreadySelected || alreadyDialing || alreadyConnected { /* log.Info("Cannot dial address", "addr", try, @@ -198,20 +194,20 @@ func (pexR *PEXReactor) ensurePeers() { // Dial picked addresses for _, item := range toDial.Values() { go func(picked *NetAddress) { - _, err := pexR.Switch.DialPeerWithAddress(picked, false) + _, err := r.Switch.DialPeerWithAddress(picked, false) if err != nil { - pexR.book.MarkAttempt(picked) + r.book.MarkAttempt(picked) } }(item.(*NetAddress)) } // If we need more addresses, pick a random peer and ask for more. - if pexR.book.NeedMoreAddrs() { - if peers := pexR.Switch.Peers().List(); len(peers) > 0 { + if r.book.NeedMoreAddrs() { + if peers := r.Switch.Peers().List(); len(peers) > 0 { i := rand.Int() % len(peers) peer := peers[i] log.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer) - pexR.RequestPEX(peer) + r.RequestPEX(peer) } } } From 3af7c677576f07e5610686e5e6a4a38e54448d6c Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Wed, 11 Jan 2017 17:17:09 +0400 Subject: [PATCH 27/47] add Dockerfile --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..3716185f2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:latest + +RUN curl https://glide.sh/get | sh + +RUN mkdir -p /go/src/github.com/tendermint/go-p2p +WORKDIR /go/src/github.com/tendermint/go-p2p + +COPY glide.yaml /go/src/github.com/tendermint/go-p2p/ +COPY glide.lock /go/src/github.com/tendermint/go-p2p/ + +RUN glide install + +COPY . /go/src/github.com/tendermint/go-p2p From 37d5a2cf3ed9f1562bbab946e960bf54eb41198d Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Wed, 11 Jan 2017 19:23:31 +0400 Subject: [PATCH 28/47] implement RemovePeer for PEXReactor --- pex_reactor.go | 7 +++++-- pex_reactor_tests.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 pex_reactor_tests.go diff --git a/pex_reactor.go b/pex_reactor.go index 01d0cbd18..8d49de992 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -73,13 +73,16 @@ func (r *PEXReactor) AddPeer(p *Peer) { r.RequestPEX(p) } } else { // For inbound connections, the peer is its own source - r.book.AddAddress(netAddr, netAddr) + addr := NewNetAddressString(p.ListenAddr) + r.book.AddAddress(addr, addr) } } // RemovePeer implements Reactor func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { - // TODO + addr := NewNetAddressString(p.ListenAddr) + // addr will be ejected from the book + r.book.MarkBad(addr) } // Receive implements Reactor by handling incoming PEX messages. diff --git a/pex_reactor_tests.go b/pex_reactor_tests.go new file mode 100644 index 000000000..5825495f7 --- /dev/null +++ b/pex_reactor_tests.go @@ -0,0 +1,50 @@ +package p2p + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + . "github.com/tendermint/go-common" +) + +func TestBasic(t *testing.T) { + book := NewAddrBook(createTempFileName("addrbook"), true) + r := NewPEXReactor(book) + + assert.NotNil(t, r) + assert.NotEmpty(t, r.GetChannels()) +} + +func TestAddRemovePeer(t *testing.T) { + book := NewAddrBook(createTempFileName("addrbook"), true) + r := NewPEXReactor(book) + + size := book.Size() + peer := createRandomPeer(false) + + r.AddPeer(peer) + assert.Equal(t, size+1, book.Size()) + + r.RemovePeer(peer, "peer not available") + assert.Equal(t, size, book.Size()) + + outboundPeer := createRandomPeer(true) + + r.AddPeer(outboundPeer) + assert.Equal(t, size, book.Size(), "size must not change") + + r.RemovePeer(outboundPeer, "peer not available") + assert.Equal(t, size, book.Size(), "size must not change") +} + +func createRandomPeer(outbound bool) *Peer { + return &Peer{ + Key: RandStr(12), + NodeInfo: &NodeInfo{ + RemoteAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + ListenAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + }, + outbound: outbound, + } +} From 0109f1e5245c0fc0f9121fde1d6b4aa8ccd4b079 Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Thu, 12 Jan 2017 00:17:15 +0400 Subject: [PATCH 29/47] test ensurePeers goroutine --- pex_reactor.go | 22 ++++++---- pex_reactor_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ pex_reactor_tests.go | 50 ----------------------- switch.go | 10 +++-- 4 files changed, 118 insertions(+), 61 deletions(-) create mode 100644 pex_reactor_test.go delete mode 100644 pex_reactor_tests.go diff --git a/pex_reactor.go b/pex_reactor.go index 8d49de992..56a1e3233 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -13,7 +13,7 @@ import ( const ( PexChannel = byte(0x00) - ensurePeersPeriodSeconds = 30 + defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 maxPexMessageSize = 1048576 // 1MB ) @@ -23,13 +23,15 @@ const ( type PEXReactor struct { BaseReactor - sw *Switch - book *AddrBook + sw *Switch + book *AddrBook + ensurePeersPeriod time.Duration } func NewPEXReactor(b *AddrBook) *PEXReactor { r := &PEXReactor{ - book: b, + book: b, + ensurePeersPeriod: defaultEnsurePeersPeriod, } r.BaseReactor = *NewBaseReactor(log, "PEXReactor", r) return r @@ -125,16 +127,22 @@ func (r *PEXReactor) SendAddrs(p *Peer, addrs []*NetAddress) { p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) } +// SetEnsurePeersPeriod sets period to ensure peers connected. +func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) { + r.ensurePeersPeriod = d +} + // Ensures that sufficient peers are connected. (continuous) func (r *PEXReactor) ensurePeersRoutine() { // Randomize when routine starts - time.Sleep(time.Duration(rand.Int63n(500*ensurePeersPeriodSeconds)) * time.Millisecond) + ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6 + time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond) // fire once immediately. r.ensurePeers() // fire periodically - timer := NewRepeatTimer("pex", ensurePeersPeriodSeconds*time.Second) + timer := NewRepeatTimer("pex", r.ensurePeersPeriod) FOR_LOOP: for { select { @@ -149,7 +157,7 @@ FOR_LOOP: timer.Stop() } -// Ensures that sufficient peers are connected. (once) +// ensurePeers ensures that sufficient peers are connected. (once) func (r *PEXReactor) ensurePeers() { numOutPeers, _, numDialing := r.Switch.NumPeers() numToDial := minNumOutboundPeers - (numOutPeers + numDialing) diff --git a/pex_reactor_test.go b/pex_reactor_test.go new file mode 100644 index 000000000..3674d3f31 --- /dev/null +++ b/pex_reactor_test.go @@ -0,0 +1,97 @@ +package p2p + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + . "github.com/tendermint/go-common" +) + +func TestPEXReactorBasic(t *testing.T) { + book := NewAddrBook(createTempFileName("addrbook"), true) + r := NewPEXReactor(book) + + assert.NotNil(t, r) + assert.NotEmpty(t, r.GetChannels()) +} + +func TestPEXReactorAddRemovePeer(t *testing.T) { + book := NewAddrBook(createTempFileName("addrbook"), true) + r := NewPEXReactor(book) + + size := book.Size() + peer := createRandomPeer(false) + + r.AddPeer(peer) + assert.Equal(t, size+1, book.Size()) + + r.RemovePeer(peer, "peer not available") + assert.Equal(t, size, book.Size()) + + outboundPeer := createRandomPeer(true) + + r.AddPeer(outboundPeer) + assert.Equal(t, size, book.Size(), "size must not change") + + r.RemovePeer(outboundPeer, "peer not available") + assert.Equal(t, size, book.Size(), "size must not change") +} + +func TestPEXReactorRunning(t *testing.T) { + N := 3 + switches := make([]*Switch, N) + + book := NewAddrBook(createTempFileName("addrbook"), false) + + // create switches + for i := 0; i < N; i++ { + switches[i] = makeSwitch(i, "172.17.0.2", "123.123.123", func(i int, sw *Switch) *Switch { + r := NewPEXReactor(book) + r.SetEnsurePeersPeriod(250 * time.Millisecond) + sw.AddReactor("pex", r) + return sw + }) + } + + // fill the address book and add listeners + for _, s := range switches { + addr := NewNetAddressString(s.NodeInfo().ListenAddr) + book.AddAddress(addr, addr) + s.AddListener(NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true)) + } + + // start switches + for _, s := range switches { + _, err := s.Start() // start switch and reactors + require.Nil(t, err) + } + + time.Sleep(1 * time.Second) + + // check peers are connected after some time + for _, s := range switches { + outbound, inbound, _ := s.NumPeers() + if outbound+inbound == 0 { + t.Errorf("%v expected to be connected to at least one peer", s.NodeInfo().ListenAddr) + } + } + + // stop them + for _, s := range switches { + s.Stop() + } +} + +func createRandomPeer(outbound bool) *Peer { + return &Peer{ + Key: RandStr(12), + NodeInfo: &NodeInfo{ + RemoteAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + ListenAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + }, + outbound: outbound, + } +} diff --git a/pex_reactor_tests.go b/pex_reactor_tests.go deleted file mode 100644 index 5825495f7..000000000 --- a/pex_reactor_tests.go +++ /dev/null @@ -1,50 +0,0 @@ -package p2p - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/assert" - . "github.com/tendermint/go-common" -) - -func TestBasic(t *testing.T) { - book := NewAddrBook(createTempFileName("addrbook"), true) - r := NewPEXReactor(book) - - assert.NotNil(t, r) - assert.NotEmpty(t, r.GetChannels()) -} - -func TestAddRemovePeer(t *testing.T) { - book := NewAddrBook(createTempFileName("addrbook"), true) - r := NewPEXReactor(book) - - size := book.Size() - peer := createRandomPeer(false) - - r.AddPeer(peer) - assert.Equal(t, size+1, book.Size()) - - r.RemovePeer(peer, "peer not available") - assert.Equal(t, size, book.Size()) - - outboundPeer := createRandomPeer(true) - - r.AddPeer(outboundPeer) - assert.Equal(t, size, book.Size(), "size must not change") - - r.RemovePeer(outboundPeer, "peer not available") - assert.Equal(t, size, book.Size(), "size must not change") -} - -func createRandomPeer(outbound bool) *Peer { - return &Peer{ - Key: RandStr(12), - NodeInfo: &NodeInfo{ - RemoteAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), - ListenAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), - }, - outbound: outbound, - } -} diff --git a/switch.go b/switch.go index 8ceb1ab70..6835e0a4b 100644 --- a/switch.go +++ b/switch.go @@ -531,10 +531,12 @@ func makeSwitch(i int, network, version string, initSwitch func(int, *Switch) *S // TODO: let the config be passed in? s := initSwitch(i, NewSwitch(cfg.NewMapConfig(nil))) s.SetNodeInfo(&NodeInfo{ - PubKey: privKey.PubKey().(crypto.PubKeyEd25519), - Moniker: Fmt("switch%d", i), - Network: network, - Version: version, + PubKey: privKey.PubKey().(crypto.PubKeyEd25519), + Moniker: Fmt("switch%d", i), + Network: network, + Version: version, + RemoteAddr: Fmt("%v:%v", network, rand.Intn(64512)+1023), + ListenAddr: Fmt("%v:%v", network, rand.Intn(64512)+1023), }) s.SetNodePrivKey(privKey) return s From 1a59b6a3b4e318fe059cfcafa1b7b5f6a029e090 Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Thu, 12 Jan 2017 13:26:29 +0400 Subject: [PATCH 30/47] replace repeate timer with simple ticker no need for repeate timer here (no need for goroutine safety) --- pex_reactor.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 56a1e3233..5cea1cb3c 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -142,19 +142,18 @@ func (r *PEXReactor) ensurePeersRoutine() { r.ensurePeers() // fire periodically - timer := NewRepeatTimer("pex", r.ensurePeersPeriod) + ticker := time.NewTicker(r.ensurePeersPeriod) FOR_LOOP: for { select { - case <-timer.Ch: + case <-ticker.C: r.ensurePeers() case <-r.Quit: break FOR_LOOP } } - // Cleanup - timer.Stop() + ticker.Stop() } // ensurePeers ensures that sufficient peers are connected. (once) From 47df1fb7d4646a5a69399839317d08d2bf18f086 Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Thu, 12 Jan 2017 15:16:51 +0400 Subject: [PATCH 31/47] test PEXReactor#Receive --- pex_reactor_test.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pex_reactor_test.go b/pex_reactor_test.go index 3674d3f31..762d96810 100644 --- a/pex_reactor_test.go +++ b/pex_reactor_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" . "github.com/tendermint/go-common" + wire "github.com/tendermint/go-wire" ) func TestPEXReactorBasic(t *testing.T) { @@ -85,13 +86,30 @@ func TestPEXReactorRunning(t *testing.T) { } } +func TestPEXReactorReceive(t *testing.T) { + book := NewAddrBook(createTempFileName("addrbook"), true) + r := NewPEXReactor(book) + + peer := createRandomPeer(false) + + size := book.Size() + addrs := []*NetAddress{NewNetAddressString(peer.ListenAddr)} + msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) + r.Receive(PexChannel, peer, msg) + assert.Equal(t, size+1, book.Size()) + + msg = wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) + r.Receive(PexChannel, peer, msg) +} + func createRandomPeer(outbound bool) *Peer { + addr := Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) return &Peer{ Key: RandStr(12), NodeInfo: &NodeInfo{ - RemoteAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), - ListenAddr: Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + ListenAddr: addr, }, outbound: outbound, + mconn: &MConnection{RemoteAddress: NewNetAddressString(addr)}, } } From 873d34157d51d09116b0c0a99557367aafbd1897 Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Thu, 12 Jan 2017 17:56:40 +0400 Subject: [PATCH 32/47] prevent abuse from peers --- pex_reactor.go | 59 ++++++++++++++++++++++++++++++++++++++------- pex_reactor_test.go | 15 ++++++++++++ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 5cea1cb3c..3e03138c1 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -12,10 +12,15 @@ import ( ) const ( - PexChannel = byte(0x00) + PexChannel = byte(0x00) + // period to ensure peers connected defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 maxPexMessageSize = 1048576 // 1MB + + // maximum messages one peer can send to us during `msgCountByPeerFlushInterval` + defaultMaxMsgCountByPeer = 1000 + msgCountByPeerFlushInterval = 1 * time.Hour ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -26,12 +31,18 @@ type PEXReactor struct { sw *Switch book *AddrBook ensurePeersPeriod time.Duration + + // tracks message count by peer, so we can prevent abuse + msgCountByPeer map[string]uint16 + maxMsgCountByPeer uint16 } func NewPEXReactor(b *AddrBook) *PEXReactor { r := &PEXReactor{ book: b, ensurePeersPeriod: defaultEnsurePeersPeriod, + msgCountByPeer: make(map[string]uint16), + maxMsgCountByPeer: defaultMaxMsgCountByPeer, } r.BaseReactor = *NewBaseReactor(log, "PEXReactor", r) return r @@ -41,6 +52,7 @@ func (r *PEXReactor) OnStart() error { r.BaseReactor.OnStart() r.book.Start() go r.ensurePeersRoutine() + go r.flushMsgCountByPeer() return nil } @@ -89,24 +101,29 @@ func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { + srcAddr := src.Connection().RemoteAddress + srcAddrStr := srcAddr.String() + r.msgCountByPeer[srcAddrStr]++ + if r.ReachedMaxMsgCountForPeer(srcAddrStr) { + log.Warn("Maximum number of messages reached for peer", "peer", srcAddrStr) + // TODO remove src from peers? + return + } + _, msg, err := DecodeMessage(msgBytes) if err != nil { log.Warn("Error decoding message", "error", err) return } - log.Notice("Received message", "msg", msg) switch msg := msg.(type) { case *pexRequestMessage: // src requested some peers. - // TODO: prevent abuse. r.SendAddrs(src, r.book.GetSelection()) case *pexAddrsMessage: // We received some peer addresses from src. - // TODO: prevent abuse. // (We don't want to get spammed with bad peers) - srcAddr := src.Connection().RemoteAddress for _, addr := range msg.Addrs { if addr != nil { r.book.AddAddress(addr, srcAddr) @@ -132,6 +149,17 @@ func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) { r.ensurePeersPeriod = d } +// SetMaxMsgCountByPeer sets maximum messages one peer can send to us during 'msgCountByPeerFlushInterval'. +func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) { + r.maxMsgCountByPeer = v +} + +// ReachedMaxMsgCountForPeer returns true if we received too many +// messages from peer with address `addr`. +func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool { + return r.msgCountByPeer[addr] >= r.maxMsgCountByPeer +} + // Ensures that sufficient peers are connected. (continuous) func (r *PEXReactor) ensurePeersRoutine() { // Randomize when routine starts @@ -143,17 +171,16 @@ func (r *PEXReactor) ensurePeersRoutine() { // fire periodically ticker := time.NewTicker(r.ensurePeersPeriod) -FOR_LOOP: + for { select { case <-ticker.C: r.ensurePeers() case <-r.Quit: - break FOR_LOOP + ticker.Stop() + return } } - - ticker.Stop() } // ensurePeers ensures that sufficient peers are connected. (once) @@ -222,6 +249,20 @@ func (r *PEXReactor) ensurePeers() { } } +func (r *PEXReactor) flushMsgCountByPeer() { + ticker := time.NewTicker(msgCountByPeerFlushInterval) + + for { + select { + case <-ticker.C: + r.msgCountByPeer = make(map[string]uint16) + case <-r.Quit: + ticker.Stop() + return + } + } +} + //----------------------------------------------------------------------------- // Messages diff --git a/pex_reactor_test.go b/pex_reactor_test.go index 762d96810..7c3a2dae4 100644 --- a/pex_reactor_test.go +++ b/pex_reactor_test.go @@ -102,6 +102,21 @@ func TestPEXReactorReceive(t *testing.T) { r.Receive(PexChannel, peer, msg) } +func TestPEXReactorAbuseFromPeer(t *testing.T) { + book := NewAddrBook(createTempFileName("addrbook"), true) + r := NewPEXReactor(book) + r.SetMaxMsgCountByPeer(5) + + peer := createRandomPeer(false) + + msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) + for i := 0; i < 10; i++ { + r.Receive(PexChannel, peer, msg) + } + + assert.True(t, r.ReachedMaxMsgCountForPeer(peer.ListenAddr)) +} + func createRandomPeer(outbound bool) *Peer { addr := Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) return &Peer{ From 07e7b98c7034befb350c878bf68e6175d6a5658a Mon Sep 17 00:00:00 2001 From: Anton Kalyaev Date: Thu, 12 Jan 2017 22:28:40 +0400 Subject: [PATCH 33/47] improve ensurePeers routine optimizations: - if we move peer to the old bucket as soon as connected and pick only from new group, we can skip alreadyConnected check --- pex_reactor.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 3e03138c1..a16823e33 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -191,30 +191,27 @@ func (r *PEXReactor) ensurePeers() { if numToDial <= 0 { return } - toDial := NewCMap() + + toDial := make(map[string]*NetAddress) // Try to pick numToDial addresses to dial. - // TODO: improve logic. for i := 0; i < numToDial; i++ { - newBias := MinInt(numOutPeers, 8)*10 + 10 var picked *NetAddress // Try to fetch a new peer 3 times. // This caps the maximum number of tries to 3 * numToDial. for j := 0; j < 3; j++ { - try := r.book.PickAddress(newBias) + // NOTE always picking from the new group because old one stores already + // connected peers. + try := r.book.PickAddress(100) if try == nil { break } - alreadySelected := toDial.Has(try.IP.String()) + _, alreadySelected := toDial[try.IP.String()] alreadyDialing := r.Switch.IsDialing(try) - alreadyConnected := r.Switch.Peers().Has(try.IP.String()) - if alreadySelected || alreadyDialing || alreadyConnected { - /* - log.Info("Cannot dial address", "addr", try, - "alreadySelected", alreadySelected, - "alreadyDialing", alreadyDialing, - "alreadyConnected", alreadyConnected) - */ + if alreadySelected || alreadyDialing { + // log.Info("Cannot dial address", "addr", try, + // "alreadySelected", alreadySelected, + // "alreadyDialing", alreadyDialing) continue } else { log.Info("Will dial address", "addr", try) @@ -225,17 +222,20 @@ func (r *PEXReactor) ensurePeers() { if picked == nil { continue } - toDial.Set(picked.IP.String(), picked) + toDial[picked.IP.String()] = picked } // Dial picked addresses - for _, item := range toDial.Values() { + for _, item := range toDial { go func(picked *NetAddress) { _, err := r.Switch.DialPeerWithAddress(picked, false) if err != nil { r.book.MarkAttempt(picked) + } else { + // move address to the old group + r.book.MarkGood(picked) } - }(item.(*NetAddress)) + }(item) } // If we need more addresses, pick a random peer and ask for more. From 5eeaffd38ee08ab2b765583df2682225b7f9fc0d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 16 Jan 2017 20:31:50 +0400 Subject: [PATCH 34/47] do not create file, just temp dir --- pex_reactor_test.go | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/pex_reactor_test.go b/pex_reactor_test.go index 7c3a2dae4..67d123bec 100644 --- a/pex_reactor_test.go +++ b/pex_reactor_test.go @@ -1,18 +1,24 @@ package p2p import ( + "io/ioutil" "math/rand" + "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" wire "github.com/tendermint/go-wire" ) func TestPEXReactorBasic(t *testing.T) { - book := NewAddrBook(createTempFileName("addrbook"), true) + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) + book := NewAddrBook(dir+"addrbook.json", true) + r := NewPEXReactor(book) assert.NotNil(t, r) @@ -20,7 +26,11 @@ func TestPEXReactorBasic(t *testing.T) { } func TestPEXReactorAddRemovePeer(t *testing.T) { - book := NewAddrBook(createTempFileName("addrbook"), true) + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) + book := NewAddrBook(dir+"addrbook.json", true) + r := NewPEXReactor(book) size := book.Size() @@ -45,11 +55,14 @@ func TestPEXReactorRunning(t *testing.T) { N := 3 switches := make([]*Switch, N) - book := NewAddrBook(createTempFileName("addrbook"), false) + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) + book := NewAddrBook(dir+"addrbook.json", false) // create switches for i := 0; i < N; i++ { - switches[i] = makeSwitch(i, "172.17.0.2", "123.123.123", func(i int, sw *Switch) *Switch { + switches[i] = makeSwitch(i, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { r := NewPEXReactor(book) r.SetEnsurePeersPeriod(250 * time.Millisecond) sw.AddReactor("pex", r) @@ -87,7 +100,11 @@ func TestPEXReactorRunning(t *testing.T) { } func TestPEXReactorReceive(t *testing.T) { - book := NewAddrBook(createTempFileName("addrbook"), true) + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) + book := NewAddrBook(dir+"addrbook.json", true) + r := NewPEXReactor(book) peer := createRandomPeer(false) @@ -103,7 +120,11 @@ func TestPEXReactorReceive(t *testing.T) { } func TestPEXReactorAbuseFromPeer(t *testing.T) { - book := NewAddrBook(createTempFileName("addrbook"), true) + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) + book := NewAddrBook(dir+"addrbook.json", true) + r := NewPEXReactor(book) r.SetMaxMsgCountByPeer(5) @@ -118,9 +139,9 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { } func createRandomPeer(outbound bool) *Peer { - addr := Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) + addr := cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) return &Peer{ - Key: RandStr(12), + Key: cmn.RandStr(12), NodeInfo: &NodeInfo{ ListenAddr: addr, }, From 590efc10404e3bf7795e382012d71b0005f44693 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 16 Jan 2017 23:36:10 +0400 Subject: [PATCH 35/47] call saveToFile OnStop This is better than waiting because while we wait, anything could happen (crash, timeout of the code who's using addrbook, ...). If we save immediately, we have much greater chances of success. --- addrbook.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/addrbook.go b/addrbook.go index ace0dba4a..7a65cb4be 100644 --- a/addrbook.go +++ b/addrbook.go @@ -15,7 +15,6 @@ import ( "time" . "github.com/tendermint/go-common" - "github.com/tendermint/go-crypto" ) const ( @@ -86,7 +85,6 @@ type AddrBook struct { addrLookup map[string]*knownAddress // new & old addrNew []map[string]*knownAddress addrOld []map[string]*knownAddress - wg sync.WaitGroup nOld int nNew int } @@ -128,7 +126,6 @@ func (a *AddrBook) init() { func (a *AddrBook) OnStart() error { a.BaseService.OnStart() a.loadFromFile(a.filePath) - a.wg.Add(1) go a.saveRoutine() return nil } @@ -139,6 +136,7 @@ func (a *AddrBook) OnStop() { func (a *AddrBook) Wait() { a.wg.Wait() + a.saveToFile(a.filePath) } func (a *AddrBook) AddOurAddress(addr *NetAddress) { @@ -309,6 +307,8 @@ type addrBookJSON struct { } func (a *AddrBook) saveToFile(filePath string) { + log.Info("Saving AddrBook to file", "size", a.Size()) + // Compile Addrs addrs := []*knownAddress{} for _, ka := range a.addrLookup { @@ -386,7 +386,6 @@ out: for { select { case <-dumpAddressTicker.C: - log.Info("Saving AddrBook to file", "size", a.Size()) a.saveToFile(a.filePath) case <-a.Quit: break out @@ -394,7 +393,6 @@ out: } dumpAddressTicker.Stop() a.saveToFile(a.filePath) - a.wg.Done() log.Notice("Address handler done") } From 52d9cf080e08fd539a8aebbb82aacaa9a5459551 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 16 Jan 2017 23:57:07 +0400 Subject: [PATCH 36/47] make GoLint happy --- pex_reactor.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index a16823e33..89599f4b4 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -7,12 +7,13 @@ import ( "reflect" "time" - . "github.com/tendermint/go-common" wire "github.com/tendermint/go-wire" ) const ( + // PexChannel is a channel for PEX messages PexChannel = byte(0x00) + // period to ensure peers connected defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 @@ -25,6 +26,8 @@ const ( // PEXReactor handles PEX (peer exchange) and ensures that an // adequate number of peers are connected to the switch. +// +// It uses `AddrBook` (address book) to store `NetAddress`es of the peers. type PEXReactor struct { BaseReactor @@ -37,6 +40,7 @@ type PEXReactor struct { maxMsgCountByPeer uint16 } +// NewPEXReactor creates new PEX reactor. func NewPEXReactor(b *AddrBook) *PEXReactor { r := &PEXReactor{ book: b, @@ -48,6 +52,7 @@ func NewPEXReactor(b *AddrBook) *PEXReactor { return r } +// OnStart implements BaseService func (r *PEXReactor) OnStart() error { r.BaseReactor.OnStart() r.book.Start() @@ -56,6 +61,7 @@ func (r *PEXReactor) OnStart() error { return nil } +// OnStop implements BaseService func (r *PEXReactor) OnStop() { r.BaseReactor.OnStop() r.book.Stop() @@ -92,7 +98,7 @@ func (r *PEXReactor) AddPeer(p *Peer) { } } -// RemovePeer implements Reactor +// RemovePeer implements Reactor by removing peer from the address book. func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { addr := NewNetAddressString(p.ListenAddr) // addr will be ejected from the book @@ -130,7 +136,7 @@ func (r *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { } } default: - log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) + log.Warn(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) } } @@ -271,6 +277,8 @@ const ( msgTypeAddrs = byte(0x02) ) +// PexMessage is a primary type for PEX messages. Underneath, it could contain +// either pexRequestMessage, or pexAddrsMessage messages. type PexMessage interface{} var _ = wire.RegisterInterface( @@ -279,6 +287,7 @@ var _ = wire.RegisterInterface( wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs}, ) +// DecodeMessage implements interface registered above. func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) { msgType = bz[0] n := new(int) From 324293f4cbbc108c66fa09bc24ac3e863f2b7671 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 17 Jan 2017 22:30:03 +0400 Subject: [PATCH 37/47] note on preventing abuse [ci skip] --- pex_reactor.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pex_reactor.go b/pex_reactor.go index 89599f4b4..3a3d66714 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -28,6 +28,18 @@ const ( // adequate number of peers are connected to the switch. // // It uses `AddrBook` (address book) to store `NetAddress`es of the peers. +// +// ## Preventing abuse +// +// For now, it just limits the number of messages from one peer to +// `defaultMaxMsgCountByPeer` messages per `msgCountByPeerFlushInterval` (1000 +// msg/hour). +// +// NOTE [2017-01-17]: +// Limiting is fine for now. Maybe down the road we want to keep track of the +// quality of peer messages so if peerA keeps telling us about peers we can't +// connect to then maybe we should care less about peerA. But I don't think +// that kind of complexity is priority right now. type PEXReactor struct { BaseReactor From cf18bf296628e6b32f6562c40671e0c331102b0e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 20 Jan 2017 15:27:33 +0400 Subject: [PATCH 38/47] add public RemoveAddress API after discussion with @ebuchman (https://github.com/tendermint/go-p2p/pull/10#discussion_r96471729) --- addrbook.go | 10 ++++++++-- addrbook_test.go | 19 +++++++++++++++++++ pex_reactor.go | 8 ++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/addrbook.go b/addrbook.go index 7a65cb4be..9dbbc2bf5 100644 --- a/addrbook.go +++ b/addrbook.go @@ -252,15 +252,21 @@ func (a *AddrBook) MarkAttempt(addr *NetAddress) { ka.markAttempt() } +// MarkBad currently just ejects the address. In the future, consider +// blacklisting. func (a *AddrBook) MarkBad(addr *NetAddress) { + a.RemoveAddress(addr) +} + +// RemoveAddress removes the address from the book. +func (a *AddrBook) RemoveAddress(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() ka := a.addrLookup[addr.String()] if ka == nil { return } - // We currently just eject the address. - // In the future, consider blacklisting. + log.Info("Remove address from book", "addr", addr) a.removeFromAllBuckets(ka) } diff --git a/addrbook_test.go b/addrbook_test.go index 7e8cb8d76..0f5ced5cf 100644 --- a/addrbook_test.go +++ b/addrbook_test.go @@ -9,6 +9,9 @@ import ( "github.com/stretchr/testify/assert" ) + "github.com/stretchr/testify/assert" +) + func createTempFileName(prefix string) string { f, err := ioutil.TempFile("", prefix) if err != nil { @@ -148,3 +151,19 @@ func randIPv4Address(t *testing.T) *NetAddress { } } } + +func TestAddrBookRemoveAddress(t *testing.T) { + fname := createTempFileName("addrbook_test") + book := NewAddrBook(fname, true) + + addr := randIPv4Address() + book.AddAddress(addr, addr) + assert.Equal(t, 1, book.Size()) + + book.RemoveAddress(addr) + assert.Equal(t, 0, book.Size()) + + nonExistingAddr := randIPv4Address() + book.RemoveAddress(nonExistingAddr) + assert.Equal(t, 0, book.Size()) +} diff --git a/pex_reactor.go b/pex_reactor.go index 3a3d66714..79a9200ff 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -111,10 +111,14 @@ func (r *PEXReactor) AddPeer(p *Peer) { } // RemovePeer implements Reactor by removing peer from the address book. +// +// The peer will be proposed to us by other peers (PexAddrsMessage) or himself +// and we will add him again upon successful connection. Note that other peers +// will remove him too. The peer will need to send first requests to others by +// himself (he will have an addrbook or the seeds). func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { addr := NewNetAddressString(p.ListenAddr) - // addr will be ejected from the book - r.book.MarkBad(addr) + r.book.RemoveAddress(addr) } // Receive implements Reactor by handling incoming PEX messages. From 0277e52bd5f27e4f2148353be6d674871a6ba41e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Apr 2017 23:59:22 +0400 Subject: [PATCH 39/47] fix merge --- addrbook.go | 2 +- addrbook_test.go | 7 ++----- pex_reactor.go | 21 ++++++++++++--------- pex_reactor_test.go | 8 +++++--- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/addrbook.go b/addrbook.go index 9dbbc2bf5..c33b97aa8 100644 --- a/addrbook.go +++ b/addrbook.go @@ -15,6 +15,7 @@ import ( "time" . "github.com/tendermint/go-common" + crypto "github.com/tendermint/go-crypto" ) const ( @@ -135,7 +136,6 @@ func (a *AddrBook) OnStop() { } func (a *AddrBook) Wait() { - a.wg.Wait() a.saveToFile(a.filePath) } diff --git a/addrbook_test.go b/addrbook_test.go index 0f5ced5cf..16aea8ef9 100644 --- a/addrbook_test.go +++ b/addrbook_test.go @@ -9,9 +9,6 @@ import ( "github.com/stretchr/testify/assert" ) - "github.com/stretchr/testify/assert" -) - func createTempFileName(prefix string) string { f, err := ioutil.TempFile("", prefix) if err != nil { @@ -156,14 +153,14 @@ func TestAddrBookRemoveAddress(t *testing.T) { fname := createTempFileName("addrbook_test") book := NewAddrBook(fname, true) - addr := randIPv4Address() + addr := randIPv4Address(t) book.AddAddress(addr, addr) assert.Equal(t, 1, book.Size()) book.RemoveAddress(addr) assert.Equal(t, 0, book.Size()) - nonExistingAddr := randIPv4Address() + nonExistingAddr := randIPv4Address(t) book.RemoveAddress(nonExistingAddr) assert.Equal(t, 0, book.Size()) } diff --git a/pex_reactor.go b/pex_reactor.go index 79a9200ff..a86bebe12 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -93,19 +93,17 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { // AddPeer implements Reactor by adding peer to the address book (if inbound) // or by requesting more addresses (if outbound). func (r *PEXReactor) AddPeer(p *Peer) { - netAddr, err := NewNetAddressString(p.ListenAddr) - if err != nil { - // this should never happen - log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err) - return - } - if p.IsOutbound() { // For outbound peers, the address is already in the books if r.book.NeedMoreAddrs() { r.RequestPEX(p) } } else { // For inbound connections, the peer is its own source - addr := NewNetAddressString(p.ListenAddr) + addr, err := NewNetAddressString(p.ListenAddr) + if err != nil { + // this should never happen + log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err) + return + } r.book.AddAddress(addr, addr) } } @@ -117,7 +115,12 @@ func (r *PEXReactor) AddPeer(p *Peer) { // will remove him too. The peer will need to send first requests to others by // himself (he will have an addrbook or the seeds). func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { - addr := NewNetAddressString(p.ListenAddr) + addr, err := NewNetAddressString(p.ListenAddr) + if err != nil { + // this should never happen + log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err) + return + } r.book.RemoveAddress(addr) } diff --git a/pex_reactor_test.go b/pex_reactor_test.go index 67d123bec..525efd3cb 100644 --- a/pex_reactor_test.go +++ b/pex_reactor_test.go @@ -72,7 +72,7 @@ func TestPEXReactorRunning(t *testing.T) { // fill the address book and add listeners for _, s := range switches { - addr := NewNetAddressString(s.NodeInfo().ListenAddr) + addr, _ := NewNetAddressString(s.NodeInfo().ListenAddr) book.AddAddress(addr, addr) s.AddListener(NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true)) } @@ -110,7 +110,8 @@ func TestPEXReactorReceive(t *testing.T) { peer := createRandomPeer(false) size := book.Size() - addrs := []*NetAddress{NewNetAddressString(peer.ListenAddr)} + netAddr, _ := NewNetAddressString(peer.ListenAddr) + addrs := []*NetAddress{netAddr} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) r.Receive(PexChannel, peer, msg) assert.Equal(t, size+1, book.Size()) @@ -140,12 +141,13 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { func createRandomPeer(outbound bool) *Peer { addr := cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) + netAddr, _ := NewNetAddressString(addr) return &Peer{ Key: cmn.RandStr(12), NodeInfo: &NodeInfo{ ListenAddr: addr, }, outbound: outbound, - mconn: &MConnection{RemoteAddress: NewNetAddressString(addr)}, + mconn: &MConnection{RemoteAddress: netAddr}, } } From 4c0d1d3ad2987fe99826915dc935d4c51339aade Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Apr 2017 13:03:26 +0400 Subject: [PATCH 40/47] return wg to addrbook --- addrbook.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/addrbook.go b/addrbook.go index c33b97aa8..5450c515f 100644 --- a/addrbook.go +++ b/addrbook.go @@ -73,7 +73,12 @@ const ( serializationVersion = 1 ) -/* AddrBook - concurrency safe peer address manager */ +const ( + bucketTypeNew = 0x01 + bucketTypeOld = 0x02 +) + +// AddrBook - concurrency safe peer address manager. type AddrBook struct { BaseService @@ -86,15 +91,12 @@ type AddrBook struct { addrLookup map[string]*knownAddress // new & old addrNew []map[string]*knownAddress addrOld []map[string]*knownAddress + wg sync.WaitGroup nOld int nNew int } -const ( - bucketTypeNew = 0x01 - bucketTypeOld = 0x02 -) - +// NewAddrBook creates a new address book. // Use Start to begin processing asynchronous address updates. func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook { am := &AddrBook{ @@ -124,19 +126,22 @@ func (a *AddrBook) init() { } } +// OnStart implements Service. func (a *AddrBook) OnStart() error { a.BaseService.OnStart() a.loadFromFile(a.filePath) + a.wg.Add(1) go a.saveRoutine() return nil } -func (a *AddrBook) OnStop() { - a.BaseService.OnStop() +func (a *AddrBook) Wait() { + a.wg.Wait() } -func (a *AddrBook) Wait() { - a.saveToFile(a.filePath) +// OnStop implements Service. +func (a *AddrBook) OnStop() { + a.BaseService.OnStop() } func (a *AddrBook) AddOurAddress(addr *NetAddress) { @@ -399,6 +404,7 @@ out: } dumpAddressTicker.Stop() a.saveToFile(a.filePath) + a.wg.Done() log.Notice("Address handler done") } From 5ab8ca0868a94e776e297d47c31226d9e66cca38 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Apr 2017 13:22:59 +0400 Subject: [PATCH 41/47] fix race --- addrbook.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/addrbook.go b/addrbook.go index 5450c515f..8a1698f41 100644 --- a/addrbook.go +++ b/addrbook.go @@ -135,15 +135,15 @@ func (a *AddrBook) OnStart() error { return nil } -func (a *AddrBook) Wait() { - a.wg.Wait() -} - // OnStop implements Service. func (a *AddrBook) OnStop() { a.BaseService.OnStop() } +func (a *AddrBook) Wait() { + a.wg.Wait() +} + func (a *AddrBook) AddOurAddress(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() @@ -320,6 +320,8 @@ type addrBookJSON struct { func (a *AddrBook) saveToFile(filePath string) { log.Info("Saving AddrBook to file", "size", a.Size()) + a.mtx.Lock() + defer a.mtx.Unlock() // Compile Addrs addrs := []*knownAddress{} for _, ka := range a.addrLookup { From 9ce71013df9ccf08890307314ec13500ec58f6f3 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 20 Apr 2017 12:49:54 +0400 Subject: [PATCH 42/47] revert e448199 --- pex_reactor.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index a86bebe12..489635556 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -7,6 +7,7 @@ import ( "reflect" "time" + cmn "github.com/tendermint/go-common" wire "github.com/tendermint/go-wire" ) @@ -209,6 +210,17 @@ func (r *PEXReactor) ensurePeersRoutine() { } // ensurePeers ensures that sufficient peers are connected. (once) +// +// Old bucket / New bucket are arbitrary categories to denote whether an +// address is vetted or not, and this needs to be determined over time via a +// heuristic that we haven't perfected yet, or, perhaps is manually edited by +// the node operator. It should not be used to compute what addresses are +// already connected or not. +// +// TODO Basically, we need to work harder on our good-peer/bad-peer marking. +// What we're currently doing in terms of marking good/bad peers is just a +// placeholder. It should not be the case that an address becomes old/vetted +// upon a single successful connection. func (r *PEXReactor) ensurePeers() { numOutPeers, _, numDialing := r.Switch.NumPeers() numToDial := minNumOutboundPeers - (numOutPeers + numDialing) @@ -221,22 +233,28 @@ func (r *PEXReactor) ensurePeers() { // Try to pick numToDial addresses to dial. for i := 0; i < numToDial; i++ { + // The purpose of newBias is to first prioritize old (more vetted) peers + // when we have few connections, but to allow for new (less vetted) peers + // if we already have many connections. This algorithm isn't perfect, but + // it somewhat ensures that we prioritize connecting to more-vetted + // peers. + newBias := cmn.MinInt(numOutPeers, 8)*10 + 10 var picked *NetAddress // Try to fetch a new peer 3 times. // This caps the maximum number of tries to 3 * numToDial. for j := 0; j < 3; j++ { - // NOTE always picking from the new group because old one stores already - // connected peers. - try := r.book.PickAddress(100) + try := r.book.PickAddress(newBias) if try == nil { break } _, alreadySelected := toDial[try.IP.String()] alreadyDialing := r.Switch.IsDialing(try) - if alreadySelected || alreadyDialing { + alreadyConnected := r.Switch.Peers().Has(try.IP.String()) + if alreadySelected || alreadyDialing || alreadyConnected { // log.Info("Cannot dial address", "addr", try, // "alreadySelected", alreadySelected, - // "alreadyDialing", alreadyDialing) + // "alreadyDialing", alreadyDialing, + // "alreadyConnected", alreadyConnected) continue } else { log.Info("Will dial address", "addr", try) @@ -256,9 +274,6 @@ func (r *PEXReactor) ensurePeers() { _, err := r.Switch.DialPeerWithAddress(picked, false) if err != nil { r.book.MarkAttempt(picked) - } else { - // move address to the old group - r.book.MarkGood(picked) } }(item) } From 17ec70fc096be1cc3eee7b8580caee21ab0f0c71 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 20 Apr 2017 13:04:40 +0400 Subject: [PATCH 43/47] revert 2710873 --- pex_reactor.go | 16 +++------------- pex_reactor_test.go | 38 ++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 489635556..c6e4fbf3d 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -109,20 +109,10 @@ func (r *PEXReactor) AddPeer(p *Peer) { } } -// RemovePeer implements Reactor by removing peer from the address book. -// -// The peer will be proposed to us by other peers (PexAddrsMessage) or himself -// and we will add him again upon successful connection. Note that other peers -// will remove him too. The peer will need to send first requests to others by -// himself (he will have an addrbook or the seeds). +// RemovePeer implements Reactor. func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { - addr, err := NewNetAddressString(p.ListenAddr) - if err != nil { - // this should never happen - log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err) - return - } - r.book.RemoveAddress(addr) + // If we aren't keeping track of local temp data for each peer here, then we + // don't have to do anything. } // Receive implements Reactor by handling incoming PEX messages. diff --git a/pex_reactor_test.go b/pex_reactor_test.go index 525efd3cb..13f2fa208 100644 --- a/pex_reactor_test.go +++ b/pex_reactor_test.go @@ -14,20 +14,24 @@ import ( ) func TestPEXReactorBasic(t *testing.T) { + assert, require := assert.New(t), require.New(t) + dir, err := ioutil.TempDir("", "pex_reactor") - require.Nil(t, err) + require.Nil(err) defer os.RemoveAll(dir) book := NewAddrBook(dir+"addrbook.json", true) r := NewPEXReactor(book) - assert.NotNil(t, r) - assert.NotEmpty(t, r.GetChannels()) + assert.NotNil(r) + assert.NotEmpty(r.GetChannels()) } func TestPEXReactorAddRemovePeer(t *testing.T) { + assert, require := assert.New(t), require.New(t) + dir, err := ioutil.TempDir("", "pex_reactor") - require.Nil(t, err) + require.Nil(err) defer os.RemoveAll(dir) book := NewAddrBook(dir+"addrbook.json", true) @@ -37,26 +41,28 @@ func TestPEXReactorAddRemovePeer(t *testing.T) { peer := createRandomPeer(false) r.AddPeer(peer) - assert.Equal(t, size+1, book.Size()) + assert.Equal(size+1, book.Size()) r.RemovePeer(peer, "peer not available") - assert.Equal(t, size, book.Size()) + assert.Equal(size+1, book.Size()) outboundPeer := createRandomPeer(true) r.AddPeer(outboundPeer) - assert.Equal(t, size, book.Size(), "size must not change") + assert.Equal(size+1, book.Size(), "outbound peers should not be added to the address book") r.RemovePeer(outboundPeer, "peer not available") - assert.Equal(t, size, book.Size(), "size must not change") + assert.Equal(size+1, book.Size()) } func TestPEXReactorRunning(t *testing.T) { + require := require.New(t) + N := 3 switches := make([]*Switch, N) dir, err := ioutil.TempDir("", "pex_reactor") - require.Nil(t, err) + require.Nil(err) defer os.RemoveAll(dir) book := NewAddrBook(dir+"addrbook.json", false) @@ -80,7 +86,7 @@ func TestPEXReactorRunning(t *testing.T) { // start switches for _, s := range switches { _, err := s.Start() // start switch and reactors - require.Nil(t, err) + require.Nil(err) } time.Sleep(1 * time.Second) @@ -100,8 +106,10 @@ func TestPEXReactorRunning(t *testing.T) { } func TestPEXReactorReceive(t *testing.T) { + assert, require := assert.New(t), require.New(t) + dir, err := ioutil.TempDir("", "pex_reactor") - require.Nil(t, err) + require.Nil(err) defer os.RemoveAll(dir) book := NewAddrBook(dir+"addrbook.json", true) @@ -114,15 +122,17 @@ func TestPEXReactorReceive(t *testing.T) { addrs := []*NetAddress{netAddr} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) r.Receive(PexChannel, peer, msg) - assert.Equal(t, size+1, book.Size()) + assert.Equal(size+1, book.Size()) msg = wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) r.Receive(PexChannel, peer, msg) } func TestPEXReactorAbuseFromPeer(t *testing.T) { + assert, require := assert.New(t), require.New(t) + dir, err := ioutil.TempDir("", "pex_reactor") - require.Nil(t, err) + require.Nil(err) defer os.RemoveAll(dir) book := NewAddrBook(dir+"addrbook.json", true) @@ -136,7 +146,7 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { r.Receive(PexChannel, peer, msg) } - assert.True(t, r.ReachedMaxMsgCountForPeer(peer.ListenAddr)) + assert.True(r.ReachedMaxMsgCountForPeer(peer.ListenAddr)) } func createRandomPeer(outbound bool) *Peer { From 8655e2456e7f7551549f1c2b7d65115925a9b0f8 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 20 Apr 2017 13:33:27 +0400 Subject: [PATCH 44/47] it is non-deterministic (could fail sometimes) --- connection_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/connection_test.go b/connection_test.go index 84e20eee3..33d8adfd1 100644 --- a/connection_test.go +++ b/connection_test.go @@ -39,7 +39,6 @@ func TestMConnectionSend(t *testing.T) { assert.True(mconn.Send(0x01, msg)) // Note: subsequent Send/TrySend calls could pass because we are reading from // the send queue in a separate goroutine. - assert.False(mconn.CanSend(0x01), "CanSend should return false because queue is full") server.Read(make([]byte, len(msg))) assert.True(mconn.CanSend(0x01)) From 391c738959f7c800e0ff04d49b7d3cbfda5df427 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 20 Apr 2017 12:21:45 -0400 Subject: [PATCH 45/47] update comment about outbound peers and addrbook --- pex_reactor.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pex_reactor.go b/pex_reactor.go index c6e4fbf3d..0244416d1 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -94,7 +94,10 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { // AddPeer implements Reactor by adding peer to the address book (if inbound) // or by requesting more addresses (if outbound). func (r *PEXReactor) AddPeer(p *Peer) { - if p.IsOutbound() { // For outbound peers, the address is already in the books + if p.IsOutbound() { + // For outbound peers, the address is already in the books. + // Either it was added in DialSeeds or when we + // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) } From 75bad132fc71e08b13dbf3b1b15b6fa8026d7adf Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 20 Apr 2017 17:29:43 -0400 Subject: [PATCH 46/47] msgCountByPeer is a CMap --- pex_reactor.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/pex_reactor.go b/pex_reactor.go index 0244416d1..4b6129762 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -49,7 +49,7 @@ type PEXReactor struct { ensurePeersPeriod time.Duration // tracks message count by peer, so we can prevent abuse - msgCountByPeer map[string]uint16 + msgCountByPeer *cmn.CMap maxMsgCountByPeer uint16 } @@ -58,7 +58,7 @@ func NewPEXReactor(b *AddrBook) *PEXReactor { r := &PEXReactor{ book: b, ensurePeersPeriod: defaultEnsurePeersPeriod, - msgCountByPeer: make(map[string]uint16), + msgCountByPeer: cmn.NewCMap(), maxMsgCountByPeer: defaultMaxMsgCountByPeer, } r.BaseReactor = *NewBaseReactor(log, "PEXReactor", r) @@ -122,7 +122,8 @@ func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) { func (r *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { srcAddr := src.Connection().RemoteAddress srcAddrStr := srcAddr.String() - r.msgCountByPeer[srcAddrStr]++ + + r.IncrementMsgCountForPeer(srcAddrStr) if r.ReachedMaxMsgCountForPeer(srcAddrStr) { log.Warn("Maximum number of messages reached for peer", "peer", srcAddrStr) // TODO remove src from peers? @@ -175,8 +176,20 @@ func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) { // ReachedMaxMsgCountForPeer returns true if we received too many // messages from peer with address `addr`. +// NOTE: assumes the value in the CMap is non-nil func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool { - return r.msgCountByPeer[addr] >= r.maxMsgCountByPeer + return r.msgCountByPeer.Get(addr).(uint16) >= r.maxMsgCountByPeer +} + +// Increment or initialize the msg count for the peer in the CMap +func (r *PEXReactor) IncrementMsgCountForPeer(addr string) { + var count uint16 + countI := r.msgCountByPeer.Get(addr) + if countI != nil { + count = countI.(uint16) + } + count++ + r.msgCountByPeer.Set(addr, count) } // Ensures that sufficient peers are connected. (continuous) @@ -288,7 +301,7 @@ func (r *PEXReactor) flushMsgCountByPeer() { for { select { case <-ticker.C: - r.msgCountByPeer = make(map[string]uint16) + r.msgCountByPeer.Clear() case <-r.Quit: ticker.Stop() return From 58ccefa407c843123ecb9224ed2b16d387f6e953 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 21 Apr 2017 13:06:26 -0400 Subject: [PATCH 47/47] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bba6dd77b..cae2f4c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.5.0 (April 18, 2017) +## 0.5.0 (April 21, 2017) BREAKING CHANGES: