Remove whisper
This commit is contained in:
parent
be01875d1d
commit
57b1bc193f
|
@ -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.
|
||||
|
|
102
bridge/bridge.go
102
bridge/bridge.go
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
|
133
params/config.go
133
params/config.go
|
@ -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 {
|
||||
|
|
|
@ -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.
|
604
whisper/api.go
604
whisper/api.go
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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(),
|
||||
}
|
||||
}
|
|
@ -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(¶ms)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
||||
}
|
||||
|
||||
e, err := msg.Wrap(¶ms, 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 = ¶ms.Src.PublicKey
|
||||
filter2.Src = ¶ms.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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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:]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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, ¶ms.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, ¶ms.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)
|
||||
}
|
|
@ -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)
|
||||
}
|
312
whisper/peer.go
312
whisper/peer.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 = ©
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -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 }
|
|
@ -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)
|
||||
}
|
|
@ -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[:])
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
1689
whisper/whisper.go
1689
whisper/whisper.go
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue