Remove whisper

This commit is contained in:
Andrea Maria Piana 2021-05-13 18:05:34 +02:00
parent be01875d1d
commit 57b1bc193f
50 changed files with 11 additions and 10633 deletions

View file

@ -523,7 +523,6 @@ func (b *GethStatusBackend) loadNodeConfig() (*params.NodeConfig, error) {
}
conf.WakuConfig.Enabled = true
conf.WhisperConfig.Enabled = false
// NodeConfig.Version should be taken from params.Version
// which is set at the compile time.

View file

@ -1,102 +0,0 @@
package bridge
import (
"sync"
"unsafe"
"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"
)
type Bridge struct {
whisper *whisper.Whisper
waku *waku.Waku
logger *zap.Logger
cancel chan struct{}
wg sync.WaitGroup
whisperIn chan *whisper.Envelope
whisperOut chan *whisper.Envelope
wakuIn chan *wakucommon.Envelope
wakuOut chan *wakucommon.Envelope
}
func New(shh *whisper.Whisper, w *waku.Waku, logger *zap.Logger) *Bridge {
return &Bridge{
whisper: shh,
waku: w,
logger: logger,
whisperOut: make(chan *whisper.Envelope),
whisperIn: make(chan *whisper.Envelope),
wakuIn: make(chan *wakucommon.Envelope),
wakuOut: make(chan *wakucommon.Envelope),
}
}
type bridgeWhisper struct {
*Bridge
}
func (b *bridgeWhisper) Pipe() (<-chan *whisper.Envelope, chan<- *whisper.Envelope) {
return b.whisperOut, b.whisperIn
}
type bridgeWaku struct {
*Bridge
}
func (b *bridgeWaku) Pipe() (<-chan *wakucommon.Envelope, chan<- *wakucommon.Envelope) {
return b.wakuOut, b.wakuIn
}
func (b *Bridge) Start() {
b.cancel = make(chan struct{})
b.waku.RegisterBridge(&bridgeWaku{Bridge: b})
b.whisper.RegisterBridge(&bridgeWhisper{Bridge: b})
b.wg.Add(1)
go func() {
defer b.wg.Done()
for {
select {
case <-b.cancel:
return
case env := <-b.wakuIn:
shhEnvelope := (*whisper.Envelope)(unsafe.Pointer(env)) // nolint: gosec
b.logger.Debug(
"received whisper envelope from waku",
zap.ByteString("hash", shhEnvelope.Hash().Bytes()),
)
b.whisperOut <- shhEnvelope
}
}
}()
b.wg.Add(1)
go func() {
defer b.wg.Done()
for {
select {
case <-b.cancel:
return
case env := <-b.whisperIn:
wakuEnvelope := (*wakucommon.Envelope)(unsafe.Pointer(env)) // nolint: gosec
b.logger.Debug(
"received waku envelope from whisper",
zap.ByteString("hash", wakuEnvelope.Hash().Bytes()),
)
b.wakuOut <- wakuEnvelope
}
}
}()
}
func (b *Bridge) Cancel() {
close(b.cancel)
b.wg.Wait()
}

View file

@ -1,189 +0,0 @@
package bridge
import (
"math"
"testing"
"time"
"unsafe"
"go.uber.org/zap"
"github.com/stretchr/testify/require"
"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"
)
func TestEnvelopesBeingIdentical(t *testing.T) {
// whisper.Envelope --> wakucommon.Envelope
whisperEnvelope, err := createWhisperEnvelope()
require.NoError(t, err)
wakuEnvelope := (*wakucommon.Envelope)(unsafe.Pointer(whisperEnvelope)) // nolint: gosec
require.Equal(t, whisperEnvelope.Hash(), wakuEnvelope.Hash())
// wakucommon.Envelope --> whisper.Envelope
wakuEnvelope, err = createWakuEnvelope()
require.NoError(t, err)
whisperEnvelope = (*whisper.Envelope)(unsafe.Pointer(wakuEnvelope)) // nolint: gosec
require.Equal(t, wakuEnvelope.Hash(), whisperEnvelope.Hash())
}
func TestBridgeWhisperToWaku(t *testing.T) {
shh := whisper.New(nil)
shh.SetTimeSource(time.Now)
wak := waku.New(nil, nil)
wak.SetTimeSource(time.Now)
b := New(shh, wak, zap.NewNop())
b.Start()
defer b.Cancel()
server1 := createServer()
err := shh.Start(server1)
require.NoError(t, err)
server2 := createServer()
err = wak.Start(server2)
require.NoError(t, err)
// Subscribe for envelope events in Waku.
eventsWaku := make(chan wakucommon.EnvelopeEvent, 10)
sub1 := wak.SubscribeEnvelopeEvents(eventsWaku)
defer sub1.Unsubscribe()
// Subscribe for envelope events in Whisper.
eventsWhsiper := make(chan whisper.EnvelopeEvent, 10)
sub2 := shh.SubscribeEnvelopeEvents(eventsWhsiper)
defer sub2.Unsubscribe()
// Send message to Whisper and receive in Waku.
envelope, err := createWhisperEnvelope()
require.NoError(t, err)
err = shh.Send(envelope)
require.NoError(t, err)
<-eventsWhsiper // skip event resulting from calling Send()
// Verify that the message was received by waku.
select {
case err := <-sub1.Err():
require.NoError(t, err)
case event := <-eventsWaku:
require.Equal(t, envelope.Hash(), event.Hash)
case <-time.After(time.Second):
t.Fatal("timed out")
}
// Verify that the message was NOT received by whisper.
select {
case err := <-sub1.Err():
require.NoError(t, err)
case event := <-eventsWhsiper:
t.Fatalf("unexpected event: %v", event)
case <-time.After(time.Second):
// expect to time out; TODO: replace with a bridge event which should not be sent by Waku
}
}
func TestBridgeWakuToWhisper(t *testing.T) {
shh := whisper.New(nil)
shh.SetTimeSource(time.Now)
wak := waku.New(nil, nil)
wak.SetTimeSource(time.Now)
b := New(shh, wak, zap.NewNop())
b.Start()
defer b.Cancel()
server1 := createServer()
err := shh.Start(server1)
require.NoError(t, err)
server2 := createServer()
err = wak.Start(server2)
require.NoError(t, err)
// Subscribe for envelope events in Whisper.
eventsWhisper := make(chan whisper.EnvelopeEvent, 10)
sub1 := shh.SubscribeEnvelopeEvents(eventsWhisper)
defer sub1.Unsubscribe()
// Subscribe for envelope events in Waku.
eventsWaku := make(chan wakucommon.EnvelopeEvent, 10)
sub2 := wak.SubscribeEnvelopeEvents(eventsWaku)
defer sub2.Unsubscribe()
// Send message to Waku and receive in Whisper.
envelope, err := createWakuEnvelope()
require.NoError(t, err)
err = wak.Send(envelope)
require.NoError(t, err)
<-eventsWaku // skip event resulting from calling Send()
// Verify that the message was received by Whisper.
select {
case err := <-sub1.Err():
require.NoError(t, err)
case event := <-eventsWhisper:
require.Equal(t, envelope.Hash(), event.Hash)
case <-time.After(time.Second):
t.Fatal("timed out")
}
// Verify that the message was NOT received by Waku.
select {
case err := <-sub1.Err():
require.NoError(t, err)
case event := <-eventsWaku:
t.Fatalf("unexpected event: %v", event)
case <-time.After(time.Second):
// expect to time out; TODO: replace with a bridge event which should not be sent by Waku
}
}
func createServer() *p2p.Server {
return &p2p.Server{
Config: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
},
}
}
func createWhisperEnvelope() (*whisper.Envelope, error) {
messageParams := &whisper.MessageParams{
TTL: 120,
KeySym: []byte{0xaa, 0xbb, 0xcc},
Topic: whisper.BytesToTopic([]byte{0x01}),
WorkTime: 10,
PoW: 2.0,
Payload: []byte("hello!"),
}
sentMessage, err := whisper.NewSentMessage(messageParams)
if err != nil {
return nil, err
}
envelope := whisper.NewEnvelope(120, whisper.BytesToTopic([]byte{0x01}), sentMessage, time.Now())
if err := envelope.Seal(messageParams); err != nil {
return nil, err
}
return envelope, nil
}
func createWakuEnvelope() (*wakucommon.Envelope, error) {
messageParams := &wakucommon.MessageParams{
TTL: 120,
KeySym: []byte{0xaa, 0xbb, 0xcc},
Topic: wakucommon.BytesToTopic([]byte{0x01}),
WorkTime: 10,
PoW: 2.0,
Payload: []byte("hello!"),
}
sentMessage, err := wakucommon.NewSentMessage(messageParams)
if err != nil {
return nil, err
}
envelope := wakucommon.NewEnvelope(120, wakucommon.BytesToTopic([]byte{0x01}), sentMessage, time.Now())
if err := envelope.Seal(messageParams); err != nil {
return nil, err
}
return envelope, nil
}

View file

@ -1,6 +0,0 @@
// Bridge bridges Whisper and Waku subprotocols.
// This is possible because both use the same envelope format.
// What's more, both envelope formats are identical structs,
// that is having the same ordered fields.
package bridge

View file

@ -6,58 +6,8 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/eth-node/types"
waku "github.com/status-im/status-go/waku/common"
"github.com/status-im/status-go/whisper"
)
type whisperEnvelope struct {
env *whisper.Envelope
}
// NewWhisperEnvelope returns an object that wraps Geth's Whisper Envelope in a types interface.
func NewWhisperEnvelope(e *whisper.Envelope) types.Envelope {
return &whisperEnvelope{env: e}
}
func (w *whisperEnvelope) Unwrap() interface{} {
return w.env
}
func (w *whisperEnvelope) Hash() types.Hash {
return types.Hash(w.env.Hash())
}
func (w *whisperEnvelope) Bloom() []byte {
return w.env.Bloom()
}
func (w *whisperEnvelope) PoW() float64 {
return w.env.PoW()
}
func (w *whisperEnvelope) Expiry() uint32 {
return w.env.Expiry
}
func (w *whisperEnvelope) TTL() uint32 {
return w.env.TTL
}
func (w *whisperEnvelope) Topic() types.TopicType {
return types.TopicType(w.env.Topic)
}
func (w *whisperEnvelope) Size() int {
return len(w.env.Data)
}
func (w *whisperEnvelope) DecodeRLP(s *rlp.Stream) error {
return w.env.DecodeRLP(s)
}
func (w *whisperEnvelope) EncodeRLP(writer io.Writer) error {
return rlp.Encode(writer, w.env)
}
type wakuEnvelope struct {
env *waku.Envelope
}

View file

@ -3,22 +3,8 @@ package gethbridge
import (
"github.com/status-im/status-go/eth-node/types"
waku "github.com/status-im/status-go/waku/common"
"github.com/status-im/status-go/whisper"
)
// NewWhisperEnvelopeErrorWrapper returns a types.EnvelopeError object that mimics Geth's EnvelopeError
func NewWhisperEnvelopeErrorWrapper(envelopeError *whisper.EnvelopeError) *types.EnvelopeError {
if envelopeError == nil {
panic("envelopeError should not be nil")
}
return &types.EnvelopeError{
Hash: types.Hash(envelopeError.Hash),
Code: mapGethErrorCode(envelopeError.Code),
Description: envelopeError.Description,
}
}
// NewWakuEnvelopeErrorWrapper returns a types.EnvelopeError object that mimics Geth's EnvelopeError
func NewWakuEnvelopeErrorWrapper(envelopeError *waku.EnvelopeError) *types.EnvelopeError {
if envelopeError == nil {
@ -34,10 +20,8 @@ func NewWakuEnvelopeErrorWrapper(envelopeError *waku.EnvelopeError) *types.Envel
func mapGethErrorCode(code uint) uint {
switch code {
case whisper.EnvelopeTimeNotSynced:
case waku.EnvelopeTimeNotSynced:
return types.EnvelopeTimeNotSynced
case whisper.EnvelopeOtherError:
case waku.EnvelopeOtherError:
return types.EnvelopeOtherError
}

View file

@ -4,36 +4,8 @@ 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"
)
// NewWhisperEnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent
func NewWhisperEnvelopeEventWrapper(envelopeEvent *whisper.EnvelopeEvent) *types.EnvelopeEvent {
if envelopeEvent == nil {
panic("envelopeEvent should not be nil")
}
wrappedData := envelopeEvent.Data
switch data := envelopeEvent.Data.(type) {
case []whisper.EnvelopeError:
wrappedData := make([]types.EnvelopeError, len(data))
for index := range data {
wrappedData[index] = *NewWhisperEnvelopeErrorWrapper(&data[index])
}
case *whisper.MailServerResponse:
wrappedData = NewWhisperMailServerResponseWrapper(data)
case whisper.SyncEventResponse:
wrappedData = NewGethSyncEventResponseWrapper(data)
}
return &types.EnvelopeEvent{
Event: types.EventType(envelopeEvent.Event),
Hash: types.Hash(envelopeEvent.Hash),
Batch: types.Hash(envelopeEvent.Batch),
Peer: types.EnodeID(envelopeEvent.Peer),
Data: wrappedData,
}
}
// NewWakuEnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent
func NewWakuEnvelopeEventWrapper(envelopeEvent *wakucommon.EnvelopeEvent) *types.EnvelopeEvent {
if envelopeEvent == nil {

View file

@ -3,22 +3,8 @@ package gethbridge
import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/waku"
"github.com/status-im/status-go/whisper"
)
// NewWhisperMailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse
func NewWhisperMailServerResponseWrapper(mailServerResponse *whisper.MailServerResponse) *types.MailServerResponse {
if mailServerResponse == nil {
panic("mailServerResponse should not be nil")
}
return &types.MailServerResponse{
LastEnvelopeHash: types.Hash(mailServerResponse.LastEnvelopeHash),
Cursor: mailServerResponse.Cursor,
Error: mailServerResponse.Error,
}
}
// NewWakuMailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse
func NewWakuMailServerResponseWrapper(mailServerResponse *waku.MailServerResponse) *types.MailServerResponse {
if mailServerResponse == nil {

View file

@ -13,7 +13,6 @@ import (
gethens "github.com/status-im/status-go/eth-node/bridge/geth/ens"
"github.com/status-im/status-go/eth-node/types"
enstypes "github.com/status-im/status-go/eth-node/types/ens"
"github.com/status-im/status-go/whisper"
)
type gethNodeWrapper struct {
@ -32,29 +31,6 @@ func (w *gethNodeWrapper) NewENSVerifier(logger *zap.Logger) enstypes.ENSVerifie
return gethens.NewVerifier(logger)
}
func (w *gethNodeWrapper) GetWhisper(ctx interface{}) (types.Whisper, error) {
var nativeWhisper *whisper.Whisper
if ctx == nil || ctx == w {
err := w.stack.Service(&nativeWhisper)
if err != nil {
return nil, err
}
} else {
switch serviceProvider := ctx.(type) {
case *node.ServiceContext:
err := serviceProvider.Service(&nativeWhisper)
if err != nil {
return nil, err
}
}
}
if nativeWhisper == nil {
return nil, errors.New("whisper service is not available")
}
return NewGethWhisperWrapper(nativeWhisper), nil
}
func (w *gethNodeWrapper) GetWaku(ctx interface{}) (types.Waku, error) {
var nativeWaku *waku.Waku
if ctx == nil || ctx == w {

View file

@ -1,103 +0,0 @@
package gethbridge
import (
"context"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/whisper"
)
type gethPublicWhisperAPIWrapper struct {
publicWhisperAPI *whisper.PublicWhisperAPI
}
// NewGethPublicWhisperAPIWrapper returns an object that wraps Geth's PublicWhisperAPI in a types interface
func NewGethPublicWhisperAPIWrapper(publicWhisperAPI *whisper.PublicWhisperAPI) types.PublicWhisperAPI {
if publicWhisperAPI == nil {
panic("publicWhisperAPI cannot be nil")
}
return &gethPublicWhisperAPIWrapper{
publicWhisperAPI: publicWhisperAPI,
}
}
// AddPrivateKey imports the given private key.
func (w *gethPublicWhisperAPIWrapper) AddPrivateKey(ctx context.Context, privateKey types.HexBytes) (string, error) {
return w.publicWhisperAPI.AddPrivateKey(ctx, hexutil.Bytes(privateKey))
}
// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID.
func (w *gethPublicWhisperAPIWrapper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
return w.publicWhisperAPI.GenerateSymKeyFromPassword(ctx, passwd)
}
// DeleteKeyPair removes the key with the given key if it exists.
func (w *gethPublicWhisperAPIWrapper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
return w.publicWhisperAPI.DeleteKeyPair(ctx, key)
}
// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
func (w *gethPublicWhisperAPIWrapper) NewMessageFilter(req types.Criteria) (string, error) {
topics := make([]whisper.TopicType, len(req.Topics))
for index, tt := range req.Topics {
topics[index] = whisper.TopicType(tt)
}
criteria := whisper.Criteria{
SymKeyID: req.SymKeyID,
PrivateKeyID: req.PrivateKeyID,
Sig: req.Sig,
MinPow: req.MinPow,
Topics: topics,
AllowP2P: req.AllowP2P,
}
return w.publicWhisperAPI.NewMessageFilter(criteria)
}
// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
func (w *gethPublicWhisperAPIWrapper) GetFilterMessages(id string) ([]*types.Message, error) {
msgs, err := w.publicWhisperAPI.GetFilterMessages(id)
if err != nil {
return nil, err
}
wrappedMsgs := make([]*types.Message, len(msgs))
for index, msg := range msgs {
wrappedMsgs[index] = &types.Message{
Sig: msg.Sig,
TTL: msg.TTL,
Timestamp: msg.Timestamp,
Topic: types.TopicType(msg.Topic),
Payload: msg.Payload,
Padding: msg.Padding,
PoW: msg.PoW,
Hash: msg.Hash,
Dst: msg.Dst,
P2P: msg.P2P,
}
}
return wrappedMsgs, nil
}
// Post posts a message on the Whisper network.
// returns the hash of the message in case of success.
func (w *gethPublicWhisperAPIWrapper) Post(ctx context.Context, req types.NewMessage) ([]byte, error) {
msg := whisper.NewMessage{
SymKeyID: req.SymKeyID,
PublicKey: req.PublicKey,
Sig: req.SigID, // Sig is really a SigID
TTL: req.TTL,
Topic: whisper.TopicType(req.Topic),
Payload: req.Payload,
Padding: req.Padding,
PowTime: req.PowTime,
PowTarget: req.PowTarget,
TargetPeer: req.TargetPeer,
}
return w.publicWhisperAPI.Post(ctx, msg)
}

View file

@ -1,14 +0,0 @@
package gethbridge
import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/whisper"
)
// NewGethSyncEventResponseWrapper returns a types.SyncEventResponse object that mimics Geth's SyncEventResponse
func NewGethSyncEventResponseWrapper(syncEventResponse whisper.SyncEventResponse) types.SyncEventResponse {
return types.SyncEventResponse{
Cursor: syncEventResponse.Cursor,
Error: syncEventResponse.Error,
}
}

View file

@ -1,17 +0,0 @@
package gethbridge
import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/whisper"
)
// GetGethSyncMailRequestFrom converts a whisper SyncMailRequest struct from a SyncMailRequest struct
func GetGethSyncMailRequestFrom(r *types.SyncMailRequest) *whisper.SyncMailRequest {
return &whisper.SyncMailRequest{
Lower: r.Lower,
Upper: r.Upper,
Bloom: r.Bloom,
Limit: r.Limit,
Cursor: r.Cursor,
}
}

View file

@ -1,216 +0,0 @@
package gethbridge
import (
"crypto/ecdsa"
"time"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/whisper"
)
type gethWhisperWrapper struct {
whisper *whisper.Whisper
}
// NewGethWhisperWrapper returns an object that wraps Geth's Whisper in a types interface
func NewGethWhisperWrapper(w *whisper.Whisper) types.Whisper {
if w == nil {
panic("w cannot be nil")
}
return &gethWhisperWrapper{
whisper: w,
}
}
// GetGethWhisperFrom retrieves the underlying whisper Whisper struct from a wrapped Whisper interface
func GetGethWhisperFrom(m types.Whisper) *whisper.Whisper {
return m.(*gethWhisperWrapper).whisper
}
func (w *gethWhisperWrapper) PublicWhisperAPI() types.PublicWhisperAPI {
return NewGethPublicWhisperAPIWrapper(whisper.NewPublicWhisperAPI(w.whisper))
}
// MinPow returns the PoW value required by this node.
func (w *gethWhisperWrapper) MinPow() float64 {
return w.whisper.MinPow()
}
// MaxMessageSize returns the MaxMessageSize set
func (w *gethWhisperWrapper) MaxMessageSize() uint32 {
return w.whisper.MaxMessageSize()
}
// BloomFilter returns the aggregated bloom filter for all the topics of interest.
// The nodes are required to send only messages that match the advertised bloom filter.
// If a message does not match the bloom, it will tantamount to spam, and the peer will
// be disconnected.
func (w *gethWhisperWrapper) BloomFilter() []byte {
return w.whisper.BloomFilter()
}
// GetCurrentTime returns current time.
func (w *gethWhisperWrapper) GetCurrentTime() time.Time {
return w.whisper.GetCurrentTime()
}
// SetTimeSource assigns a particular source of time to a whisper object.
func (w *gethWhisperWrapper) SetTimeSource(timesource func() time.Time) {
w.whisper.SetTimeSource(timesource)
}
func (w *gethWhisperWrapper) SubscribeEnvelopeEvents(eventsProxy chan<- types.EnvelopeEvent) types.Subscription {
events := make(chan whisper.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper
go func() {
for e := range events {
eventsProxy <- *NewWhisperEnvelopeEventWrapper(&e)
}
}()
return NewGethSubscriptionWrapper(w.whisper.SubscribeEnvelopeEvents(events))
}
func (w *gethWhisperWrapper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
return w.whisper.GetPrivateKey(id)
}
// AddKeyPair imports a asymmetric private key and returns a deterministic identifier.
func (w *gethWhisperWrapper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
return w.whisper.AddKeyPair(key)
}
// DeleteKeyPair deletes the key with the specified ID if it exists.
func (w *gethWhisperWrapper) DeleteKeyPair(keyID string) bool {
return w.whisper.DeleteKeyPair(keyID)
}
// DeleteKeyPairs removes all cryptographic identities known to the node
func (w *gethWhisperWrapper) DeleteKeyPairs() error {
return w.whisper.DeleteKeyPairs()
}
func (w *gethWhisperWrapper) AddSymKeyDirect(key []byte) (string, error) {
return w.whisper.AddSymKeyDirect(key)
}
func (w *gethWhisperWrapper) AddSymKeyFromPassword(password string) (string, error) {
return w.whisper.AddSymKeyFromPassword(password)
}
func (w *gethWhisperWrapper) DeleteSymKey(id string) bool {
return w.whisper.DeleteSymKey(id)
}
func (w *gethWhisperWrapper) GetSymKey(id string) ([]byte, error) {
return w.whisper.GetSymKey(id)
}
func (w *gethWhisperWrapper) Subscribe(opts *types.SubscriptionOptions) (string, error) {
var (
err error
keyAsym *ecdsa.PrivateKey
keySym []byte
)
if opts.SymKeyID != "" {
keySym, err = w.GetSymKey(opts.SymKeyID)
if err != nil {
return "", err
}
}
if opts.PrivateKeyID != "" {
keyAsym, err = w.GetPrivateKey(opts.PrivateKeyID)
if err != nil {
return "", err
}
}
f, err := w.createFilterWrapper("", keyAsym, keySym, opts.PoW, opts.Topics)
if err != nil {
return "", err
}
id, err := w.whisper.Subscribe(GetWhisperFilterFrom(f))
if err != nil {
return "", err
}
f.(*whisperFilterWrapper).id = id
return id, nil
}
func (w *gethWhisperWrapper) GetFilter(id string) types.Filter {
return NewWhisperFilterWrapper(w.whisper.GetFilter(id), id)
}
func (w *gethWhisperWrapper) Unsubscribe(id string) error {
return w.whisper.Unsubscribe(id)
}
func (w *gethWhisperWrapper) UnsubscribeMany(ids []string) error {
return w.whisper.UnsubscribeMany(ids)
}
func (w *gethWhisperWrapper) createFilterWrapper(id string, keyAsym *ecdsa.PrivateKey, keySym []byte, pow float64, topics [][]byte) (types.Filter, error) {
return NewWhisperFilterWrapper(&whisper.Filter{
KeyAsym: keyAsym,
KeySym: keySym,
PoW: pow,
AllowP2P: true,
Topics: topics,
Messages: whisper.NewMemoryMessageStore(),
}, id), nil
}
func (w *gethWhisperWrapper) SendMessagesRequest(peerID []byte, r types.MessagesRequest) error {
return w.whisper.SendMessagesRequest(peerID, whisper.MessagesRequest{
ID: r.ID,
From: r.From,
To: r.To,
Limit: r.Limit,
Cursor: r.Cursor,
Bloom: r.Bloom,
})
}
// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
// which is known to implement MailServer interface, and is supposed to process this
// request and respond with a number of peer-to-peer messages (possibly expired),
// which are not supposed to be forwarded any further.
// The whisper protocol is agnostic of the format and contents of envelope.
func (w *gethWhisperWrapper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope types.Envelope, timeout time.Duration) error {
return w.whisper.RequestHistoricMessagesWithTimeout(peerID, envelope.Unwrap().(*whisper.Envelope), timeout)
}
// SyncMessages can be sent between two Mail Servers and syncs envelopes between them.
func (w *gethWhisperWrapper) SyncMessages(peerID []byte, req types.SyncMailRequest) error {
return w.whisper.SyncMessages(peerID, *GetGethSyncMailRequestFrom(&req))
}
type whisperFilterWrapper struct {
filter *whisper.Filter
id string
}
// NewWhisperFilterWrapper returns an object that wraps Geth's Filter in a types interface
func NewWhisperFilterWrapper(f *whisper.Filter, id string) types.Filter {
if f.Messages == nil {
panic("Messages should not be nil")
}
return &whisperFilterWrapper{
filter: f,
id: id,
}
}
// GetWhisperFilterFrom retrieves the underlying whisper Filter struct from a wrapped Filter interface
func GetWhisperFilterFrom(f types.Filter) *whisper.Filter {
return f.(*whisperFilterWrapper).filter
}
// ID returns the filter ID
func (w *whisperFilterWrapper) ID() string {
return w.id
}

View file

@ -18,7 +18,6 @@ func (n EnodeID) String() string {
type Node interface {
NewENSVerifier(logger *zap.Logger) enstypes.ENSVerifier
GetWhisper(ctx interface{}) (Whisper, error)
GetWaku(ctx interface{}) (Waku, error)
AddPeer(url string) error
RemovePeer(url string) error

View file

@ -18,11 +18,9 @@ package mailserver
import (
"crypto/ecdsa"
"encoding/binary"
"errors"
"fmt"
"math/rand"
"reflect"
"sync"
"time"
@ -37,7 +35,6 @@ import (
"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"
)
const (
@ -77,253 +74,6 @@ type Config struct {
PostgresURI string
}
// -----------------
// WhisperMailServer
// -----------------
type WhisperMailServer struct {
ms *mailServer
shh *whisper.Whisper
minRequestPoW float64
symFilter *whisper.Filter
asymFilter *whisper.Filter
}
func (s *WhisperMailServer) Init(shh *whisper.Whisper, cfg *params.WhisperConfig) error {
s.shh = shh
s.minRequestPoW = cfg.MinimumPoW
config := Config{
DataDir: cfg.DataDir,
Password: cfg.MailServerPassword,
AsymKey: cfg.MailServerAsymKey,
MinimumPoW: cfg.MinimumPoW,
DataRetention: cfg.MailServerDataRetention,
RateLimit: cfg.MailServerRateLimit,
PostgresEnabled: cfg.DatabaseConfig.PGConfig.Enabled,
PostgresURI: cfg.DatabaseConfig.PGConfig.URI,
}
var err error
s.ms, err = newMailServer(
config,
&whisperAdapter{},
&whisperService{Whisper: shh},
)
if err != nil {
return err
}
if err := s.setupDecryptor(config.Password, config.AsymKey); err != nil {
return err
}
return nil
}
func (s *WhisperMailServer) Close() {
if s.ms != nil {
s.ms.Close()
}
}
func (s *WhisperMailServer) Archive(env *whisper.Envelope) {
s.ms.Archive(gethbridge.NewWhisperEnvelope(env))
}
// DEPRECATED; user Deliver instead
func (s *WhisperMailServer) DeliverMail(peerID []byte, req *whisper.Envelope) {
payload, err := s.decodeRequest(peerID, req)
if err != nil {
log.Debug(
"[mailserver:DeliverMail] failed to decode request",
"err", err,
"peerID", types.BytesToHash(peerID),
"requestID", req.Hash().String(),
)
payload, err = s.decompositeRequest(peerID, req)
}
if err != nil {
deliveryFailuresCounter.WithLabelValues("validation").Inc()
log.Error(
"[mailserver:DeliverMail] request failed validaton",
"peerID", types.BytesToHash(peerID),
"requestID", req.Hash().String(),
"err", err,
)
s.ms.sendHistoricMessageErrorResponse(types.BytesToHash(peerID), types.Hash(req.Hash()), err)
return
}
s.ms.DeliverMail(types.BytesToHash(peerID), types.Hash(req.Hash()), payload)
}
func (s *WhisperMailServer) Deliver(peerID []byte, req whisper.MessagesRequest) {
s.ms.DeliverMail(types.BytesToHash(peerID), types.BytesToHash(req.ID), MessagesRequestPayload{
Lower: req.From,
Upper: req.To,
Bloom: req.Bloom,
Limit: req.Limit,
Cursor: req.Cursor,
Batch: true,
})
}
func (s *WhisperMailServer) SyncMail(peerID []byte, req whisper.SyncMailRequest) error {
return s.ms.SyncMail(types.BytesToHash(peerID), MessagesRequestPayload{
Lower: req.Lower,
Upper: req.Upper,
Bloom: req.Bloom,
Limit: req.Limit,
Cursor: req.Cursor,
})
}
func (s *WhisperMailServer) setupDecryptor(password, asymKey string) error {
s.symFilter = nil
s.asymFilter = nil
if password != "" {
keyID, err := s.shh.AddSymKeyFromPassword(password)
if err != nil {
return fmt.Errorf("create symmetric key: %v", err)
}
symKey, err := s.shh.GetSymKey(keyID)
if err != nil {
return fmt.Errorf("save symmetric key: %v", err)
}
s.symFilter = &whisper.Filter{KeySym: symKey}
}
if asymKey != "" {
keyAsym, err := crypto.HexToECDSA(asymKey)
if err != nil {
return err
}
s.asymFilter = &whisper.Filter{KeyAsym: keyAsym}
}
return nil
}
func (s *WhisperMailServer) decodeRequest(peerID []byte, request *whisper.Envelope) (MessagesRequestPayload, error) {
var payload MessagesRequestPayload
if s.minRequestPoW > 0.0 && request.PoW() < s.minRequestPoW {
return payload, errors.New("PoW too low")
}
decrypted := s.openEnvelope(request)
if decrypted == nil {
log.Warn("Failed to decrypt p2p request")
return payload, errors.New("failed to decrypt p2p request")
}
if err := checkMsgSignature(decrypted.Src, peerID); err != nil {
log.Warn("Check message signature failed", "err", err.Error())
return payload, fmt.Errorf("check message signature failed: %v", err)
}
if err := rlp.DecodeBytes(decrypted.Payload, &payload); err != nil {
return payload, fmt.Errorf("failed to decode data: %v", err)
}
if payload.Upper == 0 {
payload.Upper = uint32(time.Now().Unix() + whisperTTLSafeThreshold)
}
if payload.Upper < payload.Lower {
log.Error("Query range is invalid: lower > upper", "lower", payload.Lower, "upper", payload.Upper)
return payload, errors.New("query range is invalid: lower > upper")
}
return payload, nil
}
// openEnvelope tries to decrypt an envelope, first based on asymmetric key (if
// provided) and second on the symmetric key (if provided)
func (s *WhisperMailServer) openEnvelope(request *whisper.Envelope) *whisper.ReceivedMessage {
if s.asymFilter != nil {
if d := request.Open(s.asymFilter); d != nil {
return d
}
}
if s.symFilter != nil {
if d := request.Open(s.symFilter); d != nil {
return d
}
}
return nil
}
func (s *WhisperMailServer) decompositeRequest(peerID []byte, request *whisper.Envelope) (MessagesRequestPayload, error) {
var (
payload MessagesRequestPayload
err error
)
if s.minRequestPoW > 0.0 && request.PoW() < s.minRequestPoW {
return payload, fmt.Errorf("PoW() is too low")
}
decrypted := s.openEnvelope(request)
if decrypted == nil {
return payload, fmt.Errorf("failed to decrypt p2p request")
}
if err := checkMsgSignature(decrypted.Src, peerID); err != nil {
return payload, err
}
payload.Bloom, err = s.bloomFromReceivedMessage(decrypted)
if err != nil {
return payload, err
}
payload.Lower = binary.BigEndian.Uint32(decrypted.Payload[:4])
payload.Upper = binary.BigEndian.Uint32(decrypted.Payload[4:8])
if payload.Upper < payload.Lower {
err := fmt.Errorf("query range is invalid: from > to (%d > %d)", payload.Lower, payload.Upper)
return payload, err
}
lowerTime := time.Unix(int64(payload.Lower), 0)
upperTime := time.Unix(int64(payload.Upper), 0)
if upperTime.Sub(lowerTime) > maxQueryRange {
err := fmt.Errorf("query range too big for peer %s", string(peerID))
return payload, err
}
if len(decrypted.Payload) >= requestTimeRangeLength+whisper.BloomFilterSize+requestLimitLength {
payload.Limit = binary.BigEndian.Uint32(decrypted.Payload[requestTimeRangeLength+whisper.BloomFilterSize:])
}
if len(decrypted.Payload) == requestTimeRangeLength+whisper.BloomFilterSize+requestLimitLength+DBKeyLength {
payload.Cursor = decrypted.Payload[requestTimeRangeLength+whisper.BloomFilterSize+requestLimitLength:]
}
return payload, nil
}
// bloomFromReceivedMessage for a given whisper.ReceivedMessage it extracts the
// used bloom filter.
func (s *WhisperMailServer) bloomFromReceivedMessage(msg *whisper.ReceivedMessage) ([]byte, error) {
payloadSize := len(msg.Payload)
if payloadSize < 8 {
return nil, errors.New("Undersized p2p request")
} else if payloadSize == 8 {
return whisper.MakeFullNodeBloom(), nil
} else if payloadSize < 8+whisper.BloomFilterSize {
return nil, errors.New("Undersized bloom filter in p2p request")
}
return msg.Payload[8 : 8+whisper.BloomFilterSize], nil
}
// --------------
// WakuMailServer
// --------------
@ -495,44 +245,6 @@ type adapter interface {
CreateRawSyncResponse(envelopes []rlp.RawValue, cursor []byte, final bool, err string) interface{}
}
// --------------
// whisperAdapter
// --------------
type whisperAdapter struct{}
var _ adapter = (*whisperAdapter)(nil)
func (whisperAdapter) CreateRequestFailedPayload(reqID types.Hash, err error) []byte {
return whisper.CreateMailServerRequestFailedPayload(common.Hash(reqID), err)
}
func (whisperAdapter) CreateRequestCompletedPayload(reqID, lastEnvelopeHash types.Hash, cursor []byte) []byte {
return whisper.CreateMailServerRequestCompletedPayload(common.Hash(reqID), common.Hash(lastEnvelopeHash), cursor)
}
func (whisperAdapter) CreateSyncResponse(envelopes []types.Envelope, cursor []byte, final bool, err string) interface{} {
whisperEnvelopes := make([]*whisper.Envelope, len(envelopes))
for i, env := range envelopes {
whisperEnvelopes[i] = env.Unwrap().(*whisper.Envelope)
}
return whisper.SyncResponse{
Envelopes: whisperEnvelopes,
Cursor: cursor,
Final: final,
Error: err,
}
}
func (whisperAdapter) CreateRawSyncResponse(envelopes []rlp.RawValue, cursor []byte, final bool, err string) interface{} {
return whisper.RawSyncResponse{
Envelopes: envelopes,
Cursor: cursor,
Final: final,
Error: err,
}
}
// -----------
// wakuAdapter
// -----------
@ -569,30 +281,6 @@ type service interface {
SendSyncResponse(peerID []byte, data interface{}) error // optional
}
// --------------
// whisperService
// --------------
type whisperService struct {
*whisper.Whisper
}
func (s *whisperService) SendRawSyncResponse(peerID []byte, data interface{}) error {
resp, ok := data.(whisper.RawSyncResponse)
if !ok {
panic(fmt.Sprintf("invalid data type, got %s", reflect.TypeOf(data)))
}
return s.Whisper.SendRawSyncResponse(peerID, resp)
}
func (s *whisperService) SendSyncResponse(peerID []byte, data interface{}) error {
resp, ok := data.(whisper.SyncResponse)
if !ok {
panic(fmt.Sprintf("invalid data type, got %s", reflect.TypeOf(data)))
}
return s.Whisper.SendSyncResponse(peerID, resp)
}
// -----------
// wakuService
// -----------
@ -1149,7 +837,7 @@ func (s *mailServer) sendHistoricMessageErrorResponse(peerID, reqID types.Hash,
}
func extractBloomFromEncodedEnvelope(rawValue rlp.RawValue) ([]byte, error) {
var envelope whisper.Envelope
var envelope wakucommon.Envelope
decodeErr := rlp.DecodeBytes(rawValue, &envelope)
if decodeErr != nil {
return nil, decodeErr

View file

@ -13,7 +13,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/whisper"
waku "github.com/status-im/status-go/waku/common"
)
type LevelDB struct {
@ -201,7 +201,7 @@ func (db *LevelDB) SaveEnvelope(env types.Envelope) error {
}
archivedEnvelopesGauge.WithLabelValues(db.name).Inc()
archivedEnvelopeSizeMeter.WithLabelValues(db.name).Observe(
float64(whisper.EnvelopeHeaderLength + env.Size()))
float64(waku.EnvelopeHeaderLength + env.Size()))
return err
}

View file

@ -20,7 +20,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/whisper"
waku "github.com/status-im/status-go/waku/common"
)
type PostgresDB struct {
@ -274,7 +274,7 @@ func (i *PostgresDB) SaveEnvelope(env types.Envelope) error {
archivedEnvelopesGauge.WithLabelValues(i.name).Inc()
archivedEnvelopeSizeMeter.WithLabelValues(i.name).Observe(
float64(whisper.EnvelopeHeaderLength + env.Size()))
float64(waku.EnvelopeHeaderLength + env.Size()))
return nil
}

View file

@ -41,8 +41,8 @@ func (r MessagesRequestPayload) Validate() error {
if r.Upper < r.Lower {
return errors.New("query range is invalid: lower > upper")
}
if len(r.Bloom) == 0 {
return errors.New("bloom filter is empty")
if len(r.Bloom) == 0 && len(r.Topics) == 0 {
return errors.New("bloom filter and topics is empty")
}
if r.Limit > maxMessagesRequestPayloadLimit {
return errors.New("limit exceeds the maximum allowed value")

View file

@ -25,10 +25,8 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/status-im/status-go/bridge"
"github.com/status-im/status-go/db"
"github.com/status-im/status-go/discovery"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/peers"
"github.com/status-im/status-go/rpc"
@ -40,7 +38,6 @@ import (
"github.com/status-im/status-go/services/wakuext"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/waku"
"github.com/status-im/status-go/whisper"
)
// tickerResolution is the delta to check blockchain sync progress.
@ -71,8 +68,6 @@ type StatusNode struct {
peerPool *peers.PeerPool
db *leveldb.DB // used as a cache for PeerPool
bridge *bridge.Bridge // Whisper-Waku bridge
log log.Logger
}
@ -180,10 +175,6 @@ func (n *StatusNode) startWithDB(config *params.NodeConfig, accs *accounts.Manag
return err
}
if err := n.setupBridge(); err != nil {
return err
}
return nil
}
@ -224,28 +215,6 @@ func (n *StatusNode) setupRPCClient() (err error) {
return
}
func (n *StatusNode) setupBridge() error {
if !n.config.BridgeConfig.Enabled {
log.Info("Whisper-Waku bridge is disabled")
return nil
}
var shh *whisper.Whisper
if err := n.gethService(&shh); err != nil {
return fmt.Errorf("setup bridge: failed to get Whisper: %v", err)
}
var wak *waku.Waku
if err := n.gethService(&wak); err != nil {
return fmt.Errorf("setup bridge: failed to get Waku: %v", err)
}
n.bridge = bridge.New(shh, wak, logutils.ZapLogger())
n.bridge.Start()
log.Info("setup a Whisper-Waku bridge successfully")
return nil
}
func (n *StatusNode) discoveryEnabled() bool {
return n.config != nil && (!n.config.NoDiscovery || n.config.Rendezvous) && n.config.ClusterConfig.Enabled
}
@ -385,11 +354,6 @@ func (n *StatusNode) stop() error {
n.discovery = nil
}
if n.bridge != nil {
n.bridge.Cancel()
n.bridge = nil
}
if err := n.gethNode.Stop(); err != nil {
return err
}
@ -608,20 +572,7 @@ func (n *StatusNode) PeerService() (st *peer.Service, err error) {
return
}
// WhisperService exposes reference to Whisper service running on top of the node
func (n *StatusNode) WhisperService() (w *whisper.Whisper, err error) {
n.mu.RLock()
defer n.mu.RUnlock()
err = n.gethService(&w)
if err == node.ErrServiceUnknown {
err = ErrServiceUnknown
}
return
}
// WakuService exposes reference to Whisper service running on top of the node
// WakuService exposes reference to Waku service running on top of the node
func (n *StatusNode) WakuService() (w *waku.Waku, err error) {
n.mu.RLock()
defer n.mu.RUnlock()

View file

@ -33,19 +33,16 @@ import (
"github.com/status-im/status-go/services/nodebridge"
"github.com/status-im/status-go/services/peer"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/status"
"github.com/status-im/status-go/services/wakuext"
"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"
)
// Errors related to node and services creation.
var (
ErrNodeMakeFailureFormat = "error creating p2p node: %s"
ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
ErrWakuServiceRegistrationFailure = errors.New("failed to register the Waku service")
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
ErrLightEthRegistrationFailureUpstreamEnabled = errors.New("failed to register the LES service, upstream is also configured")
@ -143,11 +140,6 @@ func activateNodeServices(stack *node.Node, config *params.NodeConfig, db *level
return fmt.Errorf("%v: %v", ErrWakuServiceRegistrationFailure, err)
}
// start status service.
if err := activateStatusService(stack, config); err != nil {
return fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err)
}
// start peer service
if err := activatePeerService(stack); err != nil {
return fmt.Errorf("%v: %v", ErrPeerServiceRegistrationFailure, err)
@ -281,22 +273,6 @@ func activatePersonalService(stack *node.Node, accs *accounts.Manager, config *p
})
}
func activateStatusService(stack *node.Node, config *params.NodeConfig) error {
if !config.EnableStatusService {
logger.Info("Status service api is disabled")
return nil
}
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var service *nodebridge.WhisperService
if err := ctx.Service(&service); err != nil {
return nil, err
}
svc := status.New(service.Whisper)
return svc, nil
})
}
func activatePeerService(stack *node.Node) error {
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
svc := peer.New()
@ -304,13 +280,6 @@ func activatePeerService(stack *node.Node) error {
})
}
func registerWhisperMailServer(whisperService *whisper.Whisper, config *params.WhisperConfig) (err error) {
var mailServer mailserver.WhisperMailServer
whisperService.RegisterMailServer(&mailServer)
return mailServer.Init(whisperService, config)
}
func registerWakuMailServer(wakuService *waku.Waku, config *params.WakuConfig) (err error) {
var mailServer mailserver.WakuMailServer
wakuService.RegisterMailServer(&mailServer)
@ -358,47 +327,6 @@ func activateWakuService(stack *node.Node, config *params.NodeConfig, db *leveld
})
}
func createShhService(ctx *node.ServiceContext, whisperConfig *params.WhisperConfig, clusterConfig *params.ClusterConfig) (*whisper.Whisper, error) {
whisperServiceConfig := &whisper.Config{
MaxMessageSize: whisper.DefaultMaxMessageSize,
MinimumAcceptedPOW: params.WhisperMinimumPoW,
}
if whisperConfig.MaxMessageSize > 0 {
whisperServiceConfig.MaxMessageSize = whisperConfig.MaxMessageSize
}
if whisperConfig.MinimumPoW > 0 {
whisperServiceConfig.MinimumAcceptedPOW = whisperConfig.MinimumPoW
}
whisperService := whisper.New(whisperServiceConfig)
if whisperConfig.EnableRateLimiter {
r := whisperRateLimiter(whisperConfig, clusterConfig)
whisperService.SetRateLimiter(r)
}
if timesource, err := timeSource(ctx); err == nil {
whisperService.SetTimeSource(timesource)
}
// enable mail service
if whisperConfig.EnableMailServer {
if err := registerWhisperMailServer(whisperService, whisperConfig); err != nil {
return nil, fmt.Errorf("failed to register MailServer: %v", err)
}
}
if whisperConfig.LightClient {
emptyBloomFilter := make([]byte, 64)
if err := whisperService.SetBloomFilter(emptyBloomFilter); err != nil {
return nil, err
}
}
return whisperService, nil
}
func createWakuService(ctx *node.ServiceContext, wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) (*waku.Waku, error) {
cfg := &waku.Config{
MaxMessageSize: wakucommon.DefaultMaxMessageSize,
@ -491,33 +419,6 @@ func timeSource(ctx *node.ServiceContext) (func() time.Time, error) {
return timeSource.Now, nil
}
func whisperRateLimiter(whisperConfig *params.WhisperConfig, clusterConfig *params.ClusterConfig) *whisper.PeerRateLimiter {
enodes := append(
parseNodes(clusterConfig.StaticNodes),
parseNodes(clusterConfig.TrustedMailServers)...,
)
var (
ips []string
peerIDs []enode.ID
)
for _, item := range enodes {
ips = append(ips, item.IP().String())
peerIDs = append(peerIDs, item.ID())
}
return whisper.NewPeerRateLimiter(
&whisper.PeerRateLimiterConfig{
LimitPerSecIP: whisperConfig.RateLimitIP,
LimitPerSecPeerID: whisperConfig.RateLimitPeerID,
WhitelistedIPs: ips,
WhitelistedPeerIDs: peerIDs,
},
&whisper.MetricsRateLimiterHandler{},
&whisper.DropPeerRateLimiterHandler{
Tolerance: whisperConfig.RateLimitTolerance,
},
)
}
func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) *wakucommon.PeerRateLimiter {
enodes := append(
parseNodes(clusterCfg.StaticNodes),
@ -540,8 +441,8 @@ func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfi
WhitelistedIPs: ips,
WhitelistedPeerIDs: peerIDs,
},
&whisper.MetricsRateLimiterHandler{},
&whisper.DropPeerRateLimiterHandler{
&wakucommon.MetricsRateLimiterHandler{},
&wakucommon.DropPeerRateLimiterHandler{
Tolerance: wakuCfg.RateLimitTolerance,
},
)

View file

@ -24,7 +24,6 @@ import (
"github.com/status-im/status-go/protocol/pushnotificationserver"
"github.com/status-im/status-go/static"
wakucommon "github.com/status-im/status-go/waku/common"
"github.com/status-im/status-go/whisper"
)
// ----------
@ -66,75 +65,6 @@ type PGConfig struct {
URI string
}
// ----------
// WhisperConfig
// ----------
// WhisperConfig holds SHH-related configuration
type WhisperConfig struct {
// Enabled flag specifies whether protocol is enabled
Enabled bool
// LightClient should be true if the node should start with an empty bloom filter and not forward messages from other nodes
LightClient bool
// EnableMailServer is mode when node is capable of delivering expired messages on demand
EnableMailServer bool
// DataDir is the file system folder Whisper should use for any data storage needs.
// For instance, MailServer will use this directory to store its data.
DataDir string
// MinimumPoW minimum PoW for Whisper messages
// We enforce a minimum as a bland spam prevention mechanism.
MinimumPoW float64
// MailServerPassword for symmetric encryption of whisper message history requests.
// (if no account file selected, then this password is used for symmetric encryption).
MailServerPassword string
// MailServerAsymKey is an hex-encoded asymmetric key to decrypt messages sent to MailServer.
MailServerAsymKey string
// MailServerRateLimit minimum time between queries to mail server per peer.
MailServerRateLimit int
// MailServerDataRetention is a number of days data should be stored by MailServer.
MailServerDataRetention int
// TTL time to live for messages, in seconds
TTL int
// MaxMessageSize is a maximum size of a devp2p packet handled by the Whisper protocol,
// not only the size of envelopes sent in that packet.
MaxMessageSize uint32
// DatabaseConfig is configuration for which datastore we use
DatabaseConfig DatabaseConfig
// EnableRateLimiter set to true enables IP and peer ID rate limiting.
EnableRateLimiter bool
// RateLimitIP sets the limit on the number of messages per second
// from a given IP.
RateLimitIP int64
// RateLimitPeerID sets the limit on the number of messages per second
// from a given peer ID.
RateLimitPeerID int64
// RateLimitTolerance is a number of how many a limit must be exceeded
// in order to drop a peer.
// If equal to 0, the peers are never dropped.
RateLimitTolerance int64
}
// String dumps config object as nicely indented JSON
func (c *WhisperConfig) String() string {
data, _ := json.MarshalIndent(c, "", " ") // nolint: gas
return string(data)
}
// ----------
// WakuConfig
// ----------
@ -424,9 +354,6 @@ type NodeConfig struct {
// LightEthConfig extra configuration for LES
LightEthConfig LightEthConfig `json:"LightEthConfig," validate:"structonly"`
// WhisperConfig extra configuration for SHH
WhisperConfig WhisperConfig `json:"WhisperConfig," validate:"structonly"`
// WakuConfig provides a configuration for Waku subprotocol.
WakuConfig WakuConfig `json:"WakuConfig" validate:"structonly"`
@ -684,10 +611,6 @@ func (c *NodeConfig) UpdateWithDefaults() error {
c.WakuConfig.MinimumPoW = WakuMinimumPoW
}
if c.WhisperConfig.Enabled {
c.WhisperConfig.MinimumPoW = WhisperMinimumPoW
}
return c.setDefaultPushNotificationsServers()
}
@ -721,10 +644,6 @@ func (c *NodeConfig) updatePeerLimits() {
if c.NoDiscovery && !c.Rendezvous {
return
}
if c.WhisperConfig.Enabled {
c.RequireTopics[WhisperDiscv5Topic] = WhisperDiscv5Limits
// TODO(dshulyak) register mailserver limits when we will change how they are handled.
}
if c.LightEthConfig.Enabled {
c.RequireTopics[discv5.Topic(LesTopic(int(c.NetworkID)))] = LesDiscoveryLimits
}
@ -733,11 +652,10 @@ func (c *NodeConfig) updatePeerLimits() {
// NewNodeConfig creates new node configuration object with bare-minimum defaults.
// Important: the returned config is not validated.
func NewNodeConfig(dataDir string, networkID uint64) (*NodeConfig, error) {
var keyStoreDir, wnodeDir, wakuDir string
var keyStoreDir, wakuDir string
if dataDir != "" {
keyStoreDir = filepath.Join(dataDir, "keystore")
wnodeDir = filepath.Join(dataDir, "wnode")
wakuDir = filepath.Join(dataDir, "waku")
}
@ -771,12 +689,6 @@ func NewNodeConfig(dataDir string, networkID uint64) (*NodeConfig, error) {
TTL: WakuTTL,
MaxMessageSize: wakucommon.DefaultMaxMessageSize,
},
WhisperConfig: WhisperConfig{
DataDir: wnodeDir,
MinimumPoW: WhisperMinimumPoW,
TTL: WhisperTTL,
MaxMessageSize: whisper.DefaultMaxMessageSize,
},
ShhextConfig: ShhextConfig{
BackupDisabledDataDir: dataDir,
},
@ -874,18 +786,6 @@ func (c *NodeConfig) Validate() error {
return err
}
if c.WhisperConfig.Enabled && c.WakuConfig.Enabled && c.WhisperConfig.DataDir == c.WakuConfig.DataDir {
return fmt.Errorf("both Whisper and Waku are enabled and use the same data dir")
}
// Whisper's data directory must be relative to the main data directory
// if EnableMailServer is true.
if c.WhisperConfig.Enabled && c.WhisperConfig.EnableMailServer {
if !strings.HasPrefix(c.WhisperConfig.DataDir, c.DataDir) {
return fmt.Errorf("WhisperConfig.DataDir must start with DataDir fragment")
}
}
// Waku's data directory must be relative to the main data directory
// if EnableMailServer is true.
if c.WakuConfig.Enabled && c.WakuConfig.EnableMailServer {
@ -922,9 +822,6 @@ func (c *NodeConfig) validateChildStructs(validate *validator.Validate) error {
if err := c.LightEthConfig.Validate(validate); err != nil {
return err
}
if err := c.WhisperConfig.Validate(validate); err != nil {
return err
}
if err := c.SwarmConfig.Validate(validate); err != nil {
return err
}
@ -977,34 +874,6 @@ func (c *LightEthConfig) Validate(validate *validator.Validate) error {
return nil
}
// Validate validates the WhisperConfig struct and returns an error if inconsistent values are found
func (c *WhisperConfig) Validate(validate *validator.Validate) error {
if !c.Enabled {
return nil
}
if err := validate.Struct(c); err != nil {
return err
}
if c.EnableMailServer {
if c.DataDir == "" {
return fmt.Errorf("WhisperConfig.DataDir must be specified when WhisperConfig.EnableMailServer is true")
}
if c.MailServerPassword == "" && c.MailServerAsymKey == "" {
return fmt.Errorf("WhisperConfig.MailServerPassword or WhisperConfig.MailServerAsymKey must be specified when WhisperConfig.EnableMailServer is true")
}
if c.MailServerAsymKey != "" {
if _, err := crypto.HexToECDSA(c.MailServerAsymKey); err != nil {
return fmt.Errorf("WhisperConfig.MailServerAsymKey is invalid: %s", c.MailServerAsymKey)
}
}
}
return nil
}
// Validate validates the SwarmConfig struct and returns an error if inconsistent values are found
func (c *SwarmConfig) Validate(validate *validator.Validate) error {
if !c.Enabled {

View file

@ -1,9 +0,0 @@
# Whisper
Go implementation of the [Whisper specifications](https://geth.ethereum.org/docs/whisper/whisper-overview)
# Deprecation Warning!
This package is **DEPRECATED** and, except for security patches, should not receive any further updates.
Whisper has been forked into Waku. See [Go implementation](https://github.com/status-im/status-go/tree/develop/waku), [spec](https://specs.vac.dev/specs/waku/waku.html) and [motivation](https://vac.dev/fixing-whisper-with-waku) for more.

View file

@ -1,604 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rpc"
)
// List of errors
var (
ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key")
ErrInvalidSymmetricKey = errors.New("invalid symmetric key")
ErrInvalidPublicKey = errors.New("invalid public key")
ErrInvalidSigningPubKey = errors.New("invalid signing public key")
ErrTooLowPoW = errors.New("message rejected, PoW too low")
ErrNoTopics = errors.New("missing topic(s)")
)
// PublicWhisperAPI provides the whisper RPC service that can be
// use publicly without security implications.
type PublicWhisperAPI struct {
w *Whisper
mu sync.Mutex
lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
}
// NewPublicWhisperAPI create a new RPC whisper service.
func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
api := &PublicWhisperAPI{
w: w,
lastUsed: make(map[string]time.Time),
}
return api
}
// Version returns the Whisper sub-protocol version.
func (api *PublicWhisperAPI) Version(ctx context.Context) string {
return ProtocolVersionStr
}
// Info contains diagnostic information.
type Info struct {
Memory int `json:"memory"` // Memory size of the floating messages in bytes.
Messages int `json:"messages"` // Number of floating messages.
MinPow float64 `json:"minPow"` // Minimal accepted PoW
MaxMessageSize uint32 `json:"maxMessageSize"` // Maximum accepted message size
}
// Info returns diagnostic information about the whisper node.
func (api *PublicWhisperAPI) Info(ctx context.Context) Info {
stats := api.w.Stats()
return Info{
Memory: stats.memoryUsed,
Messages: len(api.w.messageQueue) + len(api.w.p2pMsgQueue),
MinPow: api.w.MinPow(),
MaxMessageSize: api.w.MaxMessageSize(),
}
}
// SetMaxMessageSize sets the maximum message size that is accepted.
// Upper limit is defined by MaxMessageSize.
func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) {
return true, api.w.SetMaxMessageSize(size)
}
// SetMinPoW sets the minimum PoW, and notifies the peers.
func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
return true, api.w.SetMinimumPoW(pow)
}
// SetBloomFilter sets the new value of bloom filter, and notifies the peers.
func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) {
return true, api.w.SetBloomFilter(bloom)
}
// MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages.
// Note: This function is not adding new nodes, the node needs to exists as a peer.
func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, url string) (bool, error) {
n, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return false, err
}
return true, api.w.AllowP2PMessagesFromPeer(n.ID().Bytes())
}
// NewKeyPair generates a new public and private key pair for message decryption and encryption.
// It returns an ID that can be used to refer to the keypair.
func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) {
return api.w.NewKeyPair()
}
// AddPrivateKey imports the given private key.
func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) {
key, err := crypto.ToECDSA(privateKey)
if err != nil {
return "", err
}
return api.w.AddKeyPair(key)
}
// DeleteKeyPair removes the key with the given key if it exists.
func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
if ok := api.w.DeleteKeyPair(key); ok {
return true, nil
}
return false, fmt.Errorf("key pair %s not found", key)
}
// HasKeyPair returns an indication if the node has a key pair that is associated with the given id.
func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool {
return api.w.HasKeyPair(id)
}
// GetPublicKey returns the public key associated with the given key. The key is the hex
// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) {
key, err := api.w.GetPrivateKey(id)
if err != nil {
return hexutil.Bytes{}, err
}
return crypto.FromECDSAPub(&key.PublicKey), nil
}
// GetPrivateKey returns the private key associated with the given key. The key is the hex
// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) {
key, err := api.w.GetPrivateKey(id)
if err != nil {
return hexutil.Bytes{}, err
}
return crypto.FromECDSA(key), nil
}
// NewSymKey generate a random symmetric key.
// It returns an ID that can be used to refer to the key.
// Can be used encrypting and decrypting messages where the key is known to both parties.
func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) {
return api.w.GenerateSymKey()
}
// AddSymKey import a symmetric key.
// It returns an ID that can be used to refer to the key.
// Can be used encrypting and decrypting messages where the key is known to both parties.
func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) {
return api.w.AddSymKeyDirect([]byte(key))
}
// GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID.
func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
return api.w.AddSymKeyFromPassword(passwd)
}
// HasSymKey returns an indication if the node has a symmetric key associated with the given key.
func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool {
return api.w.HasSymKey(id)
}
// GetSymKey returns the symmetric key associated with the given id.
func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) {
return api.w.GetSymKey(id)
}
// DeleteSymKey deletes the symmetric key that is associated with the given id.
func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool {
return api.w.DeleteSymKey(id)
}
// MakeLightClient turns the node into light client, which does not forward
// any incoming messages, and sends only messages originated in this node.
func (api *PublicWhisperAPI) MakeLightClient(ctx context.Context) bool {
api.w.SetLightClientMode(true)
return api.w.LightClientMode()
}
// CancelLightClient cancels light client mode.
func (api *PublicWhisperAPI) CancelLightClient(ctx context.Context) bool {
api.w.SetLightClientMode(false)
return !api.w.LightClientMode()
}
//go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go
// NewMessage represents a new whisper 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"`
}
type newMessageOverride struct {
PublicKey hexutil.Bytes
Payload hexutil.Bytes
Padding hexutil.Bytes
}
// Post posts a message on the Whisper network.
// returns the hash of the message in case of success.
func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) {
var (
symKeyGiven = len(req.SymKeyID) > 0
pubKeyGiven = len(req.PublicKey) > 0
err error
)
// user must specify either a symmetric or an asymmetric key
if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
return nil, ErrSymAsym
}
params := &MessageParams{
TTL: req.TTL,
Payload: req.Payload,
Padding: req.Padding,
WorkTime: req.PowTime,
PoW: req.PowTarget,
Topic: req.Topic,
}
// Set key that is used to sign the message
if len(req.Sig) > 0 {
if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil {
return nil, err
}
}
// Set symmetric key that is used to encrypt the message
if symKeyGiven {
if params.Topic == (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) {
return nil, ErrInvalidSymmetricKey
}
}
// Set asymmetric key that is used to encrypt the message
if pubKeyGiven {
if params.Dst, err = crypto.UnmarshalPubkey(req.PublicKey); err != nil {
return nil, ErrInvalidPublicKey
}
}
// encrypt and sent message
whisperMsg, err := NewSentMessage(params)
if err != nil {
return nil, err
}
var result []byte
env, err := whisperMsg.Wrap(params, api.w.GetCurrentTime())
if err != nil {
return nil, err
}
// send to specific node (skip PoW check)
if len(req.TargetPeer) > 0 {
n, err := enode.Parse(enode.ValidSchemes, req.TargetPeer)
if err != nil {
return nil, fmt.Errorf("failed to parse target peer: %s", err)
}
err = api.w.SendP2PMessage(n.ID().Bytes(), env)
if err == nil {
hash := env.Hash()
result = hash[:]
}
return result, err
}
// ensure that the message PoW meets the node's minimum accepted PoW
if req.PowTarget < api.w.MinPow() {
return nil, ErrTooLowPoW
}
err = api.w.Send(env)
if err == nil {
hash := env.Hash()
result = hash[:]
}
return result, err
}
// UninstallFilter is alias for Unsubscribe
func (api *PublicWhisperAPI) UninstallFilter(id string) {
_ = api.w.Unsubscribe(id)
}
// Unsubscribe disables and removes an existing filter.
func (api *PublicWhisperAPI) Unsubscribe(id string) {
_ = api.w.Unsubscribe(id)
}
//go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go
// 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"`
}
type criteriaOverride struct {
Sig hexutil.Bytes
}
// Messages set up a subscription that fires events when messages arrive that match
// the given set of criteria.
func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) {
var (
symKeyGiven = len(crit.SymKeyID) > 0
pubKeyGiven = len(crit.PrivateKeyID) > 0
err error
)
// ensure that the RPC connection supports subscriptions
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return nil, rpc.ErrNotificationsUnsupported
}
// user must specify either a symmetric or an asymmetric key
if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
return nil, ErrSymAsym
}
filter := Filter{
PoW: crit.MinPow,
Messages: api.w.NewMessageStore(),
AllowP2P: crit.AllowP2P,
}
if len(crit.Sig) > 0 {
if filter.Src, err = crypto.UnmarshalPubkey(crit.Sig); err != nil {
return nil, ErrInvalidSigningPubKey
}
}
for i, bt := range crit.Topics {
if len(bt) == 0 || len(bt) > 4 {
return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt))
}
filter.Topics = append(filter.Topics, bt[:])
}
// listen for message that are encrypted with the given symmetric key
if symKeyGiven {
if len(filter.Topics) == 0 {
return nil, ErrNoTopics
}
key, err := api.w.GetSymKey(crit.SymKeyID)
if err != nil {
return nil, err
}
if !validateDataIntegrity(key, aesKeyLength) {
return nil, ErrInvalidSymmetricKey
}
filter.KeySym = key
filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
}
// listen for messages that are encrypted with the given public key
if pubKeyGiven {
filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID)
if err != nil || filter.KeyAsym == nil {
return nil, ErrInvalidPublicKey
}
}
id, err := api.w.Subscribe(&filter)
if err != nil {
return nil, err
}
// create subscription and start waiting for message events
rpcSub := notifier.CreateSubscription()
go func() {
// for now poll internally, refactor whisper internal for channel support
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if filter := api.w.GetFilter(id); filter != nil {
for _, rpcMessage := range toMessage(filter.Retrieve()) {
if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil {
log.Error("Failed to send notification", "err", err)
}
}
}
case <-rpcSub.Err():
_ = api.w.Unsubscribe(id)
return
case <-notifier.Closed():
_ = api.w.Unsubscribe(id)
return
}
}
}()
return rpcSub, nil
}
//go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go
// Message is the RPC representation of a whisper 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"`
}
type messageOverride struct {
Sig hexutil.Bytes
Payload hexutil.Bytes
Padding hexutil.Bytes
Hash hexutil.Bytes
Dst hexutil.Bytes
}
// ToWhisperMessage converts an internal message into an API version.
func ToWhisperMessage(message *ReceivedMessage) *Message {
msg := Message{
Payload: message.Payload,
Padding: message.Padding,
Timestamp: message.Sent,
TTL: message.TTL,
PoW: message.PoW,
Hash: message.EnvelopeHash.Bytes(),
Topic: message.Topic,
P2P: message.P2P,
}
if message.Dst != nil {
b := crypto.FromECDSAPub(message.Dst)
if b != nil {
msg.Dst = b
}
}
if isMessageSigned(message.Raw[0]) {
b := crypto.FromECDSAPub(message.SigToPubKey())
if b != nil {
msg.Sig = b
}
}
return &msg
}
// toMessage converts a set of messages to its RPC representation.
func toMessage(messages []*ReceivedMessage) []*Message {
msgs := make([]*Message, len(messages))
for i, msg := range messages {
msgs[i] = ToWhisperMessage(msg)
}
return msgs
}
// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) {
api.mu.Lock()
f := api.w.GetFilter(id)
if f == nil {
api.mu.Unlock()
return nil, fmt.Errorf("filter not found")
}
api.lastUsed[id] = time.Now()
api.mu.Unlock()
receivedMessages := f.Retrieve()
messages := make([]*Message, 0, len(receivedMessages))
for _, msg := range receivedMessages {
messages = append(messages, ToWhisperMessage(msg))
}
return messages, nil
}
// DeleteMessageFilter deletes a filter.
func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) {
api.mu.Lock()
defer api.mu.Unlock()
delete(api.lastUsed, id)
return true, api.w.Unsubscribe(id)
}
// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) {
var (
src *ecdsa.PublicKey
keySym []byte
keyAsym *ecdsa.PrivateKey
topics [][]byte
symKeyGiven = len(req.SymKeyID) > 0
asymKeyGiven = len(req.PrivateKeyID) > 0
err error
)
// user must specify either a symmetric or an asymmetric key
if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) {
return "", ErrSymAsym
}
if len(req.Sig) > 0 {
if src, err = crypto.UnmarshalPubkey(req.Sig); err != nil {
return "", ErrInvalidSigningPubKey
}
}
if symKeyGiven {
if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
return "", err
}
if !validateDataIntegrity(keySym, aesKeyLength) {
return "", ErrInvalidSymmetricKey
}
}
if asymKeyGiven {
if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil {
return "", err
}
}
if len(req.Topics) > 0 {
topics = make([][]byte, len(req.Topics))
for i, topic := range req.Topics {
topics[i] = make([]byte, TopicLength)
copy(topics[i], topic[:])
}
}
f := &Filter{
Src: src,
KeySym: keySym,
KeyAsym: keyAsym,
PoW: req.MinPow,
AllowP2P: req.AllowP2P,
Topics: topics,
Messages: api.w.NewMessageStore(),
}
id, err := api.w.Subscribe(f)
if err != nil {
return "", err
}
api.mu.Lock()
api.lastUsed[id] = time.Now()
api.mu.Unlock()
return id, nil
}

View file

@ -1,63 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"bytes"
"testing"
"time"
)
func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) {
w := New(nil)
keyID, err := w.GenerateSymKey()
if err != nil {
t.Fatalf("Error generating symmetric key: %v", err)
}
api := PublicWhisperAPI{
w: w,
lastUsed: make(map[string]time.Time),
}
t1 := [4]byte{0xde, 0xea, 0xbe, 0xef}
t2 := [4]byte{0xca, 0xfe, 0xde, 0xca}
crit := Criteria{
SymKeyID: keyID,
Topics: []TopicType{TopicType(t1), TopicType(t2)},
}
_, err = api.NewMessageFilter(crit)
if err != nil {
t.Fatalf("Error creating the filter: %v", err)
}
found := false
candidates := w.filters.getWatchersByTopic(TopicType(t1))
for _, f := range candidates {
if len(f.Topics) == 2 {
if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) {
found = true
}
}
}
if !found {
t.Fatalf("Could not find filter with both topics")
}
}

View file

@ -1,210 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"crypto/sha256"
"testing"
"time"
"golang.org/x/crypto/pbkdf2"
"github.com/ethereum/go-ethereum/crypto"
)
func BenchmarkDeriveKeyMaterial(b *testing.B) {
for i := 0; i < b.N; i++ {
pbkdf2.Key([]byte("test"), nil, 65356, aesKeyLength, sha256.New)
}
}
func BenchmarkEncryptionSym(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
for i := 0; i < b.N; i++ {
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params, time.Now())
if err != nil {
b.Errorf("failed Wrap with seed %d: %s.", seed, err)
b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload))
return
}
}
}
func BenchmarkEncryptionAsym(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
for i := 0; i < b.N; i++ {
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params, time.Now())
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
}
}
func BenchmarkDecryptionSymValid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params, time.Now())
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeySym: params.KeySym}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg == nil {
b.Fatalf("failed to open with seed %d.", seed)
}
}
}
func BenchmarkDecryptionSymInvalid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params, time.Now())
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeySym: []byte("arbitrary stuff here")}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg != nil {
b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
}
}
}
func BenchmarkDecryptionAsymValid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params, time.Now())
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg == nil {
b.Fatalf("fail to open, seed: %d.", seed)
}
}
}
func BenchmarkDecryptionAsymInvalid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params, time.Now())
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
key, err = crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg != nil {
b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
}
}
}
func increment(x []byte) {
for i := 0; i < len(x); i++ {
x[i]++
if x[i] != 0 {
break
}
}
}
func BenchmarkPoW(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.Payload = make([]byte, 32)
params.PoW = 10.0
params.TTL = 1
for i := 0; i < b.N; i++ {
increment(params.Payload)
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params, time.Now())
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
}
}

View file

@ -1,32 +0,0 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
// Config represents the configuration state of a whisper node.
type Config struct {
MaxMessageSize uint32 `toml:",omitempty"`
MinimumAcceptedPOW float64 `toml:",omitempty"`
RestrictConnectionBetweenLightClients bool `toml:",omitempty"`
DisableConfirmations bool `toml:",omitempty"`
}
// DefaultConfig represents (shocker!) the default configuration.
var DefaultConfig = Config{
MaxMessageSize: DefaultMaxMessageSize,
MinimumAcceptedPOW: DefaultMinimumPoW,
RestrictConnectionBetweenLightClients: true,
}

View file

@ -1,97 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/*
Package whisper implements the Whisper protocol (version 6).
Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
As such it may be likened and compared to both, not dissimilar to the
matter/energy duality (apologies to physicists for the blatant abuse of a
fundamental and beautiful natural principle).
Whisper is a pure identity-based messaging system. Whisper provides a low-level
(non-application-specific) but easily-accessible API without being based upon
or prejudiced by the low-level hardware attributes and characteristics,
particularly the notion of singular endpoints.
*/
// Contains the Whisper protocol constant definitions
// Originally inherited from https://github.com/ethereum/go-ethereum/blob/master/whisper/whisperv6/doc.go,
// refactored due to https://github.com/status-im/status-go/pull/1950
package whisper
import (
"time"
"github.com/ethereum/go-ethereum/crypto"
)
// Whisper protocol parameters
const (
ProtocolVersion = uint64(6) // Protocol version number
ProtocolVersionStr = "6.0" // The same, as a string
ProtocolName = "shh" // Nickname of the protocol in geth
// whisper protocol message codes, according to EIP-627
statusCode = 0 // used by whisper protocol
messagesCode = 1 // normal whisper message
powRequirementCode = 2 // PoW requirement
bloomFilterExCode = 3 // bloom filter exchange
batchAcknowledgedCode = 11 // confirmation that batch of envelopes was received
messageResponseCode = 12 // includes confirmation for delivery and information about errors
rateLimitingCode = 20 // includes peer's rate limiting settings
p2pSyncRequestCode = 123 // used to sync envelopes between two mail servers
p2pSyncResponseCode = 124 // used to sync envelopes between two mail servers
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
aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize()
keyIDSize = 32 // in bytes
BloomFilterSize = 64 // in bytes
flagsLength = 1
EnvelopeHeaderLength = 20
MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message.
DefaultMaxMessageSize = uint32(1024 * 1024)
DefaultMinimumPoW = 0.2
padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol
messageQueueLimit = 1024
expirationCycle = time.Second
transmissionCycle = 300 * time.Millisecond
DefaultTTL = 50 // seconds
DefaultSyncAllowance = 10 // seconds
MaxLimitInSyncMailRequest = 1000
EnvelopeTimeNotSynced uint = iota + 1
EnvelopeOtherError
MaxLimitInMessagesRequest = 1000
)

View file

@ -1,301 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Envelope element.
package whisper
import (
"crypto/ecdsa"
"encoding/binary"
"fmt"
gmath "math"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/rlp"
)
// Envelope represents a clear-text data packet to transmit through the Whisper
// network. Its contents may or may not be encrypted and signed.
type Envelope struct {
Expiry uint32
TTL uint32
Topic TopicType
Data []byte
Nonce uint64
pow float64 // Message-specific PoW as described in the Whisper specification.
// the following variables should not be accessed directly, use the corresponding function instead: Hash(), Bloom()
hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
bloom []byte
}
// 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)
}
// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
func (e *Envelope) rlpWithoutNonce() []byte {
res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Data})
return res
}
// NewEnvelope wraps a Whisper message with expiration and destination data
// included into an envelope for network forwarding.
func NewEnvelope(ttl uint32, topic TopicType, msg *sentMessage, now time.Time) *Envelope {
env := Envelope{
Expiry: uint32(now.Add(time.Second * time.Duration(ttl)).Unix()),
TTL: ttl,
Topic: topic,
Data: msg.Raw,
Nonce: 0,
}
return &env
}
// Seal closes the envelope by spending the requested amount of time as a proof
// of work on hashing the data.
func (e *Envelope) Seal(options *MessageParams) error {
if options.PoW == 0 {
// PoW is not required
return nil
}
var target, bestLeadingZeros int
if options.PoW < 0 {
// target is not set - the function should run for a period
// of time specified in WorkTime param. Since we can predict
// the execution time, we can also adjust Expiry.
e.Expiry += options.WorkTime
} else {
target = e.powToFirstBit(options.PoW)
}
rlp := e.rlpWithoutNonce()
buf := make([]byte, len(rlp)+8)
copy(buf, rlp)
asAnInt := new(big.Int)
finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
for nonce := uint64(0); time.Now().UnixNano() < finish; {
for i := 0; i < 1024; i++ {
binary.BigEndian.PutUint64(buf[len(rlp):], nonce)
h := crypto.Keccak256(buf)
asAnInt.SetBytes(h)
leadingZeros := 256 - asAnInt.BitLen()
if leadingZeros > bestLeadingZeros {
e.Nonce, bestLeadingZeros = nonce, leadingZeros
if target > 0 && bestLeadingZeros >= target {
return nil
}
}
nonce++
}
}
if target > 0 && bestLeadingZeros < target {
return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
}
return nil
}
// PoW computes (if necessary) and returns the proof of work target
// of the envelope.
func (e *Envelope) PoW() float64 {
if e.pow == 0 {
e.calculatePoW(0)
}
return e.pow
}
func (e *Envelope) calculatePoW(diff uint32) {
rlp := e.rlpWithoutNonce()
buf := make([]byte, len(rlp)+8)
copy(buf, rlp)
binary.BigEndian.PutUint64(buf[len(rlp):], e.Nonce)
powHash := new(big.Int).SetBytes(crypto.Keccak256(buf))
leadingZeroes := 256 - powHash.BitLen()
x := gmath.Pow(2, float64(leadingZeroes))
x /= float64(len(rlp))
x /= float64(e.TTL + diff)
e.pow = x
}
func (e *Envelope) powToFirstBit(pow float64) int {
x := pow
x *= float64(e.size())
x *= float64(e.TTL)
bits := gmath.Log2(x)
bits = gmath.Ceil(bits)
res := int(bits)
if res < 1 {
res = 1
}
return res
}
// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
func (e *Envelope) Hash() common.Hash {
if (e.hash == common.Hash{}) {
encoded, _ := rlp.EncodeToBytes(e)
e.hash = crypto.Keccak256Hash(encoded)
}
return e.hash
}
// DecodeRLP decodes an Envelope from an RLP data stream.
func (e *Envelope) DecodeRLP(s *rlp.Stream) error {
raw, err := s.Raw()
if err != nil {
return err
}
// The decoding of Envelope uses the struct fields but also needs
// to compute the hash of the whole RLP-encoded envelope. This
// type has the same structure as Envelope but is not an
// rlp.Decoder (does not implement DecodeRLP function).
// Only public members will be encoded.
type rlpenv Envelope
if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil {
return err
}
e.hash = crypto.Keccak256Hash(raw)
return nil
}
// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) {
message := &ReceivedMessage{Raw: e.Data}
err := message.decryptAsymmetric(key)
switch err {
case nil:
return message, nil
case ecies.ErrInvalidPublicKey: // addressed to somebody else
return nil, err
default:
return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
}
}
// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
msg = &ReceivedMessage{Raw: e.Data}
err = msg.decryptSymmetric(key)
if err != nil {
msg = nil
}
return msg, err
}
// Open tries to decrypt an envelope, and populates the message fields in case of success.
func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
if watcher == nil {
return nil
}
// The API interface forbids filters doing both symmetric and asymmetric encryption.
if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() {
return nil
}
if watcher.expectsAsymmetricEncryption() {
msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
if msg != nil {
msg.Dst = &watcher.KeyAsym.PublicKey
}
} else if watcher.expectsSymmetricEncryption() {
msg, _ = e.OpenSymmetric(watcher.KeySym)
if msg != nil {
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
}
}
if msg != nil {
ok := msg.ValidateAndParse()
if !ok {
return nil
}
msg.Topic = e.Topic
msg.PoW = e.PoW()
msg.TTL = e.TTL
msg.Sent = e.Expiry - e.TTL
msg.EnvelopeHash = e.Hash()
}
return msg
}
// Bloom maps 4-bytes Topic into 64-byte bloom filter with 3 bits set (at most).
func (e *Envelope) Bloom() []byte {
if e.bloom == nil {
e.bloom = TopicToBloom(e.Topic)
}
return e.bloom
}
// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes)
func TopicToBloom(topic TopicType) []byte {
b := make([]byte, BloomFilterSize)
var index [3]int
for j := 0; j < 3; j++ {
index[j] = int(topic[j])
if (topic[3] & (1 << uint(j))) != 0 {
index[j] += 256
}
}
for j := 0; j < 3; j++ {
byteIndex := index[j] / 8
bitIndex := index[j] % 8
b[byteIndex] = (1 << uint(bitIndex))
}
return b
}
// GetEnvelope retrieves an envelope from the message queue by its hash.
// It returns nil if the envelope can not be found.
func (w *Whisper) GetEnvelope(hash common.Hash) *Envelope {
w.poolMu.RLock()
defer w.poolMu.RUnlock()
return w.envelopes[hash]
}
// EnvelopeError code and optional description of the error.
type EnvelopeError struct {
Hash common.Hash
Code uint
Description string
}
// ErrorToEnvelopeError converts common golang error into EnvelopeError with a code.
func ErrorToEnvelopeError(hash common.Hash, err error) EnvelopeError {
code := EnvelopeOtherError
switch err.(type) {
case TimeSyncError:
code = EnvelopeTimeNotSynced
}
return EnvelopeError{
Hash: hash,
Code: code,
Description: err.Error(),
}
}

View file

@ -1,92 +0,0 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the tests associated with the Whisper protocol Envelope object.
package whisper
import (
mrand "math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/crypto"
)
func TestPoWCalculationsWithNoLeadingZeros(t *testing.T) {
e := Envelope{
TTL: 1,
Data: []byte{0xde, 0xad, 0xbe, 0xef},
Nonce: 100000,
}
e.calculatePoW(0)
if e.pow != 0.07692307692307693 {
t.Fatalf("invalid PoW calculation. Expected 0.07692307692307693, got %v", e.pow)
}
}
func TestPoWCalculationsWith8LeadingZeros(t *testing.T) {
e := Envelope{
TTL: 1,
Data: []byte{0xde, 0xad, 0xbe, 0xef},
Nonce: 276,
}
e.calculatePoW(0)
if e.pow != 19.692307692307693 {
t.Fatalf("invalid PoW calculation. Expected 19.692307692307693, got %v", e.pow)
}
}
func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) {
symKey := make([]byte, aesKeyLength)
mrand.Read(symKey) // nolint: gosec
asymKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params := MessageParams{
PoW: 0.01,
WorkTime: 1,
TTL: uint32(mrand.Intn(1024)), // nolint: gosec
Payload: make([]byte, 50),
KeySym: symKey,
Dst: nil,
}
mrand.Read(params.Payload) // nolint: gosec
msg, err := NewSentMessage(&params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
e, err := msg.Wrap(&params, time.Now())
if err != nil {
t.Fatalf("Failed to Wrap the message in an envelope with seed %d: %s", seed, err)
}
f := Filter{KeySym: symKey, KeyAsym: asymKey}
decrypted := e.Open(&f)
if decrypted != nil {
t.Fatalf("Managed to decrypt a message with an invalid filter, seed %d", seed)
}
}

View file

@ -1,53 +0,0 @@
package whisper
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enode"
)
// EventType used to define known envelope events.
type EventType string
const (
// EventEnvelopeSent fires when envelope was sent to a peer.
EventEnvelopeSent EventType = "envelope.sent"
// EventEnvelopeExpired fires when envelop expired
EventEnvelopeExpired EventType = "envelope.expired"
// EventEnvelopeReceived is sent once envelope was received from a peer.
// EventEnvelopeReceived must be sent to the feed even if envelope was previously in the cache.
// And event, ideally, should contain information about peer that sent envelope to us.
EventEnvelopeReceived EventType = "envelope.received"
// EventBatchAcknowledged is sent when batch of envelopes was acknowledged by a peer.
EventBatchAcknowledged EventType = "batch.acknowleged"
// EventEnvelopeAvailable fires when envelop is available for filters
EventEnvelopeAvailable EventType = "envelope.available"
// EventMailServerRequestSent fires when such request is sent.
EventMailServerRequestSent EventType = "mailserver.request.sent"
// EventMailServerRequestCompleted fires after mailserver sends all the requested messages
EventMailServerRequestCompleted EventType = "mailserver.request.completed"
// EventMailServerRequestExpired fires after mailserver the request TTL ends.
// This event is independent and concurrent to EventMailServerRequestCompleted.
// Request should be considered as expired only if expiry event was received first.
EventMailServerRequestExpired EventType = "mailserver.request.expired"
// EventMailServerEnvelopeArchived fires after an envelope has been archived
EventMailServerEnvelopeArchived EventType = "mailserver.envelope.archived"
// EventMailServerSyncFinished fires when the sync of messages is finished.
EventMailServerSyncFinished EventType = "mailserver.sync.finished"
)
// EnvelopeEvent used for envelopes events.
type EnvelopeEvent struct {
Event EventType
Topic TopicType
Hash common.Hash
Batch common.Hash
Peer enode.ID
Data interface{}
}
// SyncEventResponse is a response from the Mail Server
// form which the peer received envelopes.
type SyncEventResponse struct {
Cursor []byte
Error string
}

View file

@ -1,293 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"crypto/ecdsa"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)
// MessageStore defines interface for temporary message store.
type MessageStore interface {
Add(*ReceivedMessage) error
Pop() ([]*ReceivedMessage, error)
}
// NewMemoryMessageStore returns pointer to an instance of the MemoryMessageStore.
func NewMemoryMessageStore() *MemoryMessageStore {
return &MemoryMessageStore{
messages: map[common.Hash]*ReceivedMessage{},
}
}
// MemoryMessageStore stores massages in memory hash table.
type MemoryMessageStore struct {
mu sync.Mutex
messages map[common.Hash]*ReceivedMessage
}
// Add adds message to store.
func (store *MemoryMessageStore) Add(msg *ReceivedMessage) error {
store.mu.Lock()
defer store.mu.Unlock()
if _, exist := store.messages[msg.EnvelopeHash]; !exist {
store.messages[msg.EnvelopeHash] = msg
}
return nil
}
// Pop returns all available messages and cleans the store.
func (store *MemoryMessageStore) Pop() ([]*ReceivedMessage, error) {
store.mu.Lock()
defer store.mu.Unlock()
all := make([]*ReceivedMessage, 0, len(store.messages))
for hash, msg := range store.messages {
delete(store.messages, hash)
all = append(all, msg)
}
return all, nil
}
// Filter represents a Whisper message filter
type Filter struct {
Src *ecdsa.PublicKey // Sender of the message
KeyAsym *ecdsa.PrivateKey // Private Key of recipient
KeySym []byte // Key associated with the Topic
Topics [][]byte // Topics to filter messages with
PoW float64 // Proof of work as described in the Whisper spec
AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
id string // unique identifier
Messages MessageStore
}
// Filters represents a collection of filters
type Filters struct {
watchers map[string]*Filter
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
whisper *Whisper
mutex sync.RWMutex
}
// NewFilters returns a newly created filter collection
func NewFilters(w *Whisper) *Filters {
return &Filters{
watchers: make(map[string]*Filter),
topicMatcher: make(map[TopicType]map[*Filter]struct{}),
allTopicsMatcher: make(map[*Filter]struct{}),
whisper: w,
}
}
// Install will add a new filter to the filter collection
func (fs *Filters) Install(watcher *Filter) (string, error) {
if watcher.KeySym != nil && watcher.KeyAsym != nil {
return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
}
id, err := GenerateRandomID()
if err != nil {
return "", err
}
fs.mutex.Lock()
defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
return "", fmt.Errorf("failed to generate unique ID")
}
if watcher.expectsSymmetricEncryption() {
watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
}
watcher.id = id
fs.watchers[id] = watcher
fs.addTopicMatcher(watcher)
return id, err
}
// Uninstall will remove a filter whose id has been specified from
// the filter collection
func (fs *Filters) Uninstall(id string) bool {
fs.mutex.Lock()
defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
fs.removeFromTopicMatchers(fs.watchers[id])
delete(fs.watchers, id)
return true
}
return false
}
// addTopicMatcher adds a filter to the topic matchers.
// If the filter's Topics array is empty, it will be tried on every topic.
// Otherwise, it will be tried on the topics specified.
func (fs *Filters) addTopicMatcher(watcher *Filter) {
if len(watcher.Topics) == 0 {
fs.allTopicsMatcher[watcher] = struct{}{}
} else {
for _, t := range watcher.Topics {
topic := BytesToTopic(t)
if fs.topicMatcher[topic] == nil {
fs.topicMatcher[topic] = make(map[*Filter]struct{})
}
fs.topicMatcher[topic][watcher] = struct{}{}
}
}
}
// removeFromTopicMatchers removes a filter from the topic matchers
func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
delete(fs.allTopicsMatcher, watcher)
for _, topic := range watcher.Topics {
delete(fs.topicMatcher[BytesToTopic(topic)], watcher)
}
}
// getWatchersByTopic returns a slice containing the filters that
// match a specific topic
func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter {
res := make([]*Filter, 0, len(fs.allTopicsMatcher))
for watcher := range fs.allTopicsMatcher {
res = append(res, watcher)
}
for watcher := range fs.topicMatcher[topic] {
res = append(res, watcher)
}
return res
}
// Get returns a filter from the collection with a specific ID
func (fs *Filters) Get(id string) *Filter {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
return fs.watchers[id]
}
// NotifyWatchers notifies any filter that has declared interest
// for the envelope's topic.
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
var msg *ReceivedMessage
fs.mutex.RLock()
defer fs.mutex.RUnlock()
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))
continue
}
var match bool
if msg != nil {
match = watcher.MatchMessage(msg)
} else {
match = watcher.MatchEnvelope(env)
if match {
msg = env.Open(watcher)
if msg == nil {
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id)
}
} else {
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id)
}
}
if match && msg != nil {
msg.P2P = p2pMessage
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
watcher.Trigger(msg)
}
}
}
}
func (f *Filter) expectsAsymmetricEncryption() bool {
return f.KeyAsym != nil
}
func (f *Filter) expectsSymmetricEncryption() bool {
return f.KeySym != nil
}
// Trigger adds a yet-unknown message to the filter's list of
// received messages.
func (f *Filter) Trigger(msg *ReceivedMessage) {
err := f.Messages.Add(msg)
if err != nil {
log.Error("failed to add msg into the filters store", "hash", msg.EnvelopeHash, "error", err)
}
}
// Retrieve will return the list of all received messages associated
// to a filter.
func (f *Filter) Retrieve() []*ReceivedMessage {
msgs, err := f.Messages.Pop()
if err != nil {
log.Error("failed to retrieve messages from filter store", "error", err)
return nil
}
return msgs
}
// MatchMessage checks if the filter matches an already decrypted
// message (i.e. a Message that has already been handled by
// MatchEnvelope when checked by a previous filter).
// Topics are not checked here, since this is done by topic matchers.
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
if f.PoW > 0 && msg.PoW < f.PoW {
return false
}
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
return f.SymKeyHash == msg.SymKeyHash
}
return false
}
// MatchEnvelope checks if it's worth decrypting the message. If
// it returns `true`, client code is expected to attempt decrypting
// the message and subsequently call MatchMessage.
// Topics are not checked here, since this is done by topic matchers.
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
}

View file

@ -1,826 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"math/big"
mrand "math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
var seed int64
// InitSingleTest should be called in the beginning of every
// test, which uses RNG, in order to make the tests
// reproducibility independent of their sequence.
func InitSingleTest() {
seed = time.Now().Unix()
mrand.Seed(seed)
}
type FilterTestCase struct {
f *Filter
id string
alive bool
msgCnt int
}
func generateFilter(t *testing.T, symmetric bool) (*Filter, error) {
var f Filter
f.Messages = 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, 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 generateTestCases(t *testing.T, SizeTestFilters int) []FilterTestCase {
cases := make([]FilterTestCase, SizeTestFilters)
for i := 0; i < SizeTestFilters; i++ {
f, _ := generateFilter(t, true)
cases[i].f = f
cases[i].alive = mrand.Int()&1 == 0 // nolint: gosec
}
return cases
}
func TestInstallFilters(t *testing.T) {
InitSingleTest()
const SizeTestFilters = 256
w := New(&Config{})
filters := NewFilters(w)
tst := generateTestCases(t, SizeTestFilters)
var err error
var j string
for i := 0; i < SizeTestFilters; i++ {
j, err = filters.Install(tst[i].f)
if err != nil {
t.Fatalf("seed %d: failed to install filter: %s", seed, err)
}
tst[i].id = j
if len(j) != keyIDSize*2 {
t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j))
}
}
for _, testCase := range tst {
if !testCase.alive {
filters.Uninstall(testCase.id)
}
}
for i, testCase := range tst {
fil := filters.Get(testCase.id)
exist := fil != nil
if exist != testCase.alive {
t.Fatalf("seed %d: failed alive: %d, %v, %v", seed, i, exist, testCase.alive)
}
if exist && fil.PoW != testCase.f.PoW {
t.Fatalf("seed %d: failed Get: %d, %v, %v", seed, i, exist, testCase.alive)
}
}
}
func TestInstallSymKeyGeneratesHash(t *testing.T) {
InitSingleTest()
w := New(&Config{})
filters := NewFilters(w)
filter, _ := generateFilter(t, true)
// save the current SymKeyHash for comparison
initialSymKeyHash := filter.SymKeyHash
// ensure the SymKeyHash is invalid, for Install to recreate it
var invalid common.Hash
filter.SymKeyHash = invalid
_, err := filters.Install(filter)
if err != nil {
t.Fatalf("Error installing the filter: %s", err)
}
for i, b := range filter.SymKeyHash {
if b != initialSymKeyHash[i] {
t.Fatalf("The filter's symmetric key hash was not properly generated by Install")
}
}
}
func TestInstallIdenticalFilters(t *testing.T) {
InitSingleTest()
w := New(&Config{})
filters := NewFilters(w)
filter1, _ := generateFilter(t, true)
// Copy the first filter since some of its fields
// are randomly gnerated.
filter2 := &Filter{
KeySym: filter1.KeySym,
Topics: filter1.Topics,
PoW: filter1.PoW,
AllowP2P: filter1.AllowP2P,
Messages: NewMemoryMessageStore(),
}
_, err := filters.Install(filter1)
if err != nil {
t.Fatalf("Error installing the first filter with seed %d: %s", seed, err)
}
_, err = filters.Install(filter2)
if err != nil {
t.Fatalf("Error installing the second filter with seed %d: %s", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("Error generating message parameters with seed %d: %s", seed, err)
}
params.KeySym = filter1.KeySym
params.Topic = BytesToTopic(filter1.Topics[0])
filter1.Src = &params.Src.PublicKey
filter2.Src = &params.Src.PublicKey
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
msg := env.Open(filter1)
if msg == nil {
t.Fatalf("failed to Open with filter1")
}
if !filter1.MatchEnvelope(env) {
t.Fatalf("failed matching with the first filter")
}
if !filter2.MatchEnvelope(env) {
t.Fatalf("failed matching with the first filter")
}
if !filter1.MatchMessage(msg) {
t.Fatalf("failed matching with the second filter")
}
if !filter2.MatchMessage(msg) {
t.Fatalf("failed matching with the second filter")
}
}
func TestInstallFilterWithSymAndAsymKeys(t *testing.T) {
InitSingleTest()
w := New(&Config{})
filters := NewFilters(w)
filter1, _ := generateFilter(t, true)
asymKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("Unable to create asymetric keys: %v", err)
}
// Copy the first filter since some of its fields
// are randomly gnerated.
filter := &Filter{
KeySym: filter1.KeySym,
KeyAsym: asymKey,
Topics: filter1.Topics,
PoW: filter1.PoW,
AllowP2P: filter1.AllowP2P,
Messages: NewMemoryMessageStore(),
}
_, err = filters.Install(filter)
if err == nil {
t.Fatalf("Error detecting that a filter had both an asymmetric and symmetric key, with seed %d", seed)
}
}
func TestComparePubKey(t *testing.T) {
InitSingleTest()
key1, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate first key with seed %d: %s.", seed, err)
}
key2, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate second key with seed %d: %s.", seed, err)
}
if IsPubKeyEqual(&key1.PublicKey, &key2.PublicKey) {
t.Fatalf("public keys are equal, seed %d.", seed)
}
// generate key3 == key1
mrand.Seed(seed)
key3, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate third key with seed %d: %s.", seed, err)
}
if IsPubKeyEqual(&key1.PublicKey, &key3.PublicKey) {
t.Fatalf("key1 == key3, seed %d.", seed)
}
}
func TestMatchEnvelope(t *testing.T) {
InitSingleTest()
fsym, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
fasym, err := generateFilter(t, false)
if err != nil {
t.Fatalf("failed generateFilter() with seed %d: %s.", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.Topic[0] = 0xFF // topic mismatch
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
_, err = msg.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
// encrypt symmetrically
i := mrand.Int() % 4 // nolint: gosec
fsym.Topics[i] = params.Topic[:]
fasym.Topics[i] = params.Topic[:]
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)
}
// symmetric + matching topic: match
match := fsym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed)
}
// symmetric + matching topic + insufficient PoW: mismatch
fsym.PoW = env.PoW() + 1.0
match = fsym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(symmetric + matching topic + insufficient PoW) asymmetric with seed %d.", seed)
}
// symmetric + matching topic + sufficient PoW: match
fsym.PoW = env.PoW() / 2
match = fsym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(symmetric + matching topic + sufficient PoW) with seed %d.", seed)
}
// symmetric + topics are nil (wildcard): match
prevTopics := fsym.Topics
fsym.Topics = nil
match = fsym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(symmetric + topics are nil) with seed %d.", seed)
}
fsym.Topics = prevTopics
// encrypt asymmetrically
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
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)
}
// encryption method mismatch
match = fsym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
}
// asymmetric + mismatching topic: mismatch
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + mismatching topic) with seed %d.", seed)
}
// asymmetric + matching topic: match
fasym.Topics[i] = fasym.Topics[i+1]
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + matching topic) with seed %d.", seed)
}
// asymmetric + filter without topic (wildcard): match
fasym.Topics = nil
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + filter without topic) with seed %d.", seed)
}
// asymmetric + insufficient PoW: mismatch
fasym.PoW = env.PoW() + 1.0
match = fasym.MatchEnvelope(env)
if match {
t.Fatalf("failed MatchEnvelope(asymmetric + insufficient PoW) with seed %d.", seed)
}
// asymmetric + sufficient PoW: match
fasym.PoW = env.PoW() / 2
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(asymmetric + sufficient PoW) with seed %d.", seed)
}
// filter without topic + envelope without topic: match
env.Topic = TopicType{}
match = fasym.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed)
}
// filter with topic + envelope without topic: mismatch
fasym.Topics = fsym.Topics
match = fasym.MatchEnvelope(env)
if !match {
// topic mismatch should have no affect, as topics are handled by topic matchers
t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed)
}
}
func TestMatchMessageSym(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
f, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
const index = 1
params.KeySym = f.KeySym
params.Topic = BytesToTopic(f.Topics[index])
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
msg := env.Open(f)
if msg == nil {
t.Fatalf("failed Open with seed %d.", seed)
}
// Src: match
*f.Src.X = *params.Src.PublicKey.X
*f.Src.Y = *params.Src.PublicKey.Y
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(src match) with seed %d.", seed)
}
// insufficient PoW: mismatch
f.PoW = msg.PoW + 1.0
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed)
}
// sufficient PoW: match
f.PoW = msg.PoW / 2
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed)
}
// topic mismatch
f.Topics[index][0]++
if !f.MatchMessage(msg) {
// topic mismatch should have no affect, as topics are handled by topic matchers
t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed)
}
f.Topics[index][0]--
// key mismatch
f.SymKeyHash[0]++
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed)
}
f.SymKeyHash[0]--
// Src absent: match
f.Src = nil
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed)
}
// key hash mismatch
h := f.SymKeyHash
f.SymKeyHash = common.Hash{}
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key hash mismatch) with seed %d.", seed)
}
f.SymKeyHash = h
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key hash match) with seed %d.", seed)
}
// encryption method mismatch
f.KeySym = nil
f.KeyAsym, err = crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
}
}
func TestMatchMessageAsym(t *testing.T) {
InitSingleTest()
f, err := generateFilter(t, false)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
const index = 1
params.Topic = BytesToTopic(f.Topics[index])
params.Dst = &f.KeyAsym.PublicKey
keySymOrig := params.KeySym
params.KeySym = nil
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
msg := env.Open(f)
if msg == nil {
t.Fatalf("failed to open with seed %d.", seed)
}
// Src: match
*f.Src.X = *params.Src.PublicKey.X
*f.Src.Y = *params.Src.PublicKey.Y
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchMessage(src match) with seed %d.", seed)
}
// insufficient PoW: mismatch
f.PoW = msg.PoW + 1.0
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed)
}
// sufficient PoW: match
f.PoW = msg.PoW / 2
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed)
}
// topic mismatch
f.Topics[index][0]++
if !f.MatchMessage(msg) {
// topic mismatch should have no affect, as topics are handled by topic matchers
t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed)
}
f.Topics[index][0]--
// key mismatch
prev := *f.KeyAsym.PublicKey.X
zero := *big.NewInt(0)
*f.KeyAsym.PublicKey.X = zero
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed)
}
*f.KeyAsym.PublicKey.X = prev
// Src absent: match
f.Src = nil
if !f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed)
}
// encryption method mismatch
f.KeySym = keySymOrig
f.KeyAsym = nil
if f.MatchMessage(msg) {
t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
}
}
func cloneFilter(orig *Filter) *Filter {
var clone Filter
clone.Messages = NewMemoryMessageStore()
clone.Src = orig.Src
clone.KeyAsym = orig.KeyAsym
clone.KeySym = orig.KeySym
clone.Topics = orig.Topics
clone.PoW = orig.PoW
clone.AllowP2P = orig.AllowP2P
clone.SymKeyHash = orig.SymKeyHash
return &clone
}
func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
return nil
}
params.KeySym = f.KeySym
params.Topic = BytesToTopic(f.Topics[2])
sentMessage, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := sentMessage.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
return nil
}
return env
}
func TestWatchers(t *testing.T) {
InitSingleTest()
const NumFilters = 16
const NumMessages = 256
var i int
var j uint32
var e *Envelope
var x, firstID string
var err error
w := New(&Config{})
filters := NewFilters(w)
tst := generateTestCases(t, NumFilters)
for i = 0; i < NumFilters; i++ {
tst[i].f.Src = nil
x, err = filters.Install(tst[i].f)
if err != nil {
t.Fatalf("failed to install filter with seed %d: %s.", seed, err)
}
tst[i].id = x
if len(firstID) == 0 {
firstID = x
}
}
lastID := x
var envelopes [NumMessages]*Envelope
for i = 0; i < NumMessages; i++ {
j = mrand.Uint32() % NumFilters // nolint: gosec
e = generateCompatibeEnvelope(t, tst[j].f)
envelopes[i] = e
tst[j].msgCnt++
}
for i = 0; i < NumMessages; i++ {
filters.NotifyWatchers(envelopes[i], false)
}
var total int
var mail []*ReceivedMessage
var count [NumFilters]int
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
count[i] = len(mail)
total += len(mail)
}
if total != NumMessages {
t.Fatalf("failed with seed %d: total = %d, want: %d.", seed, total, NumMessages)
}
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
if len(mail) != 0 {
t.Fatalf("failed with seed %d: i = %d.", seed, i)
}
if tst[i].msgCnt != count[i] {
t.Fatalf("failed with seed %d: count[%d]: get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
}
}
// another round with a cloned filter
clone := cloneFilter(tst[0].f)
filters.Uninstall(lastID)
total = 0
last := NumFilters - 1
tst[last].f = clone
_, _ = filters.Install(clone)
for i = 0; i < NumFilters; i++ {
tst[i].msgCnt = 0
count[i] = 0
}
// make sure that the first watcher receives at least one message
e = generateCompatibeEnvelope(t, tst[0].f)
envelopes[0] = e
tst[0].msgCnt++
for i = 1; i < NumMessages; i++ {
j = mrand.Uint32() % NumFilters // nolint: gosec
e = generateCompatibeEnvelope(t, tst[j].f)
envelopes[i] = e
tst[j].msgCnt++
}
for i = 0; i < NumMessages; i++ {
filters.NotifyWatchers(envelopes[i], false)
}
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
count[i] = len(mail)
total += len(mail)
}
combined := tst[0].msgCnt + tst[last].msgCnt
if total != NumMessages+count[0] {
t.Fatalf("failed with seed %d: total = %d, count[0] = %d.", seed, total, count[0])
}
if combined != count[0] {
t.Fatalf("failed with seed %d: combined = %d, count[0] = %d.", seed, combined, count[0])
}
if combined != count[last] {
t.Fatalf("failed with seed %d: combined = %d, count[last] = %d.", seed, combined, count[last])
}
for i = 1; i < NumFilters-1; i++ {
mail = tst[i].f.Retrieve()
if len(mail) != 0 {
t.Fatalf("failed with seed %d: i = %d.", seed, i)
}
if tst[i].msgCnt != count[i] {
t.Fatalf("failed with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
}
}
// test AcceptP2P
total = 0
filters.NotifyWatchers(envelopes[0], true)
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
total += len(mail)
}
if total != 0 {
t.Fatalf("failed with seed %d: total: got %d, want 0.", seed, total)
}
f := filters.Get(firstID)
if f == nil {
t.Fatalf("failed to get the filter with seed %d.", seed)
}
f.AllowP2P = true
total = 0
filters.NotifyWatchers(envelopes[0], true)
for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve()
total += len(mail)
}
if total != 1 {
t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total)
}
}
func TestVariableTopics(t *testing.T) {
InitSingleTest()
const lastTopicByte = 3
var match bool
params, err := generateMessageParams()
if err != nil {
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)
}
env, err := msg.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f, err := generateFilter(t, true)
if err != nil {
t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
}
for i := 0; i < 4; i++ {
env.Topic = BytesToTopic(f.Topics[i])
match = f.MatchEnvelope(env)
if !match {
t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i)
}
f.Topics[i][lastTopicByte]++
match = f.MatchEnvelope(env)
if !match {
// topic mismatch should have no affect, as topics are handled by topic matchers
t.Fatalf("MatchEnvelope symmetric with seed %d, step %d.", seed, i)
}
}
}

View file

@ -1,14 +0,0 @@
// +build gofuzz
package whisper
func Fuzz(data []byte) int {
if len(data) < 2 {
return -1
}
msg := &ReceivedMessage{Raw: data}
msg.ValidateAndParse()
return 0
}

View file

@ -1,66 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package whisper
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*criteriaOverride)(nil)
// MarshalJSON marshals type Criteria to a json string
func (c Criteria) MarshalJSON() ([]byte, error) {
type Criteria struct {
SymKeyID string `json:"symKeyID"`
PrivateKeyID string `json:"privateKeyID"`
Sig hexutil.Bytes `json:"sig"`
MinPow float64 `json:"minPow"`
Topics []TopicType `json:"topics"`
AllowP2P bool `json:"allowP2P"`
}
var enc Criteria
enc.SymKeyID = c.SymKeyID
enc.PrivateKeyID = c.PrivateKeyID
enc.Sig = c.Sig
enc.MinPow = c.MinPow
enc.Topics = c.Topics
enc.AllowP2P = c.AllowP2P
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals type Criteria to a json string
func (c *Criteria) UnmarshalJSON(input []byte) error {
type Criteria struct {
SymKeyID *string `json:"symKeyID"`
PrivateKeyID *string `json:"privateKeyID"`
Sig *hexutil.Bytes `json:"sig"`
MinPow *float64 `json:"minPow"`
Topics []TopicType `json:"topics"`
AllowP2P *bool `json:"allowP2P"`
}
var dec Criteria
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.SymKeyID != nil {
c.SymKeyID = *dec.SymKeyID
}
if dec.PrivateKeyID != nil {
c.PrivateKeyID = *dec.PrivateKeyID
}
if dec.Sig != nil {
c.Sig = *dec.Sig
}
if dec.MinPow != nil {
c.MinPow = *dec.MinPow
}
if dec.Topics != nil {
c.Topics = dec.Topics
}
if dec.AllowP2P != nil {
c.AllowP2P = *dec.AllowP2P
}
return nil
}

View file

@ -1,84 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package whisper
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*messageOverride)(nil)
// MarshalJSON marshals type Message to a json string
func (m Message) MarshalJSON() ([]byte, error) {
type Message struct {
Sig hexutil.Bytes `json:"sig,omitempty"`
TTL uint32 `json:"ttl"`
Timestamp uint32 `json:"timestamp"`
Topic TopicType `json:"topic"`
Payload hexutil.Bytes `json:"payload"`
Padding hexutil.Bytes `json:"padding"`
PoW float64 `json:"pow"`
Hash hexutil.Bytes `json:"hash"`
Dst hexutil.Bytes `json:"recipientPublicKey,omitempty"`
}
var enc Message
enc.Sig = m.Sig
enc.TTL = m.TTL
enc.Timestamp = m.Timestamp
enc.Topic = m.Topic
enc.Payload = m.Payload
enc.Padding = m.Padding
enc.PoW = m.PoW
enc.Hash = m.Hash
enc.Dst = m.Dst
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals type Message to a json string
func (m *Message) UnmarshalJSON(input []byte) error {
type Message struct {
Sig *hexutil.Bytes `json:"sig,omitempty"`
TTL *uint32 `json:"ttl"`
Timestamp *uint32 `json:"timestamp"`
Topic *TopicType `json:"topic"`
Payload *hexutil.Bytes `json:"payload"`
Padding *hexutil.Bytes `json:"padding"`
PoW *float64 `json:"pow"`
Hash *hexutil.Bytes `json:"hash"`
Dst *hexutil.Bytes `json:"recipientPublicKey,omitempty"`
}
var dec Message
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Sig != nil {
m.Sig = *dec.Sig
}
if dec.TTL != nil {
m.TTL = *dec.TTL
}
if dec.Timestamp != nil {
m.Timestamp = *dec.Timestamp
}
if dec.Topic != nil {
m.Topic = *dec.Topic
}
if dec.Payload != nil {
m.Payload = *dec.Payload
}
if dec.Padding != nil {
m.Padding = *dec.Padding
}
if dec.PoW != nil {
m.PoW = *dec.PoW
}
if dec.Hash != nil {
m.Hash = *dec.Hash
}
if dec.Dst != nil {
m.Dst = *dec.Dst
}
return nil
}

View file

@ -1,90 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package whisper
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*newMessageOverride)(nil)
// MarshalJSON marshals type NewMessage to a json string
func (n NewMessage) MarshalJSON() ([]byte, error) {
type NewMessage struct {
SymKeyID string `json:"symKeyID"`
PublicKey hexutil.Bytes `json:"pubKey"`
Sig string `json:"sig"`
TTL uint32 `json:"ttl"`
Topic TopicType `json:"topic"`
Payload hexutil.Bytes `json:"payload"`
Padding hexutil.Bytes `json:"padding"`
PowTime uint32 `json:"powTime"`
PowTarget float64 `json:"powTarget"`
TargetPeer string `json:"targetPeer"`
}
var enc NewMessage
enc.SymKeyID = n.SymKeyID
enc.PublicKey = n.PublicKey
enc.Sig = n.Sig
enc.TTL = n.TTL
enc.Topic = n.Topic
enc.Payload = n.Payload
enc.Padding = n.Padding
enc.PowTime = n.PowTime
enc.PowTarget = n.PowTarget
enc.TargetPeer = n.TargetPeer
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals type NewMessage to a json string
func (n *NewMessage) UnmarshalJSON(input []byte) error {
type NewMessage struct {
SymKeyID *string `json:"symKeyID"`
PublicKey *hexutil.Bytes `json:"pubKey"`
Sig *string `json:"sig"`
TTL *uint32 `json:"ttl"`
Topic *TopicType `json:"topic"`
Payload *hexutil.Bytes `json:"payload"`
Padding *hexutil.Bytes `json:"padding"`
PowTime *uint32 `json:"powTime"`
PowTarget *float64 `json:"powTarget"`
TargetPeer *string `json:"targetPeer"`
}
var dec NewMessage
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.SymKeyID != nil {
n.SymKeyID = *dec.SymKeyID
}
if dec.PublicKey != nil {
n.PublicKey = *dec.PublicKey
}
if dec.Sig != nil {
n.Sig = *dec.Sig
}
if dec.TTL != nil {
n.TTL = *dec.TTL
}
if dec.Topic != nil {
n.Topic = *dec.Topic
}
if dec.Payload != nil {
n.Payload = *dec.Payload
}
if dec.Padding != nil {
n.Padding = *dec.Padding
}
if dec.PowTime != nil {
n.PowTime = *dec.PowTime
}
if dec.PowTarget != nil {
n.PowTarget = *dec.PowTarget
}
if dec.TargetPeer != nil {
n.TargetPeer = *dec.TargetPeer
}
return nil
}

View file

@ -1,204 +0,0 @@
package whisper
import (
"bytes"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
)
const (
mailServerFailedPayloadPrefix = "ERROR="
cursorSize = 36
)
// MailServer represents a mail server, capable of
// archiving the old messages for subsequent delivery
// to the peers. Any implementation must ensure that both
// functions are thread-safe. Also, they must return ASAP.
// DeliverMail should use directMessagesCode for delivery,
// in order to bypass the expiry checks.
type MailServer interface {
Archive(env *Envelope)
DeliverMail(peerID []byte, req *Envelope) // DEPRECATED; user Deliver instead
Deliver(peerID []byte, req MessagesRequest)
SyncMail(peerID []byte, req SyncMailRequest) error
}
// SyncMailRequest contains details which envelopes should be synced
// between Mail Servers.
type SyncMailRequest struct {
// Lower is a lower bound of time range for which messages are requested.
Lower uint32
// Upper is a lower bound of time range for which messages are requested.
Upper uint32
// Bloom is a bloom filter to filter envelopes.
Bloom []byte
// Limit is the max number of envelopes to return.
Limit uint32
// Cursor is used for pagination of the results.
Cursor []byte
}
// Validate checks request's fields if they are valid.
func (r SyncMailRequest) Validate() error {
if r.Limit == 0 {
return errors.New("invalid 'Limit' value, expected value greater than 0")
}
if r.Limit > MaxLimitInSyncMailRequest {
return fmt.Errorf("invalid 'Limit' value, expected value lower than %d", MaxLimitInSyncMailRequest)
}
if r.Lower > r.Upper {
return errors.New("invalid 'Lower' value, can't be greater than 'Upper'")
}
return nil
}
// SyncResponse is a struct representing a response sent to the peer
// asking for syncing archived envelopes.
type SyncResponse struct {
Envelopes []*Envelope
Cursor []byte
Final bool // if true it means all envelopes were processed
Error string
}
// RawSyncResponse is a struct representing a response sent to the peer
// asking for syncing archived envelopes.
type RawSyncResponse struct {
Envelopes []rlp.RawValue
Cursor []byte
Final bool // if true it means all envelopes were processed
Error string
}
func invalidResponseSizeError(size int) error {
return fmt.Errorf("unexpected payload size: %d", size)
}
// CreateMailServerRequestCompletedPayload creates a payload representing
// a successful request to mailserver
func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash common.Hash, cursor []byte) []byte {
payload := make([]byte, len(requestID))
copy(payload, requestID[:])
payload = append(payload, lastEnvelopeHash[:]...)
payload = append(payload, cursor...)
return payload
}
// CreateMailServerRequestFailedPayload creates a payload representing
// a failed request to a mailserver
func CreateMailServerRequestFailedPayload(requestID common.Hash, err error) []byte {
payload := []byte(mailServerFailedPayloadPrefix)
payload = append(payload, requestID[:]...)
payload = append(payload, []byte(err.Error())...)
return payload
}
// CreateMailServerEvent returns EnvelopeEvent with correct data
// if payload corresponds to any of the know mailserver events:
// * 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 {
return nil, invalidResponseSizeError(len(payload))
}
event, err := tryCreateMailServerRequestFailedEvent(nodeID, payload)
if err != nil || event != nil {
return event, err
}
return tryCreateMailServerRequestCompletedEvent(nodeID, payload)
}
func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) {
if len(payload) < common.HashLength+len(mailServerFailedPayloadPrefix) {
return nil, nil
}
prefix, remainder := extractPrefix(payload, len(mailServerFailedPayloadPrefix))
if !bytes.Equal(prefix, []byte(mailServerFailedPayloadPrefix)) {
return nil, nil
}
var (
requestID common.Hash
errorMsg string
)
requestID, remainder = extractHash(remainder)
errorMsg = string(remainder)
event := EnvelopeEvent{
Peer: nodeID,
Hash: requestID,
Event: EventMailServerRequestCompleted,
Data: &MailServerResponse{
Error: errors.New(errorMsg),
},
}
return &event, nil
}
func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) {
// check if payload is
// - requestID or
// - requestID + lastEnvelopeHash or
// - requestID + lastEnvelopeHash + cursor
// 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 {
return nil, invalidResponseSizeError(len(payload))
}
var (
requestID common.Hash
lastEnvelopeHash common.Hash
cursor []byte
)
requestID, remainder := extractHash(payload)
if len(remainder) >= common.HashLength {
lastEnvelopeHash, remainder = extractHash(remainder)
}
if len(remainder) >= cursorSize {
cursor = remainder
}
event := EnvelopeEvent{
Peer: nodeID,
Hash: requestID,
Event: EventMailServerRequestCompleted,
Data: &MailServerResponse{
LastEnvelopeHash: lastEnvelopeHash,
Cursor: cursor,
},
}
return &event, nil
}
func extractHash(payload []byte) (common.Hash, []byte) {
prefix, remainder := extractPrefix(payload, common.HashLength)
return common.BytesToHash(prefix), remainder
}
func extractPrefix(payload []byte, size int) ([]byte, []byte) {
return payload[:size], payload[size:]
}

View file

@ -1,116 +0,0 @@
package whisper
import (
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/p2p/enode"
)
func checkValidErrorPayload(t *testing.T, id []byte, errorMsg string) {
requestID := common.BytesToHash(id)
errPayload := CreateMailServerRequestFailedPayload(requestID, errors.New(errorMsg))
nid := enode.ID{1}
event, err := CreateMailServerEvent(nid, errPayload)
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, nid, event.Peer)
require.Equal(t, requestID, event.Hash)
eventData, ok := event.Data.(*MailServerResponse)
if !ok {
require.FailNow(t, "Unexpected data in event: %v, expected a MailServerResponse", event.Data)
}
require.EqualError(t, eventData.Error, errorMsg)
}
func checkValidSuccessPayload(t *testing.T, id []byte, lastHash []byte, timestamp uint32, envHash []byte) {
requestID := common.BytesToHash(id)
lastEnvelopeHash := common.BytesToHash(lastHash)
timestampBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(timestampBytes, timestamp)
envelopeHash := common.BytesToHash(envHash)
cursor := append(timestampBytes, envelopeHash[:]...)
successPayload := CreateMailServerRequestCompletedPayload(common.BytesToHash(id), lastEnvelopeHash, cursor)
nid := enode.ID{1}
event, err := CreateMailServerEvent(nid, successPayload)
require.NoError(t, err)
require.NotNil(t, event)
require.Equal(t, nid, event.Peer)
require.Equal(t, requestID, event.Hash)
eventData, ok := event.Data.(*MailServerResponse)
if !ok {
require.FailNow(t, "Unexpected data in event: %v, expected a MailServerResponse", event.Data)
}
require.Equal(t, lastEnvelopeHash, eventData.LastEnvelopeHash)
require.Equal(t, cursor, eventData.Cursor)
require.NoError(t, eventData.Error)
}
func TestCreateMailServerEvent(t *testing.T) {
// valid cases
longErrorMessage := "longMessage|"
for i := 0; i < 5; i++ {
longErrorMessage = longErrorMessage + longErrorMessage
}
checkValidErrorPayload(t, []byte{0x01}, "test error 1")
checkValidErrorPayload(t, []byte{0x02}, "test error 2")
checkValidErrorPayload(t, []byte{0x02}, "")
checkValidErrorPayload(t, []byte{0x00}, "test error 3")
checkValidErrorPayload(t, []byte{}, "test error 4")
checkValidSuccessPayload(t, []byte{0x01}, []byte{0x02}, 123, []byte{0x03})
// invalid payloads
// too small
_, err := CreateMailServerEvent(enode.ID{}, []byte{0x00})
require.Error(t, err)
// too big and not error payload
payloadTooBig := make([]byte, common.HashLength*2+cursorSize+100)
_, err = CreateMailServerEvent(enode.ID{}, payloadTooBig)
require.Error(t, err)
}
func TestSyncMailRequestValidate(t *testing.T) {
testCases := []struct {
Name string
Req SyncMailRequest
Error string
}{
{
Name: "invalid zero Limit",
Req: SyncMailRequest{},
Error: "invalid 'Limit' value, expected value greater than 0",
},
{
Name: "invalid large Limit",
Req: SyncMailRequest{Limit: 1e6},
Error: "invalid 'Limit' value, expected value lower than 1000",
},
{
Name: "invalid Lower",
Req: SyncMailRequest{Limit: 10, Lower: 10, Upper: 5},
Error: "invalid 'Lower' value, can't be greater than 'Upper'",
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
err := tc.Req.Validate()
if tc.Error != "" {
assert.EqualError(t, err, tc.Error)
} else {
assert.NoError(t, err)
}
})
}
}

View file

@ -1,442 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Message element.
package whisper
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
crand "crypto/rand"
"encoding/binary"
"errors"
"fmt"
mrand "math/rand"
"strconv"
"time"
"github.com/ethereum/go-ethereum/common"
"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
// into an Envelope.
type MessageParams struct {
TTL uint32
Src *ecdsa.PrivateKey
Dst *ecdsa.PublicKey
KeySym []byte
Topic TopicType
WorkTime uint32
PoW float64
Payload []byte
Padding []byte
}
// SentMessage represents an end-user data packet to transmit through the
// Whisper protocol. These are wrapped into Envelopes that need not be
// understood by intermediate nodes, just forwarded.
type sentMessage struct {
Raw []byte
}
// ReceivedMessage represents a data packet to be received through the
// Whisper protocol and successfully decrypted.
type ReceivedMessage struct {
Raw []byte
Payload []byte
Padding []byte
Signature []byte
Salt []byte
PoW float64 // Proof of work as described in the Whisper spec
Sent uint32 // Time when the message was posted into the network
TTL uint32 // Maximum time to live allowed for the message
Src *ecdsa.PublicKey // Message recipient (identity used to decode the message)
Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message)
Topic TopicType
SymKeyHash common.Hash // The Keccak256Hash of the key
EnvelopeHash common.Hash // Message envelope hash to act as a unique id
P2P bool // is set to true if this message was received from mail server.
}
// MessagesRequest contains details of a request of historic messages.
type MessagesRequest struct {
// ID of the request. The current implementation requires ID to be 32-byte array,
// however, it's not enforced for future implementation.
ID []byte `json:"id"`
// From is a lower bound of time range.
From uint32 `json:"from"`
// To is a upper bound of time range.
To uint32 `json:"to"`
// Limit determines the number of messages sent by the mail server
// for the current paginated request.
Limit uint32 `json:"limit"`
// Cursor is used as starting point for paginated requests.
Cursor []byte `json:"cursor"`
// Bloom is a filter to match requested messages.
Bloom []byte `json:"bloom"`
}
func (r MessagesRequest) Validate() error {
if len(r.ID) != common.HashLength {
return errors.New("invalid 'ID', expected a 32-byte slice")
}
if r.From > r.To {
return errors.New("invalid 'From' value which is greater than To")
}
if r.Limit > MaxLimitInMessagesRequest {
return fmt.Errorf("invalid 'Limit' value, expected value lower than %d", MaxLimitInMessagesRequest)
}
if len(r.Bloom) == 0 {
return errors.New("invalid 'Bloom' provided")
}
return nil
}
// MessagesResponse sent as a response after processing batch of envelopes.
type MessagesResponse struct {
// Hash is a hash of all envelopes sent in the single batch.
Hash common.Hash
// Per envelope error.
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 instane 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 {
return (flags & signatureFlag) != 0
}
func (msg *ReceivedMessage) isSymmetricEncryption() bool {
return msg.SymKeyHash != common.Hash{}
}
func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
return msg.Dst != nil
}
// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message.
func NewSentMessage(params *MessageParams) (*sentMessage, error) {
const payloadSizeFieldMaxSize = 4
msg := sentMessage{}
msg.Raw = make([]byte, 1,
flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
msg.Raw[0] = 0 // set all the flags to zero
msg.addPayloadSizeField(params.Payload)
msg.Raw = append(msg.Raw, params.Payload...)
err := msg.appendPadding(params)
return &msg, err
}
// addPayloadSizeField appends the auxiliary field containing the size of payload
func (msg *sentMessage) addPayloadSizeField(payload []byte) {
fieldSize := getSizeOfPayloadSizeField(payload)
field := make([]byte, 4)
binary.LittleEndian.PutUint32(field, uint32(len(payload)))
field = field[:fieldSize]
msg.Raw = append(msg.Raw, field...)
msg.Raw[0] |= byte(fieldSize)
}
// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload
func getSizeOfPayloadSizeField(payload []byte) int {
s := 1
for i := len(payload); i >= 256; i /= 256 {
s++
}
return s
}
// appendPadding appends the padding specified in params.
// If no padding is provided in params, then random padding is generated.
func (msg *sentMessage) appendPadding(params *MessageParams) error {
if len(params.Padding) != 0 {
// padding data was provided by the Dapp, just use it as is
msg.Raw = append(msg.Raw, params.Padding...)
return nil
}
rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload)
if params.Src != nil {
rawSize += signatureLength
}
odd := rawSize % padSizeLimit
paddingSize := padSizeLimit - odd
pad := make([]byte, paddingSize)
_, err := crand.Read(pad)
if err != nil {
return err
}
if !validateDataIntegrity(pad, paddingSize) {
return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize))
}
msg.Raw = append(msg.Raw, pad...)
return nil
}
// 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]) {
// this should not happen, but no reason to panic
log.Error("failed to sign the message: already signed")
return nil
}
msg.Raw[0] |= signatureFlag // it is important to set this flag before signing
hash := crypto.Keccak256(msg.Raw)
signature, err := crypto.Sign(hash, key)
if err != nil {
msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag
return err
}
msg.Raw = append(msg.Raw, signature...)
return nil
}
// encryptAsymmetric encrypts a message with a public key.
func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
if !ValidatePublicKey(key) {
return errors.New("invalid public key provided for asymmetric encryption")
}
encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil)
if err == nil {
msg.Raw = encrypted
}
return err
}
// 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) {
return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key)))
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key
if err != nil {
return err
}
encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil)
msg.Raw = append(encrypted, salt...)
return nil
}
// 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) {
x := make([]byte, length)
y := make([]byte, length)
res := make([]byte, length)
_, err := crand.Read(x)
if err != nil {
return nil, err
} 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) {
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) {
return nil, errors.New("failed to generate secure random data")
}
return res, nil
}
// Wrap bundles the message into an Envelope to transmit over the network.
func (msg *sentMessage) Wrap(options *MessageParams, now time.Time) (envelope *Envelope, err error) {
if options.TTL == 0 {
options.TTL = DefaultTTL
}
if options.Src != nil {
if err = msg.sign(options.Src); err != nil {
return nil, err
}
}
if options.Dst != nil {
err = msg.encryptAsymmetric(options.Dst)
} else if options.KeySym != nil {
err = msg.encryptSymmetric(options.KeySym)
} else {
err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
}
if err != nil {
return nil, err
}
envelope = NewEnvelope(options.TTL, options.Topic, msg, now)
if err = envelope.Seal(options); err != nil {
return nil, err
}
return envelope, nil
}
// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256.
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
func (msg *ReceivedMessage) decryptSymmetric(key []byte) error {
// symmetric messages are expected to contain the 12-byte nonce at the end of the payload
if len(msg.Raw) < aesNonceLength {
return errors.New("missing salt or invalid payload in symmetric message")
}
salt := msg.Raw[len(msg.Raw)-aesNonceLength:]
block, err := aes.NewCipher(key)
if err != nil {
return err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil)
if err != nil {
return err
}
msg.Raw = decrypted
msg.Salt = salt
return nil
}
// decryptAsymmetric decrypts an encrypted payload with a private key.
func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil)
if err == nil {
msg.Raw = decrypted
}
return err
}
// ValidateAndParse checks the message validity and extracts the fields in case of success.
func (msg *ReceivedMessage) ValidateAndParse() bool {
end := len(msg.Raw)
if end < 1 {
return false
}
if isMessageSigned(msg.Raw[0]) {
end -= signatureLength
if end <= 1 {
return false
}
msg.Signature = msg.Raw[end : end+signatureLength]
msg.Src = msg.SigToPubKey()
if msg.Src == nil {
return false
}
}
beg := 1
payloadSize := 0
sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload
if sizeOfPayloadSizeField != 0 {
if end < beg+sizeOfPayloadSizeField {
return false
}
payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField]))
beg += sizeOfPayloadSizeField
if beg+payloadSize > end {
return false
}
msg.Payload = msg.Raw[beg : beg+payloadSize]
}
beg += payloadSize
msg.Padding = msg.Raw[beg:end]
return true
}
// SigToPubKey returns the public key associated to the message's
// signature.
func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
defer func() { _ = recover() }() // in case of invalid signature
pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
if err != nil {
log.Error("failed to recover public key from signature", "err", err)
return nil
}
return pub
}
// 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]) {
sz := len(msg.Raw) - signatureLength
return crypto.Keccak256(msg.Raw[:sz])
}
return crypto.Keccak256(msg.Raw)
}

View file

@ -1,506 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"bytes"
"crypto/aes"
"crypto/cipher"
mrand "math/rand"
"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) {
// set all the parameters except p.Dst and p.Padding
buf := make([]byte, 4)
mrand.Read(buf) // nolint: gosec
sz := mrand.Intn(400) // nolint: gosec
var p MessageParams
p.PoW = 0.01
p.WorkTime = 1
p.TTL = uint32(mrand.Intn(1024)) // nolint: gosec
p.Payload = make([]byte, sz)
p.KeySym = make([]byte, aesKeyLength)
mrand.Read(p.Payload) // nolint: gosec
mrand.Read(p.KeySym) // nolint: gosec
p.Topic = BytesToTopic(buf)
var err error
p.Src, err = crypto.GenerateKey()
if err != nil {
return nil, err
}
return &p, nil
}
func singleMessageTest(t *testing.T, symmetric bool) {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
if !symmetric {
params.KeySym = nil
params.Dst = &key.PublicKey
}
text := make([]byte, 0, 512)
text = append(text, params.Payload...)
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)
}
var decrypted *ReceivedMessage
if symmetric {
decrypted, err = env.OpenSymmetric(params.KeySym)
} else {
decrypted, err = env.OpenAsymmetric(key)
}
if err != nil {
t.Fatalf("failed to encrypt with seed %d: %s.", seed, err)
}
if !decrypted.ValidateAndParse() {
t.Fatalf("failed to validate with seed %d, symmetric = %v.", seed, symmetric)
}
if !bytes.Equal(text, decrypted.Payload) {
t.Fatalf("failed with seed %d: compare payload.", seed)
}
if !isMessageSigned(decrypted.Raw[0]) {
t.Fatalf("failed with seed %d: unsigned.", seed)
}
if len(decrypted.Signature) != signatureLength {
t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
}
if !IsPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
t.Fatalf("failed with seed %d: signature mismatch.", seed)
}
}
func TestMessageEncryption(t *testing.T) {
InitSingleTest()
var symmetric bool
for i := 0; i < 256; i++ {
singleMessageTest(t, symmetric)
symmetric = !symmetric
}
}
func TestMessageWrap(t *testing.T) {
seed = int64(1777444222)
mrand.Seed(seed)
target := 128.0
params, err := generateMessageParams()
if err != nil {
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.TTL = 1
params.WorkTime = 12
params.PoW = target
env, err := msg.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
pow := env.PoW()
if pow < target {
t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
}
// set PoW target too high, expect error
msg2, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
params.TTL = 1000000
params.WorkTime = 1
params.PoW = 10000000.0
_, err = msg2.Wrap(params, time.Now())
if err == nil {
t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed)
}
}
func TestMessageSeal(t *testing.T) {
// this test depends on deterministic choice of seed (1976726903)
seed = int64(1976726903)
mrand.Seed(seed)
params, err := generateMessageParams()
if err != nil {
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.TTL = 1
env := NewEnvelope(params.TTL, params.Topic, msg, time.Now())
env.Expiry = uint32(seed) // make it deterministic
target := 32.0
params.WorkTime = 4
params.PoW = target
_ = env.Seal(params)
env.calculatePoW(0)
pow := env.PoW()
if pow < target {
t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
}
params.WorkTime = 1
params.PoW = 1000000000.0
_ = env.Seal(params)
env.calculatePoW(0)
pow = env.PoW()
if pow < 2*target {
t.Fatalf("failed Wrap with seed %d: pow too small %f.", seed, pow)
}
}
func TestEnvelopeOpen(t *testing.T) {
InitSingleTest()
var symmetric bool
for i := 0; i < 32; i++ {
singleEnvelopeOpenTest(t, symmetric)
symmetric = !symmetric
}
}
func singleEnvelopeOpenTest(t *testing.T, symmetric bool) {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
if !symmetric {
params.KeySym = nil
params.Dst = &key.PublicKey
}
text := make([]byte, 0, 512)
text = append(text, params.Payload...)
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)
}
var f Filter
if symmetric {
f = Filter{KeySym: params.KeySym}
} else {
f = Filter{KeyAsym: key}
}
decrypted := env.Open(&f)
if decrypted == nil {
t.Fatalf("failed to open with seed %d.", seed)
}
if !bytes.Equal(text, decrypted.Payload) {
t.Fatalf("failed with seed %d: compare payload.", seed)
}
if !isMessageSigned(decrypted.Raw[0]) {
t.Fatalf("failed with seed %d: unsigned.", seed)
}
if len(decrypted.Signature) != signatureLength {
t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
}
if !IsPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
t.Fatalf("failed with seed %d: signature mismatch.", seed)
}
if decrypted.isAsymmetricEncryption() == symmetric {
t.Fatalf("failed with seed %d: asymmetric %v vs. %v.", seed, decrypted.isAsymmetricEncryption(), symmetric)
}
if decrypted.isSymmetricEncryption() != symmetric {
t.Fatalf("failed with seed %d: symmetric %v vs. %v.", seed, decrypted.isSymmetricEncryption(), symmetric)
}
if !symmetric {
if decrypted.Dst == nil {
t.Fatalf("failed with seed %d: dst is nil.", seed)
}
if !IsPubKeyEqual(decrypted.Dst, &key.PublicKey) {
t.Fatalf("failed with seed %d: Dst.", seed)
}
}
}
func TestEncryptWithZeroKey(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
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)
_, err = msg.Wrap(params, time.Now())
if err == nil {
t.Fatalf("wrapped with zero key, seed: %d.", seed)
}
params, err = generateMessageParams()
if err != nil {
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, 0)
_, err = msg.Wrap(params, time.Now())
if err == nil {
t.Fatalf("wrapped with empty key, seed: %d.", seed)
}
params, err = generateMessageParams()
if err != nil {
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 = nil
_, err = msg.Wrap(params, time.Now())
if err == nil {
t.Fatalf("wrapped with nil key, seed: %d.", seed)
}
}
func TestRlpEncode(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
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)
}
env, err := msg.Wrap(params, time.Now())
if err != nil {
t.Fatalf("wrapped with zero key, seed: %d.", seed)
}
raw, err := rlp.EncodeToBytes(env)
if err != nil {
t.Fatalf("RLP encode failed: %s.", err)
}
var decoded Envelope
err = rlp.DecodeBytes(raw, &decoded)
if err != nil {
t.Fatalf("RLP decode failed: %s.", err)
}
he := env.Hash()
hd := decoded.Hash()
if he != hd {
t.Fatalf("Hashes are not equal: %x vs. %x", he, hd)
}
}
func singlePaddingTest(t *testing.T, padSize int) {
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err)
}
params.Padding = make([]byte, padSize)
params.PoW = 0.0000000001
pad := make([]byte, padSize)
_, err = mrand.Read(pad) // nolint: gosec
if err != nil {
t.Fatalf("padding is not generated (seed %d): %s", seed, err)
}
n := copy(params.Padding, pad)
if n != padSize {
t.Fatalf("padding is not copied (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)
}
env, err := msg.Wrap(params, time.Now())
if err != nil {
t.Fatalf("failed to wrap, seed: %d and sz=%d.", seed, padSize)
}
f := Filter{KeySym: params.KeySym}
decrypted := env.Open(&f)
if decrypted == nil {
t.Fatalf("failed to open, seed and sz=%d: %d.", seed, padSize)
}
if !bytes.Equal(pad, decrypted.Padding) {
t.Fatalf("padding is not retireved as expected with seed %d and sz=%d:\n[%x]\n[%x].", seed, padSize, pad, decrypted.Padding)
}
}
func TestPadding(t *testing.T) {
InitSingleTest()
for i := 1; i < 260; i++ {
singlePaddingTest(t, i)
}
lim := 256 * 256
for i := lim - 5; i < lim+2; i++ {
singlePaddingTest(t, i)
}
for i := 0; i < 256; i++ {
n := mrand.Intn(256*254) + 256 // nolint: gosec
singlePaddingTest(t, n)
}
for i := 0; i < 256; i++ {
n := mrand.Intn(256*1024) + 256*256 // nolint: gosec
singlePaddingTest(t, n)
}
}
func TestPaddingAppendedToSymMessagesWithSignature(t *testing.T) {
params := &MessageParams{
Payload: make([]byte, 246),
KeySym: make([]byte, aesKeyLength),
}
pSrc, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("Error creating the signature key %v", err)
return
}
params.Src = pSrc
// Simulate a message with a payload just under 256 so that
// payload + flag + signature > 256. Check that the result
// is padded on the next 256 boundary.
msg := sentMessage{}
const payloadSizeFieldMinSize = 1
msg.Raw = make([]byte, flagsLength+payloadSizeFieldMinSize+len(params.Payload))
err = msg.appendPadding(params)
if err != nil {
t.Fatalf("Error appending padding to message %v", err)
return
}
if len(msg.Raw) != 512-signatureLength {
t.Errorf("Invalid size %d != 512", len(msg.Raw))
}
}
func TestAesNonce(t *testing.T) {
key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31")
block, err := aes.NewCipher(key)
if err != nil {
t.Fatalf("NewCipher failed: %s", err)
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
t.Fatalf("NewGCM failed: %s", err)
}
// This is the most important single test in this package.
// If it fails, whisper will not be working.
if aesgcm.NonceSize() != aesNonceLength {
t.Fatalf("Nonce size is wrong. This is a critical error. Apparently AES nonce size have changed in the new version of AES GCM package. Whisper will not be working until this problem is resolved.")
}
}
func TestValidateAndParseSizeOfPayloadSize(t *testing.T) {
testCases := []struct {
Name string
Raw []byte
}{
{
Name: "one byte of value 1",
Raw: []byte{1},
},
{
Name: "two bytes of values 1 and 1",
Raw: []byte{1, 1},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
msg := ReceivedMessage{Raw: tc.Raw}
msg.ValidateAndParse()
})
}
}
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)
}

View file

@ -1,68 +0,0 @@
package whisper
import (
prom "github.com/prometheus/client_golang/prometheus"
)
var (
envelopesReceivedCounter = prom.NewCounter(prom.CounterOpts{
Name: "whisper_envelopes_received_total",
Help: "Number of envelopes received.",
})
envelopesValidatedCounter = prom.NewCounter(prom.CounterOpts{
Name: "whisper_envelopes_validated_total",
Help: "Number of envelopes processed successfully.",
})
envelopesRejectedCounter = prom.NewCounterVec(prom.CounterOpts{
Name: "whisper_envelopes_rejected_total",
Help: "Number of envelopes rejected.",
}, []string{"reason"})
envelopesCacheFailedCounter = prom.NewCounterVec(prom.CounterOpts{
Name: "whisper_envelopes_cache_failures_total",
Help: "Number of envelopes which failed to be cached.",
}, []string{"type"})
envelopesCachedCounter = prom.NewCounterVec(prom.CounterOpts{
Name: "whisper_envelopes_cached_total",
Help: "Number of envelopes cached.",
}, []string{"cache"})
envelopesSizeMeter = prom.NewHistogram(prom.HistogramOpts{
Name: "whisper_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{
Name: "whisper_rate_limits_processed_total",
Help: "Number of packets Waku rate limiter processed.",
})
rateLimitsExceeded = prom.NewCounterVec(prom.CounterOpts{
Name: "whisper_rate_limits_exceeded_total",
Help: "Number of times the Waku rate limits were exceeded",
}, []string{"type"})
// bridging
bridgeSent = prom.NewCounter(prom.CounterOpts{
Name: "whisper_bridge_sent_total",
Help: "Number of envelopes bridged from Whisper",
})
bridgeReceivedSucceed = prom.NewCounter(prom.CounterOpts{
Name: "whisper_bridge_received_success_total",
Help: "Number of envelopes bridged to Whisper and successfully added",
})
bridgeReceivedFailed = prom.NewCounter(prom.CounterOpts{
Name: "whisper_bridge_received_failure_total",
Help: "Number of envelopes bridged to Whisper 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)
}

View file

@ -1,312 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"bytes"
"fmt"
"math"
"sync"
"time"
mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
)
// Peer represents a whisper protocol peer connection.
type Peer struct {
host *Whisper
peer *p2p.Peer
ws p2p.MsgReadWriter
trusted bool
powRequirement float64
bloomMu sync.Mutex
bloomFilter []byte
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 whisper peer object, but does not run the handshake itself.
func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
return &Peer{
host: host,
peer: remote,
ws: rw,
trusted: false,
powRequirement: 0.0,
known: mapset.NewSet(),
quit: make(chan struct{}),
bloomFilter: MakeFullNodeBloom(),
fullNode: true,
}
}
// start initiates the peer updater, periodically broadcasting the whisper packets
// into the network.
func (peer *Peer) start() {
go peer.update()
log.Trace("start", "peer", peer.ID())
}
// stop terminates the peer updater, stopping message forwarding to it.
func (peer *Peer) stop() {
close(peer.quit)
log.Trace("stop", "peer", peer.ID())
}
// handshake sends the protocol initiation status message to the remote peer and
// verifies the remote status too.
func (peer *Peer) handshake() error {
// Send the handshake status message asynchronously
errc := make(chan error, 1)
isLightNode := peer.host.LightClientMode()
isRestrictedLightNodeConnection := peer.host.LightClientModeConnectionRestricted()
go func() {
pow := peer.host.MinPow()
powConverted := math.Float64bits(pow)
bloom := peer.host.BloomFilter()
confirmationsEnabled := !peer.host.disableConfirmations
rateLimits := peer.host.RateLimits()
errc <- p2p.SendItems(peer.ws, statusCode, ProtocolVersion, powConverted, bloom, isLightNode, confirmationsEnabled, rateLimits)
}()
// Fetch the remote status packet and verify protocol match
packet, err := peer.ws.ReadMsg()
if err != nil {
return err
}
if packet.Code != statusCode {
return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code)
}
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
_, err = s.List()
if err != nil {
return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err)
}
peerVersion, err := s.Uint()
if err != nil {
return fmt.Errorf("peer [%x] sent bad status message (unable to decode version): %v", peer.ID(), err)
}
if peerVersion != ProtocolVersion {
return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion)
}
// only version is mandatory, subsequent parameters are optional
powRaw, err := s.Uint()
if err == nil {
pow := math.Float64frombits(powRaw)
if math.IsInf(pow, 0) || math.IsNaN(pow) || pow < 0.0 {
return fmt.Errorf("peer [%x] sent bad status message: invalid pow", peer.ID())
}
peer.powRequirement = pow
var bloom []byte
err = s.Decode(&bloom)
if err == nil {
sz := len(bloom)
if sz != BloomFilterSize && sz != 0 {
return fmt.Errorf("peer [%x] sent bad status message: wrong bloom filter size %d", peer.ID(), sz)
}
peer.setBloomFilter(bloom)
}
}
isRemotePeerLightNode, _ := s.Bool()
if isRemotePeerLightNode && isLightNode && isRestrictedLightNodeConnection {
return fmt.Errorf("peer [%x] is useless: two light client communication restricted", peer.ID())
}
confirmationsEnabled, err := s.Bool()
if err != nil || !confirmationsEnabled {
log.Warn("confirmations are disabled", "peer", peer.ID())
} else {
peer.confirmationsEnabled = confirmationsEnabled
}
var rateLimits RateLimits
if err := s.Decode(&rateLimits); err != nil {
log.Info("rate limiting disabled", "err", err)
} else {
peer.setRateLimits(rateLimits)
}
if err := <-errc; err != nil {
return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err)
}
return nil
}
// update executes periodic operations on the peer, including message transmission
// and expiration.
func (peer *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:
peer.expire()
case <-transmit.C:
if err := peer.broadcast(); err != nil {
log.Trace("broadcast failed", "reason", err, "peer", peer.ID())
return
}
case <-peer.quit:
return
}
}
}
// mark marks an envelope known to the peer so that it won't be sent back.
func (peer *Peer) mark(envelope *Envelope) {
peer.known.Add(envelope.Hash())
}
// marked checks if an envelope is already known to the remote peer.
func (peer *Peer) marked(envelope *Envelope) bool {
return peer.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 (peer *Peer) expire() {
unmark := make(map[common.Hash]struct{})
peer.known.Each(func(v interface{}) bool {
if !peer.host.isEnvelopeCached(v.(common.Hash)) {
unmark[v.(common.Hash)] = struct{}{}
}
return true
})
// Dump all known but no longer cached
for hash := range unmark {
peer.known.Remove(hash)
}
}
// broadcast iterates over the collection of envelopes and transmits yet unknown
// ones over the network.
func (peer *Peer) broadcast() error {
envelopes := peer.host.Envelopes()
bundle := make([]*Envelope, 0, len(envelopes))
for _, envelope := range envelopes {
if !peer.marked(envelope) && envelope.PoW() >= peer.powRequirement && peer.bloomMatch(envelope) {
bundle = append(bundle, envelope)
}
}
if len(bundle) > 0 {
batchHash, err := sendBundle(peer.ws, bundle)
if err != nil {
log.Warn("failed to deliver envelopes", "peer", peer.peer.ID(), "error", err)
return err
}
// mark envelopes only if they were successfully sent
for _, e := range bundle {
peer.mark(e)
event := EnvelopeEvent{
Event: EventEnvelopeSent,
Hash: e.Hash(),
Peer: peer.peer.ID(),
}
if peer.confirmationsEnabled {
event.Batch = batchHash
}
peer.host.envelopeFeed.Send(event)
}
log.Trace("broadcast", "num. messages", len(bundle))
}
return nil
}
// ID returns a peer's id
func (peer *Peer) ID() []byte {
id := peer.peer.ID()
return id[:]
}
func (peer *Peer) notifyAboutPowRequirementChange(pow float64) error {
i := math.Float64bits(pow)
return p2p.Send(peer.ws, powRequirementCode, i)
}
func (peer *Peer) notifyAboutBloomFilterChange(bloom []byte) error {
return p2p.Send(peer.ws, bloomFilterExCode, bloom)
}
func (peer *Peer) bloomMatch(env *Envelope) bool {
peer.bloomMu.Lock()
defer peer.bloomMu.Unlock()
return peer.fullNode || BloomFilterMatch(peer.bloomFilter, env.Bloom())
}
func (peer *Peer) setBloomFilter(bloom []byte) {
peer.bloomMu.Lock()
defer peer.bloomMu.Unlock()
peer.bloomFilter = bloom
peer.fullNode = isFullNode(bloom)
if peer.fullNode && peer.bloomFilter == nil {
peer.bloomFilter = MakeFullNodeBloom()
}
}
func (peer *Peer) setRateLimits(r RateLimits) {
peer.rateLimitsMu.Lock()
peer.rateLimits = r
peer.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
}

View file

@ -1,583 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"bytes"
"crypto/ecdsa"
"fmt"
mrand "math/rand"
"sync"
"sync/atomic"
"testing"
"time"
"net"
"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"
)
var keys = []string{
"d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9",
"73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98",
"119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc",
"deeda8709dea935bb772248a3144dea449ffcc13e8e5a1fd4ef20ce4e9c87837",
"5bd208a079633befa349441bdfdc4d85ba9bd56081525008380a63ac38a407cf",
"1d27fb4912002d58a2a42a50c97edb05c1b3dffc665dbaa42df1fe8d3d95c9b5",
"15def52800c9d6b8ca6f3066b7767a76afc7b611786c1276165fbc61636afb68",
"51be6ab4b2dc89f251ff2ace10f3c1cc65d6855f3e083f91f6ff8efdfd28b48c",
"ef1ef7441bf3c6419b162f05da6037474664f198b58db7315a6f4de52414b4a0",
"09bdf6985aabc696dc1fbeb5381aebd7a6421727343872eb2fadfc6d82486fd9",
"15d811bf2e01f99a224cdc91d0cf76cea08e8c67905c16fee9725c9be71185c4",
"2f83e45cf1baaea779789f755b7da72d8857aeebff19362dd9af31d3c9d14620",
"73f04e34ac6532b19c2aae8f8e52f38df1ac8f5cd10369f92325b9b0494b0590",
"1e2e07b69e5025537fb73770f483dc8d64f84ae3403775ef61cd36e3faf162c1",
"8963d9bbb3911aac6d30388c786756b1c423c4fbbc95d1f96ddbddf39809e43a",
"0422da85abc48249270b45d8de38a4cc3c02032ede1fcf0864a51092d58a2f1f",
"8ae5c15b0e8c7cade201fdc149831aa9b11ff626a7ffd27188886cc108ad0fa8",
"acd8f5a71d4aecfcb9ad00d32aa4bcf2a602939b6a9dd071bab443154184f805",
"a285a922125a7481600782ad69debfbcdb0316c1e97c267aff29ef50001ec045",
"28fd4eee78c6cd4bf78f39f8ab30c32c67c24a6223baa40e6f9c9a0e1de7cef5",
"c5cca0c9e6f043b288c6f1aef448ab59132dab3e453671af5d0752961f013fc7",
"46df99b051838cb6f8d1b73f232af516886bd8c4d0ee07af9a0a033c391380fd",
"c6a06a53cbaadbb432884f36155c8f3244e244881b5ee3e92e974cfa166d793f",
"783b90c75c63dc72e2f8d11b6f1b4de54d63825330ec76ee8db34f06b38ea211",
"9450038f10ca2c097a8013e5121b36b422b95b04892232f930a29292d9935611",
"e215e6246ed1cfdcf7310d4d8cdbe370f0d6a8371e4eb1089e2ae05c0e1bc10f",
"487110939ed9d64ebbc1f300adeab358bc58875faf4ca64990fbd7fe03b78f2b",
"824a70ea76ac81366da1d4f4ac39de851c8ac49dca456bb3f0a186ceefa269a5",
"ba8f34fa40945560d1006a328fe70c42e35cc3d1017e72d26864cd0d1b150f15",
"30a5dfcfd144997f428901ea88a43c8d176b19c79dde54cc58eea001aa3d246c",
"de59f7183aca39aa245ce66a05245fecfc7e2c75884184b52b27734a4a58efa2",
"92629e2ff5f0cb4f5f08fffe0f64492024d36f045b901efb271674b801095c5a",
"7184c1701569e3a4c4d2ddce691edd983b81e42e09196d332e1ae2f1e062cff4",
}
type TestData struct {
started int64
counter [NumNodes]int
mutex sync.RWMutex
}
type TestNode struct {
shh *Whisper
id *ecdsa.PrivateKey
server *p2p.Server
filerID string
}
const NumNodes = 8 // must not exceed the number of keys (32)
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 expectedMessage = []byte("per aspera ad astra")
var unexpectedMessage = []byte("per rectum ad astra")
var masterBloomFilter []byte
var masterPow = 0.00000001
var round = 1
var debugMode = false
var prevTime time.Time
var cntPrev int
func TestSimulation(t *testing.T) {
// create a chain of whisper nodes,
// installs the filters with shared (predefined) parameters
initialize(t)
// each node sends one random (not decryptable) message
for i := 0; i < NumNodes; i++ {
sendMsg(t, false, i)
}
// node #0 sends one expected (decryptable) message
sendMsg(t, true, 0)
// check if each node have received and decrypted exactly one message
checkPropagation(t, true)
// check if Status message was correctly decoded
checkBloomFilterExchange(t)
checkPowExchange(t)
// send new pow and bloom exchange messages
resetParams(t)
// node #1 sends one expected (decryptable) message
sendMsg(t, true, 1)
// check if each node (except node #0) have received and decrypted exactly one message
checkPropagation(t, false)
// check if corresponding protocol-level messages were correctly decoded
checkPowExchangeForNodeZero(t)
checkBloomFilterExchange(t)
stopServers()
}
func resetParams(t *testing.T) {
// change pow only for node zero
masterPow = 7777777.0
_ = nodes[0].shh.SetMinimumPoW(masterPow)
// change bloom for all nodes
masterBloomFilter = TopicToBloom(sharedTopic)
for i := 0; i < NumNodes; i++ {
_ = nodes[i].shh.SetBloomFilter(masterBloomFilter)
}
round++
}
func initBloom(t *testing.T) {
masterBloomFilter = make([]byte, BloomFilterSize)
_, err := mrand.Read(masterBloomFilter) // nolint: gosec
if err != nil {
t.Fatalf("rand failed: %s.", err)
}
msgBloom := TopicToBloom(sharedTopic)
masterBloomFilter = addBloom(masterBloomFilter, msgBloom)
for i := 0; i < 32; i++ {
masterBloomFilter[i] = 0xFF
}
if !BloomFilterMatch(masterBloomFilter, msgBloom) {
t.Fatalf("bloom mismatch on initBloom.")
}
}
func initialize(t *testing.T) {
initBloom(t)
var err error
for i := 0; i < NumNodes; i++ {
var node TestNode
b := make([]byte, BloomFilterSize)
copy(b, masterBloomFilter)
node.shh = New(&DefaultConfig)
_ = node.shh.SetMinimumPoW(masterPow)
_ = node.shh.SetBloomFilter(b)
if !bytes.Equal(node.shh.BloomFilter(), masterBloomFilter) {
t.Fatalf("bloom mismatch on init.")
}
_ = node.shh.Start(nil)
topics := make([]TopicType, 0)
topics = append(topics, sharedTopic)
f := Filter{KeySym: sharedKey, Messages: NewMemoryMessageStore()}
f.Topics = [][]byte{topics[0][:]}
node.filerID, err = node.shh.Subscribe(&f)
if err != nil {
t.Fatalf("failed to install the filter: %s.", err)
}
node.id, err = crypto.HexToECDSA(keys[i])
if err != nil {
t.Fatalf("failed convert the key: %s.", keys[i])
}
name := common.MakeName("whisper-go", "2.0")
node.server = &p2p.Server{
Config: p2p.Config{
PrivateKey: node.id,
MaxPeers: NumNodes/2 + 1,
Name: name,
Protocols: node.shh.Protocols(),
ListenAddr: "127.0.0.1:0",
NAT: nat.Any(),
},
}
go startServer(t, node.server) // nolint: staticcheck
nodes[i] = &node
}
waitForServersToStart(t)
for i := 0; i < NumNodes; i++ {
for j := 0; j < i; j++ {
peerNodeID := nodes[j].id
address, _ := net.ResolveTCPAddr("tcp", nodes[j].server.ListenAddr)
peer := enode.NewV4(&peerNodeID.PublicKey, address.IP, address.Port, address.Port)
nodes[i].server.AddPeer(peer)
}
}
}
func startServer(t *testing.T, s *p2p.Server) {
err := s.Start()
if err != nil {
t.Fatalf("failed to start the first server. err: %v", err)
}
atomic.AddInt64(&result.started, 1)
}
func stopServers() {
for i := 0; i < NumNodes; i++ {
n := nodes[i]
if n != nil {
_ = n.shh.Unsubscribe(n.filerID)
_ = n.shh.Stop()
n.server.Stop()
}
}
}
func checkPropagation(t *testing.T, includingNodeZero bool) {
if t.Failed() {
return
}
prevTime = time.Now()
// (cycle * iterations) should not exceed 50 seconds, since TTL=50
const cycle = 200 // time in milliseconds
const iterations = 250
first := 0
if !includingNodeZero {
first = 1
}
for j := 0; j < iterations; j++ {
for i := first; i < NumNodes; i++ {
f := nodes[i].shh.GetFilter(nodes[i].filerID)
if f == nil {
t.Fatalf("failed to get filterId %s from node %d, round %d.", nodes[i].filerID, i, round)
}
mail := f.Retrieve()
validateMail(t, i, mail)
if isTestComplete() {
checkTestStatus()
return
}
}
checkTestStatus()
time.Sleep(cycle * time.Millisecond)
}
if !includingNodeZero {
f := nodes[0].shh.GetFilter(nodes[0].filerID)
if f != nil {
t.Fatalf("node zero received a message with low PoW.")
}
}
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) {
var cnt int
for _, m := range mail {
if bytes.Equal(m.Payload, expectedMessage) {
cnt++
}
}
if cnt == 0 {
// no messages received yet: nothing is wrong
return
}
if cnt > 1 {
t.Fatalf("node %d received %d.", index, cnt)
}
if cnt == 1 {
result.mutex.Lock()
defer result.mutex.Unlock()
result.counter[index] += cnt
if result.counter[index] > 1 {
t.Fatalf("node %d accumulated %d.", index, result.counter[index])
}
}
}
func checkTestStatus() {
var cnt int
var arr [NumNodes]int
for i := 0; i < NumNodes; i++ {
arr[i] = nodes[i].server.PeerCount()
envelopes := nodes[i].shh.Envelopes()
if len(envelopes) >= NumNodes {
cnt++
}
}
if debugMode {
if cntPrev != cnt {
fmt.Printf(" %v \t number of nodes that have received all msgs: %d, number of peers per node: %v \n",
time.Since(prevTime), cnt, arr)
prevTime = time.Now()
cntPrev = cnt
}
}
}
func isTestComplete() bool {
result.mutex.RLock()
defer result.mutex.RUnlock()
for i := 0; i < NumNodes; i++ {
if result.counter[i] < 1 {
return false
}
}
for i := 0; i < NumNodes; i++ {
envelopes := nodes[i].shh.Envelopes()
if len(envelopes) < NumNodes+1 {
return false
}
}
return true
}
func sendMsg(t *testing.T, expected bool, id int) {
if t.Failed() {
return
}
opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1}
if !expected {
opt.KeySym = wrongKey
opt.Topic = wrongTopic
opt.Payload = unexpectedMessage
opt.Payload[0] = byte(id)
}
msg, err := NewSentMessage(&opt)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
envelope, err := msg.Wrap(&opt, time.Now())
if err != nil {
t.Fatalf("failed to seal message: %s", err)
}
err = nodes[id].shh.Send(envelope)
if err != nil {
t.Fatalf("failed to send message: %s", err)
}
}
func TestPeerBasic(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d.", seed)
}
params.PoW = 0.001
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.", seed)
}
p := newPeer(nil, nil, nil)
p.mark(env)
if !p.marked(env) {
t.Fatalf("failed mark with seed %d.", seed)
}
}
func checkPowExchangeForNodeZero(t *testing.T) {
const iterations = 200
for j := 0; j < iterations; j++ {
lastCycle := j == iterations-1
ok := checkPowExchangeForNodeZeroOnce(t, lastCycle)
if ok {
break
}
time.Sleep(50 * time.Millisecond)
}
}
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() {
cnt++
if peer.powRequirement != masterPow {
if mustPass {
t.Fatalf("node %d: failed to set the new pow requirement for node zero.", i)
} else {
return false
}
}
}
}
}
if cnt == 0 {
t.Fatalf("looking for node zero: no matching peers found.")
}
return true
}
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 {
t.Fatalf("node %d: failed to exchange pow requirement in round %d; expected %f, got %f",
i, round, masterPow, peer.powRequirement)
}
}
}
}
}
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()
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)
} else {
return false
}
}
}
}
return true
}
func checkBloomFilterExchange(t *testing.T) {
const iterations = 200
for j := 0; j < iterations; j++ {
lastCycle := j == iterations-1
ok := checkBloomFilterExchangeOnce(t, lastCycle)
if ok {
break
}
time.Sleep(50 * time.Millisecond)
}
}
func waitForServersToStart(t *testing.T) {
const iterations = 200
var started int64
for j := 0; j < iterations; j++ {
time.Sleep(50 * time.Millisecond)
started = atomic.LoadInt64(&result.started)
if started == NumNodes {
return
}
}
t.Fatalf("Failed to start all the servers, running: %d", started)
}
//two generic whisper node handshake
func TestPeerHandshakeWithTwoFullNode(t *testing.T) {
w1 := Whisper{}
p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), false}})
err := p1.handshake()
if err != nil {
t.Fatal()
}
}
//two generic whisper node handshake. one don't send light flag
func TestHandshakeWithOldVersionWithoutLightModeFlag(t *testing.T) {
w1 := Whisper{}
p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize)}})
err := p1.handshake()
if err != nil {
t.Fatal()
}
}
//two light nodes handshake. restriction disabled
func TestTwoLightPeerHandshakeRestrictionOff(t *testing.T) {
w1 := Whisper{}
w1.settings.Store(restrictConnectionBetweenLightClientsIdx, false)
w1.SetLightClientMode(true)
p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}})
err := p1.handshake()
if err != nil {
t.FailNow()
}
}
//two light nodes handshake. restriction enabled
func TestTwoLightPeerHandshakeError(t *testing.T) {
w1 := Whisper{}
w1.settings.Store(restrictConnectionBetweenLightClientsIdx, true)
w1.SetLightClientMode(true)
p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}})
err := p1.handshake()
if err == nil {
t.FailNow()
}
}
func TestRateLimitsInHandshake(t *testing.T) {
w1 := Whisper{}
rateLimits := RateLimits{IPLimits: 20, PeerIDLimits: 10}
p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{
payload: []interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true, true, rateLimits},
})
err := p1.handshake()
if err != nil {
t.Fatal(err)
}
if p1.rateLimits != rateLimits {
t.Errorf("rate limits from handshake is not stored properly in Peer object")
}
}
type rwStub struct {
payload []interface{}
}
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
}
func (stub *rwStub) WriteMsg(m p2p.Msg) error {
return nil
}

View file

@ -1,267 +0,0 @@
package whisper
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/tsenart/tb"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
)
type runLoop func(p *Peer, rw p2p.MsgReadWriter) error
type RateLimiterHandler interface {
ExceedPeerLimit() error
ExceedIPLimit() error
}
type MetricsRateLimiterHandler struct{}
func (MetricsRateLimiterHandler) ExceedPeerLimit() error {
rateLimitsExceeded.WithLabelValues("peer_id").Inc()
return nil
}
func (MetricsRateLimiterHandler) ExceedIPLimit() error {
rateLimitsExceeded.WithLabelValues("ip").Inc()
return nil
}
// RateLimits contains information about rate limit settings.
// It is exchanged using rateLimitingCode packet or in the handshake.
type RateLimits struct {
IPLimits uint64 // messages per second from a single IP (default 0, no limits)
PeerIDLimits uint64 // messages per second from a single peer ID (default 0, no limits)
TopicLimits uint64 // messages per second from a single topic (default 0, no limits)
}
func (r RateLimits) IsZero() bool {
return r == (RateLimits{})
}
var ErrRateLimitExceeded = errors.New("rate limit has been exceeded")
type DropPeerRateLimiterHandler struct {
// Tolerance is a number of how many a limit must be exceeded
// in order to drop a peer.
Tolerance int64
peerLimitExceeds int64
ipLimitExceeds int64
}
func (h *DropPeerRateLimiterHandler) ExceedPeerLimit() error {
h.peerLimitExceeds++
if h.Tolerance > 0 && h.peerLimitExceeds >= h.Tolerance {
return ErrRateLimitExceeded
}
return nil
}
func (h *DropPeerRateLimiterHandler) ExceedIPLimit() error {
h.ipLimitExceeds++
if h.Tolerance > 0 && h.ipLimitExceeds >= h.Tolerance {
return ErrRateLimitExceeded
}
return nil
}
type PeerRateLimiterConfig struct {
LimitPerSecIP int64
LimitPerSecPeerID int64
WhitelistedIPs []string
WhitelistedPeerIDs []enode.ID
}
var defaultPeerRateLimiterConfig = PeerRateLimiterConfig{
LimitPerSecIP: 10,
LimitPerSecPeerID: 5,
WhitelistedIPs: nil,
WhitelistedPeerIDs: nil,
}
type PeerRateLimiter struct {
peerIDThrottler *tb.Throttler
ipThrottler *tb.Throttler
limitPerSecIP int64
limitPerSecPeerID int64
whitelistedPeerIDs []enode.ID
whitelistedIPs []string
handlers []RateLimiterHandler
}
func NewPeerRateLimiter(cfg *PeerRateLimiterConfig, handlers ...RateLimiterHandler) *PeerRateLimiter {
if cfg == nil {
copy := defaultPeerRateLimiterConfig
cfg = &copy
}
return &PeerRateLimiter{
peerIDThrottler: tb.NewThrottler(time.Millisecond * 100),
ipThrottler: tb.NewThrottler(time.Millisecond * 100),
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 {
in, out := p2p.MsgPipe()
defer in.Close()
defer out.Close()
errC := make(chan error, 1)
// Read from the original reader and write to the message pipe.
go func() {
for {
packet, err := rw.ReadMsg()
if err != nil {
// We don't block as that might leak goroutines
select {
case errC <- fmt.Errorf("failed to read packet: %v", err):
return
default:
return
}
}
rateLimitsProcessed.Inc()
var ip string
if p != nil && p.peer != nil {
ip = p.peer.Node().IP().String()
}
if halted := r.throttleIP(ip); halted {
for _, h := range r.handlers {
if err := h.ExceedIPLimit(); err != nil {
// We don't block as that might leak goroutines
select {
case errC <- fmt.Errorf("exceed rate limit by IP: %v", err):
return
default:
return
}
}
}
}
var peerID []byte
if p != nil {
peerID = p.ID()
}
if halted := r.throttlePeer(peerID); halted {
for _, h := range r.handlers {
if err := h.ExceedPeerLimit(); err != nil {
// We don't block as that might leak goroutines
select {
case errC <- fmt.Errorf("exceeded rate limit by peer: %v", err):
return
default:
return
}
}
}
}
if err := in.WriteMsg(packet); err != nil {
// We don't block as that might leak goroutines
select {
case errC <- fmt.Errorf("failed to write packet to pipe: %v", err):
return
default:
return
}
}
}
}()
// Read from the message pipe and write to the original writer.
go func() {
for {
packet, err := in.ReadMsg()
if err != nil {
// We don't block as that might leak goroutines
select {
case errC <- fmt.Errorf("failed to read packet from pipe: %v", err):
return
default:
return
}
}
if err := rw.WriteMsg(packet); err != nil {
// We don't block as that might leak goroutines
select {
case errC <- fmt.Errorf("failed to write packet: %v", err):
return
default:
return
}
}
}
}()
go func() {
// We don't block as that might leak goroutines
select {
case errC <- runLoop(p, out):
return
default:
return
}
}()
return <-errC
}
// 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 {
return false
}
if stringSliceContains(r.whitelistedIPs, ip) {
return false
}
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 {
return false
}
var id enode.ID
copy(id[:], peerID)
if enodeIDSliceContains(r.whitelistedPeerIDs, id) {
return false
}
return r.peerIDThrottler.Halt(id.String(), 1, r.limitPerSecPeerID)
}
func stringSliceContains(s []string, searched string) bool {
for _, item := range s {
if item == searched {
return true
}
}
return false
}
func enodeIDSliceContains(s []enode.ID, searched enode.ID) bool {
for _, item := range s {
if bytes.Equal(item.Bytes(), searched.Bytes()) {
return true
}
}
return false
}

View file

@ -1,158 +0,0 @@
package whisper
import (
"bytes"
"testing"
"time"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/p2p"
)
func TestPeerRateLimiterDecorator(t *testing.T) {
in, out := p2p.MsgPipe()
payload := []byte{0x01, 0x02, 0x03}
msg := p2p.Msg{
Code: 1,
Size: uint32(len(payload)),
Payload: bytes.NewReader(payload),
ReceivedAt: time.Now(),
}
go func() {
err := in.WriteMsg(msg)
require.NoError(t, err)
}()
messages := make(chan p2p.Msg, 1)
runLoop := func(p *Peer, rw p2p.MsgReadWriter) error {
msg, err := rw.ReadMsg()
if err != nil {
return err
}
messages <- msg
return nil
}
r := NewPeerRateLimiter(nil, &mockRateLimiterHandler{})
err := r.decorate(nil, out, runLoop)
require.NoError(t, err)
receivedMsg := <-messages
receivedPayload := make([]byte, receivedMsg.Size)
_, err = receivedMsg.Payload.Read(receivedPayload)
require.NoError(t, err)
require.Equal(t, msg.Code, receivedMsg.Code)
require.Equal(t, payload, receivedPayload)
}
func TestPeerLimiterThrottlingWithZeroLimit(t *testing.T) {
r := NewPeerRateLimiter(&PeerRateLimiterConfig{}, &mockRateLimiterHandler{})
for i := 0; i < 1000; i++ {
throttle := r.throttleIP("<nil>")
require.False(t, throttle)
throttle = r.throttlePeer([]byte{0x01, 0x02, 0x03})
require.False(t, throttle)
}
}
func TestPeerLimiterHandler(t *testing.T) {
h := &mockRateLimiterHandler{}
r := NewPeerRateLimiter(nil, h)
p := &Peer{
peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil),
}
rw1, rw2 := p2p.MsgPipe()
count := 100
go func() {
err := echoMessages(r, p, rw2)
require.NoError(t, err)
}()
done := make(chan struct{})
go func() {
for i := 0; i < count; i++ {
msg, err := rw1.ReadMsg()
require.NoError(t, err)
require.EqualValues(t, 101, msg.Code)
}
close(done)
}()
for i := 0; i < count; i++ {
err := rw1.WriteMsg(p2p.Msg{Code: 101})
require.NoError(t, err)
}
<-done
require.EqualValues(t, 100-defaultPeerRateLimiterConfig.LimitPerSecIP, h.exceedIPLimit)
require.EqualValues(t, 100-defaultPeerRateLimiterConfig.LimitPerSecPeerID, h.exceedPeerLimit)
}
func TestPeerLimiterHandlerWithWhitelisting(t *testing.T) {
h := &mockRateLimiterHandler{}
r := NewPeerRateLimiter(&PeerRateLimiterConfig{
LimitPerSecIP: 1,
LimitPerSecPeerID: 1,
WhitelistedIPs: []string{"<nil>"}, // no IP is represented as <nil> string
WhitelistedPeerIDs: []enode.ID{{0xaa, 0xbb, 0xcc}},
}, h)
p := &Peer{
peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil),
}
rw1, rw2 := p2p.MsgPipe()
count := 100
go func() {
err := echoMessages(r, p, rw2)
require.NoError(t, err)
}()
done := make(chan struct{})
go func() {
for i := 0; i < count; i++ {
msg, err := rw1.ReadMsg()
require.NoError(t, err)
require.EqualValues(t, 101, msg.Code)
}
close(done)
}()
for i := 0; i < count; i++ {
err := rw1.WriteMsg(p2p.Msg{Code: 101})
require.NoError(t, err)
}
<-done
require.Equal(t, 0, h.exceedIPLimit)
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 {
for {
msg, err := rw.ReadMsg()
if err != nil {
return err
}
err = rw.WriteMsg(msg)
if err != nil {
return err
}
}
})
}
type mockRateLimiterHandler struct {
exceedPeerLimit int
exceedIPLimit int
}
func (m *mockRateLimiterHandler) ExceedPeerLimit() error { m.exceedPeerLimit++; return nil }
func (m *mockRateLimiterHandler) ExceedIPLimit() error { m.exceedIPLimit++; return nil }

View file

@ -1,194 +0,0 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package shhclient
import (
"context"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/whisper"
)
// Client defines typed wrappers for the Whisper v6 RPC API.
type Client struct {
c *rpc.Client
}
// Dial connects a client to the given URL.
func Dial(rawurl string) (*Client, error) {
c, err := rpc.Dial(rawurl)
if err != nil {
return nil, err
}
return NewClient(c), nil
}
// NewClient creates a client that uses the given RPC client.
func NewClient(c *rpc.Client) *Client {
return &Client{c}
}
// Version returns the Whisper sub-protocol version.
func (sc *Client) Version(ctx context.Context) (string, error) {
var result string
err := sc.c.CallContext(ctx, &result, "shh_version")
return result, err
}
// Info returns diagnostic information about the whisper node.
func (sc *Client) Info(ctx context.Context) (whisper.Info, error) {
var info whisper.Info
err := sc.c.CallContext(ctx, &info, "shh_info")
return info, err
}
// SetMaxMessageSize sets the maximal message size allowed by this node. Incoming
// and outgoing messages with a larger size will be rejected. Whisper message size
// can never exceed the limit imposed by the underlying P2P protocol (10 Mb).
func (sc *Client) SetMaxMessageSize(ctx context.Context, size uint32) error {
var ignored bool
return sc.c.CallContext(ctx, &ignored, "shh_setMaxMessageSize", size)
}
// SetMinimumPoW (experimental) sets the minimal PoW required by this node.
// This experimental function was introduced for the future dynamic adjustment of
// PoW requirement. If the node is overwhelmed with messages, it should raise the
// PoW requirement and notify the peers. The new value should be set relative to
// the old value (e.g. double). The old value could be obtained via shh_info call.
func (sc *Client) SetMinimumPoW(ctx context.Context, pow float64) error {
var ignored bool
return sc.c.CallContext(ctx, &ignored, "shh_setMinPoW", pow)
}
// MarkTrustedPeer marks specific peer trusted, which will allow it to send historic (expired) messages.
// Note This function is not adding new nodes, the node needs to exists as a peer.
func (sc *Client) MarkTrustedPeer(ctx context.Context, enode string) error {
var ignored bool
return sc.c.CallContext(ctx, &ignored, "shh_markTrustedPeer", enode)
}
// NewKeyPair generates a new public and private key pair for message decryption and encryption.
// It returns an identifier that can be used to refer to the key.
func (sc *Client) NewKeyPair(ctx context.Context) (string, error) {
var id string
return id, sc.c.CallContext(ctx, &id, "shh_newKeyPair")
}
// AddPrivateKey stored the key pair, and returns its ID.
func (sc *Client) AddPrivateKey(ctx context.Context, key []byte) (string, error) {
var id string
return id, sc.c.CallContext(ctx, &id, "shh_addPrivateKey", hexutil.Bytes(key))
}
// DeleteKeyPair delete the specifies key.
func (sc *Client) DeleteKeyPair(ctx context.Context, id string) (string, error) {
var ignored bool
return id, sc.c.CallContext(ctx, &ignored, "shh_deleteKeyPair", id)
}
// HasKeyPair returns an indication if the node has a private key or
// key pair matching the given ID.
func (sc *Client) HasKeyPair(ctx context.Context, id string) (bool, error) {
var has bool
return has, sc.c.CallContext(ctx, &has, "shh_hasKeyPair", id)
}
// PublicKey return the public key for a key ID.
func (sc *Client) PublicKey(ctx context.Context, id string) ([]byte, error) {
var key hexutil.Bytes
return key, sc.c.CallContext(ctx, &key, "shh_getPublicKey", id)
}
// PrivateKey return the private key for a key ID.
func (sc *Client) PrivateKey(ctx context.Context, id string) ([]byte, error) {
var key hexutil.Bytes
return key, sc.c.CallContext(ctx, &key, "shh_getPrivateKey", id)
}
// NewSymmetricKey generates a random symmetric key and returns its identifier.
// Can be used encrypting and decrypting messages where the key is known to both parties.
func (sc *Client) NewSymmetricKey(ctx context.Context) (string, error) {
var id string
return id, sc.c.CallContext(ctx, &id, "shh_newSymKey")
}
// AddSymmetricKey stores the key, and returns its identifier.
func (sc *Client) AddSymmetricKey(ctx context.Context, key []byte) (string, error) {
var id string
return id, sc.c.CallContext(ctx, &id, "shh_addSymKey", hexutil.Bytes(key))
}
// GenerateSymmetricKeyFromPassword generates the key from password, stores it, and returns its identifier.
func (sc *Client) GenerateSymmetricKeyFromPassword(ctx context.Context, passwd string) (string, error) {
var id string
return id, sc.c.CallContext(ctx, &id, "shh_generateSymKeyFromPassword", passwd)
}
// HasSymmetricKey returns an indication if the key associated with the given id is stored in the node.
func (sc *Client) HasSymmetricKey(ctx context.Context, id string) (bool, error) {
var found bool
return found, sc.c.CallContext(ctx, &found, "shh_hasSymKey", id)
}
// GetSymmetricKey returns the symmetric key associated with the given identifier.
func (sc *Client) GetSymmetricKey(ctx context.Context, id string) ([]byte, error) {
var key hexutil.Bytes
return key, sc.c.CallContext(ctx, &key, "shh_getSymKey", id)
}
// DeleteSymmetricKey deletes the symmetric key associated with the given identifier.
func (sc *Client) DeleteSymmetricKey(ctx context.Context, id string) error {
var ignored bool
return sc.c.CallContext(ctx, &ignored, "shh_deleteSymKey", id)
}
// Post a message onto the network.
func (sc *Client) Post(ctx context.Context, message whisper.NewMessage) (string, error) {
var hash string
return hash, sc.c.CallContext(ctx, &hash, "shh_post", message)
}
// SubscribeMessages subscribes to messages that match the given criteria. This method
// is only supported on bi-directional connections such as websockets and IPC.
// NewMessageFilter uses polling and is supported over HTTP.
func (sc *Client) SubscribeMessages(ctx context.Context, criteria whisper.Criteria, ch chan<- *whisper.Message) (ethereum.Subscription, error) {
return sc.c.ShhSubscribe(ctx, ch, "messages", criteria)
}
// NewMessageFilter creates a filter within the node. This filter can be used to poll
// for new messages (see FilterMessages) that satisfy the given criteria. A filter can
// timeout when it was polled for in whisper.filterTimeout.
func (sc *Client) NewMessageFilter(ctx context.Context, criteria whisper.Criteria) (string, error) {
var id string
return id, sc.c.CallContext(ctx, &id, "shh_newMessageFilter", criteria)
}
// DeleteMessageFilter removes the filter associated with the given id.
func (sc *Client) DeleteMessageFilter(ctx context.Context, id string) error {
var ignored bool
return sc.c.CallContext(ctx, &ignored, "shh_deleteMessageFilter", id)
}
// FilterMessages retrieves all messages that are received between the last call to
// this function and match the criteria that where given when the filter was created.
func (sc *Client) FilterMessages(ctx context.Context, id string) ([]*whisper.Message, error) {
var messages []*whisper.Message
return messages, sc.c.CallContext(ctx, &messages, "shh_getFilterMessages", id)
}

View file

@ -1,57 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Topic element.
package whisper
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// TopicType represents a cryptographically secure, probabilistic partial
// classifications of a message, determined as the first (left) 4 bytes of the
// SHA3 hash of some arbitrary data given by the original author of the message.
type TopicType [TopicLength]byte
// BytesToTopic converts from the byte array representation of a topic
// into the TopicType type.
func BytesToTopic(b []byte) (t TopicType) {
sz := TopicLength
if x := len(b); x < TopicLength {
sz = x
}
for i := 0; i < sz; i++ {
t[i] = b[i]
}
return t
}
// String converts a topic byte array to a string representation.
func (t *TopicType) String() string {
return common.ToHex(t[:]) // nolint: staticcheck
}
// MarshalText returns the hex representation of t.
func (t TopicType) MarshalText() ([]byte, error) {
return hexutil.Bytes(t[:]).MarshalText()
}
// UnmarshalText parses a hex representation to a topic.
func (t *TopicType) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("Topic", input, t[:])
}

View file

@ -1,134 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisper
import (
"encoding/json"
"testing"
)
var topicStringTests = []struct {
topic TopicType
str string
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"},
{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"},
{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"},
}
func TestTopicString(t *testing.T) {
for i, tst := range topicStringTests {
s := tst.topic.String()
if s != tst.str {
t.Fatalf("failed test %d: have %s, want %s.", i, s, tst.str)
}
}
}
var bytesToTopicTests = []struct {
data []byte
topic TopicType
}{
{topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}},
{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}},
{topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}},
{topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}},
{topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil},
}
var unmarshalTestsGood = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x00000000"`)},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte(`"0x007f80ff"`)},
{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte(`"0xff807f00"`)},
{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte(`"0xf26e7779"`)},
}
var unmarshalTestsBad = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"abcdefg0"`)},
}
var unmarshalTestsUgly = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte(`"0x00000001"`)},
}
func TestBytesToTopic(t *testing.T) {
for i, tst := range bytesToTopicTests {
top := BytesToTopic(tst.data)
if top != tst.topic {
t.Fatalf("failed test %d: have %v, want %v.", i, t, tst.topic)
}
}
}
func TestUnmarshalTestsGood(t *testing.T) {
for i, tst := range unmarshalTestsGood {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err != nil {
t.Errorf("failed test %d. input: %v. err: %v", i, tst.data, err)
} else if top != tst.topic {
t.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
}
}
}
func TestUnmarshalTestsBad(t *testing.T) {
// in this test UnmarshalJSON() is supposed to fail
for i, tst := range unmarshalTestsBad {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err == nil {
t.Fatalf("failed test %d. input: %v.", i, tst.data)
}
}
}
func TestUnmarshalTestsUgly(t *testing.T) {
// in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong
for i, tst := range unmarshalTestsUgly {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err != nil {
t.Errorf("failed test %d. input: %v.", i, tst.data)
} else if top == tst.topic {
t.Errorf("failed test %d: have %v, want %v.", i, top, tst.topic)
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff