diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 223d8246ee..3a9e27ca03 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -15,9 +15,7 @@ // along with the go-ethereum library. If not, see . /* -Package whisper implements the Whisper PoC-1. - -(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec) +Package whisper implements the Whisper protocol (version 5). 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 @@ -57,11 +55,12 @@ const ( saltLength = 12 AESNonceMaxLength = 12 - MaxMessageLength = 0xFFFF // todo: remove this restriction after testing in morden and analizing stats. this should be regulated by MinimumPoW. - MinimumPoW = 10.0 // todo: review + MaxMessageLength = 0xFFFF // todo: remove this restriction after testing. this should be regulated by PoW. + MinimumPoW = 10.0 // todo: review after testing. padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility + messageQueueLimit = 1024 expirationCycle = time.Second transmissionCycle = 300 * time.Millisecond diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go index 3d048bb445..1b976705d4 100644 --- a/whisper/whisperv5/envelope.go +++ b/whisper/whisperv5/envelope.go @@ -14,14 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Contains the Whisper protocol Envelope element. For formal details please see -// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#envelopes. +// Contains the Whisper protocol Envelope element. package whisperv5 import ( "crypto/ecdsa" "encoding/binary" + "errors" "fmt" "math" "time" @@ -86,8 +86,8 @@ func (e *Envelope) Ver() uint64 { // 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) { - var target int +func (e *Envelope) Seal(options *MessageParams) error { + var target, bestBit int if options.PoW == 0 { // adjust for the duration of Seal() execution only if execution time is predefined unconditionally e.Expiry += options.WorkTime @@ -99,7 +99,7 @@ func (e *Envelope) Seal(options *MessageParams) { h := crypto.Keccak256(e.rlpWithoutNonce()) copy(buf[:32], h) - finish, bestBit := time.Now().Add(time.Duration(options.WorkTime)*time.Second).UnixNano(), 0 + 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[56:], nonce) @@ -108,12 +108,18 @@ func (e *Envelope) Seal(options *MessageParams) { if firstBit > bestBit { e.EnvNonce, bestBit = nonce, firstBit if target > 0 && bestBit >= target { - return + return nil } } nonce++ } } + + if target > 0 && bestBit < target { + return errors.New("Failed to reach the PoW target") + } + + return nil } func (e *Envelope) PoW() float64 { diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index f3812b1d8a..a095f7c0cd 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -14,9 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Contains the Whisper protocol Message element. For formal details please see -// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages. -// todo: fix the spec link, and move it to doc.go +// Contains the Whisper protocol Message element. package whisperv5 @@ -256,7 +254,11 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er } envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg) - envelope.Seal(options) + err = envelope.Seal(options) + if err != nil { + return nil, err + } + return envelope, nil } diff --git a/whisper/whisperv5/message_test.go b/whisper/whisperv5/message_test.go index cd327d9d90..394291a837 100644 --- a/whisper/whisperv5/message_test.go +++ b/whisper/whisperv5/message_test.go @@ -30,11 +30,15 @@ func copyFromBuf(dst []byte, src []byte, beg int) int { } func generateMessageParams() (*MessageParams, error) { + // set all the parameters except p.Dst + buf := make([]byte, 1024) randomize(buf) sz := rand.Intn(400) var p MessageParams + p.PoW = 0.01 + p.WorkTime = 1 p.TTL = uint32(rand.Intn(1024)) p.Payload = make([]byte, sz) p.Padding = make([]byte, padSizeLimitUpper) @@ -52,8 +56,6 @@ func generateMessageParams() (*MessageParams, error) { return nil, err } - // p.Dst, p.PoW, p.WorkTime are not set - p.PoW = 0.01 return &p, nil } @@ -152,6 +154,16 @@ func TestMessageWrap(t *testing.T) { 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 := NewSentMessage(params) + params.TTL = 1000000 + params.WorkTime = 1 + params.PoW = 10000000.0 + env, err = msg2.Wrap(params) + if err == nil { + t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed) + } } func TestMessageSeal(t *testing.T) { @@ -274,3 +286,32 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { } } } + +func TestEncryptWithZeroKey(t *testing.T) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + msg := NewSentMessage(params) + + params.KeySym = make([]byte, aesKeyLength) + _, err = msg.Wrap(params) + if err == nil { + t.Fatalf("wrapped with zero key, seed: %d.", seed) + } + + params.KeySym = make([]byte, 0) + _, err = msg.Wrap(params) + if err == nil { + t.Fatalf("wrapped with empty key, seed: %d.", seed) + } + + params.KeySym = nil + _, err = msg.Wrap(params) + if err == nil { + t.Fatalf("wrapped with nil key, seed: %d.", seed) + } +} diff --git a/whisper/whisperv5/topic.go b/whisper/whisperv5/topic.go index c29c344bef..54d7422d17 100644 --- a/whisper/whisperv5/topic.go +++ b/whisper/whisperv5/topic.go @@ -14,8 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Contains the Whisper protocol Topic element. For formal details please see -// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#topics. +// Contains the Whisper protocol Topic element. package whisperv5 diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index f8a170930d..2381ce4443 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -51,7 +51,7 @@ type Whisper struct { symKeys map[string][]byte keyMu sync.RWMutex - envelopes map[common.Hash]*Envelope // Pool of messages currently tracked by this node + envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node messages map[common.Hash]*ReceivedMessage // Pool of successfully decrypted messages, which are not expired yet expirations map[uint32]*set.SetNonTS // Message expiration pool poolMu sync.RWMutex // Mutex to sync the message and expiration pools @@ -64,7 +64,8 @@ type Whisper struct { messageQueue chan QueueItem quit chan struct{} - test bool + overflow bool + test bool } // New creates a Whisper client ready to communicate through the Ethereum P2P network. @@ -78,7 +79,7 @@ func NewWhisper(server MailServer) *Whisper { expirations: make(map[uint32]*set.SetNonTS), peers: make(map[*Peer]struct{}), mailServer: server, - messageQueue: make(chan QueueItem, 1024), + messageQueue: make(chan QueueItem, messageQueueLimit), quit: make(chan struct{}), } whisper.filters = NewFilters(whisper) @@ -397,7 +398,7 @@ func (wh *Whisper) add(envelope *Envelope) error { if sent > now { if sent-SynchAllowance > now { - return fmt.Errorf("message created in the future") + return fmt.Errorf("envelope created in the future [%x]", envelope.Hash()) } else { // recalculate PoW, adjusted for the time difference, plus one second for latency envelope.calculatePoW(sent - now + 1) @@ -408,30 +409,31 @@ func (wh *Whisper) add(envelope *Envelope) error { if envelope.Expiry+SynchAllowance*2 < now { return fmt.Errorf("very old message") } else { + glog.V(logger.Debug).Infof("expired envelope dropped [%x]", envelope.Hash()) return nil // drop envelope without error } } if len(envelope.Data) > MaxMessageLength { - return fmt.Errorf("huge messages are not allowed") + return fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } if len(envelope.Version) > 4 { - return fmt.Errorf("oversized Version") + return fmt.Errorf("oversized version [%x]", envelope.Hash()) } if len(envelope.AESNonce) > AESNonceMaxLength { // the standard AES GSM nonce size is 12, // but const gcmStandardNonceSize cannot be accessed directly - return fmt.Errorf("oversized AESNonce") + return fmt.Errorf("oversized AESNonce [%x]", envelope.Hash()) } if len(envelope.Salt) > saltLength { - return fmt.Errorf("oversized Salt") + return fmt.Errorf("oversized salt [%x]", envelope.Hash()) } if envelope.PoW() < MinimumPoW && !wh.test { - glog.V(logger.Debug).Infof("envelope with low PoW dropped: %f", envelope.PoW()) + glog.V(logger.Debug).Infof("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash()) return nil // drop envelope without error } @@ -451,10 +453,10 @@ func (wh *Whisper) add(envelope *Envelope) error { wh.poolMu.Unlock() if alreadyCached { - glog.V(logger.Detail).Infof("whisper envelope already cached: %x\n", envelope) + glog.V(logger.Detail).Infof("whisper envelope already cached [%x]\n", envelope.Hash()) } else { + glog.V(logger.Detail).Infof("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope) wh.postEvent(envelope, messagesCode) // notify the local node about the new message - glog.V(logger.Detail).Infof("cached whisper envelope %v\n", envelope) } return nil } @@ -465,11 +467,28 @@ func (w *Whisper) postEvent(envelope *Envelope, messageCode uint64) { // currently supported version, we can not decrypt it, // and therefore just ignore this message if envelope.Ver() <= EnvelopeVersion { + w.checkOverflow() i := QueueItem{hash: envelope.Hash(), code: messageCode} w.messageQueue <- i } } +// checkOverflow checks if message queue overflow occurs and reports it if necessary. +func (w *Whisper) checkOverflow() { + queueSize := len(w.messageQueue) + + if queueSize == messageQueueLimit { + if !w.overflow { + w.overflow = true + glog.V(logger.Warn).Infoln("message queue overflow") + } + } else if queueSize <= messageQueueLimit/2 { + if w.overflow { + w.overflow = false + } + } +} + // processQueue delivers the messages to the watchers during the lifetime of the whisper node. func (w *Whisper) processQueue() { for { diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index 3f79a72c8c..9af95f4453 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -19,6 +19,7 @@ package whisperv5 import ( "bytes" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -309,3 +310,56 @@ func TestWhisperSymKeyManagement(t *testing.T) { t.Fatalf("failed to delete second key: second key is not nil.") } } + +func TestExpiry(t *testing.T) { + InitSingleTest() + + w := NewWhisper(nil) + w.test = true + w.Start(nil) + defer w.Stop() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + params.TTL = 1 + msg := NewSentMessage(params) + env, err := msg.Wrap(params) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + + // wait till received or timeout + var received, expired bool + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) > 0 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // wait till expired or timeout + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) == 0 { + expired = true + break + } + } + + if !expired { + t.Fatalf("expire failed, seed: %d.", seed) + } +}