Commit fd869dc8 authored by gluk256's avatar gluk256 Committed by Felix Lange

whisper/whisperv6: implement pow/bloom exchange protocol (#15802)

This is the main feature of v6.
parent 56152b31
...@@ -116,12 +116,17 @@ func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) ...@@ -116,12 +116,17 @@ func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32)
return true, api.w.SetMaxMessageSize(size) return true, api.w.SetMaxMessageSize(size)
} }
// SetMinPow sets the minimum PoW for a message before it is accepted. // SetMinPow sets the minimum PoW, and notifies the peers.
func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
return true, api.w.SetMinimumPoW(pow) return true, api.w.SetMinimumPoW(pow)
} }
// MarkTrustedPeer marks a peer trusted. , which will allow it to send historic (expired) messages. // 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. // Note: This function is not adding new nodes, the node needs to exists as a peer.
func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) { func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) {
n, err := discover.ParseNode(enode) n, err := discover.ParseNode(enode)
......
...@@ -35,7 +35,6 @@ import ( ...@@ -35,7 +35,6 @@ import (
) )
const ( const (
EnvelopeVersion = uint64(0)
ProtocolVersion = uint64(6) ProtocolVersion = uint64(6)
ProtocolVersionStr = "6.0" ProtocolVersionStr = "6.0"
ProtocolName = "shh" ProtocolName = "shh"
...@@ -52,11 +51,14 @@ const ( ...@@ -52,11 +51,14 @@ const (
paddingMask = byte(3) paddingMask = byte(3)
signatureFlag = byte(4) signatureFlag = byte(4)
TopicLength = 4 TopicLength = 4 // in bytes
signatureLength = 65 signatureLength = 65 // in bytes
aesKeyLength = 32 aesKeyLength = 32 // in bytes
AESNonceLength = 12 AESNonceLength = 12 // in bytes
keyIdSize = 32 keyIdSize = 32 // in bytes
bloomFilterSize = 64 // in bytes
EnvelopeHeaderLength = 20
MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message. MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message.
DefaultMaxMessageSize = uint32(1024 * 1024) DefaultMaxMessageSize = uint32(1024 * 1024)
...@@ -68,10 +70,8 @@ const ( ...@@ -68,10 +70,8 @@ const (
expirationCycle = time.Second expirationCycle = time.Second
transmissionCycle = 300 * time.Millisecond transmissionCycle = 300 * time.Millisecond
DefaultTTL = 50 // seconds DefaultTTL = 50 // seconds
SynchAllowance = 10 // seconds DefaultSyncAllowance = 10 // seconds
EnvelopeHeaderLength = 20
) )
type unknownVersionError uint64 type unknownVersionError uint64
......
...@@ -42,9 +42,11 @@ type Envelope struct { ...@@ -42,9 +42,11 @@ type Envelope struct {
Data []byte Data []byte
Nonce uint64 Nonce uint64
pow float64 // Message-specific PoW as described in the Whisper specification. pow float64 // Message-specific PoW as described in the Whisper specification.
hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
// Don't access hash directly, use Hash() function instead. // 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) // size returns the size of envelope as it is sent (i.e. public fields only)
...@@ -227,3 +229,30 @@ func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) { ...@@ -227,3 +229,30 @@ func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
} }
return msg 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
}
...@@ -36,6 +36,7 @@ type Peer struct { ...@@ -36,6 +36,7 @@ type Peer struct {
trusted bool trusted bool
powRequirement float64 powRequirement float64
bloomFilter []byte // may contain nil in case of full node
known *set.Set // Messages already known by the peer to avoid wasting bandwidth known *set.Set // Messages already known by the peer to avoid wasting bandwidth
...@@ -74,8 +75,12 @@ func (p *Peer) handshake() error { ...@@ -74,8 +75,12 @@ func (p *Peer) handshake() error {
// Send the handshake status message asynchronously // Send the handshake status message asynchronously
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
errc <- p2p.Send(p.ws, statusCode, ProtocolVersion) pow := p.host.MinPow()
powConverted := math.Float64bits(pow)
bloom := p.host.BloomFilter()
errc <- p2p.SendItems(p.ws, statusCode, ProtocolVersion, powConverted, bloom)
}() }()
// Fetch the remote status packet and verify protocol match // Fetch the remote status packet and verify protocol match
packet, err := p.ws.ReadMsg() packet, err := p.ws.ReadMsg()
if err != nil { if err != nil {
...@@ -85,14 +90,42 @@ func (p *Peer) handshake() error { ...@@ -85,14 +90,42 @@ func (p *Peer) handshake() error {
return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code) return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code)
} }
s := rlp.NewStream(packet.Payload, uint64(packet.Size)) s := rlp.NewStream(packet.Payload, uint64(packet.Size))
peerVersion, err := s.Uint() _, err = s.List()
if err != nil { if err != nil {
return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err) return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err)
} }
peerVersion, err := s.Uint()
if err != nil {
return fmt.Errorf("peer [%x] sent bad status message (unable to decode version): %v", p.ID(), err)
}
if peerVersion != ProtocolVersion { if peerVersion != ProtocolVersion {
return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion) return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion)
} }
// Wait until out own status is consumed too
// 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", p.ID())
}
p.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", p.ID(), sz)
}
if isFullNode(bloom) {
p.bloomFilter = nil
} else {
p.bloomFilter = bloom
}
}
}
if err := <-errc; err != nil { if err := <-errc; err != nil {
return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err) return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err)
} }
...@@ -156,7 +189,7 @@ func (p *Peer) broadcast() error { ...@@ -156,7 +189,7 @@ func (p *Peer) broadcast() error {
envelopes := p.host.Envelopes() envelopes := p.host.Envelopes()
bundle := make([]*Envelope, 0, len(envelopes)) bundle := make([]*Envelope, 0, len(envelopes))
for _, envelope := range envelopes { for _, envelope := range envelopes {
if !p.marked(envelope) && envelope.PoW() >= p.powRequirement { if !p.marked(envelope) && envelope.PoW() >= p.powRequirement && p.bloomMatch(envelope) {
bundle = append(bundle, envelope) bundle = append(bundle, envelope)
} }
} }
...@@ -186,3 +219,16 @@ func (p *Peer) notifyAboutPowRequirementChange(pow float64) error { ...@@ -186,3 +219,16 @@ func (p *Peer) notifyAboutPowRequirementChange(pow float64) error {
i := math.Float64bits(pow) i := math.Float64bits(pow)
return p2p.Send(p.ws, powRequirementCode, i) return p2p.Send(p.ws, powRequirementCode, i)
} }
func (p *Peer) notifyAboutBloomFilterChange(bloom []byte) error {
return p2p.Send(p.ws, bloomFilterExCode, bloom)
}
func (p *Peer) bloomMatch(env *Envelope) bool {
if p.bloomFilter == nil {
// no filter - full node, accepts all envelops
return true
}
return bloomFilterMatch(p.bloomFilter, env.Bloom())
}
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
mrand "math/rand"
"net" "net"
"sync" "sync"
"testing" "testing"
...@@ -87,6 +88,9 @@ var nodes [NumNodes]*TestNode ...@@ -87,6 +88,9 @@ var nodes [NumNodes]*TestNode
var sharedKey []byte = []byte("some arbitrary data here") var sharedKey []byte = []byte("some arbitrary data here")
var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0} var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0}
var expectedMessage []byte = []byte("per rectum ad astra") var expectedMessage []byte = []byte("per rectum ad astra")
var masterBloomFilter []byte
var masterPow = 0.00000001
var round int = 1
func TestSimulation(t *testing.T) { func TestSimulation(t *testing.T) {
// create a chain of whisper nodes, // create a chain of whisper nodes,
...@@ -104,8 +108,13 @@ func TestSimulation(t *testing.T) { ...@@ -104,8 +108,13 @@ func TestSimulation(t *testing.T) {
// check if each node have received and decrypted exactly one message // check if each node have received and decrypted exactly one message
checkPropagation(t, true) checkPropagation(t, true)
// send protocol-level messages (powRequirementCode) and check the new PoW requirement values // check if Status message was correctly decoded
powReqExchange(t) checkBloomFilterExchange(t)
checkPowExchange(t)
// send new pow and bloom exchange messages
resetParams(t)
round++
// node #1 sends one expected (decryptable) message // node #1 sends one expected (decryptable) message
sendMsg(t, true, 1) sendMsg(t, true, 1)
...@@ -113,18 +122,65 @@ func TestSimulation(t *testing.T) { ...@@ -113,18 +122,65 @@ func TestSimulation(t *testing.T) {
// check if each node (except node #0) have received and decrypted exactly one message // check if each node (except node #0) have received and decrypted exactly one message
checkPropagation(t, false) checkPropagation(t, false)
for i := 1; i < NumNodes; i++ {
time.Sleep(20 * time.Millisecond)
sendMsg(t, true, i)
}
// check if corresponding protocol-level messages were correctly decoded
checkPowExchangeForNodeZero(t)
checkBloomFilterExchange(t)
stopServers() 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)
}
}
func initBloom(t *testing.T) {
masterBloomFilter = make([]byte, bloomFilterSize)
_, err := mrand.Read(masterBloomFilter)
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) { func initialize(t *testing.T) {
initBloom(t)
var err error var err error
ip := net.IPv4(127, 0, 0, 1) ip := net.IPv4(127, 0, 0, 1)
port0 := 30303 port0 := 30303
for i := 0; i < NumNodes; i++ { for i := 0; i < NumNodes; i++ {
var node TestNode var node TestNode
b := make([]byte, bloomFilterSize)
copy(b, masterBloomFilter)
node.shh = New(&DefaultConfig) node.shh = New(&DefaultConfig)
node.shh.SetMinimumPowTest(0.00000001) 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) node.shh.Start(nil)
topics := make([]TopicType, 0) topics := make([]TopicType, 0)
topics = append(topics, sharedTopic) topics = append(topics, sharedTopic)
...@@ -206,7 +262,7 @@ func checkPropagation(t *testing.T, includingNodeZero bool) { ...@@ -206,7 +262,7 @@ func checkPropagation(t *testing.T, includingNodeZero bool) {
for i := first; i < NumNodes; i++ { for i := first; i < NumNodes; i++ {
f := nodes[i].shh.GetFilter(nodes[i].filerId) f := nodes[i].shh.GetFilter(nodes[i].filerId)
if f == nil { if f == nil {
t.Fatalf("failed to get filterId %s from node %d.", nodes[i].filerId, i) t.Fatalf("failed to get filterId %s from node %d, round %d.", nodes[i].filerId, i, round)
} }
mail := f.Retrieve() mail := f.Retrieve()
...@@ -332,34 +388,43 @@ func TestPeerBasic(t *testing.T) { ...@@ -332,34 +388,43 @@ func TestPeerBasic(t *testing.T) {
} }
} }
func powReqExchange(t *testing.T) { func checkPowExchangeForNodeZero(t *testing.T) {
cnt := 0
for i, node := range nodes { for i, node := range nodes {
for peer := range node.shh.peers { for peer := range node.shh.peers {
if peer.powRequirement > 1000.0 { if peer.peer.ID() == discover.PubkeyID(&nodes[0].id.PublicKey) {
t.Fatalf("node %d: one of the peers' pow requirement is too big (%f).", i, peer.powRequirement) cnt++
if peer.powRequirement != masterPow {
t.Fatalf("node %d: failed to set the new pow requirement.", i)
}
} }
} }
} }
if cnt == 0 {
t.Fatalf("no matching peers found.")
}
}
const pow float64 = 7777777.0 func checkPowExchange(t *testing.T) {
nodes[0].shh.SetMinimumPoW(pow)
// wait until all the messages are delivered
time.Sleep(64 * time.Millisecond)
cnt := 0
for i, node := range nodes { for i, node := range nodes {
for peer := range node.shh.peers { for peer := range node.shh.peers {
if peer.peer.ID() == discover.PubkeyID(&nodes[0].id.PublicKey) { if peer.peer.ID() != discover.PubkeyID(&nodes[0].id.PublicKey) {
cnt++ if peer.powRequirement != masterPow {
if peer.powRequirement != pow { t.Fatalf("node %d: failed to exchange pow requirement in round %d; expected %f, got %f",
t.Fatalf("node %d: failed to set the new pow requirement.", i) i, round, masterPow, peer.powRequirement)
} }
} }
} }
} }
}
if cnt == 0 { func checkBloomFilterExchange(t *testing.T) {
t.Fatalf("no matching peers found.") for i, node := range nodes {
for peer := range node.shh.peers {
if !bytes.Equal(peer.bloomFilter, masterBloomFilter) {
t.Fatalf("node %d: failed to exchange bloom filter requirement in round %d. \n%x expected \n%x got",
i, round, masterBloomFilter, peer.bloomFilter)
}
}
} }
} }
This diff is collapsed.
...@@ -843,3 +843,64 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { ...@@ -843,3 +843,64 @@ func TestSymmetricSendKeyMismatch(t *testing.T) {
t.Fatalf("received a message when keys weren't matching") t.Fatalf("received a message when keys weren't matching")
} }
} }
func TestBloom(t *testing.T) {
topic := TopicType{0, 0, 255, 6}
b := TopicToBloom(topic)
x := make([]byte, bloomFilterSize)
x[0] = byte(1)
x[32] = byte(1)
x[bloomFilterSize-1] = byte(128)
if !bloomFilterMatch(x, b) || !bloomFilterMatch(b, x) {
t.Fatalf("bloom filter does not match the mask")
}
_, err := mrand.Read(b)
if err != nil {
t.Fatalf("math rand error")
}
_, err = mrand.Read(x)
if err != nil {
t.Fatalf("math rand error")
}
if !bloomFilterMatch(b, b) {
t.Fatalf("bloom filter does not match self")
}
x = addBloom(x, b)
if !bloomFilterMatch(x, b) {
t.Fatalf("bloom filter does not match combined bloom")
}
if !isFullNode(nil) {
t.Fatalf("isFullNode did not recognize nil as full node")
}
x[17] = 254
if isFullNode(x) {
t.Fatalf("isFullNode false positive")
}
for i := 0; i < bloomFilterSize; i++ {
b[i] = byte(255)
}
if !isFullNode(b) {
t.Fatalf("isFullNode false negative")
}
if bloomFilterMatch(x, b) {
t.Fatalf("bloomFilterMatch false positive")
}
if !bloomFilterMatch(b, x) {
t.Fatalf("bloomFilterMatch false negative")
}
w := New(&DefaultConfig)
f := w.BloomFilter()
if f != nil {
t.Fatalf("wrong bloom on creation")
}
err = w.SetBloomFilter(x)
if err != nil {
t.Fatalf("failed to set bloom filter: %s", err)
}
f = w.BloomFilter()
if !bloomFilterMatch(f, x) || !bloomFilterMatch(x, f) {
t.Fatalf("retireved wrong bloom filter")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment