Skip to content

Commit 38b1e8e

Browse files
gluk256fjl
authored andcommitted
whisper/whisperv6: PoW requirement (ethereum#15701)
New Whisper-level message introduced (PoW requirement), corresponding logic added, plus some tests.
1 parent b0d41e3 commit 38b1e8e

File tree

5 files changed

+185
-54
lines changed

5 files changed

+185
-54
lines changed

whisper/whisperv6/doc.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,13 @@ const (
4040
ProtocolVersionStr = "6.0"
4141
ProtocolName = "shh"
4242

43-
statusCode = 0 // used by whisper protocol
44-
messagesCode = 1 // normal whisper message
45-
p2pCode = 2 // peer-to-peer message (to be consumed by the peer, but not forwarded any further)
46-
p2pRequestCode = 3 // peer-to-peer message, used by Dapp protocol
43+
// whisper protocol message codes, according to EIP-627
44+
statusCode = 0 // used by whisper protocol
45+
messagesCode = 1 // normal whisper message
46+
powRequirementCode = 2 // PoW requirement
47+
bloomFilterExCode = 3 // bloom filter exchange
48+
p2pRequestCode = 126 // peer-to-peer message, used by Dapp protocol
49+
p2pMessageCode = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further)
4750
NumberOfMessageCodes = 128
4851

4952
paddingMask = byte(3)

whisper/whisperv6/peer.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package whisperv6
1818

1919
import (
2020
"fmt"
21+
"math"
2122
"time"
2223

2324
"github.com/ethereum/go-ethereum/common"
@@ -29,10 +30,12 @@ import (
2930

3031
// peer represents a whisper protocol peer connection.
3132
type Peer struct {
32-
host *Whisper
33-
peer *p2p.Peer
34-
ws p2p.MsgReadWriter
35-
trusted bool
33+
host *Whisper
34+
peer *p2p.Peer
35+
ws p2p.MsgReadWriter
36+
37+
trusted bool
38+
powRequirement float64
3639

3740
known *set.Set // Messages already known by the peer to avoid wasting bandwidth
3841

@@ -42,12 +45,13 @@ type Peer struct {
4245
// newPeer creates a new whisper peer object, but does not run the handshake itself.
4346
func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
4447
return &Peer{
45-
host: host,
46-
peer: remote,
47-
ws: rw,
48-
trusted: false,
49-
known: set.New(),
50-
quit: make(chan struct{}),
48+
host: host,
49+
peer: remote,
50+
ws: rw,
51+
trusted: false,
52+
powRequirement: 0.0,
53+
known: set.New(),
54+
quit: make(chan struct{}),
5155
}
5256
}
5357

@@ -152,7 +156,7 @@ func (p *Peer) broadcast() error {
152156
envelopes := p.host.Envelopes()
153157
bundle := make([]*Envelope, 0, len(envelopes))
154158
for _, envelope := range envelopes {
155-
if !p.marked(envelope) {
159+
if !p.marked(envelope) && envelope.PoW() >= p.powRequirement {
156160
bundle = append(bundle, envelope)
157161
}
158162
}
@@ -177,3 +181,8 @@ func (p *Peer) ID() []byte {
177181
id := p.peer.ID()
178182
return id[:]
179183
}
184+
185+
func (p *Peer) notifyAboutPowRequirementChange(pow float64) error {
186+
i := math.Float64bits(pow)
187+
return p2p.Send(p.ws, powRequirementCode, i)
188+
}

whisper/whisperv6/peer_test.go

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,31 @@ var sharedKey []byte = []byte("some arbitrary data here")
8888
var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0}
8989
var expectedMessage []byte = []byte("per rectum ad astra")
9090

91-
// This test does the following:
92-
// 1. creates a chain of whisper nodes,
93-
// 2. installs the filters with shared (predefined) parameters,
94-
// 3. each node sends a number of random (undecryptable) messages,
95-
// 4. first node sends one expected (decryptable) message,
96-
// 5. checks if each node have received and decrypted exactly one message.
9791
func TestSimulation(t *testing.T) {
92+
// create a chain of whisper nodes,
93+
// installs the filters with shared (predefined) parameters
9894
initialize(t)
9995

96+
// each node sends a number of random (undecryptable) messages
10097
for i := 0; i < NumNodes; i++ {
10198
sendMsg(t, false, i)
10299
}
103100

101+
// node #0 sends one expected (decryptable) message
104102
sendMsg(t, true, 0)
105-
checkPropagation(t)
103+
104+
// check if each node have received and decrypted exactly one message
105+
checkPropagation(t, true)
106+
107+
// send protocol-level messages (powRequirementCode) and check the new PoW requirement values
108+
powReqExchange(t)
109+
110+
// node #1 sends one expected (decryptable) message
111+
sendMsg(t, true, 1)
112+
113+
// check if each node (except node #0) have received and decrypted exactly one message
114+
checkPropagation(t, false)
115+
106116
stopServers()
107117
}
108118

@@ -114,7 +124,7 @@ func initialize(t *testing.T) {
114124
for i := 0; i < NumNodes; i++ {
115125
var node TestNode
116126
node.shh = New(&DefaultConfig)
117-
node.shh.SetMinimumPoW(0.00000001)
127+
node.shh.SetMinimumPowTest(0.00000001)
118128
node.shh.Start(nil)
119129
topics := make([]TopicType, 0)
120130
topics = append(topics, sharedTopic)
@@ -154,13 +164,18 @@ func initialize(t *testing.T) {
154164
},
155165
}
156166

157-
err = node.server.Start()
158-
if err != nil {
159-
t.Fatalf("failed to start server %d.", i)
160-
}
161-
162167
nodes[i] = &node
163168
}
169+
170+
for i := 1; i < NumNodes; i++ {
171+
go nodes[i].server.Start()
172+
}
173+
174+
// we need to wait until the first node actually starts
175+
err = nodes[0].server.Start()
176+
if err != nil {
177+
t.Fatalf("failed to start the fisrt server.")
178+
}
164179
}
165180

166181
func stopServers() {
@@ -174,18 +189,21 @@ func stopServers() {
174189
}
175190
}
176191

177-
func checkPropagation(t *testing.T) {
192+
func checkPropagation(t *testing.T, includingNodeZero bool) {
178193
if t.Failed() {
179194
return
180195
}
181196

182-
const cycle = 100
183-
const iterations = 100
197+
const cycle = 50
198+
const iterations = 200
184199

185-
for j := 0; j < iterations; j++ {
186-
time.Sleep(cycle * time.Millisecond)
200+
first := 0
201+
if !includingNodeZero {
202+
first = 1
203+
}
187204

188-
for i := 0; i < NumNodes; i++ {
205+
for j := 0; j < iterations; j++ {
206+
for i := first; i < NumNodes; i++ {
189207
f := nodes[i].shh.GetFilter(nodes[i].filerId)
190208
if f == nil {
191209
t.Fatalf("failed to get filterId %s from node %d.", nodes[i].filerId, i)
@@ -200,9 +218,18 @@ func checkPropagation(t *testing.T) {
200218
return
201219
}
202220
}
221+
222+
time.Sleep(cycle * time.Millisecond)
203223
}
204224

205225
t.Fatalf("Test was not complete: timeout %d seconds.", iterations*cycle/1000)
226+
227+
if !includingNodeZero {
228+
f := nodes[0].shh.GetFilter(nodes[0].filerId)
229+
if f != nil {
230+
t.Fatalf("node zero received a message with low PoW.")
231+
}
232+
}
206233
}
207234

208235
func validateMail(t *testing.T, index int, mail []*ReceivedMessage) bool {
@@ -304,3 +331,35 @@ func TestPeerBasic(t *testing.T) {
304331
t.Fatalf("failed mark with seed %d.", seed)
305332
}
306333
}
334+
335+
func powReqExchange(t *testing.T) {
336+
for i, node := range nodes {
337+
for peer := range node.shh.peers {
338+
if peer.powRequirement > 1000.0 {
339+
t.Fatalf("node %d: one of the peers' pow requirement is too big (%f).", i, peer.powRequirement)
340+
}
341+
}
342+
}
343+
344+
const pow float64 = 7777777.0
345+
nodes[0].shh.SetMinimumPoW(pow)
346+
347+
// wait until all the messages are delivered
348+
time.Sleep(64 * time.Millisecond)
349+
350+
cnt := 0
351+
for i, node := range nodes {
352+
for peer := range node.shh.peers {
353+
if peer.peer.ID() == discover.PubkeyID(&nodes[0].id.PublicKey) {
354+
cnt++
355+
if peer.powRequirement != pow {
356+
t.Fatalf("node %d: failed to set the new pow requirement.", i)
357+
}
358+
}
359+
}
360+
}
361+
362+
if cnt == 0 {
363+
t.Fatalf("no matching peers found.")
364+
}
365+
}

whisper/whisperv6/whisper.go

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
crand "crypto/rand"
2323
"crypto/sha256"
2424
"fmt"
25+
"math"
2526
"runtime"
2627
"sync"
2728
"time"
@@ -30,6 +31,7 @@ import (
3031
"github.com/ethereum/go-ethereum/crypto"
3132
"github.com/ethereum/go-ethereum/log"
3233
"github.com/ethereum/go-ethereum/p2p"
34+
"github.com/ethereum/go-ethereum/rlp"
3335
"github.com/ethereum/go-ethereum/rpc"
3436
"github.com/syndtr/goleveldb/leveldb/errors"
3537
"golang.org/x/crypto/pbkdf2"
@@ -74,6 +76,8 @@ type Whisper struct {
7476

7577
settings syncmap.Map // holds configuration settings that can be dynamically changed
7678

79+
reactionAllowance int // maximum time in seconds allowed to process the whisper-related messages
80+
7781
statsMu sync.Mutex // guard stats
7882
stats Statistics // Statistics of whisper node
7983

@@ -87,14 +91,15 @@ func New(cfg *Config) *Whisper {
8791
}
8892

8993
whisper := &Whisper{
90-
privateKeys: make(map[string]*ecdsa.PrivateKey),
91-
symKeys: make(map[string][]byte),
92-
envelopes: make(map[common.Hash]*Envelope),
93-
expirations: make(map[uint32]*set.SetNonTS),
94-
peers: make(map[*Peer]struct{}),
95-
messageQueue: make(chan *Envelope, messageQueueLimit),
96-
p2pMsgQueue: make(chan *Envelope, messageQueueLimit),
97-
quit: make(chan struct{}),
94+
privateKeys: make(map[string]*ecdsa.PrivateKey),
95+
symKeys: make(map[string][]byte),
96+
envelopes: make(map[common.Hash]*Envelope),
97+
expirations: make(map[uint32]*set.SetNonTS),
98+
peers: make(map[*Peer]struct{}),
99+
messageQueue: make(chan *Envelope, messageQueueLimit),
100+
p2pMsgQueue: make(chan *Envelope, messageQueueLimit),
101+
quit: make(chan struct{}),
102+
reactionAllowance: SynchAllowance,
98103
}
99104

100105
whisper.filters = NewFilters(whisper)
@@ -177,13 +182,50 @@ func (w *Whisper) SetMaxMessageSize(size uint32) error {
177182

178183
// SetMinimumPoW sets the minimal PoW required by this node
179184
func (w *Whisper) SetMinimumPoW(val float64) error {
180-
if val <= 0.0 {
185+
if val < 0.0 {
181186
return fmt.Errorf("invalid PoW: %f", val)
182187
}
183-
w.settings.Store(minPowIdx, val)
188+
189+
w.notifyPeersAboutPowRequirementChange(val)
190+
191+
go func() {
192+
// allow some time before all the peers have processed the notification
193+
time.Sleep(time.Duration(w.reactionAllowance) * time.Second)
194+
w.settings.Store(minPowIdx, val)
195+
}()
196+
184197
return nil
185198
}
186199

200+
// SetMinimumPoW sets the minimal PoW in test environment
201+
func (w *Whisper) SetMinimumPowTest(val float64) {
202+
w.notifyPeersAboutPowRequirementChange(val)
203+
w.settings.Store(minPowIdx, val)
204+
}
205+
206+
func (w *Whisper) notifyPeersAboutPowRequirementChange(pow float64) {
207+
arr := make([]*Peer, len(w.peers))
208+
i := 0
209+
210+
w.peerMu.Lock()
211+
for p := range w.peers {
212+
arr[i] = p
213+
i++
214+
}
215+
w.peerMu.Unlock()
216+
217+
for _, p := range arr {
218+
err := p.notifyAboutPowRequirementChange(pow)
219+
if err != nil {
220+
// allow one retry
221+
err = p.notifyAboutPowRequirementChange(pow)
222+
}
223+
if err != nil {
224+
log.Warn("oversized message received", "peer", p.ID(), "error", err)
225+
}
226+
}
227+
}
228+
187229
// getPeer retrieves peer by ID
188230
func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
189231
w.peerMu.Lock()
@@ -233,7 +275,7 @@ func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
233275

234276
// SendP2PDirect sends a peer-to-peer message to a specific peer.
235277
func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error {
236-
return p2p.Send(peer.ws, p2pCode, envelope)
278+
return p2p.Send(peer.ws, p2pMessageCode, envelope)
237279
}
238280

239281
// NewKeyPair generates a new cryptographic identity for the client, and injects
@@ -536,7 +578,22 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
536578
if trouble {
537579
return errors.New("invalid envelope")
538580
}
539-
case p2pCode:
581+
case powRequirementCode:
582+
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
583+
i, err := s.Uint()
584+
if err != nil {
585+
log.Warn("failed to decode powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
586+
return errors.New("invalid powRequirementCode message")
587+
}
588+
f := math.Float64frombits(i)
589+
if math.IsInf(f, 0) || math.IsNaN(f) || f < 0.0 {
590+
log.Warn("invalid value in powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
591+
return errors.New("invalid value in powRequirementCode message")
592+
}
593+
p.powRequirement = f
594+
case bloomFilterExCode:
595+
// to be implemented
596+
case p2pMessageCode:
540597
// peer-to-peer message, sent directly to peer bypassing PoW checks, etc.
541598
// this message is not supposed to be forwarded to other peers, and
542599
// therefore might not satisfy the PoW, expiry and other requirements.
@@ -599,7 +656,10 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
599656

600657
if envelope.PoW() < wh.MinPow() {
601658
log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex())
602-
return false, nil // drop envelope without error
659+
return false, nil // drop envelope without error for now
660+
661+
// once the status message includes the PoW requirement, an error should be returned here:
662+
//return false, fmt.Errorf("envelope with low PoW dropped: PoW=%f, hash=[%v]", envelope.PoW(), envelope.Hash().Hex())
603663
}
604664

605665
hash := envelope.Hash()

0 commit comments

Comments
 (0)