mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
whisper: final polishing
This commit is contained in:
parent
e47820d164
commit
3cf9b8dc83
7 changed files with 150 additions and 30 deletions
|
|
@ -15,9 +15,7 @@
|
|||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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 {
|
||||
|
|
|
|||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue