From aa7f5915873b8cd7a0a06662fa80dbf93aeec32f Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Tue, 21 Apr 2020 14:40:30 +0200 Subject: [PATCH] Move networking code for waku under `v0` namespace Why make the change? As discussed previously, the way we will move across versions is to maintain completely separate codebases and eventually remove those that are not supported anymore. This has the drawback of some code duplication, but the advantage is that is more explicit what each version requires, and changes in one version will not impact the other, so we won't pile up backward compatible code. This is the same strategy used by `whisper` in go ethereum and is influenced by https://www.youtube.com/watch?v=oyLBGkS5ICk . All the code that is used for the networking protocol is now under `v0/`. Some of the common parts might still be refactored out. The main namespace `waku` deals with `host`->`waku` interactions (through RPC), while `v0` deals with `waku`->`remote-waku` interactions. In order to support `v1`, the namespace `v0` will be copied over, and changed to support `v1`. Once `v0` will be not used anymore, the whole namespace will be removed. This PR does not actually implement `v1`, I'd rather get things looked over to make sure the structure is what we would like before implementing the changes. What has changed? - Moved all code for the common parts under `waku/common/` namespace - Moved code used for bloomfilters in `waku/common/bloomfilter.go` - Removed all version specific code from `waku/common/const` (`ProtocolVersion`, status-codes etc) - Added interfaces for `WakuHost` and `Peer` under `waku/common/protocol.go` Things still to do Some tests in `waku/` are still testing by stubbing components of a particular version (`v0`). I started moving those tests to instead of stubbing using the actual component, which increases the testing surface. Some other tests that can't be easily ported should be likely moved under `v0` instead. Ideally no version specif code should be exported from a version namespace (for example the various codes, as those might change across versions). But this will be a work-in-progress. Some code that will be common in `v0`/`v1` could still be extract to avoid duplication, and duplicated only when implementations diverge across versions. --- VERSION | 2 +- bridge/bridge.go | 13 +- bridge/bridge_test.go | 21 +- eth-node/bridge/geth/envelope.go | 2 +- eth-node/bridge/geth/envelope_error.go | 2 +- eth-node/bridge/geth/envelope_event.go | 5 +- eth-node/bridge/geth/public_waku_api.go | 7 +- eth-node/bridge/geth/waku.go | 17 +- go.sum | 2 + mailserver/mailserver.go | 19 +- node/geth_node.go | 9 +- services/wakuext/api.go | 2 +- services/wakuext/api_test.go | 14 +- waku/api.go | 87 +- waku/api_test.go | 6 +- waku/common/bloomfilter.go | 37 + waku/{ => common}/const.go | 28 +- waku/{ => common}/envelope.go | 12 +- waku/{ => common}/envelope_test.go | 8 +- waku/common/errors.go | 4 + waku/{ => common}/events.go | 2 +- waku/{ => common}/filter.go | 23 +- waku/{ => common}/filter_test.go | 43 +- waku/common/helper.go | 78 ++ waku/{ => common}/message.go | 57 +- waku/{ => common}/message_test.go | 69 +- waku/{ => common}/metrics.go | 46 +- waku/common/protocol.go | 94 +++ waku/{ => common}/rate_limiter.go | 42 +- waku/{ => common}/rate_limiter_test.go | 28 +- waku/{ => common}/topic.go | 2 +- waku/{ => common}/topic_test.go | 2 +- waku/config.go | 8 +- waku/mailserver.go | 50 +- waku/peer.go | 390 --------- waku/v0/const.go | 19 + waku/v0/init.go | 5 + waku/v0/message.go | 55 ++ waku/v0/message_test.go | 42 + waku/v0/peer.go | 621 +++++++++++++++ waku/v0/peer_test.go | 60 ++ waku/{handshake.go => v0/statusoptions.go} | 71 +- .../statusoptions_test.go} | 22 +- waku/{peer_test.go => v0_test.go} | 267 +++---- waku/waku.go | 748 +++++------------- waku/waku_test.go | 570 ++++++------- 46 files changed, 1998 insertions(+), 1713 deletions(-) create mode 100644 waku/common/bloomfilter.go rename waku/{ => common}/const.go (59%) rename waku/{ => common}/envelope.go (97%) rename waku/{ => common}/envelope_test.go (96%) create mode 100644 waku/common/errors.go rename waku/{ => common}/events.go (99%) rename waku/{ => common}/filter.go (92%) rename waku/{ => common}/filter_test.go (95%) create mode 100644 waku/common/helper.go rename waku/{ => common}/message.go (89%) rename waku/{ => common}/message_test.go (87%) rename waku/{ => common}/metrics.go (66%) create mode 100644 waku/common/protocol.go rename waku/{ => common}/rate_limiter.go (87%) rename waku/{ => common}/rate_limiter_test.go (89%) rename waku/{ => common}/topic.go (99%) rename waku/{ => common}/topic_test.go (99%) delete mode 100644 waku/peer.go create mode 100644 waku/v0/const.go create mode 100644 waku/v0/init.go create mode 100644 waku/v0/message.go create mode 100644 waku/v0/message_test.go create mode 100644 waku/v0/peer.go create mode 100644 waku/v0/peer_test.go rename waku/{handshake.go => v0/statusoptions.go} (66%) rename waku/{handshake_test.go => v0/statusoptions_test.go} (82%) rename waku/{peer_test.go => v0_test.go} (74%) diff --git a/VERSION b/VERSION index 5e3df492e..e56c472dd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.4 +0.52.5 diff --git a/bridge/bridge.go b/bridge/bridge.go index ab9eb929e..0a7449b48 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -7,6 +7,7 @@ import ( "go.uber.org/zap" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) @@ -20,8 +21,8 @@ type Bridge struct { whisperIn chan *whisper.Envelope whisperOut chan *whisper.Envelope - wakuIn chan *waku.Envelope - wakuOut chan *waku.Envelope + wakuIn chan *wakucommon.Envelope + wakuOut chan *wakucommon.Envelope } func New(shh *whisper.Whisper, w *waku.Waku, logger *zap.Logger) *Bridge { @@ -31,8 +32,8 @@ func New(shh *whisper.Whisper, w *waku.Waku, logger *zap.Logger) *Bridge { logger: logger, whisperOut: make(chan *whisper.Envelope), whisperIn: make(chan *whisper.Envelope), - wakuIn: make(chan *waku.Envelope), - wakuOut: make(chan *waku.Envelope), + wakuIn: make(chan *wakucommon.Envelope), + wakuOut: make(chan *wakucommon.Envelope), } } @@ -48,7 +49,7 @@ type bridgeWaku struct { *Bridge } -func (b *bridgeWaku) Pipe() (<-chan *waku.Envelope, chan<- *waku.Envelope) { +func (b *bridgeWaku) Pipe() (<-chan *wakucommon.Envelope, chan<- *wakucommon.Envelope) { return b.wakuOut, b.wakuIn } @@ -84,7 +85,7 @@ func (b *Bridge) Start() { case <-b.cancel: return case env := <-b.whisperIn: - wakuEnvelope := (*waku.Envelope)(unsafe.Pointer(env)) // nolint: gosec + wakuEnvelope := (*wakucommon.Envelope)(unsafe.Pointer(env)) // nolint: gosec b.logger.Debug( "received waku envelope from whisper", zap.ByteString("hash", wakuEnvelope.Hash().Bytes()), diff --git a/bridge/bridge_test.go b/bridge/bridge_test.go index 0df6b4db5..894408d73 100644 --- a/bridge/bridge_test.go +++ b/bridge/bridge_test.go @@ -13,17 +13,18 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) func TestEnvelopesBeingIdentical(t *testing.T) { - // whisper.Envelope --> waku.Envelope + // whisper.Envelope --> wakucommon.Envelope whisperEnvelope, err := createWhisperEnvelope() require.NoError(t, err) - wakuEnvelope := (*waku.Envelope)(unsafe.Pointer(whisperEnvelope)) // nolint: gosec + wakuEnvelope := (*wakucommon.Envelope)(unsafe.Pointer(whisperEnvelope)) // nolint: gosec require.Equal(t, whisperEnvelope.Hash(), wakuEnvelope.Hash()) - // waku.Envelope --> whisper.Envelope + // wakucommon.Envelope --> whisper.Envelope wakuEnvelope, err = createWakuEnvelope() require.NoError(t, err) whisperEnvelope = (*whisper.Envelope)(unsafe.Pointer(wakuEnvelope)) // nolint: gosec @@ -47,7 +48,7 @@ func TestBridgeWhisperToWaku(t *testing.T) { require.NoError(t, err) // Subscribe for envelope events in Waku. - eventsWaku := make(chan waku.EnvelopeEvent, 10) + eventsWaku := make(chan wakucommon.EnvelopeEvent, 10) sub1 := wak.SubscribeEnvelopeEvents(eventsWaku) defer sub1.Unsubscribe() @@ -106,7 +107,7 @@ func TestBridgeWakuToWhisper(t *testing.T) { defer sub1.Unsubscribe() // Subscribe for envelope events in Waku. - eventsWaku := make(chan waku.EnvelopeEvent, 10) + eventsWaku := make(chan wakucommon.EnvelopeEvent, 10) sub2 := wak.SubscribeEnvelopeEvents(eventsWaku) defer sub2.Unsubscribe() @@ -167,20 +168,20 @@ func createWhisperEnvelope() (*whisper.Envelope, error) { return envelope, nil } -func createWakuEnvelope() (*waku.Envelope, error) { - messageParams := &waku.MessageParams{ +func createWakuEnvelope() (*wakucommon.Envelope, error) { + messageParams := &wakucommon.MessageParams{ TTL: 120, KeySym: []byte{0xaa, 0xbb, 0xcc}, - Topic: waku.BytesToTopic([]byte{0x01}), + Topic: wakucommon.BytesToTopic([]byte{0x01}), WorkTime: 10, PoW: 2.0, Payload: []byte("hello!"), } - sentMessage, err := waku.NewSentMessage(messageParams) + sentMessage, err := wakucommon.NewSentMessage(messageParams) if err != nil { return nil, err } - envelope := waku.NewEnvelope(120, waku.BytesToTopic([]byte{0x01}), sentMessage, time.Now()) + envelope := wakucommon.NewEnvelope(120, wakucommon.BytesToTopic([]byte{0x01}), sentMessage, time.Now()) if err := envelope.Seal(messageParams); err != nil { return nil, err } diff --git a/eth-node/bridge/geth/envelope.go b/eth-node/bridge/geth/envelope.go index 52814b70d..0fa0098a4 100644 --- a/eth-node/bridge/geth/envelope.go +++ b/eth-node/bridge/geth/envelope.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/status-im/status-go/eth-node/types" - "github.com/status-im/status-go/waku" + waku "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) diff --git a/eth-node/bridge/geth/envelope_error.go b/eth-node/bridge/geth/envelope_error.go index 6d24b49fc..7641650ba 100644 --- a/eth-node/bridge/geth/envelope_error.go +++ b/eth-node/bridge/geth/envelope_error.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - "github.com/status-im/status-go/waku" + waku "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) diff --git a/eth-node/bridge/geth/envelope_event.go b/eth-node/bridge/geth/envelope_event.go index c31c509c1..206fec663 100644 --- a/eth-node/bridge/geth/envelope_event.go +++ b/eth-node/bridge/geth/envelope_event.go @@ -3,6 +3,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) @@ -34,14 +35,14 @@ func NewWhisperEnvelopeEventWrapper(envelopeEvent *whisper.EnvelopeEvent) *types } // NewWakuEnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent -func NewWakuEnvelopeEventWrapper(envelopeEvent *waku.EnvelopeEvent) *types.EnvelopeEvent { +func NewWakuEnvelopeEventWrapper(envelopeEvent *wakucommon.EnvelopeEvent) *types.EnvelopeEvent { if envelopeEvent == nil { panic("envelopeEvent should not be nil") } wrappedData := envelopeEvent.Data switch data := envelopeEvent.Data.(type) { - case []waku.EnvelopeError: + case []wakucommon.EnvelopeError: wrappedData := make([]types.EnvelopeError, len(data)) for index, envError := range data { wrappedData[index] = *NewWakuEnvelopeErrorWrapper(&envError) diff --git a/eth-node/bridge/geth/public_waku_api.go b/eth-node/bridge/geth/public_waku_api.go index bfd9b76f6..8ff725b8e 100644 --- a/eth-node/bridge/geth/public_waku_api.go +++ b/eth-node/bridge/geth/public_waku_api.go @@ -7,6 +7,7 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" ) type gethPublicWakuAPIWrapper struct { @@ -42,9 +43,9 @@ func (w *gethPublicWakuAPIWrapper) DeleteKeyPair(ctx context.Context, key string // NewMessageFilter creates a new filter that can be used to poll for // (new) messages that satisfy the given criteria. func (w *gethPublicWakuAPIWrapper) NewMessageFilter(req types.Criteria) (string, error) { - topics := make([]waku.TopicType, len(req.Topics)) + topics := make([]wakucommon.TopicType, len(req.Topics)) for index, tt := range req.Topics { - topics[index] = waku.TopicType(tt) + topics[index] = wakucommon.TopicType(tt) } criteria := waku.Criteria{ @@ -92,7 +93,7 @@ func (w *gethPublicWakuAPIWrapper) Post(ctx context.Context, req types.NewMessag PublicKey: req.PublicKey, Sig: req.SigID, // Sig is really a SigID TTL: req.TTL, - Topic: waku.TopicType(req.Topic), + Topic: wakucommon.TopicType(req.Topic), Payload: req.Payload, Padding: req.Padding, PowTime: req.PowTime, diff --git a/eth-node/bridge/geth/waku.go b/eth-node/bridge/geth/waku.go index 981937cfb..56c76515a 100644 --- a/eth-node/bridge/geth/waku.go +++ b/eth-node/bridge/geth/waku.go @@ -6,6 +6,7 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" ) type gethWakuWrapper struct { @@ -56,7 +57,7 @@ func (w *gethWakuWrapper) SetTimeSource(timesource func() time.Time) { } func (w *gethWakuWrapper) SubscribeEnvelopeEvents(eventsProxy chan<- types.EnvelopeEvent) types.Subscription { - events := make(chan waku.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper + events := make(chan wakucommon.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper go func() { for e := range events { eventsProxy <- *NewWakuEnvelopeEventWrapper(&e) @@ -139,18 +140,18 @@ func (w *gethWakuWrapper) Unsubscribe(id string) error { } func (w *gethWakuWrapper) createFilterWrapper(id string, keyAsym *ecdsa.PrivateKey, keySym []byte, pow float64, topics [][]byte) (types.Filter, error) { - return NewWakuFilterWrapper(&waku.Filter{ + return NewWakuFilterWrapper(&wakucommon.Filter{ KeyAsym: keyAsym, KeySym: keySym, PoW: pow, AllowP2P: true, Topics: topics, - Messages: waku.NewMemoryMessageStore(), + Messages: wakucommon.NewMemoryMessageStore(), }, id), nil } func (w *gethWakuWrapper) SendMessagesRequest(peerID []byte, r types.MessagesRequest) error { - return w.waku.SendMessagesRequest(peerID, waku.MessagesRequest{ + return w.waku.SendMessagesRequest(peerID, wakucommon.MessagesRequest{ ID: r.ID, From: r.From, To: r.To, @@ -166,16 +167,16 @@ func (w *gethWakuWrapper) SendMessagesRequest(peerID []byte, r types.MessagesReq // which are not supposed to be forwarded any further. // The whisper protocol is agnostic of the format and contents of envelope. func (w *gethWakuWrapper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope types.Envelope, timeout time.Duration) error { - return w.waku.RequestHistoricMessagesWithTimeout(peerID, envelope.Unwrap().(*waku.Envelope), timeout) + return w.waku.RequestHistoricMessagesWithTimeout(peerID, envelope.Unwrap().(*wakucommon.Envelope), timeout) } type wakuFilterWrapper struct { - filter *waku.Filter + filter *wakucommon.Filter id string } // NewWakuFilterWrapper returns an object that wraps Geth's Filter in a types interface -func NewWakuFilterWrapper(f *waku.Filter, id string) types.Filter { +func NewWakuFilterWrapper(f *wakucommon.Filter, id string) types.Filter { if f.Messages == nil { panic("Messages should not be nil") } @@ -187,7 +188,7 @@ func NewWakuFilterWrapper(f *waku.Filter, id string) types.Filter { } // GetWakuFilterFrom retrieves the underlying whisper Filter struct from a wrapped Filter interface -func GetWakuFilterFrom(f types.Filter) *waku.Filter { +func GetWakuFilterFrom(f types.Filter) *wakucommon.Filter { return f.(*wakuFilterWrapper).filter } diff --git a/go.sum b/go.sum index b9be351f8..ab52903b4 100644 --- a/go.sum +++ b/go.sum @@ -138,6 +138,7 @@ github.com/elastic/gosigar v0.10.4/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTy github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/ethereum/go-ethereum v1.8.20/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= github.com/ethereum/go-ethereum v1.9.2/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= +github.com/ethereum/go-ethereum v1.9.13 h1:rOPqjSngvs1VSYH2H+PMPiWt4VEulvNRbFgqiGqJM3E= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= @@ -635,6 +636,7 @@ github.com/status-im/rendezvous v1.3.0/go.mod h1:+hzjuP+j/XzLPeF6E50b88pWOTLdTcw github.com/status-im/status-go/extkeys v1.0.0/go.mod h1:GdqJbrcpkNm5ZsSCpp+PdMxnXx+OcRBdm3PI0rs1FpU= github.com/status-im/status-go/extkeys v1.1.2 h1:FSjARgDathJ3rIapJt851LsIXP9Oyuu2M2jPJKuzloU= github.com/status-im/status-go/extkeys v1.1.2/go.mod h1:hCmFzb2jiiVF2voZKYbzuhOQiHHCmyLJsZJXrFFg7BY= +github.com/status-im/status-go/waku v1.3.1 h1:hXvWsS/5ZKJ5iUXJvIZRE4Z78OH5u4d7OwBEPLNY9Gs= github.com/status-im/status-go/whisper/v6 v6.2.6 h1:xELIv/8QB9CQlJjChnCPt4COWOFmgsc2kl03Y3Dspmo= github.com/status-im/status-go/whisper/v6 v6.2.6/go.mod h1:csqMoPMkCPW1NJO56HJzNTWAl9UMdetnQzkPbPjsAC4= github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 h1:oa0KU5jJRNtXaM/P465MhvSFo/HM2O8qi2DDuPcd7ro= diff --git a/mailserver/mailserver.go b/mailserver/mailserver.go index 1cea093f5..414d3b6ab 100644 --- a/mailserver/mailserver.go +++ b/mailserver/mailserver.go @@ -36,6 +36,7 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) @@ -332,8 +333,8 @@ type WakuMailServer struct { shh *waku.Waku minRequestPoW float64 - symFilter *waku.Filter - asymFilter *waku.Filter + symFilter *wakucommon.Filter + asymFilter *wakucommon.Filter } func (s *WakuMailServer) Init(waku *waku.Waku, cfg *params.WakuConfig) error { @@ -370,11 +371,11 @@ func (s *WakuMailServer) Close() { s.ms.Close() } -func (s *WakuMailServer) Archive(env *waku.Envelope) { +func (s *WakuMailServer) Archive(env *wakucommon.Envelope) { s.ms.Archive(gethbridge.NewWakuEnvelope(env)) } -func (s *WakuMailServer) Deliver(peerID []byte, req waku.MessagesRequest) { +func (s *WakuMailServer) Deliver(peerID []byte, req wakucommon.MessagesRequest) { s.ms.DeliverMail(types.BytesToHash(peerID), types.BytesToHash(req.ID), MessagesRequestPayload{ Lower: req.From, Upper: req.To, @@ -387,7 +388,7 @@ func (s *WakuMailServer) Deliver(peerID []byte, req waku.MessagesRequest) { } // DEPRECATED; user Deliver instead -func (s *WakuMailServer) DeliverMail(peerID []byte, req *waku.Envelope) { +func (s *WakuMailServer) DeliverMail(peerID []byte, req *wakucommon.Envelope) { payload, err := s.decodeRequest(peerID, req) if err != nil { deliveryFailuresCounter.WithLabelValues("validation").Inc() @@ -419,7 +420,7 @@ func (s *WakuMailServer) setupDecryptor(password, asymKey string) error { return fmt.Errorf("save symmetric key: %v", err) } - s.symFilter = &waku.Filter{KeySym: symKey} + s.symFilter = &wakucommon.Filter{KeySym: symKey} } if asymKey != "" { @@ -427,7 +428,7 @@ func (s *WakuMailServer) setupDecryptor(password, asymKey string) error { if err != nil { return err } - s.asymFilter = &waku.Filter{KeyAsym: keyAsym} + s.asymFilter = &wakucommon.Filter{KeyAsym: keyAsym} } return nil @@ -435,7 +436,7 @@ func (s *WakuMailServer) setupDecryptor(password, asymKey string) error { // openEnvelope tries to decrypt an envelope, first based on asymetric key (if // provided) and second on the symetric key (if provided) -func (s *WakuMailServer) openEnvelope(request *waku.Envelope) *waku.ReceivedMessage { +func (s *WakuMailServer) openEnvelope(request *wakucommon.Envelope) *wakucommon.ReceivedMessage { if s.asymFilter != nil { if d := request.Open(s.asymFilter); d != nil { return d @@ -449,7 +450,7 @@ func (s *WakuMailServer) openEnvelope(request *waku.Envelope) *waku.ReceivedMess return nil } -func (s *WakuMailServer) decodeRequest(peerID []byte, request *waku.Envelope) (MessagesRequestPayload, error) { +func (s *WakuMailServer) decodeRequest(peerID []byte, request *wakucommon.Envelope) (MessagesRequestPayload, error) { var payload MessagesRequestPayload if s.minRequestPoW > 0.0 && request.PoW() < s.minRequestPoW { diff --git a/node/geth_node.go b/node/geth_node.go index ded1dad5b..d640e9066 100644 --- a/node/geth_node.go +++ b/node/geth_node.go @@ -43,6 +43,7 @@ import ( "github.com/status-im/status-go/static" "github.com/status-im/status-go/timesource" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/whisper/v6" ) @@ -456,7 +457,7 @@ func createShhService(ctx *node.ServiceContext, whisperConfig *params.WhisperCon func createWakuService(ctx *node.ServiceContext, wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) (*waku.Waku, error) { cfg := &waku.Config{ - MaxMessageSize: waku.DefaultMaxMessageSize, + MaxMessageSize: wakucommon.DefaultMaxMessageSize, BloomFilterMode: wakuCfg.BloomFilterMode, MinimumAcceptedPoW: params.WakuMinimumPoW, } @@ -613,7 +614,7 @@ func whisperRateLimiter(whisperConfig *params.WhisperConfig, clusterConfig *para ) } -func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) *waku.PeerRateLimiter { +func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) *wakucommon.PeerRateLimiter { enodes := append( parseNodes(clusterCfg.StaticNodes), parseNodes(clusterCfg.TrustedMailServers)..., @@ -626,8 +627,8 @@ func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfi ips = append(ips, item.IP().String()) peerIDs = append(peerIDs, item.ID()) } - return waku.NewPeerRateLimiter( - &waku.PeerRateLimiterConfig{ + return wakucommon.NewPeerRateLimiter( + &wakucommon.PeerRateLimiterConfig{ LimitPerSecIP: wakuCfg.RateLimitIP, LimitPerSecPeerID: wakuCfg.RateLimitPeerID, WhitelistedIPs: ips, diff --git a/services/wakuext/api.go b/services/wakuext/api.go index a47d866b5..8364abc45 100644 --- a/services/wakuext/api.go +++ b/services/wakuext/api.go @@ -11,7 +11,7 @@ import ( gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/services/ext" - "github.com/status-im/status-go/waku" + waku "github.com/status-im/status-go/waku/common" ) const ( diff --git a/services/wakuext/api_test.go b/services/wakuext/api_test.go index a352f08bd..8709f88f6 100644 --- a/services/wakuext/api_test.go +++ b/services/wakuext/api_test.go @@ -31,6 +31,8 @@ import ( "github.com/status-im/status-go/sqlite" "github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/waku" + wakucommon "github.com/status-im/status-go/waku/common" + v0 "github.com/status-im/status-go/waku/v0" ) func TestRequestMessagesErrors(t *testing.T) { @@ -273,7 +275,6 @@ func (s *ShhExtSuite) TestFailedRequestWithUnknownMailServerPeer() { const ( // internal waku protocol codes - statusCode = 0 p2pRequestCompleteCode = 125 ) @@ -297,6 +298,7 @@ func (s *WakuNodeMockSuite) SetupTest() { EnableConfirmations: true, } w := waku.New(conf, nil) + w2 := waku.New(nil, nil) s.Require().NoError(w.Start(nil)) pkey, err := crypto.GenerateKey() s.Require().NoError(err) @@ -308,8 +310,10 @@ func (s *WakuNodeMockSuite) SetupTest() { panic(err) }() wakuWrapper := gethbridge.NewGethWakuWrapper(w) - s.Require().NoError(p2p.ExpectMsg(rw1, statusCode, nil)) - s.Require().NoError(p2p.SendItems(rw1, statusCode, waku.ProtocolVersion, []interface{}{})) + + peer1 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), rw1, nil) + err = peer1.Start() + s.Require().NoError(err, "failed run message loop") nodeWrapper := ext.NewTestNodeWrapper(nil, wakuWrapper) s.localService = New( @@ -352,7 +356,7 @@ func (s *RequestMessagesSyncSuite) TestExpired() { }, ext.MessagesRequest{ MailServerPeer: s.localNode.String(), - Topics: []types.TopicType{{0x01, 0x02, 0x03, 0x04}}, + Topics: []common.TopicType{{0x01, 0x02, 0x03, 0x04}}, }, ) s.Require().EqualError(err, "failed to request messages after 1 retries") @@ -374,7 +378,7 @@ func (s *RequestMessagesSyncSuite) testCompletedFromAttempt(target int) { s.Require().NoError(msg.Discard()) continue } - var e waku.Envelope + var e wakucommon.Envelope s.Require().NoError(msg.Decode(&e)) s.Require().NoError(p2p.Send(s.remoteRW, p2pRequestCompleteCode, waku.CreateMailServerRequestCompletedPayload(e.Hash(), common.Hash{}, cursor[:]))) } diff --git a/waku/api.go b/waku/api.go index ea5202605..2e4bbf61c 100644 --- a/waku/api.go +++ b/waku/api.go @@ -26,6 +26,8 @@ import ( "sync" "time" + "github.com/status-im/status-go/waku/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -61,11 +63,6 @@ func NewPublicWakuAPI(w *Waku) *PublicWakuAPI { return api } -// Version returns the Waku sub-protocol version. -func (api *PublicWakuAPI) Version(ctx context.Context) string { - return ProtocolVersionStr -} - // Info contains diagnostic information. type Info struct { Messages int `json:"messages"` // Number of floating messages. @@ -207,16 +204,16 @@ func (api *PublicWakuAPI) CancelLightClient(ctx context.Context) bool { // NewMessage represents a new waku message that is posted through the RPC. type NewMessage struct { - SymKeyID string `json:"symKeyID"` - PublicKey []byte `json:"pubKey"` - Sig string `json:"sig"` - TTL uint32 `json:"ttl"` - Topic TopicType `json:"topic"` - Payload []byte `json:"payload"` - Padding []byte `json:"padding"` - PowTime uint32 `json:"powTime"` - PowTarget float64 `json:"powTarget"` - TargetPeer string `json:"targetPeer"` + SymKeyID string `json:"symKeyID"` + PublicKey []byte `json:"pubKey"` + Sig string `json:"sig"` + TTL uint32 `json:"ttl"` + Topic common.TopicType `json:"topic"` + Payload []byte `json:"payload"` + Padding []byte `json:"padding"` + PowTime uint32 `json:"powTime"` + PowTarget float64 `json:"powTarget"` + TargetPeer string `json:"targetPeer"` } type newMessageOverride struct { // nolint: deadcode,unused @@ -239,7 +236,7 @@ func (api *PublicWakuAPI) Post(ctx context.Context, req NewMessage) (hexutil.Byt return nil, ErrSymAsym } - params := &MessageParams{ + params := &common.MessageParams{ TTL: req.TTL, Payload: req.Payload, Padding: req.Padding, @@ -257,13 +254,13 @@ func (api *PublicWakuAPI) Post(ctx context.Context, req NewMessage) (hexutil.Byt // Set symmetric key that is used to encrypt the message if symKeyGiven { - if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption + if params.Topic == (common.TopicType{}) { // topics are mandatory with symmetric encryption return nil, ErrNoTopics } if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { return nil, err } - if !validateDataIntegrity(params.KeySym, aesKeyLength) { + if !common.ValidateDataIntegrity(params.KeySym, common.AESKeyLength) { return nil, ErrInvalidSymmetricKey } } @@ -276,7 +273,7 @@ func (api *PublicWakuAPI) Post(ctx context.Context, req NewMessage) (hexutil.Byt } // encrypt and sent message - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { return nil, err } @@ -326,12 +323,12 @@ func (api *PublicWakuAPI) Unsubscribe(id string) { // Criteria holds various filter options for inbound messages. type Criteria struct { - SymKeyID string `json:"symKeyID"` - PrivateKeyID string `json:"privateKeyID"` - Sig []byte `json:"sig"` - MinPow float64 `json:"minPow"` - Topics []TopicType `json:"topics"` - AllowP2P bool `json:"allowP2P"` + SymKeyID string `json:"symKeyID"` + PrivateKeyID string `json:"privateKeyID"` + Sig []byte `json:"sig"` + MinPow float64 `json:"minPow"` + Topics []common.TopicType `json:"topics"` + AllowP2P bool `json:"allowP2P"` } // Messages set up a subscription that fires events when messages arrive that match @@ -354,9 +351,9 @@ func (api *PublicWakuAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Sub return nil, ErrSymAsym } - filter := Filter{ + filter := common.Filter{ PoW: crit.MinPow, - Messages: NewMemoryMessageStore(), + Messages: common.NewMemoryMessageStore(), AllowP2P: crit.AllowP2P, } @@ -382,7 +379,7 @@ func (api *PublicWakuAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Sub if err != nil { return nil, err } - if !validateDataIntegrity(key, aesKeyLength) { + if !common.ValidateDataIntegrity(key, common.AESKeyLength) { return nil, ErrInvalidSymmetricKey } filter.KeySym = key @@ -433,16 +430,16 @@ func (api *PublicWakuAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Sub // Message is the RPC representation of a waku message. type Message struct { - Sig []byte `json:"sig,omitempty"` - TTL uint32 `json:"ttl"` - Timestamp uint32 `json:"timestamp"` - Topic TopicType `json:"topic"` - Payload []byte `json:"payload"` - Padding []byte `json:"padding"` - PoW float64 `json:"pow"` - Hash []byte `json:"hash"` - Dst []byte `json:"recipientPublicKey,omitempty"` - P2P bool `json:"bool,omitempty"` + Sig []byte `json:"sig,omitempty"` + TTL uint32 `json:"ttl"` + Timestamp uint32 `json:"timestamp"` + Topic common.TopicType `json:"topic"` + Payload []byte `json:"payload"` + Padding []byte `json:"padding"` + PoW float64 `json:"pow"` + Hash []byte `json:"hash"` + Dst []byte `json:"recipientPublicKey,omitempty"` + P2P bool `json:"bool,omitempty"` } type messageOverride struct { // nolint: deadcode,unused @@ -454,7 +451,7 @@ type messageOverride struct { // nolint: deadcode,unused } // ToWakuMessage converts an internal message into an API version. -func ToWakuMessage(message *ReceivedMessage) *Message { +func ToWakuMessage(message *common.ReceivedMessage) *Message { msg := Message{ Payload: message.Payload, Padding: message.Padding, @@ -473,7 +470,7 @@ func ToWakuMessage(message *ReceivedMessage) *Message { } } - if isMessageSigned(message.Raw[0]) { + if common.IsMessageSigned(message.Raw[0]) { b := crypto.FromECDSAPub(message.SigToPubKey()) if b != nil { msg.Sig = b @@ -484,7 +481,7 @@ func ToWakuMessage(message *ReceivedMessage) *Message { } // toMessage converts a set of messages to its RPC representation. -func toMessage(messages []*ReceivedMessage) []*Message { +func toMessage(messages []*common.ReceivedMessage) []*Message { msgs := make([]*Message, len(messages)) for i, msg := range messages { msgs[i] = ToWakuMessage(msg) @@ -552,7 +549,7 @@ func (api *PublicWakuAPI) NewMessageFilter(req Criteria) (string, error) { if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { return "", err } - if !validateDataIntegrity(keySym, aesKeyLength) { + if !common.ValidateDataIntegrity(keySym, common.AESKeyLength) { return "", ErrInvalidSymmetricKey } } @@ -566,19 +563,19 @@ func (api *PublicWakuAPI) NewMessageFilter(req Criteria) (string, error) { if len(req.Topics) > 0 { topics = make([][]byte, len(req.Topics)) for i, topic := range req.Topics { - topics[i] = make([]byte, TopicLength) + topics[i] = make([]byte, common.TopicLength) copy(topics[i], topic[:]) } } - f := &Filter{ + f := &common.Filter{ Src: src, KeySym: keySym, KeyAsym: keyAsym, PoW: req.MinPow, AllowP2P: req.AllowP2P, Topics: topics, - Messages: NewMemoryMessageStore(), + Messages: common.NewMemoryMessageStore(), } id, err := api.w.Subscribe(f) diff --git a/waku/api_test.go b/waku/api_test.go index 2f662d527..e57470f00 100644 --- a/waku/api_test.go +++ b/waku/api_test.go @@ -22,6 +22,8 @@ import ( "bytes" "testing" "time" + + "github.com/status-im/status-go/waku/common" ) func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { @@ -41,7 +43,7 @@ func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { crit := Criteria{ SymKeyID: keyID, - Topics: []TopicType{TopicType(t1), TopicType(t2)}, + Topics: []common.TopicType{common.TopicType(t1), common.TopicType(t2)}, } _, err = api.NewMessageFilter(crit) @@ -50,7 +52,7 @@ func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { } found := false - candidates := w.filters.getWatchersByTopic(TopicType(t1)) + candidates := w.filters.GetWatchersByTopic(common.TopicType(t1)) for _, f := range candidates { if len(f.Topics) == 2 { if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) { diff --git a/waku/common/bloomfilter.go b/waku/common/bloomfilter.go new file mode 100644 index 000000000..d959163c4 --- /dev/null +++ b/waku/common/bloomfilter.go @@ -0,0 +1,37 @@ +package common + +func IsFullNode(bloom []byte) bool { + if bloom == nil { + return true + } + for _, b := range bloom { + if b != 255 { + return false + } + } + return true +} + +func BloomFilterMatch(filter, sample []byte) bool { + if filter == nil { + return true + } + + for i := 0; i < BloomFilterSize; i++ { + f := filter[i] + s := sample[i] + if (f | s) != f { + return false + } + } + + return true +} + +func MakeFullNodeBloom() []byte { + bloom := make([]byte, BloomFilterSize) + for i := 0; i < BloomFilterSize; i++ { + bloom[i] = 0xFF + } + return bloom +} diff --git a/waku/const.go b/waku/common/const.go similarity index 59% rename from waku/const.go rename to waku/common/const.go index ab13121af..2dc548432 100644 --- a/waku/const.go +++ b/waku/common/const.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "time" @@ -26,29 +26,14 @@ import ( // Waku protocol parameters const ( - ProtocolVersion = uint64(0) // Protocol version number - ProtocolVersionStr = "0" // The same, as a string - ProtocolName = "waku" // Nickname of the protocol - - // Waku protocol message codes, according to https://github.com/vacp2p/specs/blob/master/waku.md - statusCode = 0 // used in the handshake - messagesCode = 1 // regular message - statusUpdateCode = 22 // update of settings - batchAcknowledgedCode = 11 // confirmation that batch of envelopes was received - messageResponseCode = 12 // includes confirmation for delivery and information about errors - p2pRequestCompleteCode = 125 // peer-to-peer message, used by Dapp protocol - p2pRequestCode = 126 // peer-to-peer message, used by Dapp protocol - p2pMessageCode = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further) - NumberOfMessageCodes = 128 - SizeMask = byte(3) // mask used to extract the size of payload size field from the flags signatureFlag = byte(4) TopicLength = 4 // in bytes signatureLength = crypto.SignatureLength // in bytes - aesKeyLength = 32 // in bytes + AESKeyLength = 32 // in bytes aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize() - keyIDSize = 32 // in bytes + KeyIDSize = 32 // in bytes BloomFilterSize = 64 // in bytes MaxTopicInterest = 10000 flagsLength = 1 @@ -59,11 +44,10 @@ const ( DefaultMaxMessageSize = uint32(1024 * 1024) DefaultMinimumPoW = 0.2 - padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol - messageQueueLimit = 1024 + padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol - expirationCycle = time.Second - transmissionCycle = 300 * time.Millisecond + ExpirationCycle = time.Second + TransmissionCycle = 300 * time.Millisecond DefaultTTL = 50 // seconds DefaultSyncAllowance = 10 // seconds diff --git a/waku/envelope.go b/waku/common/envelope.go similarity index 97% rename from waku/envelope.go rename to waku/common/envelope.go index bba7fea10..8d47421d3 100644 --- a/waku/envelope.go +++ b/waku/common/envelope.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "crypto/ecdsa" @@ -48,8 +48,8 @@ type Envelope struct { bloom []byte } -// size returns the size of envelope as it is sent (i.e. public fields only) -func (e *Envelope) size() int { +// Size returns the size of envelope as it is sent (i.e. public fields only) +func (e *Envelope) Size() int { return EnvelopeHeaderLength + len(e.Data) } @@ -124,12 +124,12 @@ func (e *Envelope) Seal(options *MessageParams) error { // of the envelope. func (e *Envelope) PoW() float64 { if e.pow == 0 { - e.calculatePoW(0) + e.CalculatePoW(0) } return e.pow } -func (e *Envelope) calculatePoW(diff uint32) { +func (e *Envelope) CalculatePoW(diff uint32) { rlp := e.rlpWithoutNonce() buf := make([]byte, len(rlp)+8) copy(buf, rlp) @@ -144,7 +144,7 @@ func (e *Envelope) calculatePoW(diff uint32) { func (e *Envelope) powToFirstBit(pow float64) int { x := pow - x *= float64(e.size()) + x *= float64(e.Size()) x *= float64(e.TTL) bits := math.Log2(x) bits = math.Ceil(bits) diff --git a/waku/envelope_test.go b/waku/common/envelope_test.go similarity index 96% rename from waku/envelope_test.go rename to waku/common/envelope_test.go index b838f0f04..fa4be0690 100644 --- a/waku/envelope_test.go +++ b/waku/common/envelope_test.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( mrand "math/rand" @@ -33,7 +33,7 @@ func TestPoWCalculationsWithNoLeadingZeros(t *testing.T) { Nonce: 100000, } - e.calculatePoW(0) + e.CalculatePoW(0) if e.pow != 0.07692307692307693 { t.Fatalf("invalid PoW calculation. Expected 0.07692307692307693, got %v", e.pow) @@ -46,7 +46,7 @@ func TestPoWCalculationsWith8LeadingZeros(t *testing.T) { Data: []byte{0xde, 0xad, 0xbe, 0xef}, Nonce: 276, } - e.calculatePoW(0) + e.CalculatePoW(0) if e.pow != 19.692307692307693 { t.Fatalf("invalid PoW calculation. Expected 19.692307692307693, got %v", e.pow) @@ -54,7 +54,7 @@ func TestPoWCalculationsWith8LeadingZeros(t *testing.T) { } func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) { - symKey := make([]byte, aesKeyLength) + symKey := make([]byte, AESKeyLength) mrand.Read(symKey) //nolint: gosec asymKey, err := crypto.GenerateKey() diff --git a/waku/common/errors.go b/waku/common/errors.go new file mode 100644 index 000000000..7f6aeafb3 --- /dev/null +++ b/waku/common/errors.go @@ -0,0 +1,4 @@ +package common + +// TimeSyncError error for clock skew errors. +type TimeSyncError error diff --git a/waku/events.go b/waku/common/events.go similarity index 99% rename from waku/events.go rename to waku/common/events.go index 8dd022f1d..53194b8d7 100644 --- a/waku/events.go +++ b/waku/common/events.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "github.com/ethereum/go-ethereum/common" diff --git a/waku/filter.go b/waku/common/filter.go similarity index 92% rename from waku/filter.go rename to waku/common/filter.go index cbe1d5000..d97735b25 100644 --- a/waku/filter.go +++ b/waku/common/filter.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "crypto/ecdsa" @@ -49,17 +49,15 @@ type Filters struct { topicMatcher map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is - waku *Waku mutex sync.RWMutex } // NewFilters returns a newly created filter collection -func NewFilters(w *Waku) *Filters { +func NewFilters() *Filters { return &Filters{ watchers: make(map[string]*Filter), topicMatcher: make(map[TopicType]map[*Filter]struct{}), allTopicsMatcher: make(map[*Filter]struct{}), - waku: w, } } @@ -129,9 +127,9 @@ func (fs *Filters) removeFromTopicMatchers(watcher *Filter) { } } -// getWatchersByTopic returns a slice containing the filters that +// GetWatchersByTopic returns a slice containing the filters that // match a specific topic -func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter { +func (fs *Filters) GetWatchersByTopic(topic TopicType) []*Filter { res := make([]*Filter, 0, len(fs.allTopicsMatcher)) for watcher := range fs.allTopicsMatcher { res = append(res, watcher) @@ -157,7 +155,7 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { fs.mutex.RLock() defer fs.mutex.RUnlock() - candidates := fs.getWatchersByTopic(env.Topic) + candidates := fs.GetWatchersByTopic(env.Topic) for _, watcher := range candidates { if p2pMessage && !watcher.AllowP2P { log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id)) @@ -241,14 +239,3 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { func (f *Filter) MatchEnvelope(envelope *Envelope) bool { return f.PoW <= 0 || envelope.pow >= f.PoW } - -// IsPubKeyEqual checks that two public keys are equal -func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { - if !ValidatePublicKey(a) { - return false - } else if !ValidatePublicKey(b) { - return false - } - // the curve is always the same, just compare the points - return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 -} diff --git a/waku/filter_test.go b/waku/common/filter_test.go similarity index 95% rename from waku/filter_test.go rename to waku/common/filter_test.go index 577defc24..bd4306ebd 100644 --- a/waku/filter_test.go +++ b/waku/common/filter_test.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "math/big" @@ -65,7 +65,7 @@ func generateFilter(t *testing.T, symmetric bool) (*Filter, error) { f.Src = &key.PublicKey if symmetric { - f.KeySym = make([]byte, aesKeyLength) + f.KeySym = make([]byte, AESKeyLength) mrand.Read(f.KeySym) // nolint: gosec f.SymKeyHash = crypto.Keccak256Hash(f.KeySym) } else { @@ -94,8 +94,7 @@ func TestInstallFilters(t *testing.T) { InitSingleTest() const SizeTestFilters = 256 - w := New(&Config{}, nil) - filters := NewFilters(w) + filters := NewFilters() tst := generateTestCases(t, SizeTestFilters) var err error @@ -106,7 +105,7 @@ func TestInstallFilters(t *testing.T) { t.Fatalf("seed %d: failed to install filter: %s", seed, err) } tst[i].id = j - if len(j) != keyIDSize*2 { + if len(j) != KeyIDSize*2 { t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j)) } } @@ -132,8 +131,7 @@ func TestInstallFilters(t *testing.T) { func TestInstallSymKeyGeneratesHash(t *testing.T) { InitSingleTest() - w := New(&Config{}, nil) - filters := NewFilters(w) + filters := NewFilters() filter, _ := generateFilter(t, true) // save the current SymKeyHash for comparison @@ -159,8 +157,7 @@ func TestInstallSymKeyGeneratesHash(t *testing.T) { func TestInstallIdenticalFilters(t *testing.T) { InitSingleTest() - w := New(&Config{}, nil) - filters := NewFilters(w) + filters := NewFilters() filter1, _ := generateFilter(t, true) // Copy the first filter since some of its fields @@ -185,7 +182,7 @@ func TestInstallIdenticalFilters(t *testing.T) { t.Fatalf("Error installing the second filter with seed %d: %s", seed, err) } - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { t.Fatalf("Error generating message parameters with seed %d: %s", seed, err) } @@ -229,8 +226,7 @@ func TestInstallIdenticalFilters(t *testing.T) { func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { InitSingleTest() - w := New(&Config{}, nil) - filters := NewFilters(w) + filters := NewFilters() filter1, _ := generateFilter(t, true) asymKey, err := crypto.GenerateKey() @@ -295,9 +291,9 @@ func TestMatchEnvelope(t *testing.T) { t.Fatalf("failed generateFilter() with seed %d: %s.", seed, err) } - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } params.Topic[0] = 0xFF // topic mismatch @@ -428,9 +424,9 @@ func TestMatchEnvelope(t *testing.T) { func TestMatchMessageSym(t *testing.T) { InitSingleTest() - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } f, err := generateFilter(t, true) @@ -525,9 +521,9 @@ func TestMatchMessageAsym(t *testing.T) { t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) } - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } const index = 1 @@ -613,9 +609,9 @@ func cloneFilter(orig *Filter) *Filter { } func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope { - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) return nil } @@ -644,8 +640,7 @@ func TestWatchers(t *testing.T) { var x, firstID string var err error - w := New(&Config{}, nil) - filters := NewFilters(w) + filters := NewFilters() tst := generateTestCases(t, NumFilters) for i = 0; i < NumFilters; i++ { tst[i].f.Src = nil @@ -796,9 +791,9 @@ func TestVariableTopics(t *testing.T) { const lastTopicByte = 3 var match bool - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err := NewSentMessage(params) if err != nil { diff --git a/waku/common/helper.go b/waku/common/helper.go new file mode 100644 index 000000000..ef6b84288 --- /dev/null +++ b/waku/common/helper.go @@ -0,0 +1,78 @@ +package common + +import ( + "crypto/ecdsa" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// IsPubKeyEqual checks that two public keys are equal +func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { + if !ValidatePublicKey(a) { + return false + } else if !ValidatePublicKey(b) { + return false + } + // the curve is always the same, just compare the points + return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 +} + +// ValidatePublicKey checks the format of the given public key. +func ValidatePublicKey(k *ecdsa.PublicKey) bool { + return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 +} + +// BytesToUintLittleEndian converts the slice to 64-bit unsigned integer. +func BytesToUintLittleEndian(b []byte) (res uint64) { + mul := uint64(1) + for i := 0; i < len(b); i++ { + res += uint64(b[i]) * mul + mul *= 256 + } + return res +} + +// BytesToUintBigEndian converts the slice to 64-bit unsigned integer. +func BytesToUintBigEndian(b []byte) (res uint64) { + for i := 0; i < len(b); i++ { + res *= 256 + res += uint64(b[i]) + } + return res +} + +// ContainsOnlyZeros checks if the data contain only zeros. +func ContainsOnlyZeros(data []byte) bool { + for _, b := range data { + if b != 0 { + return false + } + } + return true +} + +// GenerateRandomID generates a random string, which is then returned to be used as a key id +func GenerateRandomID() (id string, err error) { + buf, err := GenerateSecureRandomData(KeyIDSize) + if err != nil { + return "", err + } + if !ValidateDataIntegrity(buf, KeyIDSize) { + return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") + } + id = common.Bytes2Hex(buf) + return id, err +} + +// ValidateDataIntegrity returns false if the data have the wrong or contains all zeros, +// which is the simplest and the most common bug. +func ValidateDataIntegrity(k []byte, expectedSize int) bool { + if len(k) != expectedSize { + return false + } + if expectedSize > 3 && ContainsOnlyZeros(k) { + return false + } + return true +} diff --git a/waku/message.go b/waku/common/message.go similarity index 89% rename from waku/message.go rename to waku/common/message.go index 05f9f4bd5..c962083be 100644 --- a/waku/message.go +++ b/waku/common/message.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "crypto/aes" @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) // MessageParams specifies the exact way a message should be wrapped @@ -137,35 +136,7 @@ type MessagesResponse struct { Errors []EnvelopeError } -// MultiVersionResponse allows to decode response into chosen version. -type MultiVersionResponse struct { - Version uint - Response rlp.RawValue -} - -// DecodeResponse1 decodes response into first version of the messages response. -func (m MultiVersionResponse) DecodeResponse1() (resp MessagesResponse, err error) { - return resp, rlp.DecodeBytes(m.Response, &resp) -} - -// Version1MessageResponse first version of the message response. -type Version1MessageResponse struct { - Version uint - Response MessagesResponse -} - -// NewMessagesResponse returns instance of the version messages response. -func NewMessagesResponse(batch common.Hash, errors []EnvelopeError) Version1MessageResponse { - return Version1MessageResponse{ - Version: 1, - Response: MessagesResponse{ - Hash: batch, - Errors: errors, - }, - } -} - -func isMessageSigned(flags byte) bool { +func IsMessageSigned(flags byte) bool { return (flags & signatureFlag) != 0 } @@ -229,7 +200,7 @@ func (msg *sentMessage) appendPadding(params *MessageParams) error { if err != nil { return err } - if !validateDataIntegrity(pad, paddingSize) { + if !ValidateDataIntegrity(pad, paddingSize) { return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize)) } msg.Raw = append(msg.Raw, pad...) @@ -239,7 +210,7 @@ func (msg *sentMessage) appendPadding(params *MessageParams) error { // sign calculates and sets the cryptographic signature for the message, // also setting the sign flag. func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error { - if isMessageSigned(msg.Raw[0]) { + if IsMessageSigned(msg.Raw[0]) { // this should not happen, but no reason to panic log.Error("failed to sign the message: already signed") return nil @@ -271,7 +242,7 @@ func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error { // encryptSymmetric encrypts a message with a topic key, using AES-GCM-256. // nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { - if !validateDataIntegrity(key, aesKeyLength) { + if !ValidateDataIntegrity(key, AESKeyLength) { return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key))) } block, err := aes.NewCipher(key) @@ -282,7 +253,7 @@ func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { if err != nil { return err } - salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key + salt, err := GenerateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key if err != nil { return err } @@ -291,12 +262,12 @@ func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { return nil } -// generateSecureRandomData generates random data where extra security is required. +// GenerateSecureRandomData generates random data where extra security is required. // The purpose of this function is to prevent some bugs in software or in hardware // from delivering not-very-random data. This is especially useful for AES nonce, // where true randomness does not really matter, but it is very important to have // a unique nonce for every message. -func generateSecureRandomData(length int) ([]byte, error) { +func GenerateSecureRandomData(length int) ([]byte, error) { x := make([]byte, length) y := make([]byte, length) res := make([]byte, length) @@ -304,19 +275,19 @@ func generateSecureRandomData(length int) ([]byte, error) { _, err := crand.Read(x) if err != nil { return nil, err - } else if !validateDataIntegrity(x, length) { + } else if !ValidateDataIntegrity(x, length) { return nil, errors.New("crypto/rand failed to generate secure random data") } _, err = mrand.Read(y) // nolint: gosec if err != nil { return nil, err - } else if !validateDataIntegrity(y, length) { + } else if !ValidateDataIntegrity(y, length) { return nil, errors.New("math/rand failed to generate secure random data") } for i := 0; i < length; i++ { res[i] = x[i] ^ y[i] } - if !validateDataIntegrity(res, length) { + if !ValidateDataIntegrity(res, length) { return nil, errors.New("failed to generate secure random data") } return res, nil @@ -392,7 +363,7 @@ func (msg *ReceivedMessage) ValidateAndParse() bool { return false } - if isMessageSigned(msg.Raw[0]) { + if IsMessageSigned(msg.Raw[0]) { end -= signatureLength if end <= 1 { return false @@ -411,7 +382,7 @@ func (msg *ReceivedMessage) ValidateAndParse() bool { if end < beg+sizeOfPayloadSizeField { return false } - payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField])) + payloadSize = int(BytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField])) beg += sizeOfPayloadSizeField if beg+payloadSize > end { return false @@ -440,7 +411,7 @@ func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { // hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding. func (msg *ReceivedMessage) hash() []byte { - if isMessageSigned(msg.Raw[0]) { + if IsMessageSigned(msg.Raw[0]) { sz := len(msg.Raw) - signatureLength return crypto.Keccak256(msg.Raw[:sz]) } diff --git a/waku/message_test.go b/waku/common/message_test.go similarity index 87% rename from waku/message_test.go rename to waku/common/message_test.go index a338167ae..1ce2dee90 100644 --- a/waku/message_test.go +++ b/waku/common/message_test.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "bytes" @@ -26,15 +26,12 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) -func generateMessageParams() (*MessageParams, error) { +func GenerateMessageParams() (*MessageParams, error) { // set all the parameters except p.Dst and p.Padding buf := make([]byte, 4) @@ -46,7 +43,7 @@ func generateMessageParams() (*MessageParams, error) { p.WorkTime = 1 p.TTL = uint32(mrand.Intn(1024)) p.Payload = make([]byte, sz) - p.KeySym = make([]byte, aesKeyLength) + p.KeySym = make([]byte, AESKeyLength) mrand.Read(p.Payload) // nolint: gosec mrand.Read(p.KeySym) // nolint: gosec p.Topic = BytesToTopic(buf) @@ -61,9 +58,9 @@ func generateMessageParams() (*MessageParams, error) { } func singleMessageTest(t *testing.T, symmetric bool) { - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } key, err := crypto.GenerateKey() @@ -106,7 +103,7 @@ func singleMessageTest(t *testing.T, symmetric bool) { if !bytes.Equal(text, decrypted.Payload) { t.Fatalf("failed with seed %d: compare payload.", seed) } - if !isMessageSigned(decrypted.Raw[0]) { + if !IsMessageSigned(decrypted.Raw[0]) { t.Fatalf("failed with seed %d: unsigned.", seed) } if len(decrypted.Signature) != signatureLength { @@ -132,9 +129,9 @@ func TestMessageWrap(t *testing.T) { mrand.Seed(seed) target := 128.0 - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err := NewSentMessage(params) @@ -173,9 +170,9 @@ func TestMessageSeal(t *testing.T) { seed = int64(1976726903) mrand.Seed(seed) - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err := NewSentMessage(params) @@ -192,7 +189,7 @@ func TestMessageSeal(t *testing.T) { params.PoW = target env.Seal(params) // nolint: errcheck - env.calculatePoW(0) + env.CalculatePoW(0) pow := env.PoW() if pow < target { t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target) @@ -201,7 +198,7 @@ func TestMessageSeal(t *testing.T) { params.WorkTime = 1 params.PoW = 1000000000.0 env.Seal(params) // nolint: errcheck - env.calculatePoW(0) + env.CalculatePoW(0) pow = env.PoW() if pow < 2*target { t.Fatalf("failed Wrap with seed %d: pow too small %f.", seed, pow) @@ -219,9 +216,9 @@ func TestEnvelopeOpen(t *testing.T) { } func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } key, err := crypto.GenerateKey() @@ -260,7 +257,7 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { if !bytes.Equal(text, decrypted.Payload) { t.Fatalf("failed with seed %d: compare payload.", seed) } - if !isMessageSigned(decrypted.Raw[0]) { + if !IsMessageSigned(decrypted.Raw[0]) { t.Fatalf("failed with seed %d: unsigned.", seed) } if len(decrypted.Signature) != signatureLength { @@ -288,23 +285,23 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { func TestEncryptWithZeroKey(t *testing.T) { InitSingleTest() - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err := NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } - params.KeySym = make([]byte, aesKeyLength) + params.KeySym = make([]byte, AESKeyLength) _, err = msg.Wrap(params, time.Now()) if err == nil { t.Fatalf("wrapped with zero key, seed: %d.", seed) } - params, err = generateMessageParams() + params, err = GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err = NewSentMessage(params) if err != nil { @@ -316,9 +313,9 @@ func TestEncryptWithZeroKey(t *testing.T) { t.Fatalf("wrapped with empty key, seed: %d.", seed) } - params, err = generateMessageParams() + params, err = GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err = NewSentMessage(params) if err != nil { @@ -334,9 +331,9 @@ func TestEncryptWithZeroKey(t *testing.T) { func TestRlpEncode(t *testing.T) { InitSingleTest() - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + t.Fatalf("failed GenerateMessageParams with seed %d: %s.", seed, err) } msg, err := NewSentMessage(params) if err != nil { @@ -367,9 +364,9 @@ func TestRlpEncode(t *testing.T) { } func singlePaddingTest(t *testing.T, padSize int) { - params, err := generateMessageParams() + params, err := GenerateMessageParams() if err != nil { - t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err) + t.Fatalf("failed GenerateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err) } params.Padding = make([]byte, padSize) params.PoW = 0.0000000001 @@ -426,7 +423,7 @@ func TestPadding(t *testing.T) { func TestPaddingAppendedToSymMessagesWithSignature(t *testing.T) { params := &MessageParams{ Payload: make([]byte, 246), - KeySym: make([]byte, aesKeyLength), + KeySym: make([]byte, AESKeyLength), } pSrc, err := crypto.GenerateKey() @@ -495,15 +492,3 @@ func TestValidateAndParseSizeOfPayloadSize(t *testing.T) { }) } } - -func TestEncodeDecodeVersionedResponse(t *testing.T) { - response := NewMessagesResponse(common.Hash{1}, []EnvelopeError{{Code: 1}}) - b, err := rlp.EncodeToBytes(response) - require.NoError(t, err) - - var mresponse MultiVersionResponse - require.NoError(t, rlp.DecodeBytes(b, &mresponse)) - v1resp, err := mresponse.DecodeResponse1() - require.NoError(t, err) - require.Equal(t, response.Response.Hash, v1resp.Hash) -} diff --git a/waku/metrics.go b/waku/common/metrics.go similarity index 66% rename from waku/metrics.go rename to waku/common/metrics.go index 994b5983b..438dd0cb6 100644 --- a/waku/metrics.go +++ b/waku/common/metrics.go @@ -16,71 +16,69 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( prom "github.com/prometheus/client_golang/prometheus" ) var ( - envelopesReceivedCounter = prom.NewCounter(prom.CounterOpts{ + EnvelopesReceivedCounter = prom.NewCounter(prom.CounterOpts{ Name: "waku_envelopes_received_total", Help: "Number of envelopes received.", }) - envelopesValidatedCounter = prom.NewCounter(prom.CounterOpts{ + EnvelopesValidatedCounter = prom.NewCounter(prom.CounterOpts{ Name: "waku_envelopes_validated_total", Help: "Number of envelopes processed successfully.", }) - envelopesRejectedCounter = prom.NewCounterVec(prom.CounterOpts{ + EnvelopesRejectedCounter = prom.NewCounterVec(prom.CounterOpts{ Name: "waku_envelopes_rejected_total", Help: "Number of envelopes rejected.", }, []string{"reason"}) - envelopesCacheFailedCounter = prom.NewCounterVec(prom.CounterOpts{ + EnvelopesCacheFailedCounter = prom.NewCounterVec(prom.CounterOpts{ Name: "waku_envelopes_cache_failures_total", Help: "Number of envelopes which failed to be cached.", }, []string{"type"}) - envelopesCachedCounter = prom.NewCounterVec(prom.CounterOpts{ + EnvelopesCachedCounter = prom.NewCounterVec(prom.CounterOpts{ Name: "waku_envelopes_cached_total", Help: "Number of envelopes cached.", }, []string{"cache"}) - envelopesSizeMeter = prom.NewHistogram(prom.HistogramOpts{ + EnvelopesSizeMeter = prom.NewHistogram(prom.HistogramOpts{ Name: "waku_envelopes_size_bytes", Help: "Size of processed Waku envelopes in bytes.", Buckets: prom.ExponentialBuckets(256, 4, 10), }) - // rate limiter metrics - rateLimitsProcessed = prom.NewCounter(prom.CounterOpts{ + RateLimitsProcessed = prom.NewCounter(prom.CounterOpts{ Name: "waku_rate_limits_processed_total", Help: "Number of packets Waku rate limiter processed.", }) - rateLimitsExceeded = prom.NewCounterVec(prom.CounterOpts{ + RateLimitsExceeded = prom.NewCounterVec(prom.CounterOpts{ Name: "waku_rate_limits_exceeded_total", Help: "Number of times the Waku rate limits were exceeded", }, []string{"type"}) - // bridging - bridgeSent = prom.NewCounter(prom.CounterOpts{ + BridgeSent = prom.NewCounter(prom.CounterOpts{ Name: "waku_bridge_sent_total", Help: "Number of envelopes bridged from Waku", }) - bridgeReceivedSucceed = prom.NewCounter(prom.CounterOpts{ + BridgeReceivedSucceed = prom.NewCounter(prom.CounterOpts{ Name: "waku_bridge_received_success_total", Help: "Number of envelopes bridged to Waku and successfully added", }) - bridgeReceivedFailed = prom.NewCounter(prom.CounterOpts{ + BridgeReceivedFailed = prom.NewCounter(prom.CounterOpts{ Name: "waku_bridge_received_failure_total", Help: "Number of envelopes bridged to Waku and failed to be added", }) ) func init() { - prom.MustRegister(envelopesReceivedCounter) - prom.MustRegister(envelopesRejectedCounter) - prom.MustRegister(envelopesCacheFailedCounter) - prom.MustRegister(envelopesCachedCounter) - prom.MustRegister(envelopesSizeMeter) - prom.MustRegister(rateLimitsProcessed) - prom.MustRegister(rateLimitsExceeded) - prom.MustRegister(bridgeSent) - prom.MustRegister(bridgeReceivedSucceed) - prom.MustRegister(bridgeReceivedFailed) + prom.MustRegister(EnvelopesReceivedCounter) + prom.MustRegister(EnvelopesRejectedCounter) + prom.MustRegister(EnvelopesCacheFailedCounter) + prom.MustRegister(EnvelopesCachedCounter) + prom.MustRegister(EnvelopesSizeMeter) + prom.MustRegister(RateLimitsProcessed) + prom.MustRegister(RateLimitsExceeded) + prom.MustRegister(BridgeSent) + prom.MustRegister(BridgeReceivedSucceed) + prom.MustRegister(BridgeReceivedFailed) } diff --git a/waku/common/protocol.go b/waku/common/protocol.go new file mode 100644 index 000000000..756114afd --- /dev/null +++ b/waku/common/protocol.go @@ -0,0 +1,94 @@ +package common + +import ( + "net" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +type Peer interface { + // Start performs the handshake and initialize the broadcasting of messages + Start() error + Stop() + // Run start the polling loop + Run() error + + // NotifyAboutPowRequirementChange notifies the peer that POW for the host has changed + NotifyAboutPowRequirementChange(float64) error + // NotifyAboutBloomFilterChange notifies the peer that bloom filter for the host has changed + NotifyAboutBloomFilterChange([]byte) error + // NotifyAboutTopicInterestChange notifies the peer that topics for the host have changed + NotifyAboutTopicInterestChange([]TopicType) error + + // SetPeerTrusted sets the value of trusted, meaning we will + // allow p2p messages from them, which is necessary to interact + // with mailservers. + SetPeerTrusted(bool) + // SetRWWriter sets the socket to read/write + SetRWWriter(p2p.MsgReadWriter) + + RequestHistoricMessages(*Envelope) error + SendMessagesRequest(MessagesRequest) error + SendHistoricMessageResponse([]byte) error + SendP2PMessages([]*Envelope) error + SendRawP2PDirect([]rlp.RawValue) error + + // Mark marks an envelope known to the peer so that it won't be sent back. + Mark(*Envelope) + // Marked checks if an envelope is already known to the remote peer. + Marked(*Envelope) bool + + ID() []byte + IP() net.IP + EnodeID() enode.ID + + PoWRequirement() float64 + BloomFilter() []byte + ConfirmationsEnabled() bool +} + +// WakuHost is the local instance of waku, which both interacts with remote clients +// (peers) and local clients (through RPC API) +type WakuHost interface { + // MaxMessageSize returns the maximum accepted message size. + MaxMessageSize() uint32 + // LightClientMode returns whether the host is running in light client mode + LightClientMode() bool + // Mailserver returns whether the host is running a mailserver + Mailserver() bool + // LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed + LightClientModeConnectionRestricted() bool + // ConfirmationsEnabled returns true if message confirmations are enabled. + ConfirmationsEnabled() bool + // RateLimits returns the current rate limits for the host + RateLimits() RateLimits + // MinPow returns the MinPow for the host + MinPow() float64 + // BloomFilterMode returns whether the host is using bloom filter + BloomFilterMode() bool + // BloomFilter returns the bloom filter for the host + BloomFilter() []byte + //TopicInterest returns the topics for the host + TopicInterest() []TopicType + // IsEnvelopeCached checks if envelope with specific hash has already been received and cached. + IsEnvelopeCached(common.Hash) bool + // Envelopes returns all the envelopes queued + Envelopes() []*Envelope + SendEnvelopeEvent(EnvelopeEvent) int + // OnNewEnvelopes handles newly received envelopes from a peer + OnNewEnvelopes([]*Envelope, Peer) ([]EnvelopeError, error) + // OnNewP2PEnvelopes handles envelopes received though the P2P + // protocol (i.e from a mailserver in most cases) + OnNewP2PEnvelopes([]*Envelope, Peer) error + // OnMessagesResponse handles when the peer receive a message response + // from a mailserver + OnMessagesResponse(MessagesResponse, Peer) error + // OnMessagesRequest handles when the peer receive a message request + // this only works if the peer is a mailserver + OnMessagesRequest(MessagesRequest, Peer) error + OnBatchAcknowledged(common.Hash, Peer) error + OnP2PRequestCompleted([]byte, Peer) error +} diff --git a/waku/rate_limiter.go b/waku/common/rate_limiter.go similarity index 87% rename from waku/rate_limiter.go rename to waku/common/rate_limiter.go index 81803bed6..ecb0a9eb9 100644 --- a/waku/rate_limiter.go +++ b/waku/common/rate_limiter.go @@ -16,12 +16,13 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "bytes" "errors" "fmt" + "net" "time" "github.com/tsenart/tb" @@ -30,7 +31,12 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" ) -type runLoop func(p *Peer, rw p2p.MsgReadWriter) error +type runLoop func(rw p2p.MsgReadWriter) error + +type RateLimiterPeer interface { + ID() []byte + IP() net.IP +} type RateLimiterHandler interface { ExceedPeerLimit() error @@ -40,11 +46,11 @@ type RateLimiterHandler interface { type MetricsRateLimiterHandler struct{} func (MetricsRateLimiterHandler) ExceedPeerLimit() error { - rateLimitsExceeded.WithLabelValues("peer_id").Inc() + RateLimitsExceeded.WithLabelValues("peer_id").Inc() return nil } func (MetricsRateLimiterHandler) ExceedIPLimit() error { - rateLimitsExceeded.WithLabelValues("ip").Inc() + RateLimitsExceeded.WithLabelValues("ip").Inc() return nil } @@ -105,8 +111,8 @@ type PeerRateLimiter struct { peerIDThrottler *tb.Throttler ipThrottler *tb.Throttler - limitPerSecIP int64 - limitPerSecPeerID int64 + LimitPerSecIP int64 + LimitPerSecPeerID int64 whitelistedPeerIDs []enode.ID whitelistedIPs []string @@ -123,15 +129,15 @@ func NewPeerRateLimiter(cfg *PeerRateLimiterConfig, handlers ...RateLimiterHandl return &PeerRateLimiter{ peerIDThrottler: tb.NewThrottler(time.Millisecond * 100), ipThrottler: tb.NewThrottler(time.Millisecond * 100), - limitPerSecIP: cfg.LimitPerSecIP, - limitPerSecPeerID: cfg.LimitPerSecPeerID, + LimitPerSecIP: cfg.LimitPerSecIP, + LimitPerSecPeerID: cfg.LimitPerSecPeerID, whitelistedPeerIDs: cfg.WhitelistedPeerIDs, whitelistedIPs: cfg.WhitelistedIPs, handlers: handlers, } } -func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoop) error { +func (r *PeerRateLimiter) Decorate(p RateLimiterPeer, rw p2p.MsgReadWriter, runLoop runLoop) error { in, out := p2p.MsgPipe() defer in.Close() defer out.Close() @@ -146,11 +152,13 @@ func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoo return } - rateLimitsProcessed.Inc() + RateLimitsProcessed.Inc() var ip string - if p != nil && p.peer != nil { - ip = p.peer.Node().IP().String() + if p != nil { + // this relies on being the string representation of nil + // as IP() might return a nil value + ip = p.IP().String() } if halted := r.throttleIP(ip); halted { for _, h := range r.handlers { @@ -197,7 +205,7 @@ func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoo }() go func() { - errC <- runLoop(p, out) + errC <- runLoop(out) }() return <-errC @@ -206,19 +214,19 @@ func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoo // throttleIP throttles a number of messages incoming from a given IP. // It allows 10 packets per second. func (r *PeerRateLimiter) throttleIP(ip string) bool { - if r.limitPerSecIP == 0 { + if r.LimitPerSecIP == 0 { return false } if stringSliceContains(r.whitelistedIPs, ip) { return false } - return r.ipThrottler.Halt(ip, 1, r.limitPerSecIP) + return r.ipThrottler.Halt(ip, 1, r.LimitPerSecIP) } // throttlePeer throttles a number of messages incoming from a peer. // It allows 3 packets per second. func (r *PeerRateLimiter) throttlePeer(peerID []byte) bool { - if r.limitPerSecIP == 0 { + if r.LimitPerSecIP == 0 { return false } var id enode.ID @@ -226,7 +234,7 @@ func (r *PeerRateLimiter) throttlePeer(peerID []byte) bool { if enodeIDSliceContains(r.whitelistedPeerIDs, id) { return false } - return r.peerIDThrottler.Halt(id.String(), 1, r.limitPerSecPeerID) + return r.peerIDThrottler.Halt(id.String(), 1, r.LimitPerSecPeerID) } func stringSliceContains(s []string, searched string) bool { diff --git a/waku/rate_limiter_test.go b/waku/common/rate_limiter_test.go similarity index 89% rename from waku/rate_limiter_test.go rename to waku/common/rate_limiter_test.go index 5d60acd83..46c5a2830 100644 --- a/waku/rate_limiter_test.go +++ b/waku/common/rate_limiter_test.go @@ -16,10 +16,11 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "bytes" + "net" "testing" "time" @@ -45,7 +46,7 @@ func TestPeerRateLimiterDecorator(t *testing.T) { }() messages := make(chan p2p.Msg, 1) - runLoop := func(p *Peer, rw p2p.MsgReadWriter) error { + runLoop := func(rw p2p.MsgReadWriter) error { msg, err := rw.ReadMsg() if err != nil { return err @@ -55,7 +56,7 @@ func TestPeerRateLimiterDecorator(t *testing.T) { } r := NewPeerRateLimiter(nil, &mockRateLimiterHandler{}) - err := r.decorate(nil, out, runLoop) + err := r.Decorate(nil, out, runLoop) require.NoError(t, err) receivedMsg := <-messages @@ -79,7 +80,7 @@ func TestPeerLimiterThrottlingWithZeroLimit(t *testing.T) { func TestPeerLimiterHandler(t *testing.T) { h := &mockRateLimiterHandler{} r := NewPeerRateLimiter(nil, h) - p := &Peer{ + p := &TestWakuPeer{ peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil), } rw1, rw2 := p2p.MsgPipe() @@ -119,7 +120,7 @@ func TestPeerLimiterHandlerWithWhitelisting(t *testing.T) { WhitelistedIPs: []string{""}, // no IP is represented as string WhitelistedPeerIDs: []enode.ID{{0xaa, 0xbb, 0xcc}}, }, h) - p := &Peer{ + p := &TestWakuPeer{ peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil), } rw1, rw2 := p2p.MsgPipe() @@ -151,8 +152,8 @@ func TestPeerLimiterHandlerWithWhitelisting(t *testing.T) { require.Equal(t, 0, h.exceedPeerLimit) } -func echoMessages(r *PeerRateLimiter, p *Peer, rw p2p.MsgReadWriter) error { - return r.decorate(p, rw, func(p *Peer, rw p2p.MsgReadWriter) error { +func echoMessages(r *PeerRateLimiter, p RateLimiterPeer, rw p2p.MsgReadWriter) error { + return r.Decorate(p, rw, func(rw p2p.MsgReadWriter) error { for { msg, err := rw.ReadMsg() if err != nil { @@ -173,3 +174,16 @@ type mockRateLimiterHandler struct { func (m *mockRateLimiterHandler) ExceedPeerLimit() error { m.exceedPeerLimit++; return nil } func (m *mockRateLimiterHandler) ExceedIPLimit() error { m.exceedIPLimit++; return nil } + +type TestWakuPeer struct { + peer *p2p.Peer +} + +func (p *TestWakuPeer) IP() net.IP { + return p.peer.Node().IP() +} + +func (p *TestWakuPeer) ID() []byte { + id := p.peer.ID() + return id[:] +} diff --git a/waku/topic.go b/waku/common/topic.go similarity index 99% rename from waku/topic.go rename to waku/common/topic.go index a9e77ff89..63a9c1cfd 100644 --- a/waku/topic.go +++ b/waku/common/topic.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "github.com/ethereum/go-ethereum/common/hexutil" diff --git a/waku/topic_test.go b/waku/common/topic_test.go similarity index 99% rename from waku/topic_test.go rename to waku/common/topic_test.go index e110d5792..55970b20a 100644 --- a/waku/topic_test.go +++ b/waku/common/topic_test.go @@ -16,7 +16,7 @@ // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. -package waku +package common import ( "encoding/json" diff --git a/waku/config.go b/waku/config.go index c77c0b02a..8dad8a6a6 100644 --- a/waku/config.go +++ b/waku/config.go @@ -18,6 +18,10 @@ package waku +import ( + "github.com/status-im/status-go/waku/common" +) + // Config represents the configuration state of a waku node. type Config struct { MaxMessageSize uint32 `toml:",omitempty"` @@ -30,7 +34,7 @@ type Config struct { } var DefaultConfig = Config{ - MaxMessageSize: DefaultMaxMessageSize, - MinimumAcceptedPoW: DefaultMinimumPoW, + MaxMessageSize: common.DefaultMaxMessageSize, + MinimumAcceptedPoW: common.DefaultMinimumPoW, RestrictLightClientsConn: true, } diff --git a/waku/mailserver.go b/waku/mailserver.go index 0f7e42bbc..8ccc3851c 100644 --- a/waku/mailserver.go +++ b/waku/mailserver.go @@ -23,7 +23,9 @@ import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/waku/common" + + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -39,14 +41,14 @@ const ( // DeliverMail should use p2pMessageCode for delivery, // in order to bypass the expiry checks. type MailServer interface { - Archive(env *Envelope) - DeliverMail(peerID []byte, request *Envelope) // DEPRECATED; use Deliver() - Deliver(peerID []byte, request MessagesRequest) + Archive(env *common.Envelope) + DeliverMail(peerID []byte, request *common.Envelope) // DEPRECATED; use Deliver() + Deliver(peerID []byte, request common.MessagesRequest) } // MailServerResponse is the response payload sent by the mailserver. type MailServerResponse struct { - LastEnvelopeHash common.Hash + LastEnvelopeHash gethcommon.Hash Cursor []byte Error error } @@ -57,7 +59,7 @@ func invalidResponseSizeError(size int) error { // CreateMailServerRequestCompletedPayload creates a payload representing // a successful request to mailserver -func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash common.Hash, cursor []byte) []byte { +func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash gethcommon.Hash, cursor []byte) []byte { payload := make([]byte, len(requestID)) copy(payload, requestID[:]) payload = append(payload, lastEnvelopeHash[:]...) @@ -67,7 +69,7 @@ func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash common. // CreateMailServerRequestFailedPayload creates a payload representing // a failed request to a mailserver -func CreateMailServerRequestFailedPayload(requestID common.Hash, err error) []byte { +func CreateMailServerRequestFailedPayload(requestID gethcommon.Hash, err error) []byte { payload := []byte(mailServerFailedPayloadPrefix) payload = append(payload, requestID[:]...) payload = append(payload, []byte(err.Error())...) @@ -79,8 +81,8 @@ func CreateMailServerRequestFailedPayload(requestID common.Hash, err error) []by // * request completed successfully // * request failed // If the payload is unknown/unparseable, it returns `nil` -func CreateMailServerEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) { - if len(payload) < common.HashLength { +func CreateMailServerEvent(nodeID enode.ID, payload []byte) (*common.EnvelopeEvent, error) { + if len(payload) < gethcommon.HashLength { return nil, invalidResponseSizeError(len(payload)) } @@ -94,8 +96,8 @@ func CreateMailServerEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, err return tryCreateMailServerRequestCompletedEvent(nodeID, payload) } -func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) { - if len(payload) < common.HashLength+len(mailServerFailedPayloadPrefix) { +func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*common.EnvelopeEvent, error) { + if len(payload) < gethcommon.HashLength+len(mailServerFailedPayloadPrefix) { return nil, nil } @@ -106,17 +108,17 @@ func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*En } var ( - requestID common.Hash + requestID gethcommon.Hash errorMsg string ) requestID, remainder = extractHash(remainder) errorMsg = string(remainder) - event := EnvelopeEvent{ + event := common.EnvelopeEvent{ Peer: nodeID, Hash: requestID, - Event: EventMailServerRequestCompleted, + Event: common.EventMailServerRequestCompleted, Data: &MailServerResponse{ Error: errors.New(errorMsg), }, @@ -126,7 +128,7 @@ func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*En } -func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) { +func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) (*common.EnvelopeEvent, error) { // check if payload is // - requestID or // - requestID + lastEnvelopeHash or @@ -134,19 +136,19 @@ func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) ( // requestID is the hash of the request envelope. // lastEnvelopeHash is the last envelope sent by the mail server // cursor is the db key, 36 bytes: 4 for the timestamp + 32 for the envelope hash. - if len(payload) > common.HashLength*2+cursorSize { + if len(payload) > gethcommon.HashLength*2+cursorSize { return nil, invalidResponseSizeError(len(payload)) } var ( - requestID common.Hash - lastEnvelopeHash common.Hash + requestID gethcommon.Hash + lastEnvelopeHash gethcommon.Hash cursor []byte ) requestID, remainder := extractHash(payload) - if len(remainder) >= common.HashLength { + if len(remainder) >= gethcommon.HashLength { lastEnvelopeHash, remainder = extractHash(remainder) } @@ -154,10 +156,10 @@ func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) ( cursor = remainder } - event := EnvelopeEvent{ + event := common.EnvelopeEvent{ Peer: nodeID, Hash: requestID, - Event: EventMailServerRequestCompleted, + Event: common.EventMailServerRequestCompleted, Data: &MailServerResponse{ LastEnvelopeHash: lastEnvelopeHash, Cursor: cursor, @@ -167,9 +169,9 @@ func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) ( return &event, nil } -func extractHash(payload []byte) (common.Hash, []byte) { - prefix, remainder := extractPrefix(payload, common.HashLength) - return common.BytesToHash(prefix), remainder +func extractHash(payload []byte) (gethcommon.Hash, []byte) { + prefix, remainder := extractPrefix(payload, gethcommon.HashLength) + return gethcommon.BytesToHash(prefix), remainder } func extractPrefix(payload []byte, size int) ([]byte, []byte) { diff --git a/waku/peer.go b/waku/peer.go deleted file mode 100644 index e0900c7e8..000000000 --- a/waku/peer.go +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2019 The Waku Library Authors. -// -// The Waku library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Waku library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty off -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Waku library. If not, see . -// -// This software uses the go-ethereum library, which is licensed -// under the GNU Lesser General Public Library, version 3 or any later. - -package waku - -import ( - "bytes" - "fmt" - "math" - "sync" - "time" - - mapset "github.com/deckarep/golang-set" - "go.uber.org/zap" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" -) - -// Peer represents a waku protocol peer connection. -type Peer struct { - host *Waku - peer *p2p.Peer - ws p2p.MsgReadWriter - logger *zap.Logger - - trusted bool - powRequirement float64 - // bloomMu is to allow thread safe access to - // the bloom filter - bloomMu sync.Mutex - bloomFilter []byte - // topicInterestMu is to allow thread safe access to - // the map of topic interests - topicInterestMu sync.Mutex - topicInterest map[TopicType]bool - // fullNode is used to indicate that the node will be accepting any - // envelope. The opposite is an "empty node" , which is when - // a bloom filter is all 0s or topic interest is an empty map (not nil). - // In that case no envelope is accepted. - fullNode bool - confirmationsEnabled bool - rateLimitsMu sync.Mutex - rateLimits RateLimits - - known mapset.Set // Messages already known by the peer to avoid wasting bandwidth - - quit chan struct{} -} - -// newPeer creates a new waku peer object, but does not run the handshake itself. -func newPeer(host *Waku, remote *p2p.Peer, rw p2p.MsgReadWriter, logger *zap.Logger) *Peer { - if logger == nil { - logger = zap.NewNop() - } - - return &Peer{ - host: host, - peer: remote, - ws: rw, - logger: logger, - trusted: false, - powRequirement: 0.0, - known: mapset.NewSet(), - quit: make(chan struct{}), - bloomFilter: MakeFullNodeBloom(), - fullNode: true, - } -} - -// start initiates the peer updater, periodically broadcasting the waku packets -// into the network. -func (p *Peer) start() { - go p.update() - p.logger.Debug("starting peer", zap.Binary("peerID", p.ID())) -} - -// stop terminates the peer updater, stopping message forwarding to it. -func (p *Peer) stop() { - close(p.quit) - p.logger.Debug("stopping peer", zap.Binary("peerID", p.ID())) -} - -// handshake sends the protocol initiation status message to the remote peer and -// verifies the remote status too. -func (p *Peer) handshake() error { - // Send the handshake status message asynchronously - errc := make(chan error, 1) - opts := p.host.toStatusOptions() - go func() { - errc <- p2p.SendItems(p.ws, statusCode, ProtocolVersion, opts) - }() - - // Fetch the remote status packet and verify protocol match - packet, err := p.ws.ReadMsg() - if err != nil { - return err - } - if packet.Code != statusCode { - return fmt.Errorf("p [%x] sent packet %x before status packet", p.ID(), packet.Code) - } - - var ( - peerProtocolVersion uint64 - peerOptions statusOptions - ) - s := rlp.NewStream(packet.Payload, uint64(packet.Size)) - if _, err := s.List(); err != nil { - return fmt.Errorf("p [%x]: failed to decode status packet: %v", p.ID(), err) - } - // Validate protocol version. - if err := s.Decode(&peerProtocolVersion); err != nil { - return fmt.Errorf("p [%x]: failed to decode peer protocol version: %v", p.ID(), err) - } - if peerProtocolVersion != ProtocolVersion { - return fmt.Errorf("p [%x]: protocol version mismatch %d != %d", p.ID(), peerProtocolVersion, ProtocolVersion) - } - // Decode and validate other status packet options. - if err := s.Decode(&peerOptions); err != nil { - return fmt.Errorf("p [%x]: failed to decode status options: %v", p.ID(), err) - } - if err := s.ListEnd(); err != nil { - return fmt.Errorf("p [%x]: failed to decode status packet: %v", p.ID(), err) - } - if err := p.setOptions(peerOptions.WithDefaults()); err != nil { - return fmt.Errorf("p [%x]: failed to set options: %v", p.ID(), err) - } - if err := <-errc; err != nil { - return fmt.Errorf("p [%x] failed to send status packet: %v", p.ID(), err) - } - return nil -} - -func (p *Peer) setOptions(peerOptions statusOptions) error { - - p.logger.Debug("settings options", zap.Binary("peerID", p.ID()), zap.Any("Options", peerOptions)) - - if err := peerOptions.Validate(); err != nil { - return fmt.Errorf("p [%x]: sent invalid options: %v", p.ID(), err) - } - // Validate and save peer's PoW. - pow := peerOptions.PoWRequirementF() - if pow != nil { - if math.IsInf(*pow, 0) || math.IsNaN(*pow) || *pow < 0.0 { - return fmt.Errorf("p [%x]: sent bad status message: invalid pow", p.ID()) - } - p.powRequirement = *pow - } - - if peerOptions.TopicInterest != nil { - p.setTopicInterest(peerOptions.TopicInterest) - } else if peerOptions.BloomFilter != nil { - // Validate and save peer's bloom filters. - bloom := peerOptions.BloomFilter - bloomSize := len(bloom) - if bloomSize != 0 && bloomSize != BloomFilterSize { - return fmt.Errorf("p [%x] sent bad status message: wrong bloom filter size %d", p.ID(), bloomSize) - } - p.setBloomFilter(bloom) - } - - if peerOptions.LightNodeEnabled != nil { - // Validate and save other peer's options. - if *peerOptions.LightNodeEnabled && p.host.LightClientMode() && p.host.LightClientModeConnectionRestricted() { - return fmt.Errorf("p [%x] is useless: two light client communication restricted", p.ID()) - } - } - if peerOptions.ConfirmationsEnabled != nil { - p.confirmationsEnabled = *peerOptions.ConfirmationsEnabled - } - if peerOptions.RateLimits != nil { - p.setRateLimits(*peerOptions.RateLimits) - } - - return nil -} - -// update executes periodic operations on the peer, including message transmission -// and expiration. -func (p *Peer) update() { - // Start the tickers for the updates - expire := time.NewTicker(expirationCycle) - transmit := time.NewTicker(transmissionCycle) - - // Loop and transmit until termination is requested - for { - select { - case <-expire.C: - p.expire() - - case <-transmit.C: - if err := p.broadcast(); err != nil { - p.logger.Debug("broadcasting failed", zap.Binary("peer", p.ID()), zap.Error(err)) - return - } - - case <-p.quit: - return - } - } -} - -// mark marks an envelope known to the peer so that it won't be sent back. -func (p *Peer) mark(envelope *Envelope) { - p.known.Add(envelope.Hash()) -} - -// marked checks if an envelope is already known to the remote peer. -func (p *Peer) marked(envelope *Envelope) bool { - return p.known.Contains(envelope.Hash()) -} - -// expire iterates over all the known envelopes in the host and removes all -// expired (unknown) ones from the known list. -func (p *Peer) expire() { - unmark := make(map[common.Hash]struct{}) - p.known.Each(func(v interface{}) bool { - if !p.host.isEnvelopeCached(v.(common.Hash)) { - unmark[v.(common.Hash)] = struct{}{} - } - return true - }) - // Dump all known but no longer cached - for hash := range unmark { - p.known.Remove(hash) - } -} - -// broadcast iterates over the collection of envelopes and transmits yet unknown -// ones over the network. -func (p *Peer) broadcast() error { - envelopes := p.host.Envelopes() - bundle := make([]*Envelope, 0, len(envelopes)) - for _, envelope := range envelopes { - if !p.marked(envelope) && envelope.PoW() >= p.powRequirement && p.topicOrBloomMatch(envelope) { - bundle = append(bundle, envelope) - } - } - - if len(bundle) == 0 { - return nil - } - - batchHash, err := sendBundle(p.ws, bundle) - if err != nil { - p.logger.Debug("failed to deliver envelopes", zap.Binary("peer", p.ID()), zap.Error(err)) - return err - } - - // mark envelopes only if they were successfully sent - for _, e := range bundle { - p.mark(e) - event := EnvelopeEvent{ - Event: EventEnvelopeSent, - Hash: e.Hash(), - Peer: p.peer.ID(), - } - if p.confirmationsEnabled { - event.Batch = batchHash - } - p.host.envelopeFeed.Send(event) - } - p.logger.Debug("broadcasted bundles successfully", zap.Binary("peer", p.ID()), zap.Int("count", len(bundle))) - return nil -} - -// ID returns a peer's id -func (p *Peer) ID() []byte { - id := p.peer.ID() - return id[:] -} - -func (p *Peer) notifyAboutPowRequirementChange(pow float64) error { - i := math.Float64bits(pow) - return p2p.Send(p.ws, statusUpdateCode, statusOptions{PoWRequirement: &i}) -} - -func (p *Peer) notifyAboutBloomFilterChange(bloom []byte) error { - return p2p.Send(p.ws, statusUpdateCode, statusOptions{BloomFilter: bloom}) -} - -func (p *Peer) notifyAboutTopicInterestChange(topics []TopicType) error { - return p2p.Send(p.ws, statusUpdateCode, statusOptions{TopicInterest: topics}) -} - -func (p *Peer) bloomMatch(env *Envelope) bool { - p.bloomMu.Lock() - defer p.bloomMu.Unlock() - return p.fullNode || BloomFilterMatch(p.bloomFilter, env.Bloom()) -} - -func (p *Peer) topicInterestMatch(env *Envelope) bool { - p.topicInterestMu.Lock() - defer p.topicInterestMu.Unlock() - - if p.topicInterest == nil { - return false - } - - return p.topicInterest[env.Topic] -} - -// topicOrBloomMatch matches against topic-interest if topic interest -// is not nil. Otherwise it will match against the bloom-filter. -// If the bloom-filter is nil, or full, the node is considered a full-node -// and any envelope will be accepted. An empty topic-interest (but not nil) -// signals that we are not interested in any envelope. -func (p *Peer) topicOrBloomMatch(env *Envelope) bool { - p.topicInterestMu.Lock() - topicInterestMode := p.topicInterest != nil - p.topicInterestMu.Unlock() - - if topicInterestMode { - return p.topicInterestMatch(env) - } - return p.bloomMatch(env) -} - -func (p *Peer) setBloomFilter(bloom []byte) { - p.bloomMu.Lock() - defer p.bloomMu.Unlock() - p.bloomFilter = bloom - p.fullNode = isFullNode(bloom) - if p.fullNode && p.bloomFilter == nil { - p.bloomFilter = MakeFullNodeBloom() - } - p.topicInterest = nil -} - -func (p *Peer) setTopicInterest(topicInterest []TopicType) { - p.topicInterestMu.Lock() - defer p.topicInterestMu.Unlock() - if topicInterest == nil { - p.topicInterest = nil - return - } - p.topicInterest = make(map[TopicType]bool) - for _, topic := range topicInterest { - p.topicInterest[topic] = true - } - p.fullNode = false - p.bloomFilter = nil -} - -func (p *Peer) setRateLimits(r RateLimits) { - p.rateLimitsMu.Lock() - p.rateLimits = r - p.rateLimitsMu.Unlock() -} - -func MakeFullNodeBloom() []byte { - bloom := make([]byte, BloomFilterSize) - for i := 0; i < BloomFilterSize; i++ { - bloom[i] = 0xFF - } - return bloom -} - -func sendBundle(rw p2p.MsgWriter, bundle []*Envelope) (rst common.Hash, err error) { - data, err := rlp.EncodeToBytes(bundle) - if err != nil { - return - } - err = rw.WriteMsg(p2p.Msg{ - Code: messagesCode, - Size: uint32(len(data)), - Payload: bytes.NewBuffer(data), - }) - if err != nil { - return - } - return crypto.Keccak256Hash(data), nil -} diff --git a/waku/v0/const.go b/waku/v0/const.go new file mode 100644 index 000000000..01c406de9 --- /dev/null +++ b/waku/v0/const.go @@ -0,0 +1,19 @@ +package v0 + +// Waku protocol parameters +const ( + Version = uint64(0) // Peer version number + VersionStr = "0" // The same, as a string + Name = "waku" // Nickname of the protocol + + // Waku protocol message codes, according to https://github.com/vacp2p/specs/blob/master/specs/waku/waku-0.md + StatusCode = 0 // used in the handshake + MessagesCode = 1 // regular message + StatusUpdateCode = 22 // update of settings + BatchAcknowledgedCode = 11 // confirmation that batch of envelopes was received + MessageResponseCode = 12 // includes confirmation for delivery and information about errors + P2PRequestCompleteCode = 125 // peer-to-peer message, used by Dapp protocol + P2PRequestCode = 126 // peer-to-peer message, used by Dapp protocol + P2PMessageCode = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further) + NumberOfMessageCodes = 128 +) diff --git a/waku/v0/init.go b/waku/v0/init.go new file mode 100644 index 000000000..57ee8abb6 --- /dev/null +++ b/waku/v0/init.go @@ -0,0 +1,5 @@ +package v0 + +func init() { + initRLPKeyFields() +} diff --git a/waku/v0/message.go b/waku/v0/message.go new file mode 100644 index 000000000..99a03df85 --- /dev/null +++ b/waku/v0/message.go @@ -0,0 +1,55 @@ +package v0 + +import ( + "bytes" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" + "github.com/status-im/status-go/waku/common" +) + +// MultiVersionResponse allows to decode response into chosen version. +type MultiVersionResponse struct { + Version uint + Response rlp.RawValue +} + +// DecodeResponse1 decodes response into first version of the messages response. +func (m MultiVersionResponse) DecodeResponse1() (resp common.MessagesResponse, err error) { + return resp, rlp.DecodeBytes(m.Response, &resp) +} + +// Version1MessageResponse first version of the message response. +type Version1MessageResponse struct { + Version uint + Response common.MessagesResponse +} + +// NewMessagesResponse returns instance of the version messages response. +func NewMessagesResponse(batch gethcommon.Hash, errors []common.EnvelopeError) Version1MessageResponse { + return Version1MessageResponse{ + Version: 1, + Response: common.MessagesResponse{ + Hash: batch, + Errors: errors, + }, + } +} + +func sendBundle(rw p2p.MsgWriter, bundle []*common.Envelope) (rst gethcommon.Hash, err error) { + data, err := rlp.EncodeToBytes(bundle) + if err != nil { + return + } + err = rw.WriteMsg(p2p.Msg{ + Code: MessagesCode, + Size: uint32(len(data)), + Payload: bytes.NewBuffer(data), + }) + if err != nil { + return + } + return crypto.Keccak256Hash(data), nil +} diff --git a/waku/v0/message_test.go b/waku/v0/message_test.go new file mode 100644 index 000000000..075172b2d --- /dev/null +++ b/waku/v0/message_test.go @@ -0,0 +1,42 @@ +// Copyright 2019 The Waku Library Authors. +// +// The Waku library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Waku library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty off +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Waku library. If not, see . +// +// This software uses the go-ethereum library, which is licensed +// under the GNU Lesser General Public Library, version 3 or any later. + +package v0 + +import ( + "testing" + + "github.com/stretchr/testify/require" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/status-im/status-go/waku/common" +) + +func TestEncodeDecodeVersionedResponse(t *testing.T) { + response := NewMessagesResponse(gethcommon.Hash{1}, []common.EnvelopeError{{Code: 1}}) + bytes, err := rlp.EncodeToBytes(response) + require.NoError(t, err) + + var mresponse MultiVersionResponse + require.NoError(t, rlp.DecodeBytes(bytes, &mresponse)) + v1resp, err := mresponse.DecodeResponse1() + require.NoError(t, err) + require.Equal(t, response.Response.Hash, v1resp.Hash) +} diff --git a/waku/v0/peer.go b/waku/v0/peer.go new file mode 100644 index 000000000..354a822d8 --- /dev/null +++ b/waku/v0/peer.go @@ -0,0 +1,621 @@ +package v0 + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "net" + "sync" + "time" + + "go.uber.org/zap" + + mapset "github.com/deckarep/golang-set" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/status-im/status-go/waku/common" +) + +type Peer struct { + host common.WakuHost + rw p2p.MsgReadWriter + p2pPeer *p2p.Peer + logger *zap.Logger + + quit chan struct{} + + trusted bool + powRequirement float64 + // bloomMu is to allow thread safe access to + // the bloom filter + bloomMu sync.Mutex + bloomFilter []byte + // topicInterestMu is to allow thread safe access to + // the map of topic interests + topicInterestMu sync.Mutex + topicInterest map[common.TopicType]bool + // fullNode is used to indicate that the node will be accepting any + // envelope. The opposite is an "empty node" , which is when + // a bloom filter is all 0s or topic interest is an empty map (not nil). + // In that case no envelope is accepted. + fullNode bool + confirmationsEnabled bool + rateLimitsMu sync.Mutex + rateLimits common.RateLimits + + known mapset.Set // Messages already known by the peer to avoid wasting bandwidth +} + +func NewPeer(host common.WakuHost, p2pPeer *p2p.Peer, rw p2p.MsgReadWriter, logger *zap.Logger) *Peer { + if logger == nil { + logger = zap.NewNop() + } + + return &Peer{ + host: host, + p2pPeer: p2pPeer, + logger: logger, + rw: rw, + trusted: false, + powRequirement: 0.0, + known: mapset.NewSet(), + quit: make(chan struct{}), + bloomFilter: common.MakeFullNodeBloom(), + fullNode: true, + } +} + +func (p *Peer) Start() error { + if err := p.handshake(); err != nil { + return err + } + go p.update() + p.logger.Debug("starting peer", zap.Binary("peerID", p.ID())) + return nil +} + +func (p *Peer) Stop() { + close(p.quit) + p.logger.Debug("stopping peer", zap.Binary("peerID", p.ID())) +} + +func (p *Peer) NotifyAboutPowRequirementChange(pow float64) error { + i := math.Float64bits(pow) + return p2p.Send(p.rw, StatusUpdateCode, StatusOptions{PoWRequirement: &i}) +} + +func (p *Peer) NotifyAboutBloomFilterChange(bloom []byte) error { + return p2p.Send(p.rw, StatusUpdateCode, StatusOptions{BloomFilter: bloom}) +} + +func (p *Peer) NotifyAboutTopicInterestChange(topics []common.TopicType) error { + return p2p.Send(p.rw, StatusUpdateCode, StatusOptions{TopicInterest: topics}) +} + +func (p *Peer) SetPeerTrusted(trusted bool) { + p.trusted = trusted +} + +func (p *Peer) RequestHistoricMessages(envelope *common.Envelope) error { + return p2p.Send(p.rw, P2PRequestCode, envelope) +} + +func (p *Peer) SendMessagesRequest(request common.MessagesRequest) error { + return p2p.Send(p.rw, P2PRequestCode, request) +} + +func (p *Peer) SendHistoricMessageResponse(payload []byte) error { + size, r, err := rlp.EncodeToReader(payload) + if err != nil { + return err + } + + return p.rw.WriteMsg(p2p.Msg{Code: P2PRequestCompleteCode, Size: uint32(size), Payload: r}) + +} + +func (p *Peer) SendP2PMessages(envelopes []*common.Envelope) error { + return p2p.Send(p.rw, P2PMessageCode, envelopes) +} + +func (p *Peer) SendRawP2PDirect(envelopes []rlp.RawValue) error { + return p2p.Send(p.rw, P2PMessageCode, envelopes) +} + +func (p *Peer) SetRWWriter(rw p2p.MsgReadWriter) { + p.rw = rw +} + +// Mark marks an envelope known to the peer so that it won't be sent back. +func (p *Peer) Mark(envelope *common.Envelope) { + p.known.Add(envelope.Hash()) +} + +// Marked checks if an envelope is already known to the remote peer. +func (p *Peer) Marked(envelope *common.Envelope) bool { + return p.known.Contains(envelope.Hash()) +} + +func (p *Peer) BloomFilter() []byte { + p.bloomMu.Lock() + defer p.bloomMu.Unlock() + + bloomFilterCopy := make([]byte, len(p.bloomFilter)) + copy(bloomFilterCopy, p.bloomFilter) + return bloomFilterCopy +} + +func (p *Peer) PoWRequirement() float64 { + return p.powRequirement +} + +func (p *Peer) ConfirmationsEnabled() bool { + return p.confirmationsEnabled +} + +// ID returns a peer's id +func (p *Peer) ID() []byte { + id := p.p2pPeer.ID() + return id[:] +} + +func (p *Peer) EnodeID() enode.ID { + return p.p2pPeer.ID() +} + +func (p *Peer) IP() net.IP { + return p.p2pPeer.Node().IP() +} + +func (p *Peer) Run() error { + logger := p.logger.Named("Run") + + for { + // fetch the next packet + packet, err := p.rw.ReadMsg() + if err != nil { + logger.Info("failed to read a message", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + + if packet.Size > p.host.MaxMessageSize() { + logger.Warn("oversize message received", zap.Binary("peer", p.ID()), zap.Uint32("size", packet.Size)) + return errors.New("oversize message received") + } + + if err := p.handlePacket(packet); err != nil { + logger.Warn("failed to handle packet message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + } + _ = packet.Discard() + } +} + +func (p *Peer) handlePacket(packet p2p.Msg) error { + switch packet.Code { + case MessagesCode: + if err := p.handleMessagesCode(packet); err != nil { + p.logger.Warn("failed to handle MessagesCode message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + case MessageResponseCode: + if err := p.handleMessageResponseCode(packet); err != nil { + p.logger.Warn("failed to handle MessageResponseCode message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + case BatchAcknowledgedCode: + if err := p.handleBatchAcknowledgeCode(packet); err != nil { + p.logger.Warn("failed to handle BatchAcknowledgedCode message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + case StatusUpdateCode: + if err := p.handleStatusUpdateCode(packet); err != nil { + p.logger.Warn("failed to decode status update message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + case P2PMessageCode: + if err := p.handleP2PMessageCode(packet); err != nil { + p.logger.Warn("failed to decode direct message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + case P2PRequestCode: + if err := p.handleP2PRequestCode(packet); err != nil { + p.logger.Warn("failed to decode p2p request message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + case P2PRequestCompleteCode: + if err := p.handleP2PRequestCompleteCode(packet); err != nil { + p.logger.Warn("failed to decode p2p request complete message, peer will be disconnected", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + default: + // New message common might be implemented in the future versions of Waku. + // For forward compatibility, just ignore. + p.logger.Debug("ignored packet with message code", zap.Uint64("code", packet.Code)) + } + + return nil +} + +func (p *Peer) handleMessagesCode(packet p2p.Msg) error { + // decode the contained envelopes + data, err := ioutil.ReadAll(packet.Payload) + if err != nil { + common.EnvelopesRejectedCounter.WithLabelValues("failed_read").Inc() + return fmt.Errorf("failed to read packet payload: %v", err) + } + + var envelopes []*common.Envelope + if err := rlp.DecodeBytes(data, &envelopes); err != nil { + common.EnvelopesRejectedCounter.WithLabelValues("invalid_data").Inc() + return fmt.Errorf("invalid payload: %v", err) + } + + envelopeErrors, err := p.host.OnNewEnvelopes(envelopes, p) + + if p.host.ConfirmationsEnabled() { + go p.sendConfirmation(data, envelopeErrors) // nolint: errcheck + } + + return err +} + +func (p *Peer) handleMessageResponseCode(packet p2p.Msg) error { + var resp MultiVersionResponse + if err := packet.Decode(&resp); err != nil { + common.EnvelopesRejectedCounter.WithLabelValues("failed_read").Inc() + return fmt.Errorf("invalid response message: %v", err) + } + if resp.Version != 1 { + p.logger.Info("received unsupported version of MultiVersionResponse for MessageResponseCode packet", zap.Uint("version", resp.Version)) + return nil + } + + response, err := resp.DecodeResponse1() + if err != nil { + common.EnvelopesRejectedCounter.WithLabelValues("invalid_data").Inc() + return fmt.Errorf("failed to decode response message: %v", err) + } + + return p.host.OnMessagesResponse(response, p) +} + +func (p *Peer) handleP2PRequestCode(packet p2p.Msg) error { + // Must be processed if mail server is implemented. Otherwise ignore. + if !p.host.Mailserver() { + return nil + } + + // Read all data as we will try to decode it possibly twice. + data, err := ioutil.ReadAll(packet.Payload) + if err != nil { + return fmt.Errorf("invalid p2p request messages: %v", err) + } + r := bytes.NewReader(data) + packet.Payload = r + + // As we failed to decode the request, let's set the offset + // to the beginning and try decode it again. + if _, err := r.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("invalid p2p request message: %v", err) + } + + var request common.MessagesRequest + errReq := packet.Decode(&request) + if errReq == nil { + return p.host.OnMessagesRequest(request, p) + } + p.logger.Info("failed to decode p2p request message", zap.Binary("peer", p.ID()), zap.Error(errReq)) + + return errors.New("invalid p2p request message") +} + +func (p *Peer) handleBatchAcknowledgeCode(packet p2p.Msg) error { + var batchHash gethcommon.Hash + if err := packet.Decode(&batchHash); err != nil { + return fmt.Errorf("invalid batch ack message: %v", err) + } + return p.host.OnBatchAcknowledged(batchHash, p) +} + +func (p *Peer) handleStatusUpdateCode(packet p2p.Msg) error { + var StatusOptions StatusOptions + err := packet.Decode(&StatusOptions) + if err != nil { + p.logger.Error("failed to decode status-options", zap.Error(err)) + common.EnvelopesRejectedCounter.WithLabelValues("invalid_settings_changed").Inc() + return err + } + + return p.setOptions(StatusOptions) + +} + +func (p *Peer) handleP2PMessageCode(packet p2p.Msg) error { + // peer-to-peer message, sent directly to peer bypassing PoW checks, etc. + // this message is not supposed to be forwarded to other peers, and + // therefore might not satisfy the PoW, expiry and other requirements. + // these messages are only accepted from the trusted peer. + if !p.trusted { + return nil + } + + var ( + envelopes []*common.Envelope + err error + ) + + if err = packet.Decode(&envelopes); err != nil { + return fmt.Errorf("invalid direct message payload: %v", err) + } + + return p.host.OnNewP2PEnvelopes(envelopes, p) +} + +func (p *Peer) handleP2PRequestCompleteCode(packet p2p.Msg) error { + if !p.trusted { + return nil + } + + var payload []byte + if err := packet.Decode(&payload); err != nil { + return fmt.Errorf("invalid p2p request complete message: %v", err) + } + return p.host.OnP2PRequestCompleted(payload, p) +} + +// sendConfirmation sends MessageResponseCode and BatchAcknowledgedCode messages. +func (p *Peer) sendConfirmation(data []byte, envelopeErrors []common.EnvelopeError) (err error) { + batchHash := crypto.Keccak256Hash(data) + err = p2p.Send(p.rw, MessageResponseCode, NewMessagesResponse(batchHash, envelopeErrors)) + if err != nil { + return + } + err = p2p.Send(p.rw, BatchAcknowledgedCode, batchHash) // DEPRECATED + return +} + +// handshake sends the protocol initiation status message to the remote peer and +// verifies the remote status too. +func (p *Peer) handshake() error { + // Send the handshake status message asynchronously + errc := make(chan error, 1) + opts := StatusOptionsFromHost(p.host) + go func() { + errc <- p2p.SendItems(p.rw, StatusCode, Version, opts) + }() + + // Fetch the remote status packet and verify protocol match + packet, err := p.rw.ReadMsg() + if err != nil { + return err + } + if packet.Code != StatusCode { + return fmt.Errorf("p [%x] sent packet %x before status packet", p.ID(), packet.Code) + } + + var ( + peerProtocolVersion uint64 + peerOptions StatusOptions + ) + s := rlp.NewStream(packet.Payload, uint64(packet.Size)) + if _, err := s.List(); err != nil { + return fmt.Errorf("p [%x]: failed to decode status packet: %v", p.ID(), err) + } + // Validate protocol version. + if err := s.Decode(&peerProtocolVersion); err != nil { + return fmt.Errorf("p [%x]: failed to decode peer protocol version: %v", p.ID(), err) + } + if peerProtocolVersion != Version { + return fmt.Errorf("p [%x]: protocol version mismatch %d != %d", p.ID(), peerProtocolVersion, Version) + } + // Decode and validate other status packet options. + if err := s.Decode(&peerOptions); err != nil { + return fmt.Errorf("p [%x]: failed to decode status options: %v", p.ID(), err) + } + if err := s.ListEnd(); err != nil { + return fmt.Errorf("p [%x]: failed to decode status packet: %v", p.ID(), err) + } + if err := p.setOptions(peerOptions.WithDefaults()); err != nil { + return fmt.Errorf("p [%x]: failed to set options: %v", p.ID(), err) + } + if err := <-errc; err != nil { + return fmt.Errorf("p [%x] failed to send status packet: %v", p.ID(), err) + } + return nil +} + +// update executes periodic operations on the peer, including message transmission +// and expiration. +func (p *Peer) update() { + // Start the tickers for the updates + expire := time.NewTicker(common.ExpirationCycle) + transmit := time.NewTicker(common.TransmissionCycle) + + // Loop and transmit until termination is requested + for { + select { + case <-expire.C: + p.expire() + + case <-transmit.C: + if err := p.broadcast(); err != nil { + p.logger.Debug("broadcasting failed", zap.Binary("peer", p.ID()), zap.Error(err)) + return + } + + case <-p.quit: + return + } + } +} + +func (p *Peer) setOptions(peerOptions StatusOptions) error { + + p.logger.Debug("settings options", zap.Binary("peerID", p.ID()), zap.Any("Options", peerOptions)) + + if err := peerOptions.Validate(); err != nil { + return fmt.Errorf("p [%x]: sent invalid options: %v", p.ID(), err) + } + // Validate and save peer's PoW. + pow := peerOptions.PoWRequirementF() + if pow != nil { + if math.IsInf(*pow, 0) || math.IsNaN(*pow) || *pow < 0.0 { + return fmt.Errorf("p [%x]: sent bad status message: invalid pow", p.ID()) + } + p.powRequirement = *pow + } + + if peerOptions.TopicInterest != nil { + p.setTopicInterest(peerOptions.TopicInterest) + } else if peerOptions.BloomFilter != nil { + // Validate and save peer's bloom filters. + bloom := peerOptions.BloomFilter + bloomSize := len(bloom) + if bloomSize != 0 && bloomSize != common.BloomFilterSize { + return fmt.Errorf("p [%x] sent bad status message: wrong bloom filter size %d", p.ID(), bloomSize) + } + p.setBloomFilter(bloom) + } + + if peerOptions.LightNodeEnabled != nil { + // Validate and save other peer's options. + if *peerOptions.LightNodeEnabled && p.host.LightClientMode() && p.host.LightClientModeConnectionRestricted() { + return fmt.Errorf("p [%x] is useless: two light client communication restricted", p.ID()) + } + } + if peerOptions.ConfirmationsEnabled != nil { + p.confirmationsEnabled = *peerOptions.ConfirmationsEnabled + } + if peerOptions.RateLimits != nil { + p.setRateLimits(*peerOptions.RateLimits) + } + + return nil +} + +// expire iterates over all the known envelopes in the host and removes all +// expired (unknown) ones from the known list. +func (p *Peer) expire() { + unmark := make(map[gethcommon.Hash]struct{}) + p.known.Each(func(v interface{}) bool { + if !p.host.IsEnvelopeCached(v.(gethcommon.Hash)) { + unmark[v.(gethcommon.Hash)] = struct{}{} + } + return true + }) + // Dump all known but no longer cached + for hash := range unmark { + p.known.Remove(hash) + } +} + +// broadcast iterates over the collection of envelopes and transmits yet unknown +// ones over the network. +func (p *Peer) broadcast() error { + envelopes := p.host.Envelopes() + bundle := make([]*common.Envelope, 0, len(envelopes)) + for _, envelope := range envelopes { + if !p.Marked(envelope) && envelope.PoW() >= p.powRequirement && p.topicOrBloomMatch(envelope) { + bundle = append(bundle, envelope) + } + } + + if len(bundle) == 0 { + return nil + } + + batchHash, err := sendBundle(p.rw, bundle) + if err != nil { + p.logger.Debug("failed to deliver envelopes", zap.Binary("peer", p.ID()), zap.Error(err)) + return err + } + + // mark envelopes only if they were successfully sent + for _, e := range bundle { + p.Mark(e) + event := common.EnvelopeEvent{ + Event: common.EventEnvelopeSent, + Hash: e.Hash(), + Peer: p.EnodeID(), + } + if p.confirmationsEnabled { + event.Batch = batchHash + } + p.host.SendEnvelopeEvent(event) + } + p.logger.Debug("broadcasted bundles successfully", zap.Binary("peer", p.ID()), zap.Int("count", len(bundle))) + return nil +} + +func (p *Peer) setBloomFilter(bloom []byte) { + p.bloomMu.Lock() + defer p.bloomMu.Unlock() + p.bloomFilter = bloom + p.fullNode = common.IsFullNode(bloom) + if p.fullNode && p.bloomFilter == nil { + p.bloomFilter = common.MakeFullNodeBloom() + } + p.topicInterest = nil +} + +func (p *Peer) setTopicInterest(topicInterest []common.TopicType) { + p.topicInterestMu.Lock() + defer p.topicInterestMu.Unlock() + if topicInterest == nil { + p.topicInterest = nil + return + } + p.topicInterest = make(map[common.TopicType]bool) + for _, topic := range topicInterest { + p.topicInterest[topic] = true + } + p.fullNode = false + p.bloomFilter = nil +} + +func (p *Peer) setRateLimits(r common.RateLimits) { + p.rateLimitsMu.Lock() + p.rateLimits = r + p.rateLimitsMu.Unlock() +} + +// topicOrBloomMatch matches against topic-interest if topic interest +// is not nil. Otherwise it will match against the bloom-filter. +// If the bloom-filter is nil, or full, the node is considered a full-node +// and any envelope will be accepted. An empty topic-interest (but not nil) +// signals that we are not interested in any envelope. +func (p *Peer) topicOrBloomMatch(env *common.Envelope) bool { + p.topicInterestMu.Lock() + topicInterestMode := p.topicInterest != nil + p.topicInterestMu.Unlock() + + if topicInterestMode { + return p.topicInterestMatch(env) + } + return p.bloomMatch(env) +} + +func (p *Peer) topicInterestMatch(env *common.Envelope) bool { + p.topicInterestMu.Lock() + defer p.topicInterestMu.Unlock() + + if p.topicInterest == nil { + return false + } + + return p.topicInterest[env.Topic] +} + +func (p *Peer) bloomMatch(env *common.Envelope) bool { + p.bloomMu.Lock() + defer p.bloomMu.Unlock() + return p.fullNode || common.BloomFilterMatch(p.bloomFilter, env.Bloom()) +} diff --git a/waku/v0/peer_test.go b/waku/v0/peer_test.go new file mode 100644 index 000000000..a130ced6a --- /dev/null +++ b/waku/v0/peer_test.go @@ -0,0 +1,60 @@ +// Copyright 2019 The Waku Library Authors. +// +// The Waku library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Waku library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty off +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Waku library. If not, see . +// +// This software uses the go-ethereum library, which is licensed +// under the GNU Lesser General Public Library, version 3 or any later. + +package v0 + +import ( + "testing" + + "github.com/status-im/status-go/waku/common" +) + +var sharedTopic = common.TopicType{0xF, 0x1, 0x2, 0} +var wrongTopic = common.TopicType{0, 0, 0, 0} + +//two generic waku node handshake. one don't send light flag +func TestTopicOrBloomMatch(t *testing.T) { + p := Peer{} + p.setTopicInterest([]common.TopicType{sharedTopic}) + envelope := &common.Envelope{Topic: sharedTopic} + if !p.topicOrBloomMatch(envelope) { + t.Fatal("envelope should match") + } + + badEnvelope := &common.Envelope{Topic: wrongTopic} + if p.topicOrBloomMatch(badEnvelope) { + t.Fatal("envelope should not match") + } + +} + +func TestTopicOrBloomMatchFullNode(t *testing.T) { + p := Peer{} + // Set as full node + p.fullNode = true + p.setTopicInterest([]common.TopicType{sharedTopic}) + envelope := &common.Envelope{Topic: sharedTopic} + if !p.topicOrBloomMatch(envelope) { + t.Fatal("envelope should match") + } + + badEnvelope := &common.Envelope{Topic: wrongTopic} + if p.topicOrBloomMatch(badEnvelope) { + t.Fatal("envelope should not match") + } +} diff --git a/waku/handshake.go b/waku/v0/statusoptions.go similarity index 66% rename from waku/handshake.go rename to waku/v0/statusoptions.go index 79a48ecf4..4ec49496e 100644 --- a/waku/handshake.go +++ b/waku/v0/statusoptions.go @@ -1,4 +1,4 @@ -package waku +package v0 import ( "errors" @@ -9,9 +9,11 @@ import ( "strings" "github.com/ethereum/go-ethereum/rlp" + + "github.com/status-im/status-go/waku/common" ) -// statusOptionKey is a current type used in statusOptions as a key. +// statusOptionKey is a current type used in StatusOptions as a key. type statusOptionKey string var ( @@ -20,24 +22,49 @@ var ( keyFieldIdx = make(map[statusOptionKey]int) ) -// statusOptions defines additional information shared between peers +// StatusOptions defines additional information shared between peers // during the handshake. -// There might be more options provided then fields in statusOptions +// There might be more options provided then fields in StatusOptions // and they should be ignored during deserialization to stay forward compatible. // In the case of RLP, options should be serialized to an array of tuples // where the first item is a field name and the second is a RLP-serialized value. -type statusOptions struct { - PoWRequirement *uint64 `rlp:"key=0"` // RLP does not support float64 natively - BloomFilter []byte `rlp:"key=1"` - LightNodeEnabled *bool `rlp:"key=2"` - ConfirmationsEnabled *bool `rlp:"key=3"` - RateLimits *RateLimits `rlp:"key=4"` - TopicInterest []TopicType `rlp:"key=5"` +type StatusOptions struct { + PoWRequirement *uint64 `rlp:"key=0"` // RLP does not support float64 natively + BloomFilter []byte `rlp:"key=1"` + LightNodeEnabled *bool `rlp:"key=2"` + ConfirmationsEnabled *bool `rlp:"key=3"` + RateLimits *common.RateLimits `rlp:"key=4"` + TopicInterest []common.TopicType `rlp:"key=5"` +} + +func StatusOptionsFromHost(host common.WakuHost) StatusOptions { + opts := StatusOptions{} + + rateLimits := host.RateLimits() + opts.RateLimits = &rateLimits + + lightNode := host.LightClientMode() + opts.LightNodeEnabled = &lightNode + + minPoW := host.MinPow() + opts.SetPoWRequirementFromF(minPoW) + + confirmationsEnabled := host.ConfirmationsEnabled() + opts.ConfirmationsEnabled = &confirmationsEnabled + + bloomFilterMode := host.BloomFilterMode() + if bloomFilterMode { + opts.BloomFilter = host.BloomFilter() + } else { + opts.TopicInterest = host.TopicInterest() + } + + return opts } // initFLPKeyFields initialises the values of `idxFieldKey` and `keyFieldIdx` func initRLPKeyFields() { - o := statusOptions{} + o := StatusOptions{} v := reflect.ValueOf(o) for i := 0; i < v.NumField(); i++ { @@ -67,7 +94,7 @@ func initRLPKeyFields() { // WithDefaults adds the default values for a given peer. // This are not the host default values, but the default values that ought to // be used when receiving from an update from a peer. -func (o statusOptions) WithDefaults() statusOptions { +func (o StatusOptions) WithDefaults() StatusOptions { if o.PoWRequirement == nil { o.PoWRequirement = &defaultMinPoW } @@ -83,17 +110,17 @@ func (o statusOptions) WithDefaults() statusOptions { } if o.RateLimits == nil { - o.RateLimits = &RateLimits{} + o.RateLimits = &common.RateLimits{} } if o.BloomFilter == nil { - o.BloomFilter = MakeFullNodeBloom() + o.BloomFilter = common.MakeFullNodeBloom() } return o } -func (o statusOptions) PoWRequirementF() *float64 { +func (o StatusOptions) PoWRequirementF() *float64 { if o.PoWRequirement == nil { return nil } @@ -101,12 +128,12 @@ func (o statusOptions) PoWRequirementF() *float64 { return &result } -func (o *statusOptions) SetPoWRequirementFromF(val float64) { +func (o *StatusOptions) SetPoWRequirementFromF(val float64) { requirement := math.Float64bits(val) o.PoWRequirement = &requirement } -func (o statusOptions) EncodeRLP(w io.Writer) error { +func (o StatusOptions) EncodeRLP(w io.Writer) error { v := reflect.ValueOf(o) var optionsList []interface{} for i := 0; i < v.NumField(); i++ { @@ -125,7 +152,7 @@ func (o statusOptions) EncodeRLP(w io.Writer) error { return rlp.Encode(w, optionsList) } -func (o *statusOptions) DecodeRLP(s *rlp.Stream) error { +func (o *StatusOptions) DecodeRLP(s *rlp.Stream) error { _, err := s.List() if err != nil { return fmt.Errorf("expected an outer list: %v", err) @@ -154,7 +181,7 @@ loop: // a higher index. idx, ok := keyFieldIdx[key] if !ok { - // Read the rest of the list items and dump them. + // Read the rest of the list items and dump peer. _, err := s.Raw() if err != nil { return fmt.Errorf("failed to read the value of key %s: %v", key, err) @@ -172,8 +199,8 @@ loop: return s.ListEnd() } -func (o statusOptions) Validate() error { - if len(o.TopicInterest) > 1000 { +func (o StatusOptions) Validate() error { + if len(o.TopicInterest) > 10000 { return errors.New("topic interest is limited by 1000 items") } return nil diff --git a/waku/handshake_test.go b/waku/v0/statusoptions_test.go similarity index 82% rename from waku/handshake_test.go rename to waku/v0/statusoptions_test.go index 647336ba2..e87a708d8 100644 --- a/waku/handshake_test.go +++ b/waku/v0/statusoptions_test.go @@ -1,4 +1,4 @@ -package waku +package v0 import ( "math" @@ -7,29 +7,31 @@ import ( "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/rlp" + "github.com/status-im/status-go/waku/common" ) func TestEncodeDecodeRLP(t *testing.T) { + initRLPKeyFields() pow := math.Float64bits(6.02) lightNodeEnabled := true confirmationsEnabled := true - opts := statusOptions{ + opts := StatusOptions{ PoWRequirement: &pow, - BloomFilter: TopicToBloom(TopicType{0xaa, 0xbb, 0xcc, 0xdd}), + BloomFilter: common.TopicToBloom(common.TopicType{0xaa, 0xbb, 0xcc, 0xdd}), LightNodeEnabled: &lightNodeEnabled, ConfirmationsEnabled: &confirmationsEnabled, - RateLimits: &RateLimits{ + RateLimits: &common.RateLimits{ IPLimits: 10, PeerIDLimits: 5, TopicLimits: 1, }, - TopicInterest: []TopicType{{0x01}, {0x02}, {0x03}, {0x04}}, + TopicInterest: []common.TopicType{{0x01}, {0x02}, {0x03}, {0x04}}, } data, err := rlp.EncodeToBytes(opts) require.NoError(t, err) - var optsDecoded statusOptions + var optsDecoded StatusOptions err = rlp.DecodeBytes(data, &optsDecoded) require.NoError(t, err) require.EqualValues(t, opts, optsDecoded) @@ -42,11 +44,11 @@ func TestBackwardCompatibility(t *testing.T) { data, err := rlp.EncodeToBytes(alist) require.NoError(t, err) - var optsDecoded statusOptions + var optsDecoded StatusOptions err = rlp.DecodeBytes(data, &optsDecoded) require.NoError(t, err) pow := math.Float64bits(2.05) - require.EqualValues(t, statusOptions{PoWRequirement: &pow}, optsDecoded) + require.EqualValues(t, StatusOptions{PoWRequirement: &pow}, optsDecoded) } func TestForwardCompatibility(t *testing.T) { @@ -58,10 +60,10 @@ func TestForwardCompatibility(t *testing.T) { data, err := rlp.EncodeToBytes(alist) require.NoError(t, err) - var optsDecoded statusOptions + var optsDecoded StatusOptions err = rlp.DecodeBytes(data, &optsDecoded) require.NoError(t, err) - require.EqualValues(t, statusOptions{PoWRequirement: &pow}, optsDecoded) + require.EqualValues(t, StatusOptions{PoWRequirement: &pow}, optsDecoded) } func TestInitRLPKeyFields(t *testing.T) { diff --git a/waku/peer_test.go b/waku/v0_test.go similarity index 74% rename from waku/peer_test.go rename to waku/v0_test.go index 9fd9e8279..0ada1fc44 100644 --- a/waku/peer_test.go +++ b/waku/v0_test.go @@ -29,13 +29,17 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rlp" + + "github.com/status-im/status-go/waku/common" + v0 "github.com/status-im/status-go/waku/v0" + + "github.com/stretchr/testify/require" ) var keys = []string{ @@ -81,7 +85,7 @@ type TestData struct { } type TestNode struct { - shh *Waku + waku *Waku id *ecdsa.PrivateKey server *p2p.Server filerID string @@ -93,8 +97,8 @@ var result TestData var nodes [NumNodes]*TestNode var sharedKey = hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31") var wrongKey = hexutil.MustDecode("0xf91156714d7ec88d3edc1c652c2181dbb3044e8771c683f3b30d33c12b986b11") -var sharedTopic = TopicType{0xF, 0x1, 0x2, 0} -var wrongTopic = TopicType{0, 0, 0, 0} +var sharedTopic = common.TopicType{0xF, 0x1, 0x2, 0} +var wrongTopic = common.TopicType{0, 0, 0, 0} var expectedMessage = []byte("per aspera ad astra") var unexpectedMessage = []byte("per rectum ad astra") var masterBloomFilter []byte @@ -143,31 +147,31 @@ func TestSimulationBloomFilter(t *testing.T) { func resetParams(t *testing.T) { // change pow only for node zero masterPow = 7777777.0 - _ = nodes[0].shh.SetMinimumPoW(masterPow, true) + _ = nodes[0].waku.SetMinimumPoW(masterPow, true) // change bloom for all nodes - masterBloomFilter = TopicToBloom(sharedTopic) + masterBloomFilter = common.TopicToBloom(sharedTopic) for i := 0; i < NumNodes; i++ { - _ = nodes[i].shh.SetBloomFilter(masterBloomFilter) + _ = nodes[i].waku.SetBloomFilter(masterBloomFilter) } round++ } func initBloom(t *testing.T) { - masterBloomFilter = make([]byte, BloomFilterSize) + masterBloomFilter = make([]byte, common.BloomFilterSize) _, err := mrand.Read(masterBloomFilter) // nolint: gosec if err != nil { t.Fatalf("rand failed: %s.", err) } - msgBloom := TopicToBloom(sharedTopic) + msgBloom := common.TopicToBloom(sharedTopic) masterBloomFilter = addBloom(masterBloomFilter, msgBloom) for i := 0; i < 32; i++ { masterBloomFilter[i] = 0xFF } - if !BloomFilterMatch(masterBloomFilter, msgBloom) { + if !common.BloomFilterMatch(masterBloomFilter, msgBloom) { t.Fatalf("bloom mismatch on initBloom.") } } @@ -179,22 +183,22 @@ func initializeBloomFilterMode(t *testing.T) { for i := 0; i < NumNodes; i++ { var node TestNode - b := make([]byte, BloomFilterSize) + b := make([]byte, common.BloomFilterSize) copy(b, masterBloomFilter) config := DefaultConfig config.BloomFilterMode = true - node.shh = New(&config, nil) - _ = node.shh.SetMinimumPoW(masterPow, false) - _ = node.shh.SetBloomFilter(b) - if !bytes.Equal(node.shh.BloomFilter(), masterBloomFilter) { + node.waku = New(&config, nil) + _ = node.waku.SetMinimumPoW(masterPow, false) + _ = node.waku.SetBloomFilter(b) + if !bytes.Equal(node.waku.BloomFilter(), masterBloomFilter) { t.Fatalf("bloom mismatch on init.") } - _ = node.shh.Start(nil) - topics := make([]TopicType, 0) + _ = node.waku.Start(nil) + topics := make([]common.TopicType, 0) topics = append(topics, sharedTopic) - f := Filter{KeySym: sharedKey, Messages: NewMemoryMessageStore()} + f := common.Filter{KeySym: sharedKey, Messages: common.NewMemoryMessageStore()} f.Topics = [][]byte{topics[0][:]} - node.filerID, err = node.shh.Subscribe(&f) + node.filerID, err = node.waku.Subscribe(&f) if err != nil { t.Fatalf("failed to install the filter: %s.", err) } @@ -202,14 +206,14 @@ func initializeBloomFilterMode(t *testing.T) { if err != nil { t.Fatalf("failed convert the key: %s.", keys[i]) } - name := common.MakeName("waku-go", "2.0") + name := gethcommon.MakeName("waku-go", "2.0") node.server = &p2p.Server{ Config: p2p.Config{ PrivateKey: node.id, MaxPeers: NumNodes/2 + 1, Name: name, - Protocols: node.shh.Protocols(), + Protocols: node.waku.Protocols(), ListenAddr: "127.0.0.1:0", NAT: nat.Any(), }, @@ -245,8 +249,8 @@ func stopServers() { for i := 0; i < NumNodes; i++ { n := nodes[i] if n != nil { - _ = n.shh.Unsubscribe(n.filerID) - _ = n.shh.Stop() + _ = n.waku.Unsubscribe(n.filerID) + _ = n.waku.Stop() n.server.Stop() } } @@ -269,7 +273,7 @@ func checkPropagation(t *testing.T, includingNodeZero bool) { for j := 0; j < iterations; j++ { for i := first; i < NumNodes; i++ { - f := nodes[i].shh.GetFilter(nodes[i].filerID) + f := nodes[i].waku.GetFilter(nodes[i].filerID) if f == nil { t.Fatalf("failed to get filterId %s from node %d, round %d.", nodes[i].filerID, i, round) } @@ -288,7 +292,7 @@ func checkPropagation(t *testing.T, includingNodeZero bool) { } if !includingNodeZero { - f := nodes[0].shh.GetFilter(nodes[0].filerID) + f := nodes[0].waku.GetFilter(nodes[0].filerID) if f != nil { t.Fatalf("node zero received a message with low PoW.") } @@ -297,7 +301,7 @@ func checkPropagation(t *testing.T, includingNodeZero bool) { t.Fatalf("Test was not complete (%d round): timeout %d seconds. nodes=%v", round, iterations*cycle/1000, nodes) } -func validateMail(t *testing.T, index int, mail []*ReceivedMessage) { +func validateMail(t *testing.T, index int, mail []*common.ReceivedMessage) { var cnt int for _, m := range mail { if bytes.Equal(m.Payload, expectedMessage) { @@ -329,7 +333,7 @@ func checkTestStatus() { for i := 0; i < NumNodes; i++ { arr[i] = nodes[i].server.PeerCount() - envelopes := nodes[i].shh.Envelopes() + envelopes := nodes[i].waku.Envelopes() if len(envelopes) >= NumNodes { cnt++ } @@ -356,7 +360,7 @@ func isTestComplete() bool { } for i := 0; i < NumNodes; i++ { - envelopes := nodes[i].shh.Envelopes() + envelopes := nodes[i].waku.Envelopes() if len(envelopes) < NumNodes+1 { return false } @@ -370,7 +374,7 @@ func sendMsg(t *testing.T, expected bool, id int) { return } - opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1} + opt := common.MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1} if !expected { opt.KeySym = wrongKey opt.Topic = wrongTopic @@ -378,7 +382,7 @@ func sendMsg(t *testing.T, expected bool, id int) { opt.Payload[0] = byte(id) } - msg, err := NewSentMessage(&opt) + msg, err := common.NewSentMessage(&opt) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -387,7 +391,7 @@ func sendMsg(t *testing.T, expected bool, id int) { t.Fatalf("failed to seal message: %s", err) } - err = nodes[id].shh.Send(envelope) + err = nodes[id].waku.Send(envelope) if err != nil { t.Fatalf("failed to send message: %s", err) } @@ -402,7 +406,7 @@ func TestPeerBasic(t *testing.T) { } params.PoW = 0.001 - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -411,9 +415,9 @@ func TestPeerBasic(t *testing.T) { t.Fatalf("failed Wrap with seed %d.", seed) } - p := newPeer(nil, nil, nil, nil) - p.mark(env) - if !p.marked(env) { + p := v0.NewPeer(nil, nil, nil, nil) + p.Mark(env) + if !p.Marked(env) { t.Fatalf("failed mark with seed %d.", seed) } } @@ -433,10 +437,10 @@ func checkPowExchangeForNodeZero(t *testing.T) { func checkPowExchangeForNodeZeroOnce(t *testing.T, mustPass bool) bool { cnt := 0 for i, node := range nodes { - for peer := range node.shh.peers { - if peer.peer.ID() == nodes[0].server.Self().ID() { + for protocol := range node.waku.peers { + if protocol.EnodeID() == nodes[0].server.Self().ID() { cnt++ - if peer.powRequirement != masterPow { + if protocol.PoWRequirement() != masterPow { if mustPass { t.Fatalf("node %d: failed to set the new pow requirement for node zero.", i) } else { @@ -454,11 +458,11 @@ func checkPowExchangeForNodeZeroOnce(t *testing.T, mustPass bool) bool { func checkPowExchange(t *testing.T) { for i, node := range nodes { - for peer := range node.shh.peers { - if peer.peer.ID() != nodes[0].server.Self().ID() { - if peer.powRequirement != masterPow { + for protocol := range node.waku.peers { + if protocol.EnodeID() != nodes[0].server.Self().ID() { + if protocol.PoWRequirement() != masterPow { t.Fatalf("node %d: failed to exchange pow requirement in round %d; expected %f, got %f", - i, round, masterPow, peer.powRequirement) + i, round, masterPow, protocol.PoWRequirement()) } } } @@ -467,14 +471,12 @@ func checkPowExchange(t *testing.T) { func checkBloomFilterExchangeOnce(t *testing.T, mustPass bool) bool { for i, node := range nodes { - for peer := range node.shh.peers { - peer.bloomMu.Lock() - equals := bytes.Equal(peer.bloomFilter, masterBloomFilter) - peer.bloomMu.Unlock() + for protocol := range node.waku.peers { + equals := bytes.Equal(protocol.BloomFilter(), masterBloomFilter) if !equals { if mustPass { t.Fatalf("node %d: failed to exchange bloom filter requirement in round %d. \n%x expected \n%x got", - i, round, masterBloomFilter, peer.bloomFilter) + i, round, masterBloomFilter, protocol.BloomFilter()) } else { return false } @@ -512,125 +514,82 @@ func waitForServersToStart(t *testing.T) { //two generic waku node handshake func TestPeerHandshakeWithTwoFullNode(t *testing.T) { - w1 := Waku{} - var pow uint64 = 123 - p1 := newPeer( - &w1, - p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), - &rwStub{[]interface{}{ - ProtocolVersion, - statusOptions{PoWRequirement: &pow}, - }}, - nil, - ) - err := p1.handshake() + rw1, rw2 := p2p.MsgPipe() + defer rw1.Close() + defer rw2.Close() + + w1 := New(nil, nil) + var pow float64 = 0.1 + err := w1.SetMinimumPoW(pow, true) + if err != nil { + t.Fatal(err) + } + + w2 := New(nil, nil) + + go w1.HandlePeer(p2p.NewPeer(enode.ID{}, "test-1", []p2p.Cap{}), rw1) // nolint: errcheck + + p2 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test-2", []p2p.Cap{}), rw2, nil) + err = p2.Start() + if err != nil { + t.Fatal(err) + } + require.Equal(t, pow, p2.PoWRequirement()) +} + +//two generic waku node handshake. one don't send light flag +func TestHandshakeWithOldVersionWithoutLightModeFlag(t *testing.T) { + rw1, rw2 := p2p.MsgPipe() + defer rw1.Close() + defer rw2.Close() + + w1 := New(nil, nil) + w1.SetLightClientMode(true) + + w2 := New(nil, nil) + + go w1.HandlePeer(p2p.NewPeer(enode.ID{}, "test-1", []p2p.Cap{}), rw1) // nolint: errcheck + + p2 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test-2", []p2p.Cap{}), rw2, nil) + err := p2.Start() if err != nil { t.Fatal(err) } } -//two generic waku node handshake. one don't send light flag -func TestHandshakeWithOldVersionWithoutLightModeFlag(t *testing.T) { - w1 := Waku{} - var pow uint64 = 123 - p1 := newPeer( - &w1, - p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), - &rwStub{[]interface{}{ - ProtocolVersion, - statusOptions{PoWRequirement: &pow}, - }}, - nil, - ) - err := p1.handshake() - if err != nil { - t.Fatal() - } -} - -//two generic waku node handshake. one don't send light flag -func TestTopicOrBloomMatch(t *testing.T) { - p := Peer{} - p.setTopicInterest([]TopicType{sharedTopic}) - envelope := &Envelope{Topic: sharedTopic} - if !p.topicOrBloomMatch(envelope) { - t.Fatal("envelope should match") - } - - badEnvelope := &Envelope{Topic: wrongTopic} - if p.topicOrBloomMatch(badEnvelope) { - t.Fatal("envelope should not match") - } - -} - -func TestTopicOrBloomMatchFullNode(t *testing.T) { - p := Peer{} - // Set as full node - p.fullNode = true - p.setTopicInterest([]TopicType{sharedTopic}) - envelope := &Envelope{Topic: sharedTopic} - if !p.topicOrBloomMatch(envelope) { - t.Fatal("envelope should match") - } - - badEnvelope := &Envelope{Topic: wrongTopic} - if p.topicOrBloomMatch(badEnvelope) { - t.Fatal("envelope should not match") - } -} - -//two light nodes handshake. restriction disabled +//two light nodes handshake. restriction enable func TestTwoLightPeerHandshakeRestrictionOff(t *testing.T) { - w1 := Waku{} - w1.settings.RestrictLightClientsConn = false + rw1, rw2 := p2p.MsgPipe() + defer rw1.Close() + defer rw2.Close() + + w1 := New(nil, nil) w1.SetLightClientMode(true) - var pow uint64 = 123 - var lightNodeEnabled = true - p1 := newPeer( - &w1, - p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), - &rwStub{[]interface{}{ - ProtocolVersion, - statusOptions{PoWRequirement: &pow, LightNodeEnabled: &lightNodeEnabled}, - }}, - nil, - ) - err := p1.handshake() - if err != nil { - t.FailNow() - } + w1.settings.RestrictLightClientsConn = false + + w2 := New(nil, nil) + w2.SetLightClientMode(true) + w2.settings.RestrictLightClientsConn = false + + go w1.HandlePeer(p2p.NewPeer(enode.ID{}, "test-1", []p2p.Cap{}), rw1) // nolint: errcheck + + p2 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test-2", []p2p.Cap{}), rw2, nil) + require.NoError(t, p2.Start()) } //two light nodes handshake. restriction enabled func TestTwoLightPeerHandshakeError(t *testing.T) { - w1 := Waku{} - w1.settings.RestrictLightClientsConn = true + rw1, rw2 := p2p.MsgPipe() + w1 := New(nil, nil) w1.SetLightClientMode(true) - p1 := newPeer( - &w1, - p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), - &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}}, - nil, - ) - err := p1.handshake() - if err == nil { - t.FailNow() - } -} + w1.settings.RestrictLightClientsConn = true -type rwStub struct { - payload []interface{} -} + w2 := New(nil, nil) + w2.SetLightClientMode(true) + w2.settings.RestrictLightClientsConn = true -func (stub *rwStub) ReadMsg() (p2p.Msg, error) { - size, r, err := rlp.EncodeToReader(stub.payload) - if err != nil { - return p2p.Msg{}, err - } - return p2p.Msg{Code: statusCode, Size: uint32(size), Payload: r}, nil -} + go w1.HandlePeer(p2p.NewPeer(enode.ID{}, "test-1", []p2p.Cap{}), rw1) // nolint: errcheck -func (stub *rwStub) WriteMsg(m p2p.Msg) error { - return nil + p2 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test-2", []p2p.Cap{}), rw2, nil) + require.Error(t, p2.Start()) } diff --git a/waku/waku.go b/waku/waku.go index e7238f6dc..b8f1d722e 100644 --- a/waku/waku.go +++ b/waku/waku.go @@ -24,8 +24,6 @@ import ( "crypto/sha256" "errors" "fmt" - "io" - "io/ioutil" "runtime" "sync" "time" @@ -37,7 +35,7 @@ import ( mapset "github.com/deckarep/golang-set" "golang.org/x/crypto/pbkdf2" - "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -45,57 +43,59 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + + "github.com/status-im/status-go/waku/common" + v0 "github.com/status-im/status-go/waku/v0" ) -// TimeSyncError error for clock skew errors. -type TimeSyncError error +const messageQueueLimit = 1024 type Bridge interface { - Pipe() (<-chan *Envelope, chan<- *Envelope) + Pipe() (<-chan *common.Envelope, chan<- *common.Envelope) } type settings struct { - MaxMsgSize uint32 // Maximal message length allowed by the waku node - EnableConfirmations bool // Enable sending message confirmations - MinPow float64 // Minimal PoW required by the waku node - MinPowTolerance float64 // Minimal PoW tolerated by the waku node for a limited time - BloomFilter []byte // Bloom filter for topics of interest for this node - BloomFilterTolerance []byte // Bloom filter tolerated by the waku node for a limited time - TopicInterest map[TopicType]bool // Topic interest for this node - TopicInterestTolerance map[TopicType]bool // Topic interest tolerated by the waku node for a limited time - BloomFilterMode bool // Whether we should match against bloom-filter only - LightClient bool // Light client mode enabled does not forward messages - RestrictLightClientsConn bool // Restrict connection between two light clients - SyncAllowance int // Maximum time in seconds allowed to process the waku-related messages + MaxMsgSize uint32 // Maximal message length allowed by the waku node + EnableConfirmations bool // Enable sending message confirmations + MinPow float64 // Minimal PoW required by the waku node + MinPowTolerance float64 // Minimal PoW tolerated by the waku node for a limited time + BloomFilter []byte // Bloom filter for topics of interest for this node + BloomFilterTolerance []byte // Bloom filter tolerated by the waku node for a limited time + TopicInterest map[common.TopicType]bool // Topic interest for this node + TopicInterestTolerance map[common.TopicType]bool // Topic interest tolerated by the waku node for a limited time + BloomFilterMode bool // Whether we should match against bloom-filter only + LightClient bool // Light client mode enabled does not forward messages + RestrictLightClientsConn bool // Restrict connection between two light clients + SyncAllowance int // Maximum time in seconds allowed to process the waku-related messages } // Waku represents a dark communication interface through the Ethereum // network, using its very own P2P communication layer. type Waku struct { - protocol p2p.Protocol // Protocol description and parameters - filters *Filters // Message filters installed with Subscribe function + protocol p2p.Protocol // Peer description and parameters + filters *common.Filters // Message filters installed with Subscribe function privateKeys map[string]*ecdsa.PrivateKey // Private key storage symKeys map[string][]byte // Symmetric key storage keyMu sync.RWMutex // Mutex associated with key stores - envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node - expirations map[uint32]mapset.Set // Message expiration pool - poolMu sync.RWMutex // Mutex to sync the message and expiration pools + envelopes map[gethcommon.Hash]*common.Envelope // Pool of envelopes currently tracked by this node + expirations map[uint32]mapset.Set // Message expiration pool + poolMu sync.RWMutex // Mutex to sync the message and expiration pools - peers map[*Peer]struct{} // Set of currently active peers - peerMu sync.RWMutex // Mutex to sync the active peer set + peers map[common.Peer]struct{} // Set of currently active peers + peerMu sync.RWMutex // Mutex to sync the active peer set - msgQueue chan *Envelope // Message queue for normal waku messages - p2pMsgQueue chan interface{} // Message queue for peer-to-peer messages (not to be forwarded any further) and history delivery confirmations. - quit chan struct{} // Channel used for graceful exit + msgQueue chan *common.Envelope // Message queue for normal waku messages + p2pMsgQueue chan interface{} // Message queue for peer-to-peer messages (not to be forwarded any further) and history delivery confirmations. + quit chan struct{} // Channel used for graceful exit settings settings // Holds configuration settings that can be dynamically changed settingsMu sync.RWMutex // Mutex to sync the settings access mailServer MailServer - rateLimiter *PeerRateLimiter + rateLimiter *common.PeerRateLimiter envelopeFeed event.Feed @@ -108,11 +108,6 @@ type Waku struct { logger *zap.Logger } -// init initialises the waku package -func init() { - initRLPKeyFields() -} - // New creates a Waku client ready to communicate through the Ethereum P2P network. func New(cfg *Config, logger *zap.Logger) *Waku { if cfg == nil { @@ -127,10 +122,10 @@ func New(cfg *Config, logger *zap.Logger) *Waku { waku := &Waku{ privateKeys: make(map[string]*ecdsa.PrivateKey), symKeys: make(map[string][]byte), - envelopes: make(map[common.Hash]*Envelope), + envelopes: make(map[gethcommon.Hash]*common.Envelope), expirations: make(map[uint32]mapset.Set), - peers: make(map[*Peer]struct{}), - msgQueue: make(chan *Envelope, messageQueueLimit), + peers: make(map[common.Peer]struct{}), + msgQueue: make(chan *common.Envelope, messageQueueLimit), p2pMsgQueue: make(chan interface{}, messageQueueLimit), quit: make(chan struct{}), timeSource: time.Now, @@ -145,25 +140,25 @@ func New(cfg *Config, logger *zap.Logger) *Waku { LightClient: cfg.LightClient, BloomFilterMode: cfg.BloomFilterMode, RestrictLightClientsConn: cfg.RestrictLightClientsConn, - SyncAllowance: DefaultSyncAllowance, + SyncAllowance: common.DefaultSyncAllowance, } if cfg.FullNode { - waku.settings.BloomFilter = MakeFullNodeBloom() - waku.settings.BloomFilterTolerance = MakeFullNodeBloom() + waku.settings.BloomFilter = common.MakeFullNodeBloom() + waku.settings.BloomFilterTolerance = common.MakeFullNodeBloom() } - waku.filters = NewFilters(waku) + waku.filters = common.NewFilters() // p2p waku sub-protocol handler waku.protocol = p2p.Protocol{ - Name: ProtocolName, - Version: uint(ProtocolVersion), - Length: NumberOfMessageCodes, + Name: v0.Name, + Version: uint(v0.Version), + Length: v0.NumberOfMessageCodes, Run: waku.HandlePeer, NodeInfo: func() interface{} { return map[string]interface{}{ - "version": ProtocolVersionStr, + "version": v0.VersionStr, "maxMessageSize": waku.MaxMessageSize(), "minimumPoW": waku.MinPow(), } @@ -252,11 +247,11 @@ func (w *Waku) BloomFilterMode() bool { // SetBloomFilter sets the new bloom filter func (w *Waku) SetBloomFilter(bloom []byte) error { - if len(bloom) != BloomFilterSize { + if len(bloom) != common.BloomFilterSize { return fmt.Errorf("invalid bloom filter size: %d", len(bloom)) } - b := make([]byte, BloomFilterSize) + b := make([]byte, common.BloomFilterSize) copy(b, bloom) w.settingsMu.Lock() @@ -286,13 +281,13 @@ func (w *Waku) SetBloomFilter(bloom []byte) error { // The nodes are required to send only messages that match the advertised topics. // If a message does not match the topic-interest, it will tantamount to spam, and the peer will // be disconnected. -func (w *Waku) TopicInterest() []TopicType { +func (w *Waku) TopicInterest() []common.TopicType { w.settingsMu.RLock() defer w.settingsMu.RUnlock() if w.settings.TopicInterest == nil { return nil } - topicInterest := make([]TopicType, len(w.settings.TopicInterest)) + topicInterest := make([]common.TopicType, len(w.settings.TopicInterest)) i := 0 for topic := range w.settings.TopicInterest { @@ -304,10 +299,10 @@ func (w *Waku) TopicInterest() []TopicType { // updateTopicInterest adds a new topic interest // and informs the peers -func (w *Waku) updateTopicInterest(f *Filter) error { +func (w *Waku) updateTopicInterest(f *common.Filter) error { newTopicInterest := w.TopicInterest() for _, t := range f.Topics { - top := BytesToTopic(t) + top := common.BytesToTopic(t) newTopicInterest = append(newTopicInterest, top) } @@ -315,14 +310,14 @@ func (w *Waku) updateTopicInterest(f *Filter) error { } // SetTopicInterest sets the new topicInterest -func (w *Waku) SetTopicInterest(topicInterest []TopicType) error { - var topicInterestMap map[TopicType]bool - if len(topicInterest) > MaxTopicInterest { +func (w *Waku) SetTopicInterest(topicInterest []common.TopicType) error { + var topicInterestMap map[common.TopicType]bool + if len(topicInterest) > common.MaxTopicInterest { return fmt.Errorf("invalid topic interest: %d", len(topicInterest)) } if topicInterest != nil { - topicInterestMap = make(map[TopicType]bool, len(topicInterest)) + topicInterestMap = make(map[common.TopicType]bool, len(topicInterest)) for _, topic := range topicInterest { topicInterestMap[topic] = true } @@ -359,8 +354,8 @@ func (w *Waku) MaxMessageSize() uint32 { // SetMaxMessageSize sets the maximal message size allowed by this node func (w *Waku) SetMaxMessageSize(size uint32) error { - if size > MaxMessageSize { - return fmt.Errorf("message size too large [%d>%d]", size, MaxMessageSize) + if size > common.MaxMessageSize { + return fmt.Errorf("message size too large [%d>%d]", size, common.MaxMessageSize) } w.settingsMu.Lock() w.settings.MaxMsgSize = size @@ -390,13 +385,13 @@ func (w *Waku) LightClientModeConnectionRestricted() bool { } // RateLimiting returns RateLimits information. -func (w *Waku) RateLimits() RateLimits { +func (w *Waku) RateLimits() common.RateLimits { if w.rateLimiter == nil { - return RateLimits{} + return common.RateLimits{} } - return RateLimits{ - IPLimits: uint64(w.rateLimiter.limitPerSecIP), - PeerIDLimits: uint64(w.rateLimiter.limitPerSecPeerID), + return common.RateLimits{ + IPLimits: uint64(w.rateLimiter.LimitPerSecIP), + PeerIDLimits: uint64(w.rateLimiter.LimitPerSecPeerID), } } @@ -421,8 +416,8 @@ func (w *Waku) SetTimeSource(timesource func() time.Time) { func (w *Waku) APIs() []rpc.API { return []rpc.API{ { - Namespace: ProtocolName, - Version: ProtocolVersionStr, + Namespace: v0.Name, + Version: v0.VersionStr, Service: NewPublicWakuAPI(w), Public: false, }, @@ -441,7 +436,7 @@ func (w *Waku) RegisterMailServer(server MailServer) { } // SetRateLimiter registers a rate limiter. -func (w *Waku) RegisterRateLimiter(r *PeerRateLimiter) { +func (w *Waku) RegisterRateLimiter(r *common.PeerRateLimiter) { w.rateLimiter = r } @@ -469,17 +464,17 @@ func (w *Waku) readBridgeLoop() { case env := <-out: _, err := w.addAndBridge(env, false, true) if err != nil { - bridgeReceivedFailed.Inc() + common.BridgeReceivedFailed.Inc() w.logger.Warn( "failed to add a bridged envelope", zap.Binary("ID", env.Hash().Bytes()), zap.Error(err), ) } else { - bridgeReceivedSucceed.Inc() + common.BridgeReceivedSucceed.Inc() w.logger.Debug("bridged envelope successfully", zap.Binary("ID", env.Hash().Bytes())) - w.envelopeFeed.Send(EnvelopeEvent{ - Event: EventEnvelopeReceived, + w.envelopeFeed.Send(common.EnvelopeEvent{ + Event: common.EventEnvelopeReceived, Topic: env.Topic, Hash: env.Hash(), }) @@ -488,19 +483,23 @@ func (w *Waku) readBridgeLoop() { } } +func (w *Waku) SendEnvelopeEvent(event common.EnvelopeEvent) int { + return w.envelopeFeed.Send(event) +} + // SubscribeEnvelopeEvents subscribes to envelopes feed. // In order to prevent blocking waku producers events must be amply buffered. -func (w *Waku) SubscribeEnvelopeEvents(events chan<- EnvelopeEvent) event.Subscription { +func (w *Waku) SubscribeEnvelopeEvents(events chan<- common.EnvelopeEvent) event.Subscription { return w.envelopeFeed.Subscribe(events) } func (w *Waku) notifyPeersAboutPowRequirementChange(pow float64) { arr := w.getPeers() for _, p := range arr { - err := p.notifyAboutPowRequirementChange(pow) + err := p.NotifyAboutPowRequirementChange(pow) if err != nil { // allow one retry - err = p.notifyAboutPowRequirementChange(pow) + err = p.NotifyAboutPowRequirementChange(pow) } if err != nil { w.logger.Warn("failed to notify peer about new pow requirement", zap.Binary("peer", p.ID()), zap.Error(err)) @@ -511,10 +510,10 @@ func (w *Waku) notifyPeersAboutPowRequirementChange(pow float64) { func (w *Waku) notifyPeersAboutBloomFilterChange(bloom []byte) { arr := w.getPeers() for _, p := range arr { - err := p.notifyAboutBloomFilterChange(bloom) + err := p.NotifyAboutBloomFilterChange(bloom) if err != nil { // allow one retry - err = p.notifyAboutBloomFilterChange(bloom) + err = p.NotifyAboutBloomFilterChange(bloom) } if err != nil { w.logger.Warn("failed to notify peer about new bloom filter change", zap.Binary("peer", p.ID()), zap.Error(err)) @@ -522,13 +521,13 @@ func (w *Waku) notifyPeersAboutBloomFilterChange(bloom []byte) { } } -func (w *Waku) notifyPeersAboutTopicInterestChange(topicInterest []TopicType) { +func (w *Waku) notifyPeersAboutTopicInterestChange(topicInterest []common.TopicType) { arr := w.getPeers() for _, p := range arr { - err := p.notifyAboutTopicInterestChange(topicInterest) + err := p.NotifyAboutTopicInterestChange(topicInterest) if err != nil { // allow one retry - err = p.notifyAboutTopicInterestChange(topicInterest) + err = p.NotifyAboutTopicInterestChange(topicInterest) } if err != nil { w.logger.Warn("failed to notify peer about new topic interest", zap.Binary("peer", p.ID()), zap.Error(err)) @@ -536,8 +535,8 @@ func (w *Waku) notifyPeersAboutTopicInterestChange(topicInterest []TopicType) { } } -func (w *Waku) getPeers() []*Peer { - arr := make([]*Peer, len(w.peers)) +func (w *Waku) getPeers() []common.Peer { + arr := make([]common.Peer, len(w.peers)) i := 0 w.peerMu.Lock() for p := range w.peers { @@ -549,12 +548,11 @@ func (w *Waku) getPeers() []*Peer { } // getPeer retrieves peer by ID -func (w *Waku) getPeer(peerID []byte) (*Peer, error) { +func (w *Waku) getPeer(peerID []byte) (common.Peer, error) { w.peerMu.Lock() defer w.peerMu.Unlock() for p := range w.peers { - id := p.peer.ID() - if bytes.Equal(peerID, id[:]) { + if bytes.Equal(peerID, p.ID()) { return p, nil } } @@ -568,7 +566,7 @@ func (w *Waku) AllowP2PMessagesFromPeer(peerID []byte) error { if err != nil { return err } - p.trusted = true + p.SetPeerTrusted(true) return nil } @@ -577,34 +575,34 @@ func (w *Waku) AllowP2PMessagesFromPeer(peerID []byte) error { // request and respond with a number of peer-to-peer messages (possibly expired), // which are not supposed to be forwarded any further. // The waku protocol is agnostic of the format and contents of envelope. -func (w *Waku) RequestHistoricMessages(peerID []byte, envelope *Envelope) error { +func (w *Waku) RequestHistoricMessages(peerID []byte, envelope *common.Envelope) error { return w.RequestHistoricMessagesWithTimeout(peerID, envelope, 0) } // RequestHistoricMessagesWithTimeout acts as RequestHistoricMessages but requires to pass a timeout. // It sends an event EventMailServerRequestExpired after the timeout. -func (w *Waku) RequestHistoricMessagesWithTimeout(peerID []byte, envelope *Envelope, timeout time.Duration) error { +func (w *Waku) RequestHistoricMessagesWithTimeout(peerID []byte, envelope *common.Envelope, timeout time.Duration) error { p, err := w.getPeer(peerID) if err != nil { return err } - p.trusted = true + p.SetPeerTrusted(true) - w.envelopeFeed.Send(EnvelopeEvent{ - Peer: p.peer.ID(), + w.envelopeFeed.Send(common.EnvelopeEvent{ + Peer: p.EnodeID(), Topic: envelope.Topic, Hash: envelope.Hash(), - Event: EventMailServerRequestSent, + Event: common.EventMailServerRequestSent, }) - err = p2p.Send(p.ws, p2pRequestCode, envelope) + err = p.RequestHistoricMessages(envelope) if timeout != 0 { - go w.expireRequestHistoricMessages(p.peer.ID(), envelope.Hash(), timeout) + go w.expireRequestHistoricMessages(p.EnodeID(), envelope.Hash(), timeout) } return err } -func (w *Waku) SendMessagesRequest(peerID []byte, request MessagesRequest) error { +func (w *Waku) SendMessagesRequest(peerID []byte, request common.MessagesRequest) error { if err := request.Validate(); err != nil { return err } @@ -612,73 +610,59 @@ func (w *Waku) SendMessagesRequest(peerID []byte, request MessagesRequest) error if err != nil { return err } - p.trusted = true - if err := p2p.Send(p.ws, p2pRequestCode, request); err != nil { + p.SetPeerTrusted(true) + if err := p.SendMessagesRequest(request); err != nil { return err } - w.envelopeFeed.Send(EnvelopeEvent{ - Peer: p.peer.ID(), - Hash: common.BytesToHash(request.ID), - Event: EventMailServerRequestSent, + w.envelopeFeed.Send(common.EnvelopeEvent{ + Peer: p.EnodeID(), + Hash: gethcommon.BytesToHash(request.ID), + Event: common.EventMailServerRequestSent, }) return nil } -func (w *Waku) expireRequestHistoricMessages(peer enode.ID, hash common.Hash, timeout time.Duration) { +func (w *Waku) expireRequestHistoricMessages(peer enode.ID, hash gethcommon.Hash, timeout time.Duration) { timer := time.NewTimer(timeout) defer timer.Stop() select { case <-w.quit: return case <-timer.C: - w.envelopeFeed.Send(EnvelopeEvent{ + w.envelopeFeed.Send(common.EnvelopeEvent{ Peer: peer, Hash: hash, - Event: EventMailServerRequestExpired, + Event: common.EventMailServerRequestExpired, }) } } func (w *Waku) SendHistoricMessageResponse(peerID []byte, payload []byte) error { - size, r, err := rlp.EncodeToReader(payload) - if err != nil { - return err - } peer, err := w.getPeer(peerID) if err != nil { return err } - return peer.ws.WriteMsg(p2p.Msg{Code: p2pRequestCompleteCode, Size: uint32(size), Payload: r}) + return peer.SendHistoricMessageResponse(payload) } // SendP2PMessage sends a peer-to-peer message to a specific peer. // It sends one or more envelopes in a single batch. -func (w *Waku) SendP2PMessages(peerID []byte, envelopes ...*Envelope) error { +func (w *Waku) SendP2PMessages(peerID []byte, envelopes ...*common.Envelope) error { p, err := w.getPeer(peerID) if err != nil { return err } - return p2p.Send(p.ws, p2pMessageCode, envelopes) -} - -// SendP2PDirect sends a peer-to-peer message to a specific peer. -// It sends one or more envelopes in a single batch. -func (w *Waku) SendP2PDirect(peerID []byte, envelopes ...*Envelope) error { - peer, err := w.getPeer(peerID) - if err != nil { - return err - } - return p2p.Send(peer.ws, p2pMessageCode, envelopes) + return p.SendP2PMessages(envelopes) } // SendRawP2PDirect sends a peer-to-peer message to a specific peer. // It sends one or more envelopes in a single batch. func (w *Waku) SendRawP2PDirect(peerID []byte, envelopes ...rlp.RawValue) error { - peer, err := w.getPeer(peerID) + p, err := w.getPeer(peerID) if err != nil { return err } - return p2p.Send(peer.ws, p2pMessageCode, envelopes) + return p.SendRawP2PDirect(envelopes) } // NewKeyPair generates a new cryptographic identity for the client, and injects @@ -695,7 +679,7 @@ func (w *Waku) NewKeyPair() (string, error) { return "", fmt.Errorf("failed to generate valid key") } - id, err := toDeterministicID(hexutil.Encode(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + id, err := toDeterministicID(hexutil.Encode(crypto.FromECDSAPub(&key.PublicKey)), common.KeyIDSize) if err != nil { return "", err } @@ -712,7 +696,7 @@ func (w *Waku) NewKeyPair() (string, error) { // DeleteKeyPair deletes the specified key if it exists. func (w *Waku) DeleteKeyPair(key string) bool { - deterministicID, err := toDeterministicID(key, keyIDSize) + deterministicID, err := toDeterministicID(key, common.KeyIDSize) if err != nil { return false } @@ -729,7 +713,7 @@ func (w *Waku) DeleteKeyPair(key string) bool { // AddKeyPair imports a asymmetric private key and returns it identifier. func (w *Waku) AddKeyPair(key *ecdsa.PrivateKey) (string, error) { - id, err := makeDeterministicID(hexutil.Encode(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + id, err := makeDeterministicID(hexutil.Encode(crypto.FromECDSAPub(&key.PublicKey)), common.KeyIDSize) if err != nil { return "", err } @@ -747,7 +731,7 @@ func (w *Waku) AddKeyPair(key *ecdsa.PrivateKey) (string, error) { // SelectKeyPair adds cryptographic identity, and makes sure // that it is the only private key known to the node. func (w *Waku) SelectKeyPair(key *ecdsa.PrivateKey) error { - id, err := makeDeterministicID(hexutil.Encode(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + id, err := makeDeterministicID(hexutil.Encode(crypto.FromECDSAPub(&key.PublicKey)), common.KeyIDSize) if err != nil { return err } @@ -774,7 +758,7 @@ func (w *Waku) DeleteKeyPairs() error { // HasKeyPair checks if the waku node is configured with the private key // of the specified public pair. func (w *Waku) HasKeyPair(id string) bool { - deterministicID, err := toDeterministicID(id, keyIDSize) + deterministicID, err := toDeterministicID(id, common.KeyIDSize) if err != nil { return false } @@ -786,7 +770,7 @@ func (w *Waku) HasKeyPair(id string) bool { // GetPrivateKey retrieves the private key of the specified identity. func (w *Waku) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { - deterministicID, err := toDeterministicID(id, keyIDSize) + deterministicID, err := toDeterministicID(id, common.KeyIDSize) if err != nil { return nil, err } @@ -803,14 +787,14 @@ func (w *Waku) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { // GenerateSymKey generates a random symmetric key and stores it under id, // which is then returned. Will be used in the future for session key exchange. func (w *Waku) GenerateSymKey() (string, error) { - key, err := generateSecureRandomData(aesKeyLength) + key, err := common.GenerateSecureRandomData(common.AESKeyLength) if err != nil { return "", err - } else if !validateDataIntegrity(key, aesKeyLength) { + } else if !common.ValidateDataIntegrity(key, common.AESKeyLength) { return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data") } - id, err := GenerateRandomID() + id, err := common.GenerateRandomID() if err != nil { return "", fmt.Errorf("failed to generate ID: %s", err) } @@ -827,7 +811,7 @@ func (w *Waku) GenerateSymKey() (string, error) { // AddSymKey stores the key with a given id. func (w *Waku) AddSymKey(id string, key []byte) (string, error) { - deterministicID, err := toDeterministicID(id, keyIDSize) + deterministicID, err := toDeterministicID(id, common.KeyIDSize) if err != nil { return "", err } @@ -844,11 +828,11 @@ func (w *Waku) AddSymKey(id string, key []byte) (string, error) { // AddSymKeyDirect stores the key, and returns its id. func (w *Waku) AddSymKeyDirect(key []byte) (string, error) { - if len(key) != aesKeyLength { + if len(key) != common.AESKeyLength { return "", fmt.Errorf("wrong key size: %d", len(key)) } - id, err := GenerateRandomID() + id, err := common.GenerateRandomID() if err != nil { return "", fmt.Errorf("failed to generate ID: %s", err) } @@ -865,7 +849,7 @@ func (w *Waku) AddSymKeyDirect(key []byte) (string, error) { // AddSymKeyFromPassword generates the key from password, stores it, and returns its id. func (w *Waku) AddSymKeyFromPassword(password string) (string, error) { - id, err := GenerateRandomID() + id, err := common.GenerateRandomID() if err != nil { return "", fmt.Errorf("failed to generate ID: %s", err) } @@ -875,7 +859,7 @@ func (w *Waku) AddSymKeyFromPassword(password string) (string, error) { // kdf should run no less than 0.1 seconds on an average computer, // because it's an once in a session experience - derived := pbkdf2.Key([]byte(password), nil, 65356, aesKeyLength, sha256.New) + derived := pbkdf2.Key([]byte(password), nil, 65356, common.AESKeyLength, sha256.New) if err != nil { return "", err } @@ -922,7 +906,7 @@ func (w *Waku) GetSymKey(id string) ([]byte, error) { // Subscribe installs a new message handler used for filtering, decrypting // and subsequent storing of incoming messages. -func (w *Waku) Subscribe(f *Filter) (string, error) { +func (w *Waku) Subscribe(f *common.Filter) (string, error) { s, err := w.filters.Install(f) if err != nil { return s, err @@ -936,7 +920,7 @@ func (w *Waku) Subscribe(f *Filter) (string, error) { return s, nil } -func (w *Waku) updateSettingsForFilter(f *Filter) error { +func (w *Waku) updateSettingsForFilter(f *common.Filter) error { w.settingsMu.RLock() topicInterestMode := !w.settings.BloomFilterMode w.settingsMu.RUnlock() @@ -957,15 +941,15 @@ func (w *Waku) updateSettingsForFilter(f *Filter) error { // updateBloomFilter recalculates the new value of bloom filter, // and informs the peers if necessary. -func (w *Waku) updateBloomFilter(f *Filter) error { - aggregate := make([]byte, BloomFilterSize) +func (w *Waku) updateBloomFilter(f *common.Filter) error { + aggregate := make([]byte, common.BloomFilterSize) for _, t := range f.Topics { - top := BytesToTopic(t) - b := TopicToBloom(top) + top := common.BytesToTopic(t) + b := common.TopicToBloom(top) aggregate = addBloom(aggregate, b) } - if !BloomFilterMatch(w.BloomFilter(), aggregate) { + if !common.BloomFilterMatch(w.BloomFilter(), aggregate) { // existing bloom filter must be updated aggregate = addBloom(w.BloomFilter(), aggregate) return w.SetBloomFilter(aggregate) @@ -974,7 +958,7 @@ func (w *Waku) updateBloomFilter(f *Filter) error { } // GetFilter returns the filter by id. -func (w *Waku) GetFilter(id string) *Filter { +func (w *Waku) GetFilter(id string) *common.Filter { return w.filters.Get(id) } @@ -994,7 +978,7 @@ func (w *Waku) Unsubscribe(id string) error { // Send injects a message into the waku send queue, to be distributed in the // network in the coming cycles. -func (w *Waku) Send(envelope *Envelope) error { +func (w *Waku) Send(envelope *common.Envelope) error { ok, err := w.add(envelope, false) if err == nil && !ok { return fmt.Errorf("failed to add envelope") @@ -1030,246 +1014,84 @@ func (w *Waku) Stop() error { // HandlePeer is called by the underlying P2P layer when the waku sub-protocol // connection is negotiated. -func (w *Waku) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { +func (w *Waku) HandlePeer(p2pPeer *p2p.Peer, rw p2p.MsgReadWriter) error { // Create the new peer and start tracking it - wakuPeer := newPeer(w, peer, rw, w.logger.Named("waku/peer")) + var peer common.Peer = v0.NewPeer(w, p2pPeer, rw, w.logger.Named("waku/peer")) w.peerMu.Lock() - w.peers[wakuPeer] = struct{}{} + w.peers[peer] = struct{}{} w.peerMu.Unlock() defer func() { w.peerMu.Lock() - delete(w.peers, wakuPeer) + delete(w.peers, peer) w.peerMu.Unlock() }() - // Run the peer handshake and state updates - if err := wakuPeer.handshake(); err != nil { + if err := peer.Start(); err != nil { return err } - wakuPeer.start() - defer wakuPeer.stop() + defer peer.Stop() if w.rateLimiter != nil { - return w.rateLimiter.decorate(wakuPeer, rw, w.runMessageLoop) + runLoop := func(out p2p.MsgReadWriter) error { + peer.SetRWWriter(out) + return peer.Run() + } + return w.rateLimiter.Decorate(peer, rw, runLoop) } - return w.runMessageLoop(wakuPeer, rw) + + return peer.Run() } -// sendConfirmation sends messageResponseCode and batchAcknowledgedCode messages. -func (w *Waku) sendConfirmation(rw p2p.MsgReadWriter, data []byte, envelopeErrors []EnvelopeError) (err error) { - batchHash := crypto.Keccak256Hash(data) - err = p2p.Send(rw, messageResponseCode, NewMessagesResponse(batchHash, envelopeErrors)) - if err != nil { - return - } - err = p2p.Send(rw, batchAcknowledgedCode, batchHash) // DEPRECATED - return -} - -// runMessageLoop reads and processes inbound messages directly to merge into client-global state. -func (w *Waku) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { - logger := w.logger.Named("runMessageLoop") - peerID := p.peer.ID() - - for { - // fetch the next packet - packet, err := rw.ReadMsg() - if err != nil { - logger.Info("failed to read a message", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - - if packet.Size > w.MaxMessageSize() { - logger.Warn("oversize message received", zap.Binary("peer", peerID[:]), zap.Uint32("size", packet.Size)) - return errors.New("oversize message received") - } - - switch packet.Code { - case messagesCode: - if err := w.handleMessagesCode(p, rw, packet, logger); err != nil { - logger.Warn("failed to handle messagesCode message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - case messageResponseCode: - if err := w.handleMessageResponseCode(p, packet, logger); err != nil { - logger.Warn("failed to handle messageResponseCode message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - case batchAcknowledgedCode: - if err := w.handleBatchAcknowledgeCode(p, packet, logger); err != nil { - logger.Warn("failed to handle batchAcknowledgedCode message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - case statusUpdateCode: - if err := w.handleStatusUpdateCode(p, packet, logger); err != nil { - logger.Warn("failed to decode status update message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - case p2pMessageCode: - if err := w.handleP2PMessageCode(p, packet, logger); err != nil { - logger.Warn("failed to decode direct message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - case p2pRequestCode: - if err := w.handleP2PRequestCode(p, packet, logger); err != nil { - logger.Warn("failed to decode p2p request message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - case p2pRequestCompleteCode: - if err := w.handleP2PRequestCompleteCode(p, packet, logger); err != nil { - logger.Warn("failed to decode p2p request complete message, peer will be disconnected", zap.Binary("peer", peerID[:]), zap.Error(err)) - return err - } - default: - // New message types might be implemented in the future versions of Waku. - // For forward compatibility, just ignore. - logger.Debug("ignored packet with message code", zap.Uint64("code", packet.Code)) - } - - _ = packet.Discard() - } -} - -func (w *Waku) handleMessagesCode(p *Peer, rw p2p.MsgReadWriter, packet p2p.Msg, logger *zap.Logger) error { - peerID := p.peer.ID() - - // decode the contained envelopes - data, err := ioutil.ReadAll(packet.Payload) - if err != nil { - envelopesRejectedCounter.WithLabelValues("failed_read").Inc() - return fmt.Errorf("failed to read packet payload: %v", err) - } - - var envelopes []*Envelope - if err := rlp.DecodeBytes(data, &envelopes); err != nil { - envelopesRejectedCounter.WithLabelValues("invalid_data").Inc() - return fmt.Errorf("invalid payload: %v", err) - } - - envelopeErrors := make([]EnvelopeError, 0) +func (w *Waku) OnNewEnvelopes(envelopes []*common.Envelope, peer common.Peer) ([]common.EnvelopeError, error) { + envelopeErrors := make([]common.EnvelopeError, 0) trouble := false for _, env := range envelopes { cached, err := w.add(env, w.LightClientMode()) if err != nil { - _, isTimeSyncError := err.(TimeSyncError) + _, isTimeSyncError := err.(common.TimeSyncError) if !isTimeSyncError { trouble = true - logger.Info("invalid envelope received", zap.Binary("peer", peerID[:]), zap.Error(err)) + w.logger.Info("invalid envelope received", zap.Binary("peer", peer.ID()), zap.Error(err)) } - envelopeErrors = append(envelopeErrors, ErrorToEnvelopeError(env.Hash(), err)) + envelopeErrors = append(envelopeErrors, common.ErrorToEnvelopeError(env.Hash(), err)) } else if cached { - p.mark(env) + peer.Mark(env) } - w.envelopeFeed.Send(EnvelopeEvent{ - Event: EventEnvelopeReceived, + w.envelopeFeed.Send(common.EnvelopeEvent{ + Event: common.EventEnvelopeReceived, Topic: env.Topic, Hash: env.Hash(), - Peer: p.peer.ID(), + Peer: peer.EnodeID(), }) - envelopesValidatedCounter.Inc() - } - - if w.ConfirmationsEnabled() { - go w.sendConfirmation(rw, data, envelopeErrors) // nolint: errcheck + common.EnvelopesValidatedCounter.Inc() } if trouble { - return errors.New("received invalid envelope") + return envelopeErrors, errors.New("received invalid envelope") } - return nil + return envelopeErrors, nil } -func (w *Waku) handleStatusUpdateCode(p *Peer, packet p2p.Msg, logger *zap.Logger) error { - var statusOptions statusOptions - err := packet.Decode(&statusOptions) - if err != nil { - logger.Error("failed to decode status-options", zap.Error(err)) - envelopesRejectedCounter.WithLabelValues("invalid_settings_changed").Inc() - return err - } - - return p.setOptions(statusOptions) -} - -func (w *Waku) handleP2PMessageCode(p *Peer, packet p2p.Msg, logger *zap.Logger) error { - // peer-to-peer message, sent directly to peer bypassing PoW checks, etc. - // this message is not supposed to be forwarded to other peers, and - // therefore might not satisfy the PoW, expiry and other requirements. - // these messages are only accepted from the trusted peer. - if !p.trusted { - return nil - } - - var ( - envelopes []*Envelope - err error - ) - - if err = packet.Decode(&envelopes); err != nil { - return fmt.Errorf("invalid direct message payload: %v", err) - } - +func (w *Waku) OnNewP2PEnvelopes(envelopes []*common.Envelope, p common.Peer) error { for _, envelope := range envelopes { w.postP2P(envelope) } return nil } - -func (w *Waku) handleP2PRequestCode(p *Peer, packet p2p.Msg, logger *zap.Logger) error { - peerID := p.peer.ID() - - // Must be processed if mail server is implemented. Otherwise ignore. - if w.mailServer == nil { - return nil - } - - // Read all data as we will try to decode it possibly twice. - data, err := ioutil.ReadAll(packet.Payload) - if err != nil { - return fmt.Errorf("invalid p2p request messages: %v", err) - } - r := bytes.NewReader(data) - packet.Payload = r - - var requestDeprecated Envelope - errDepReq := packet.Decode(&requestDeprecated) - if errDepReq == nil { - w.mailServer.DeliverMail(p.ID(), &requestDeprecated) - return nil - } - logger.Info("failed to decode p2p request message (deprecated)", zap.Binary("peer", peerID[:]), zap.Error(errDepReq)) - - // As we failed to decode the request, let's set the offset - // to the beginning and try decode it again. - if _, err := r.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("invalid p2p request message: %v", err) - } - - var request MessagesRequest - errReq := packet.Decode(&request) - if errReq == nil { - w.mailServer.Deliver(p.ID(), request) - return nil - } - logger.Info("failed to decode p2p request message", zap.Binary("peer", peerID[:]), zap.Error(errDepReq)) - - return errors.New("invalid p2p request message") +func (w *Waku) Mailserver() bool { + return w.mailServer != nil } -func (w *Waku) handleP2PRequestCompleteCode(p *Peer, packet p2p.Msg, logger *zap.Logger) error { - if !p.trusted { - return nil - } +func (w *Waku) OnMessagesRequest(request common.MessagesRequest, p common.Peer) error { + w.mailServer.Deliver(p.ID(), request) + return nil +} - var payload []byte - if err := packet.Decode(&payload); err != nil { - return fmt.Errorf("invalid p2p request complete message: %v", err) - } - - event, err := CreateMailServerEvent(p.peer.ID(), payload) +func (w *Waku) OnP2PRequestCompleted(payload []byte, p common.Peer) error { + event, err := CreateMailServerEvent(p.EnodeID(), payload) if err != nil { return fmt.Errorf("invalid p2p request complete payload: %v", err) } @@ -1278,57 +1100,37 @@ func (w *Waku) handleP2PRequestCompleteCode(p *Peer, packet p2p.Msg, logger *zap return nil } -func (w *Waku) handleMessageResponseCode(p *Peer, packet p2p.Msg, logger *zap.Logger) error { - var resp MultiVersionResponse - if err := packet.Decode(&resp); err != nil { - envelopesRejectedCounter.WithLabelValues("failed_read").Inc() - return fmt.Errorf("invalid response message: %v", err) - } - if resp.Version != 1 { - logger.Info("received unsupported version of MultiVersionResponse for messageResponseCode packet", zap.Uint("version", resp.Version)) - return nil - } - - response, err := resp.DecodeResponse1() - if err != nil { - envelopesRejectedCounter.WithLabelValues("invalid_data").Inc() - return fmt.Errorf("failed to decode response message: %v", err) - } - - w.envelopeFeed.Send(EnvelopeEvent{ +func (w *Waku) OnMessagesResponse(response common.MessagesResponse, p common.Peer) error { + w.envelopeFeed.Send(common.EnvelopeEvent{ Batch: response.Hash, - Event: EventBatchAcknowledged, - Peer: p.peer.ID(), + Event: common.EventBatchAcknowledged, + Peer: p.EnodeID(), Data: response.Errors, }) return nil } -func (w *Waku) handleBatchAcknowledgeCode(p *Peer, packet p2p.Msg, logger *zap.Logger) error { - var batchHash common.Hash - if err := packet.Decode(&batchHash); err != nil { - return fmt.Errorf("invalid batch ack message: %v", err) - } - w.envelopeFeed.Send(EnvelopeEvent{ +func (w *Waku) OnBatchAcknowledged(batchHash gethcommon.Hash, p common.Peer) error { + w.envelopeFeed.Send(common.EnvelopeEvent{ Batch: batchHash, - Event: EventBatchAcknowledged, - Peer: p.peer.ID(), + Event: common.EventBatchAcknowledged, + Peer: p.EnodeID(), }) return nil } -func (w *Waku) add(envelope *Envelope, isP2P bool) (bool, error) { +func (w *Waku) add(envelope *common.Envelope, isP2P bool) (bool, error) { return w.addAndBridge(envelope, isP2P, false) } -func (w *Waku) bloomMatch(envelope *Envelope) (bool, error) { - if !BloomFilterMatch(w.BloomFilter(), envelope.Bloom()) { +func (w *Waku) bloomMatch(envelope *common.Envelope) (bool, error) { + if !common.BloomFilterMatch(w.BloomFilter(), envelope.Bloom()) { // maybe the value was recently changed, and the peers did not adjust yet. // in this case the previous value is retrieved by BloomFilterTolerance() // for a short period of peer synchronization. - if !BloomFilterMatch(w.BloomFilterTolerance(), envelope.Bloom()) { - envelopesCacheFailedCounter.WithLabelValues("no_bloom_match").Inc() + if !common.BloomFilterMatch(w.BloomFilterTolerance(), envelope.Bloom()) { + common.EnvelopesCacheFailedCounter.WithLabelValues("no_bloom_match").Inc() return false, fmt.Errorf("envelope does not match bloom filter, hash=[%v], bloom: \n%x \n%x \n%x", envelope.Hash().Hex(), w.BloomFilter(), envelope.Bloom(), envelope.Topic) } @@ -1336,7 +1138,7 @@ func (w *Waku) bloomMatch(envelope *Envelope) (bool, error) { return true, nil } -func (w *Waku) topicInterestMatch(envelope *Envelope) (bool, error) { +func (w *Waku) topicInterestMatch(envelope *common.Envelope) (bool, error) { w.settingsMu.RLock() defer w.settingsMu.RUnlock() if w.settings.TopicInterest == nil { @@ -1344,7 +1146,7 @@ func (w *Waku) topicInterestMatch(envelope *Envelope) (bool, error) { } if !w.settings.TopicInterest[envelope.Topic] { if !w.settings.TopicInterestTolerance[envelope.Topic] { - envelopesCacheFailedCounter.WithLabelValues("no_topic_interest_match").Inc() + common.EnvelopesCacheFailedCounter.WithLabelValues("no_topic_interest_match").Inc() return false, fmt.Errorf("envelope does not match topic interest, hash=[%v], bloom: \n%x \n%x", envelope.Hash().Hex(), envelope.Bloom(), envelope.Topic) @@ -1354,7 +1156,7 @@ func (w *Waku) topicInterestMatch(envelope *Envelope) (bool, error) { return true, nil } -func (w *Waku) topicInterestOrBloomMatch(envelope *Envelope) (bool, error) { +func (w *Waku) topicInterestOrBloomMatch(envelope *common.Envelope) (bool, error) { w.settingsMu.RLock() topicInterestMode := !w.settings.BloomFilterMode w.settingsMu.RUnlock() @@ -1382,35 +1184,35 @@ func (w *Waku) SetBloomFilterMode(mode bool) { // waku network. It also inserts the envelope into the expiration pool at the // appropriate time-stamp. In case of error, connection should be dropped. // param isP2P indicates whether the message is peer-to-peer (should not be forwarded). -func (w *Waku) addAndBridge(envelope *Envelope, isP2P bool, bridged bool) (bool, error) { +func (w *Waku) addAndBridge(envelope *common.Envelope, isP2P bool, bridged bool) (bool, error) { now := uint32(w.timeSource().Unix()) sent := envelope.Expiry - envelope.TTL - envelopesReceivedCounter.Inc() + common.EnvelopesReceivedCounter.Inc() if sent > now { - if sent-DefaultSyncAllowance > now { - envelopesCacheFailedCounter.WithLabelValues("in_future").Inc() + if sent-common.DefaultSyncAllowance > now { + common.EnvelopesCacheFailedCounter.WithLabelValues("in_future").Inc() log.Warn("envelope created in the future", "hash", envelope.Hash()) - return false, TimeSyncError(errors.New("envelope from future")) + return false, common.TimeSyncError(errors.New("envelope from future")) } // recalculate PoW, adjusted for the time difference, plus one second for latency - envelope.calculatePoW(sent - now + 1) + envelope.CalculatePoW(sent - now + 1) } if envelope.Expiry < now { - if envelope.Expiry+DefaultSyncAllowance*2 < now { - envelopesCacheFailedCounter.WithLabelValues("very_old").Inc() + if envelope.Expiry+common.DefaultSyncAllowance*2 < now { + common.EnvelopesCacheFailedCounter.WithLabelValues("very_old").Inc() log.Warn("very old envelope", "hash", envelope.Hash()) - return false, TimeSyncError(errors.New("very old envelope")) + return false, common.TimeSyncError(errors.New("very old envelope")) } log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) - envelopesCacheFailedCounter.WithLabelValues("expired").Inc() + common.EnvelopesCacheFailedCounter.WithLabelValues("expired").Inc() return false, nil // drop envelope without error } - if uint32(envelope.size()) > w.MaxMessageSize() { - envelopesCacheFailedCounter.WithLabelValues("oversized").Inc() - return false, fmt.Errorf("huge messages are not allowed [%x][%d][%d]", envelope.Hash(), envelope.size(), w.MaxMessageSize()) + if uint32(envelope.Size()) > w.MaxMessageSize() { + common.EnvelopesCacheFailedCounter.WithLabelValues("oversized").Inc() + return false, fmt.Errorf("huge messages are not allowed [%x][%d][%d]", envelope.Hash(), envelope.Size(), w.MaxMessageSize()) } if envelope.PoW() < w.MinPow() { @@ -1418,7 +1220,7 @@ func (w *Waku) addAndBridge(envelope *Envelope, isP2P bool, bridged bool) (bool, // in this case the previous value is retrieved by MinPowTolerance() // for a short period of peer synchronization. if envelope.PoW() < w.MinPowTolerance() { - envelopesCacheFailedCounter.WithLabelValues("low_pow").Inc() + common.EnvelopesCacheFailedCounter.WithLabelValues("low_pow").Inc() return false, fmt.Errorf("envelope with low PoW received: PoW=%f, hash=[%v]", envelope.PoW(), envelope.Hash().Hex()) } } @@ -1449,18 +1251,18 @@ func (w *Waku) addAndBridge(envelope *Envelope, isP2P bool, bridged bool) (bool, if alreadyCached { log.Trace("w envelope already cached", "hash", envelope.Hash().Hex()) - envelopesCachedCounter.WithLabelValues("hit").Inc() + common.EnvelopesCachedCounter.WithLabelValues("hit").Inc() } else { log.Trace("cached w envelope", "hash", envelope.Hash().Hex()) - envelopesCachedCounter.WithLabelValues("miss").Inc() - envelopesSizeMeter.Observe(float64(envelope.size())) + common.EnvelopesCachedCounter.WithLabelValues("miss").Inc() + common.EnvelopesSizeMeter.Observe(float64(envelope.Size())) w.postEvent(envelope, isP2P) // notify the local node about the new message if w.mailServer != nil { w.mailServer.Archive(envelope) - w.envelopeFeed.Send(EnvelopeEvent{ + w.envelopeFeed.Send(common.EnvelopeEvent{ Topic: envelope.Topic, Hash: envelope.Hash(), - Event: EventMailServerEnvelopeArchived, + Event: common.EventMailServerEnvelopeArchived, }) } // Bridge only envelopes that are not p2p messages. @@ -1470,7 +1272,7 @@ func (w *Waku) addAndBridge(envelope *Envelope, isP2P bool, bridged bool) (bool, log.Debug("bridging envelope from Waku", "hash", envelope.Hash().Hex()) _, in := w.bridge.Pipe() in <- envelope - bridgeSent.Inc() + common.BridgeSent.Inc() } } return true, nil @@ -1481,7 +1283,7 @@ func (w *Waku) postP2P(event interface{}) { } // postEvent queues the message for further processing. -func (w *Waku) postEvent(envelope *Envelope, isP2P bool) { +func (w *Waku) postEvent(envelope *common.Envelope, isP2P bool) { if isP2P { w.postP2P(envelope) } else { @@ -1497,10 +1299,10 @@ func (w *Waku) processQueue() { return case e := <-w.msgQueue: w.filters.NotifyWatchers(e, false) - w.envelopeFeed.Send(EnvelopeEvent{ + w.envelopeFeed.Send(common.EnvelopeEvent{ Topic: e.Topic, Hash: e.Hash(), - Event: EventEnvelopeAvailable, + Event: common.EventEnvelopeAvailable, }) } } @@ -1513,14 +1315,14 @@ func (w *Waku) processP2P() { return case e := <-w.p2pMsgQueue: switch event := e.(type) { - case *Envelope: + case *common.Envelope: w.filters.NotifyWatchers(event, true) - w.envelopeFeed.Send(EnvelopeEvent{ + w.envelopeFeed.Send(common.EnvelopeEvent{ Topic: event.Topic, Hash: event.Hash(), - Event: EventEnvelopeAvailable, + Event: common.EventEnvelopeAvailable, }) - case EnvelopeEvent: + case common.EnvelopeEvent: w.envelopeFeed.Send(event) } } @@ -1531,7 +1333,7 @@ func (w *Waku) processP2P() { // state by expiring stale messages from the pool. func (w *Waku) update() { // Start a ticker to check for expirations - expire := time.NewTicker(expirationCycle) + expire := time.NewTicker(common.ExpirationCycle) // Repeat updates until termination is requested for { @@ -1556,11 +1358,11 @@ func (w *Waku) expire() { if expiry < now { // Dump all expired messages and remove timestamp hashSet.Each(func(v interface{}) bool { - delete(w.envelopes, v.(common.Hash)) - envelopesCachedCounter.WithLabelValues("clear").Inc() - w.envelopeFeed.Send(EnvelopeEvent{ - Hash: v.(common.Hash), - Event: EventEnvelopeExpired, + delete(w.envelopes, v.(gethcommon.Hash)) + common.EnvelopesCachedCounter.WithLabelValues("clear").Inc() + w.envelopeFeed.Send(common.EnvelopeEvent{ + Hash: v.(gethcommon.Hash), + Event: common.EventEnvelopeExpired, }) return false }) @@ -1570,37 +1372,12 @@ func (w *Waku) expire() { } } -func (w *Waku) toStatusOptions() statusOptions { - opts := statusOptions{} - - rateLimits := w.RateLimits() - opts.RateLimits = &rateLimits - - lightNode := w.LightClientMode() - opts.LightNodeEnabled = &lightNode - - minPoW := w.MinPow() - opts.SetPoWRequirementFromF(minPoW) - - confirmationsEnabled := w.ConfirmationsEnabled() - opts.ConfirmationsEnabled = &confirmationsEnabled - - bloomFilterMode := w.BloomFilterMode() - if bloomFilterMode { - opts.BloomFilter = w.BloomFilter() - } else { - opts.TopicInterest = w.TopicInterest() - } - - return opts -} - // Envelopes retrieves all the messages currently pooled by the node. -func (w *Waku) Envelopes() []*Envelope { +func (w *Waku) Envelopes() []*common.Envelope { w.poolMu.RLock() defer w.poolMu.RUnlock() - all := make([]*Envelope, 0, len(w.envelopes)) + all := make([]*common.Envelope, 0, len(w.envelopes)) for _, envelope := range w.envelopes { all = append(all, envelope) } @@ -1609,14 +1386,14 @@ func (w *Waku) Envelopes() []*Envelope { // GetEnvelope retrieves an envelope from the message queue by its hash. // It returns nil if the envelope can not be found. -func (w *Waku) GetEnvelope(hash common.Hash) *Envelope { +func (w *Waku) GetEnvelope(hash gethcommon.Hash) *common.Envelope { w.poolMu.RLock() defer w.poolMu.RUnlock() return w.envelopes[hash] } // isEnvelopeCached checks if envelope with specific hash has already been received and cached. -func (w *Waku) isEnvelopeCached(hash common.Hash) bool { +func (w *Waku) IsEnvelopeCached(hash gethcommon.Hash) bool { w.poolMu.Lock() defer w.poolMu.Unlock() @@ -1624,80 +1401,21 @@ func (w *Waku) isEnvelopeCached(hash common.Hash) bool { return exist } -// ValidatePublicKey checks the format of the given public key. -func ValidatePublicKey(k *ecdsa.PublicKey) bool { - return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 -} - // validatePrivateKey checks the format of the given private key. func validatePrivateKey(k *ecdsa.PrivateKey) bool { if k == nil || k.D == nil || k.D.Sign() == 0 { return false } - return ValidatePublicKey(&k.PublicKey) -} - -// validateDataIntegrity returns false if the data have the wrong or contains all zeros, -// which is the simplest and the most common bug. -func validateDataIntegrity(k []byte, expectedSize int) bool { - if len(k) != expectedSize { - return false - } - if expectedSize > 3 && containsOnlyZeros(k) { - return false - } - return true -} - -// containsOnlyZeros checks if the data contain only zeros. -func containsOnlyZeros(data []byte) bool { - for _, b := range data { - if b != 0 { - return false - } - } - return true -} - -// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer. -func bytesToUintLittleEndian(b []byte) (res uint64) { - mul := uint64(1) - for i := 0; i < len(b); i++ { - res += uint64(b[i]) * mul - mul *= 256 - } - return res -} - -// BytesToUintBigEndian converts the slice to 64-bit unsigned integer. -func BytesToUintBigEndian(b []byte) (res uint64) { - for i := 0; i < len(b); i++ { - res *= 256 - res += uint64(b[i]) - } - return res -} - -// GenerateRandomID generates a random string, which is then returned to be used as a key id -func GenerateRandomID() (id string, err error) { - buf, err := generateSecureRandomData(keyIDSize) - if err != nil { - return "", err - } - if !validateDataIntegrity(buf, keyIDSize) { - return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") - } - id = common.Bytes2Hex(buf) - return id, err + return common.ValidatePublicKey(&k.PublicKey) } // makeDeterministicID generates a deterministic ID, based on a given input func makeDeterministicID(input string, keyLen int) (id string, err error) { buf := pbkdf2.Key([]byte(input), nil, 4096, keyLen, sha256.New) - if !validateDataIntegrity(buf, keyIDSize) { + if !common.ValidateDataIntegrity(buf, common.KeyIDSize) { return "", fmt.Errorf("error in GenerateDeterministicID: failed to generate key") } - id = common.Bytes2Hex(buf) + id = gethcommon.Bytes2Hex(buf) return id, err } @@ -1718,37 +1436,9 @@ func toDeterministicID(id string, expectedLen int) (string, error) { return id, nil } -func isFullNode(bloom []byte) bool { - if bloom == nil { - return true - } - for _, b := range bloom { - if b != 255 { - return false - } - } - return true -} - -func BloomFilterMatch(filter, sample []byte) bool { - if filter == nil { - return true - } - - for i := 0; i < BloomFilterSize; i++ { - f := filter[i] - s := sample[i] - if (f | s) != f { - return false - } - } - - return true -} - func addBloom(a, b []byte) []byte { - c := make([]byte, BloomFilterSize) - for i := 0; i < BloomFilterSize; i++ { + c := make([]byte, common.BloomFilterSize) + for i := 0; i < common.BloomFilterSize; i++ { c[i] = a[i] | b[i] } return c diff --git a/waku/waku_test.go b/waku/waku_test.go index 20b385909..467dd8258 100644 --- a/waku/waku_test.go +++ b/waku/waku_test.go @@ -22,41 +22,53 @@ import ( "bytes" "crypto/ecdsa" "crypto/sha256" - "errors" "math" mrand "math/rand" "testing" "time" - "go.uber.org/zap" - "github.com/stretchr/testify/require" "golang.org/x/crypto/pbkdf2" - "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/waku/common" + v0 "github.com/status-im/status-go/waku/v0" + + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" + + "go.uber.org/zap" ) +var seed int64 + +// InitSingleTest should be called in the beginning of every +// test, which uses RNG, in order to make the tests +// reproduciblity independent of their sequence. +func InitSingleTest() { + seed = time.Now().Unix() + mrand.Seed(seed) +} + func TestBasic(t *testing.T) { w := New(nil, nil) p := w.Protocols() shh := p[0] - if shh.Name != ProtocolName { - t.Fatalf("failed Protocol Name: %v.", shh.Name) + if shh.Name != v0.Name { + t.Fatalf("failed Peer Name: %v.", shh.Name) } - if uint64(shh.Version) != ProtocolVersion { - t.Fatalf("failed Protocol Version: %v.", shh.Version) + if uint64(shh.Version) != v0.Version { + t.Fatalf("failed Peer Version: %v.", shh.Version) } - if shh.Length != NumberOfMessageCodes { - t.Fatalf("failed Protocol Length: %v.", shh.Length) + if shh.Length != v0.NumberOfMessageCodes { + t.Fatalf("failed Peer Length: %v.", shh.Length) } if shh.Run == nil { t.Fatalf("failed shh.Run.") } - if uint64(w.Version()) != ProtocolVersion { + if uint64(w.Version()) != v0.Version { t.Fatalf("failed waku Version: %v.", shh.Version) } if w.GetFilter("non-existent") != nil { @@ -88,17 +100,17 @@ func TestBasic(t *testing.T) { t.Fatalf("failed w.Envelopes().") } - derived := pbkdf2.Key(peerID, nil, 65356, aesKeyLength, sha256.New) - if !validateDataIntegrity(derived, aesKeyLength) { + derived := pbkdf2.Key(peerID, nil, 65356, common.AESKeyLength, sha256.New) + if !common.ValidateDataIntegrity(derived, common.AESKeyLength) { t.Fatalf("failed validateSymmetricKey with param = %v.", derived) } - if containsOnlyZeros(derived) { + if common.ContainsOnlyZeros(derived) { t.Fatalf("failed containsOnlyZeros with param = %v.", derived) } buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0} - le := bytesToUintLittleEndian(buf) - be := BytesToUintBigEndian(buf) + le := common.BytesToUintLittleEndian(buf) + be := common.BytesToUintBigEndian(buf) if le != uint64(0x280e5ff) { t.Fatalf("failed bytesToIntLittleEndian: %d.", le) } @@ -117,7 +129,7 @@ func TestBasic(t *testing.T) { if !validatePrivateKey(pk) { t.Fatalf("failed validatePrivateKey: %v.", pk) } - if !ValidatePublicKey(&pk.PublicKey) { + if !common.ValidatePublicKey(&pk.PublicKey) { t.Fatalf("failed ValidatePublicKey: %v.", pk) } } @@ -306,7 +318,7 @@ func TestSymKeyManagement(t *testing.T) { } // add existing id, nothing should change - randomKey := make([]byte, aesKeyLength) + randomKey := make([]byte, common.AESKeyLength) mrand.Read(randomKey) // nolint: gosec id1, err = w.AddSymKeyDirect(randomKey) if err != nil { @@ -367,10 +379,10 @@ func TestSymKeyManagement(t *testing.T) { if !bytes.Equal(k1, randomKey) { t.Fatalf("k1 != randomKey.") } - if len(k1) != aesKeyLength { + if len(k1) != common.AESKeyLength { t.Fatalf("wrong length of k1.") } - if len(k2) != aesKeyLength { + if len(k2) != common.AESKeyLength { t.Fatalf("wrong length of k2.") } @@ -425,7 +437,7 @@ func TestSymKeyManagement(t *testing.T) { t.Fatalf("failed to delete second key: second key is not nil.") } - randomKey = make([]byte, aesKeyLength+1) + randomKey = make([]byte, common.AESKeyLength+1) mrand.Read(randomKey) // nolint: gosec _, err = w.AddSymKeyDirect(randomKey) if err == nil { @@ -455,7 +467,7 @@ func TestSymKeyManagement(t *testing.T) { if !w.HasSymKey(id2) { t.Fatalf("HasSymKey(id2) failed.") } - if !validateDataIntegrity(k2, aesKeyLength) { + if !common.ValidateDataIntegrity(k2, common.AESKeyLength) { t.Fatalf("key validation failed.") } if !bytes.Equal(k1, k2) { @@ -472,7 +484,7 @@ func TestExpiry(t *testing.T) { t.Fatal("failed to set min pow") } - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck + defer w.SetMinimumPoW(common.DefaultMinimumPoW, false) // nolint: errcheck err = w.Start(nil) if err != nil { t.Fatal("failed to start waku") @@ -491,7 +503,7 @@ func TestExpiry(t *testing.T) { // with one second resolution, it covers a case when there are multiple items // in a single expiration bucket. for i := 0; i < messagesCount; i++ { - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -538,8 +550,8 @@ func TestCustomization(t *testing.T) { InitSingleTest() w := New(nil, nil) - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - defer w.SetMaxMessageSize(DefaultMaxMessageSize) // nolint: errcheck + defer w.SetMinimumPoW(common.DefaultMinimumPoW, false) // nolint: errcheck + defer w.SetMaxMessageSize(common.DefaultMaxMessageSize) // nolint: errcheck if err := w.Start(nil); err != nil { t.Fatal("failed to start node") } @@ -557,10 +569,10 @@ func TestCustomization(t *testing.T) { } params.KeySym = f.KeySym - params.Topic = BytesToTopic(f.Topics[2]) + params.Topic = common.BytesToTopic(f.Topics[2]) params.PoW = smallPoW params.TTL = 3600 * 24 // one day - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -581,7 +593,7 @@ func TestCustomization(t *testing.T) { } params.TTL++ - msg, err = NewSentMessage(params) + msg, err = common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -589,13 +601,13 @@ func TestCustomization(t *testing.T) { if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } - _ = w.SetMaxMessageSize(uint32(env.size() - 1)) + _ = w.SetMaxMessageSize(uint32(env.Size() - 1)) err = w.Send(env) if err == nil { t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed) } - _ = w.SetMaxMessageSize(DefaultMaxMessageSize) + _ = w.SetMaxMessageSize(common.DefaultMaxMessageSize) err = w.Send(env) if err != nil { t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err) @@ -631,8 +643,8 @@ func TestSymmetricSendCycle(t *testing.T) { InitSingleTest() w := New(nil, nil) - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - defer w.SetMaxMessageSize(DefaultMaxMessageSize) // nolint: errcheck + defer w.SetMinimumPoW(common.DefaultMinimumPoW, false) // nolint: errcheck + defer w.SetMaxMessageSize(common.DefaultMaxMessageSize) // nolint: errcheck err := w.Start(nil) if err != nil { t.Fatal("failed to start node") @@ -643,16 +655,16 @@ func TestSymmetricSendCycle(t *testing.T) { if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - filter1.PoW = DefaultMinimumPoW + filter1.PoW = common.DefaultMinimumPoW // Copy the first filter since some of its fields // are randomly generated. - filter2 := &Filter{ + filter2 := &common.Filter{ KeySym: filter1.KeySym, Topics: filter1.Topics, PoW: filter1.PoW, AllowP2P: filter1.AllowP2P, - Messages: NewMemoryMessageStore(), + Messages: common.NewMemoryMessageStore(), } params, err := generateMessageParams() @@ -664,11 +676,11 @@ func TestSymmetricSendCycle(t *testing.T) { filter2.Src = ¶ms.Src.PublicKey params.KeySym = filter1.KeySym - params.Topic = BytesToTopic(filter1.Topics[2]) + params.Topic = common.BytesToTopic(filter1.Topics[2]) params.PoW = filter1.PoW params.WorkTime = 10 params.TTL = 50 - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -723,8 +735,8 @@ func TestSymmetricSendCycleWithTopicInterest(t *testing.T) { InitSingleTest() w := New(nil, nil) - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - defer w.SetMaxMessageSize(DefaultMaxMessageSize) // nolint: errcheck + defer w.SetMinimumPoW(common.DefaultMinimumPoW, false) // nolint: errcheck + defer w.SetMaxMessageSize(common.DefaultMaxMessageSize) // nolint: errcheck if err := w.Start(nil); err != nil { t.Fatal("could not start node") } @@ -734,16 +746,16 @@ func TestSymmetricSendCycleWithTopicInterest(t *testing.T) { if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - filter1.PoW = DefaultMinimumPoW + filter1.PoW = common.DefaultMinimumPoW // Copy the first filter since some of its fields // are randomly generated. - filter2 := &Filter{ + filter2 := &common.Filter{ KeySym: filter1.KeySym, Topics: filter1.Topics, PoW: filter1.PoW, AllowP2P: filter1.AllowP2P, - Messages: NewMemoryMessageStore(), + Messages: common.NewMemoryMessageStore(), } params, err := generateMessageParams() @@ -755,11 +767,11 @@ func TestSymmetricSendCycleWithTopicInterest(t *testing.T) { filter2.Src = ¶ms.Src.PublicKey params.KeySym = filter1.KeySym - params.Topic = BytesToTopic(filter1.Topics[2]) + params.Topic = common.BytesToTopic(filter1.Topics[2]) params.PoW = filter1.PoW params.WorkTime = 10 params.TTL = 50 - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -814,16 +826,16 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { InitSingleTest() w := New(nil, nil) - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - defer w.SetMaxMessageSize(DefaultMaxMessageSize) // nolint: errcheck - w.Start(nil) // nolint: errcheck - defer w.Stop() // nolint: errcheck + defer w.SetMinimumPoW(common.DefaultMinimumPoW, false) // nolint: errcheck + defer w.SetMaxMessageSize(common.DefaultMaxMessageSize) // nolint: errcheck + w.Start(nil) // nolint: errcheck + defer w.Stop() // nolint: errcheck filter, err := generateFilter(t, true) if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - filter.PoW = DefaultMinimumPoW + filter.PoW = common.DefaultMinimumPoW params, err := generateMessageParams() if err != nil { @@ -833,11 +845,11 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { filter.Src = nil params.KeySym = filter.KeySym - params.Topic = BytesToTopic(filter.Topics[2]) + params.Topic = common.BytesToTopic(filter.Topics[2]) params.PoW = filter.PoW params.WorkTime = 10 params.TTL = 50 - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -882,16 +894,16 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { InitSingleTest() w := New(nil, nil) - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - defer w.SetMaxMessageSize(DefaultMaxMessageSize) // nolint: errcheck - w.Start(nil) // nolint: errcheck - defer w.Stop() // nolint: errcheck + defer w.SetMinimumPoW(common.DefaultMinimumPoW, false) // nolint: errcheck + defer w.SetMaxMessageSize(common.DefaultMaxMessageSize) // nolint: errcheck + w.Start(nil) // nolint: errcheck + defer w.Stop() // nolint: errcheck filter, err := generateFilter(t, true) if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - filter.PoW = DefaultMinimumPoW + filter.PoW = common.DefaultMinimumPoW params, err := generateMessageParams() if err != nil { @@ -899,11 +911,11 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { } params.KeySym = filter.KeySym - params.Topic = BytesToTopic(filter.Topics[2]) + params.Topic = common.BytesToTopic(filter.Topics[2]) params.PoW = filter.PoW params.WorkTime = 10 params.TTL = 50 - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -945,13 +957,13 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { } func TestBloom(t *testing.T) { - topic := TopicType{0, 0, 255, 6} - b := TopicToBloom(topic) - x := make([]byte, BloomFilterSize) + topic := common.TopicType{0, 0, 255, 6} + b := common.TopicToBloom(topic) + x := make([]byte, common.BloomFilterSize) x[0] = byte(1) x[32] = byte(1) - x[BloomFilterSize-1] = byte(128) - if !BloomFilterMatch(x, b) || !BloomFilterMatch(b, x) { + x[common.BloomFilterSize-1] = byte(128) + if !common.BloomFilterMatch(x, b) || !common.BloomFilterMatch(b, x) { t.Fatalf("bloom filter does not match the mask") } @@ -963,30 +975,30 @@ func TestBloom(t *testing.T) { if err != nil { t.Fatalf("math rand error") } - if !BloomFilterMatch(b, b) { + if !common.BloomFilterMatch(b, b) { t.Fatalf("bloom filter does not match self") } x = addBloom(x, b) - if !BloomFilterMatch(x, b) { + if !common.BloomFilterMatch(x, b) { t.Fatalf("bloom filter does not match combined bloom") } - if !isFullNode(nil) { - t.Fatalf("isFullNode did not recognize nil as full node") + if !common.IsFullNode(nil) { + t.Fatalf("common.IsFullNode did not recognize nil as full node") } x[17] = 254 - if isFullNode(x) { - t.Fatalf("isFullNode false positive") + if common.IsFullNode(x) { + t.Fatalf("common.IsFullNode false positive") } - for i := 0; i < BloomFilterSize; i++ { + for i := 0; i < common.BloomFilterSize; i++ { b[i] = byte(255) } - if !isFullNode(b) { - t.Fatalf("isFullNode false negative") + if !common.IsFullNode(b) { + t.Fatalf("common.IsFullNode false negative") } - if BloomFilterMatch(x, b) { + if common.BloomFilterMatch(x, b) { t.Fatalf("bloomFilterMatch false positive") } - if !BloomFilterMatch(b, x) { + if !common.BloomFilterMatch(b, x) { t.Fatalf("bloomFilterMatch false negative") } @@ -1000,7 +1012,7 @@ func TestBloom(t *testing.T) { t.Fatalf("failed to set bloom filter: %s", err) } f = w.BloomFilter() - if !BloomFilterMatch(f, x) || !BloomFilterMatch(x, f) { + if !common.BloomFilterMatch(f, x) || !common.BloomFilterMatch(x, f) { t.Fatalf("retireved wrong bloom filter") } } @@ -1044,63 +1056,23 @@ func TestTopicInterest(t *testing.T) { } -func TestSendP2PDirect(t *testing.T) { - InitSingleTest() - - w := New(nil, nil) - _ = w.SetMinimumPoW(0.0000001, false) // nolint: errcheck - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - _ = w.Start(nil) // nolint: errcheck - defer w.Stop() // nolint: errcheck - - rwStub := &rwP2PMessagesStub{} - peerW := newPeer(w, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), rwStub, nil) - w.peers[peerW] = struct{}{} - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - params.TTL = 1 - - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params, time.Now()) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - err = w.SendP2PDirect(peerW.ID(), env, env, env) - if err != nil { - t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) - } - if len(rwStub.messages) != 1 { - t.Fatalf("invalid number of messages sent to peer: %d, expected 1", len(rwStub.messages)) - } - var envelopes []*Envelope - if err := rwStub.messages[0].Decode(&envelopes); err != nil { - t.Fatalf("failed to decode envelopes: %s", err) - } - if len(envelopes) != 3 { - t.Fatalf("invalid number of envelopes in a message: %d, expected 3", len(envelopes)) - } - rwStub.messages = nil - envelopes = nil -} - +// TODO: Fix this to use protcol instead of stubbing func TestHandleP2PMessageCode(t *testing.T) { InitSingleTest() - w := New(nil, nil) - w.SetMinimumPoW(0.0000001, false) // nolint: errcheck - defer w.SetMinimumPoW(DefaultMinimumPoW, false) // nolint: errcheck - w.Start(nil) // nolint: errcheck - defer w.Stop() // nolint: errcheck + w1 := New(nil, nil) + w1.SetMinimumPoW(0.0000001, false) // nolint: errcheck + w1.Start(nil) // nolint: errcheck - envelopeEvents := make(chan EnvelopeEvent, 10) - sub := w.SubscribeEnvelopeEvents(envelopeEvents) + defer w1.Stop() // nolint: errcheck + + w2 := New(nil, nil) + w2.SetMinimumPoW(0.0000001, false) // nolint: errcheck + w2.Start(nil) // nolint: errcheck + defer w2.Stop() // nolint: errcheck + + envelopeEvents := make(chan common.EnvelopeEvent, 10) + sub := w1.SubscribeEnvelopeEvents(envelopeEvents) defer sub.Unsubscribe() params, err := generateMessageParams() @@ -1109,7 +1081,7 @@ func TestHandleP2PMessageCode(t *testing.T) { } params.TTL = 1 - msg, err := NewSentMessage(params) + msg, err := common.NewSentMessage(params) if err != nil { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } @@ -1118,60 +1090,37 @@ func TestHandleP2PMessageCode(t *testing.T) { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } - // read a single envelope - rwStub := &rwP2PMessagesStub{} - rwStub.payload = []interface{}{[]*Envelope{env}} + rw1, rw2 := p2p.MsgPipe() - peer := newPeer(nil, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), nil, nil) - peer.trusted = true + errorc := make(chan error, 1) + go func() { + err := w1.HandlePeer(p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), rw1) + errorc <- err + }() + go func() { + select { + case err := <-errorc: + t.Log(err) + case <-time.After(time.Second * 5): + rw1.Close() + rw2.Close() + } + }() + + peer1 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), rw2, nil) + peer1.SetPeerTrusted(true) + + err = peer1.Start() + require.NoError(t, err, "failed run message loop") + + // Simulate receiving the new envelope + _, err = w2.add(env, true) + require.NoError(t, err) - err = w.runMessageLoop(peer, rwStub) - if err != nil && err != errRWStub { - t.Fatalf("failed run message loop: %s", err) - } if e := <-envelopeEvents; e.Hash != env.Hash() { t.Fatalf("received envelope %s while expected %s", e.Hash, env.Hash()) } - - // read a batch of envelopes - rwStub = &rwP2PMessagesStub{} - rwStub.payload = []interface{}{[]*Envelope{env, env, env}} - - err = w.runMessageLoop(peer, rwStub) - if err != nil && err != errRWStub { - t.Fatalf("failed run message loop: %s", err) - } - for i := 0; i < 3; i++ { - if e := <-envelopeEvents; e.Hash != env.Hash() { - t.Fatalf("received envelope %s while expected %s", e.Hash, env.Hash()) - } - } -} - -var errRWStub = errors.New("no more messages") - -type rwP2PMessagesStub struct { - // payload stores individual messages that will be sent returned - // on ReadMsg() class - payload []interface{} - messages []p2p.Msg -} - -func (stub *rwP2PMessagesStub) ReadMsg() (p2p.Msg, error) { - if len(stub.payload) == 0 { - return p2p.Msg{}, errRWStub - } - size, r, err := rlp.EncodeToReader(stub.payload[0]) - if err != nil { - return p2p.Msg{}, err - } - stub.payload = stub.payload[1:] - return p2p.Msg{Code: p2pMessageCode, Size: uint32(size), Payload: r}, nil -} - -func (stub *rwP2PMessagesStub) WriteMsg(m p2p.Msg) error { - stub.messages = append(stub.messages, m) - return nil + peer1.Stop() } func testConfirmationsHandshake(t *testing.T, expectConfirmations bool) { @@ -1195,10 +1144,10 @@ func testConfirmationsHandshake(t *testing.T, expectConfirmations bool) { t, p2p.ExpectMsg( rw1, - statusCode, + v0.StatusCode, []interface{}{ - ProtocolVersion, - w.toStatusOptions(), + v0.Version, + v0.StatusOptionsFromHost(w), }, ), ) @@ -1243,10 +1192,10 @@ func TestConfirmationReceived(t *testing.T) { t, p2p.ExpectMsg( rw1, - statusCode, + v0.StatusCode, []interface{}{ - ProtocolVersion, - w.toStatusOptions(), + v0.Version, + v0.StatusOptionsFromHost(w), }, ), ) @@ -1254,9 +1203,9 @@ func TestConfirmationReceived(t *testing.T) { t, p2p.SendItems( rw1, - statusCode, - ProtocolVersion, - statusOptions{ + v0.StatusCode, + v0.Version, + v0.StatusOptions{ PoWRequirement: &pow, BloomFilter: w.BloomFilter(), ConfirmationsEnabled: &confirmationsEnabled, @@ -1265,19 +1214,19 @@ func TestConfirmationReceived(t *testing.T) { ), ) - e := Envelope{ + e := common.Envelope{ Expiry: uint32(time.Now().Add(10 * time.Second).Unix()), TTL: 10, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: make([]byte, 1<<10), Nonce: 1, } - data, err := rlp.EncodeToBytes([]*Envelope{&e}) + data, err := rlp.EncodeToBytes([]*common.Envelope{&e}) require.NoError(t, err) hash := crypto.Keccak256Hash(data) - require.NoError(t, p2p.SendItems(rw1, messagesCode, &e)) - require.NoError(t, p2p.ExpectMsg(rw1, messageResponseCode, nil)) - require.NoError(t, p2p.ExpectMsg(rw1, batchAcknowledgedCode, hash)) + require.NoError(t, p2p.SendItems(rw1, v0.MessagesCode, &e)) + require.NoError(t, p2p.ExpectMsg(rw1, v0.MessageResponseCode, nil)) + require.NoError(t, p2p.ExpectMsg(rw1, v0.BatchAcknowledgedCode, hash)) } func TestMessagesResponseWithError(t *testing.T) { @@ -1306,10 +1255,10 @@ func TestMessagesResponseWithError(t *testing.T) { t, p2p.ExpectMsg( rw1, - statusCode, + v0.StatusCode, []interface{}{ - ProtocolVersion, - w.toStatusOptions(), + v0.Version, + v0.StatusOptionsFromHost(w), }, ), ) @@ -1317,9 +1266,9 @@ func TestMessagesResponseWithError(t *testing.T) { t, p2p.SendItems( rw1, - statusCode, - ProtocolVersion, - statusOptions{ + v0.StatusCode, + v0.Version, + v0.StatusOptions{ PoWRequirement: &pow, BloomFilter: w.BloomFilter(), ConfirmationsEnabled: &confirmationsEnabled, @@ -1328,39 +1277,39 @@ func TestMessagesResponseWithError(t *testing.T) { ), ) - failed := Envelope{ + failed := common.Envelope{ Expiry: uint32(time.Now().Add(time.Hour).Unix()), TTL: 10, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: make([]byte, 1<<10), Nonce: 1, } - normal := Envelope{ + normal := common.Envelope{ Expiry: uint32(time.Now().Unix()), TTL: 10, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: make([]byte, 1<<10), Nonce: 1, } - data, err := rlp.EncodeToBytes([]*Envelope{&failed, &normal}) + data, err := rlp.EncodeToBytes([]*common.Envelope{&failed, &normal}) require.NoError(t, err) hash := crypto.Keccak256Hash(data) - require.NoError(t, p2p.SendItems(rw1, messagesCode, &failed, &normal)) - require.NoError(t, p2p.ExpectMsg(rw1, messageResponseCode, NewMessagesResponse(hash, []EnvelopeError{ - {Hash: failed.Hash(), Code: EnvelopeTimeNotSynced, Description: "envelope from future"}, + require.NoError(t, p2p.SendItems(rw1, v0.MessagesCode, &failed, &normal)) + require.NoError(t, p2p.ExpectMsg(rw1, v0.MessageResponseCode, v0.NewMessagesResponse(hash, []common.EnvelopeError{ + {Hash: failed.Hash(), Code: common.EnvelopeTimeNotSynced, Description: "envelope from future"}, }))) - require.NoError(t, p2p.ExpectMsg(rw1, batchAcknowledgedCode, hash)) + require.NoError(t, p2p.ExpectMsg(rw1, v0.BatchAcknowledgedCode, hash)) } -func testConfirmationEvents(t *testing.T, envelope Envelope, envelopeErrors []EnvelopeError) { +func testConfirmationEvents(t *testing.T, envelope common.Envelope, envelopeErrors []common.EnvelopeError) { conf := &Config{ MinimumAcceptedPoW: 0, MaxMessageSize: 10 << 20, EnableConfirmations: true, } w := New(conf, nil) - events := make(chan EnvelopeEvent, 2) + events := make(chan common.EnvelopeEvent, 2) sub := w.SubscribeEnvelopeEvents(events) defer sub.Unsubscribe() @@ -1381,17 +1330,17 @@ func testConfirmationEvents(t *testing.T, envelope Envelope, envelopeErrors []En require.NoError(t, p2p.ExpectMsg( rw1, - statusCode, + v0.StatusCode, []interface{}{ - ProtocolVersion, - w.toStatusOptions(), + v0.Version, + v0.StatusOptionsFromHost(w), }, )) require.NoError(t, p2p.SendItems( rw1, - statusCode, - ProtocolVersion, - statusOptions{ + v0.StatusCode, + v0.Version, + v0.StatusOptions{ PoWRequirement: &pow, BloomFilter: w.BloomFilter(), ConfirmationsEnabled: &confirmationsEnabled, @@ -1399,23 +1348,23 @@ func testConfirmationEvents(t *testing.T, envelope Envelope, envelopeErrors []En }, )) require.NoError(t, w.Send(&envelope)) - require.NoError(t, p2p.ExpectMsg(rw1, messagesCode, []*Envelope{&envelope})) + require.NoError(t, p2p.ExpectMsg(rw1, v0.MessagesCode, []*common.Envelope{&envelope})) - var hash common.Hash + var hash gethcommon.Hash select { case ev := <-events: - require.Equal(t, EventEnvelopeSent, ev.Event) + require.Equal(t, common.EventEnvelopeSent, ev.Event) require.Equal(t, p.ID(), ev.Peer) - require.NotEqual(t, common.Hash{}, ev.Batch) + require.NotEqual(t, gethcommon.Hash{}, ev.Batch) hash = ev.Batch case <-time.After(5 * time.Second): require.FailNow(t, "timed out waiting for an envelope.sent event") } - require.NoError(t, p2p.Send(rw1, messageResponseCode, NewMessagesResponse(hash, envelopeErrors))) - require.NoError(t, p2p.Send(rw1, batchAcknowledgedCode, hash)) + require.NoError(t, p2p.Send(rw1, v0.MessageResponseCode, v0.NewMessagesResponse(hash, envelopeErrors))) + require.NoError(t, p2p.Send(rw1, v0.BatchAcknowledgedCode, hash)) select { case ev := <-events: - require.Equal(t, EventBatchAcknowledged, ev.Event) + require.Equal(t, common.EventBatchAcknowledged, ev.Event) require.Equal(t, p.ID(), ev.Peer) require.Equal(t, hash, ev.Batch) require.Equal(t, envelopeErrors, ev.Data) @@ -1425,28 +1374,28 @@ func testConfirmationEvents(t *testing.T, envelope Envelope, envelopeErrors []En } func TestConfirmationEventsReceived(t *testing.T) { - e := Envelope{ + e := common.Envelope{ Expiry: uint32(time.Now().Add(10 * time.Second).Unix()), TTL: 10, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: make([]byte, 1<<10), Nonce: 1, } - testConfirmationEvents(t, e, []EnvelopeError{}) + testConfirmationEvents(t, e, []common.EnvelopeError{}) } func TestConfirmationEventsExtendedWithErrors(t *testing.T) { - e := Envelope{ + e := common.Envelope{ Expiry: uint32(time.Now().Unix()), TTL: 10, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: make([]byte, 1<<10), Nonce: 1, } - testConfirmationEvents(t, e, []EnvelopeError{ + testConfirmationEvents(t, e, []common.EnvelopeError{ { Hash: e.Hash(), - Code: EnvelopeTimeNotSynced, + Code: common.EnvelopeTimeNotSynced, Description: "test error", }}, ) @@ -1458,7 +1407,7 @@ func TestEventsWithoutConfirmation(t *testing.T) { MaxMessageSize: 10 << 20, } w := New(conf, nil) - events := make(chan EnvelopeEvent, 2) + events := make(chan common.EnvelopeEvent, 2) sub := w.SubscribeEnvelopeEvents(events) defer sub.Unsubscribe() @@ -1480,10 +1429,10 @@ func TestEventsWithoutConfirmation(t *testing.T) { t, p2p.ExpectMsg( rw1, - statusCode, + v0.StatusCode, []interface{}{ - ProtocolVersion, - w.toStatusOptions(), + v0.Version, + v0.StatusOptionsFromHost(w), }, ), ) @@ -1491,9 +1440,9 @@ func TestEventsWithoutConfirmation(t *testing.T) { t, p2p.SendItems( rw1, - statusCode, - ProtocolVersion, - statusOptions{ + v0.StatusCode, + v0.Version, + v0.StatusOptions{ PoWRequirement: &pow, BloomFilter: w.BloomFilter(), LightNodeEnabled: &lightNodeEnabled, @@ -1501,21 +1450,21 @@ func TestEventsWithoutConfirmation(t *testing.T) { ), ) - e := Envelope{ + e := common.Envelope{ Expiry: uint32(time.Now().Add(10 * time.Second).Unix()), TTL: 10, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: make([]byte, 1<<10), Nonce: 1, } require.NoError(t, w.Send(&e)) - require.NoError(t, p2p.ExpectMsg(rw1, messagesCode, []*Envelope{&e})) + require.NoError(t, p2p.ExpectMsg(rw1, v0.MessagesCode, []*common.Envelope{&e})) select { case ev := <-events: - require.Equal(t, EventEnvelopeSent, ev.Event) + require.Equal(t, common.EventEnvelopeSent, ev.Event) require.Equal(t, p.ID(), ev.Peer) - require.Equal(t, common.Hash{}, ev.Batch) + require.Equal(t, gethcommon.Hash{}, ev.Batch) case <-time.After(5 * time.Second): require.FailNow(t, "timed out waiting for an envelope.sent event") } @@ -1537,7 +1486,7 @@ func discardPipe() *p2p.MsgPipeRW { func TestWakuTimeDesyncEnvelopeIgnored(t *testing.T) { c := &Config{ - MaxMessageSize: DefaultMaxMessageSize, + MaxMessageSize: common.DefaultMaxMessageSize, MinimumAcceptedPoW: 0, } rw1, rw2 := p2p.MsgPipe() @@ -1558,10 +1507,10 @@ func TestWakuTimeDesyncEnvelopeIgnored(t *testing.T) { w1.SetTimeSource(func() time.Time { return time.Now().Add(time.Hour) }) - env := &Envelope{ + env := &common.Envelope{ Expiry: uint32(time.Now().Add(time.Hour).Unix()), TTL: 30, - Topic: TopicType{1}, + Topic: common.TopicType{1}, Data: []byte{1, 1, 1}, } require.NoError(t, w1.Send(env)) @@ -1584,13 +1533,13 @@ func TestRequestSentEventWithExpiry(t *testing.T) { p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) rw := discardPipe() defer rw.Close() - w.peers[newPeer(w, p, rw, nil)] = struct{}{} - events := make(chan EnvelopeEvent, 1) + w.peers[v0.NewPeer(w, p, rw, nil)] = struct{}{} + events := make(chan common.EnvelopeEvent, 1) sub := w.SubscribeEnvelopeEvents(events) defer sub.Unsubscribe() - e := &Envelope{Nonce: 1} + e := &common.Envelope{Nonce: 1} require.NoError(t, w.RequestHistoricMessagesWithTimeout(p.ID().Bytes(), e, time.Millisecond)) - verifyEvent := func(etype EventType) { + verifyEvent := func(etype common.EventType) { select { case <-time.After(time.Second): require.FailNow(t, "error waiting for a event type %s", etype) @@ -1600,12 +1549,12 @@ func TestRequestSentEventWithExpiry(t *testing.T) { require.Equal(t, e.Hash(), ev.Hash) } } - verifyEvent(EventMailServerRequestSent) - verifyEvent(EventMailServerRequestExpired) + verifyEvent(common.EventMailServerRequestSent) + verifyEvent(common.EventMailServerRequestExpired) } func TestSendMessagesRequest(t *testing.T) { - validMessagesRequest := MessagesRequest{ + validMessagesRequest := common.MessagesRequest{ ID: make([]byte, 32), From: 0, To: 10, @@ -1614,7 +1563,7 @@ func TestSendMessagesRequest(t *testing.T) { t.Run("InvalidID", func(t *testing.T) { w := New(nil, nil) - err := w.SendMessagesRequest([]byte{0x01, 0x02}, MessagesRequest{}) + err := w.SendMessagesRequest([]byte{0x01, 0x02}, common.MessagesRequest{}) require.EqualError(t, err, "invalid 'ID', expected a 32-byte slice") }) @@ -1628,14 +1577,14 @@ func TestSendMessagesRequest(t *testing.T) { p := p2p.NewPeer(enode.ID{0x01}, "peer01", nil) rw1, rw2 := p2p.MsgPipe() w := New(nil, nil) - w.peers[newPeer(w, p, rw1, nil)] = struct{}{} + w.peers[v0.NewPeer(w, p, rw1, nil)] = struct{}{} go func() { err := w.SendMessagesRequest(p.ID().Bytes(), validMessagesRequest) require.NoError(t, err) }() - require.NoError(t, p2p.ExpectMsg(rw2, p2pRequestCode, nil)) + require.NoError(t, p2p.ExpectMsg(rw2, v0.P2PRequestCode, nil)) }) } @@ -1645,7 +1594,7 @@ func TestRateLimiterIntegration(t *testing.T) { MaxMessageSize: 10 << 20, } w := New(conf, nil) - w.RegisterRateLimiter(NewPeerRateLimiter(nil, &MetricsRateLimiterHandler{})) + w.RegisterRateLimiter(common.NewPeerRateLimiter(nil, &common.MetricsRateLimiterHandler{})) p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"waku", 0}}) rw1, rw2 := p2p.MsgPipe() defer func() { @@ -1663,10 +1612,10 @@ func TestRateLimiterIntegration(t *testing.T) { t, p2p.ExpectMsg( rw1, - statusCode, + v0.StatusCode, []interface{}{ - ProtocolVersion, - w.toStatusOptions(), + v0.Version, + v0.StatusOptionsFromHost(w), }, ), ) @@ -1678,26 +1627,38 @@ func TestRateLimiterIntegration(t *testing.T) { } func TestMailserverCompletionEvent(t *testing.T) { - w := New(nil, nil) - require.NoError(t, w.Start(nil)) - defer w.Stop() // nolint: errcheck + w1 := New(nil, nil) + require.NoError(t, w1.Start(nil)) + defer w1.Stop() // nolint: errcheck rw1, rw2 := p2p.MsgPipe() - peer := newPeer(w, p2p.NewPeer(enode.ID{1}, "1", nil), rw1, nil) - peer.trusted = true - w.peers[peer] = struct{}{} + peer1 := v0.NewPeer(w1, p2p.NewPeer(enode.ID{1}, "1", nil), rw1, nil) + peer1.SetPeerTrusted(true) + w1.peers[peer1] = struct{}{} - events := make(chan EnvelopeEvent) - sub := w.SubscribeEnvelopeEvents(events) + w2 := New(nil, nil) + require.NoError(t, w2.Start(nil)) + defer w2.Stop() // nolint: errcheck + + peer2 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{1}, "1", nil), rw2, nil) + peer2.SetPeerTrusted(true) + w2.peers[peer2] = struct{}{} + + events := make(chan common.EnvelopeEvent) + sub := w1.SubscribeEnvelopeEvents(events) defer sub.Unsubscribe() - envelopes := []*Envelope{{Data: []byte{1}}, {Data: []byte{2}}} + envelopes := []*common.Envelope{{Data: []byte{1}}, {Data: []byte{2}}} go func() { - require.NoError(t, p2p.Send(rw2, p2pMessageCode, envelopes)) - require.NoError(t, p2p.Send(rw2, p2pRequestCompleteCode, [100]byte{})) // 2 hashes + cursor size + + require.NoError(t, peer2.Start()) + require.NoError(t, p2p.Send(rw2, v0.P2PMessageCode, envelopes)) + require.NoError(t, p2p.Send(rw2, v0.P2PRequestCompleteCode, [100]byte{})) // 2 hashes + cursor size rw2.Close() }() - require.EqualError(t, w.runMessageLoop(peer, rw1), "p2p: read or write on closed message pipe") + + require.NoError(t, peer1.Start(), "p2p: read or write on closed message pipe") + require.EqualError(t, peer1.Run(), "p2p: read or write on closed message pipe") after := time.After(2 * time.Second) count := 0 @@ -1707,9 +1668,9 @@ func TestMailserverCompletionEvent(t *testing.T) { require.FailNow(t, "timed out waiting for all events") case ev := <-events: switch ev.Event { - case EventEnvelopeAvailable: + case common.EventEnvelopeAvailable: count++ - case EventMailServerRequestCompleted: + case common.EventMailServerRequestCompleted: require.Equal(t, count, len(envelopes), "all envelope.avaiable events mut be recevied before request is compelted") return @@ -1717,3 +1678,64 @@ func TestMailserverCompletionEvent(t *testing.T) { } } } + +func generateFilter(t *testing.T, symmetric bool) (*common.Filter, error) { + var f common.Filter + f.Messages = common.NewMemoryMessageStore() + + const topicNum = 8 + f.Topics = make([][]byte, topicNum) + for i := 0; i < topicNum; i++ { + f.Topics[i] = make([]byte, 4) + mrand.Read(f.Topics[i]) // nolint: gosec + f.Topics[i][0] = 0x01 + } + + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("generateFilter 1 failed with seed %d.", seed) + return nil, err + } + f.Src = &key.PublicKey + + if symmetric { + f.KeySym = make([]byte, common.AESKeyLength) + mrand.Read(f.KeySym) // nolint: gosec + f.SymKeyHash = crypto.Keccak256Hash(f.KeySym) + } else { + f.KeyAsym, err = crypto.GenerateKey() + if err != nil { + t.Fatalf("generateFilter 2 failed with seed %d.", seed) + return nil, err + } + } + + // AcceptP2P & PoW are not set + return &f, nil +} + +func generateMessageParams() (*common.MessageParams, error) { + // set all the parameters except p.Dst and p.Padding + + buf := make([]byte, 4) + mrand.Read(buf) // nolint: gosec + sz := mrand.Intn(400) + + var p common.MessageParams + p.PoW = 0.01 + p.WorkTime = 1 + p.TTL = uint32(mrand.Intn(1024)) + p.Payload = make([]byte, sz) + p.KeySym = make([]byte, common.AESKeyLength) + mrand.Read(p.Payload) // nolint: gosec + mrand.Read(p.KeySym) // nolint: gosec + p.Topic = common.BytesToTopic(buf) + + var err error + p.Src, err = crypto.GenerateKey() + if err != nil { + return nil, err + } + + return &p, nil +}