Implement waku/1

Why make the change?

This implements waku/1 which is a breaking change as waku/0 diverged
from the specs.

What has changed?

- Added v1 namespace
- Changed the testing code to test from the outside rather than stubbing
p2p specific messages, so that any version specific code is under `vx`
namespaces
- Split waku vs waku/x test code
- Added a test suite that can be used for different versions

Things still to do

I kept the tests we have for the versioned aspect of waku, probably we
want to add some or change other to provide the optimal test coverage.
This commit is contained in:
Andrea Maria Piana 2020-04-29 20:07:25 +02:00
parent 8aa42e4148
commit 299b3fc093
6 changed files with 98 additions and 42 deletions

View File

@ -1,4 +1,3 @@
### waku/1 ### waku/1
This namespace implements `waku` 1.0 as described in https://github.com/vacp2p/specs/blob/master/specs/waku/waku-1.md. This namespace implements `waku` 1.0 as described in https://github.com/vacp2p/specs/blob/master/specs/waku/waku-1.md

View File

@ -2,8 +2,8 @@ package v1
// Waku protocol parameters // Waku protocol parameters
const ( const (
Version = uint64(0) // Peer version number Version = uint64(1) // Peer version number
VersionStr = "0" // The same, as a string VersionStr = "1" // The same, as a string
Name = "waku" // Nickname of the protocol Name = "waku" // Nickname of the protocol
// Waku protocol message codes, according to https://github.com/vacp2p/specs/blob/master/specs/waku/waku-0.md // Waku protocol message codes, according to https://github.com/vacp2p/specs/blob/master/specs/waku/waku-0.md

View File

@ -389,7 +389,7 @@ func (p *Peer) handshake() error {
errc := make(chan error, 1) errc := make(chan error, 1)
opts := StatusOptionsFromHost(p.host) opts := StatusOptionsFromHost(p.host)
go func() { go func() {
errc <- p2p.SendItems(p.rw, statusCode, Version, opts) errc <- p2p.Send(p.rw, statusCode, opts)
}() }()
// Fetch the remote status packet and verify protocol match // Fetch the remote status packet and verify protocol match
@ -401,34 +401,21 @@ func (p *Peer) handshake() error {
return fmt.Errorf("p [%x] sent packet %x before status packet", p.ID(), packet.Code) return fmt.Errorf("p [%x] sent packet %x before status packet", p.ID(), packet.Code)
} }
var ( var peerOptions StatusOptions
peerProtocolVersion uint64
peerOptions StatusOptions
)
s := rlp.NewStream(packet.Payload, uint64(packet.Size)) s := rlp.NewStream(packet.Payload, uint64(packet.Size))
if _, err := s.List(); err != nil {
return fmt.Errorf("p [%x]: failed to decode status packet: %v", p.ID(), err)
}
// Validate protocol version.
if err := s.Decode(&peerProtocolVersion); err != nil {
return fmt.Errorf("p [%x]: failed to decode peer protocol version: %v", p.ID(), err)
}
if peerProtocolVersion != Version {
return fmt.Errorf("p [%x]: protocol version mismatch %d != %d", p.ID(), peerProtocolVersion, Version)
}
// Decode and validate other status packet options. // Decode and validate other status packet options.
if err := s.Decode(&peerOptions); err != nil { if err := s.Decode(&peerOptions); err != nil {
return fmt.Errorf("p [%x]: failed to decode status options: %v", p.ID(), err) return fmt.Errorf("p [%x]: failed to decode status options: %v", p.ID(), err)
} }
if err := s.ListEnd(); err != nil {
return fmt.Errorf("p [%x]: failed to decode status packet: %v", p.ID(), err)
}
if err := p.setOptions(peerOptions.WithDefaults()); err != nil { if err := p.setOptions(peerOptions.WithDefaults()); err != nil {
return fmt.Errorf("p [%x]: failed to set options: %v", p.ID(), err) return fmt.Errorf("p [%x]: failed to set options: %v", p.ID(), err)
} }
if err := <-errc; err != nil { if err := <-errc; err != nil {
return fmt.Errorf("p [%x] failed to send status packet: %v", p.ID(), err) return fmt.Errorf("p [%x] failed to send status packet: %v", p.ID(), err)
} }
_ = packet.Discard()
return nil return nil
} }

View File

@ -19,11 +19,25 @@
package v1 package v1
import ( import (
mrand "math/rand"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/waku/common" "github.com/status-im/status-go/waku/common"
) )
var seed int64
// initSingleTest should be called in the beginning of every
// test, which uses RNG, in order to make the tests
// reproduciblity independent of their sequence.
func initSingleTest() {
seed = time.Now().Unix()
mrand.Seed(seed)
}
var sharedTopic = common.TopicType{0xF, 0x1, 0x2, 0} var sharedTopic = common.TopicType{0xF, 0x1, 0x2, 0}
var wrongTopic = common.TopicType{0, 0, 0, 0} var wrongTopic = common.TopicType{0, 0, 0, 0}
@ -58,3 +72,54 @@ func TestTopicOrBloomMatchFullNode(t *testing.T) {
t.Fatal("envelope should not match") t.Fatal("envelope should not match")
} }
} }
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 := common.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, nil)
p.Mark(env)
if !p.Marked(env) {
t.Fatalf("failed mark with seed %d.", seed)
}
}
func generateMessageParams() (*common.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)
var p common.MessageParams
p.PoW = 0.01
p.WorkTime = 1
p.TTL = uint32(mrand.Intn(1024))
p.Payload = make([]byte, sz)
p.KeySym = make([]byte, common.AESKeyLength)
mrand.Read(p.Payload) // nolint: gosec
mrand.Read(p.KeySym) // nolint: gosec
p.Topic = common.BytesToTopic(buf)
var err error
p.Src, err = crypto.GenerateKey()
if err != nil {
return nil, err
}
return &p, nil
}

View File

@ -6,6 +6,7 @@ import (
"io" "io"
"math" "math"
"reflect" "reflect"
"strconv"
"strings" "strings"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -14,7 +15,7 @@ import (
) )
// statusOptionKey is a current type used in StatusOptions as a key. // statusOptionKey is a current type used in StatusOptions as a key.
type statusOptionKey string type statusOptionKey uint64
var ( var (
defaultMinPoW = math.Float64bits(0.001) defaultMinPoW = math.Float64bits(0.001)
@ -84,10 +85,14 @@ func initRLPKeyFields() {
if len(keys) != 2 || keys[0] != "key" { if len(keys) != 2 || keys[0] != "key" {
panic("invalid value of \"rlp\" tag, expected \"key=N\" where N is uint") panic("invalid value of \"rlp\" tag, expected \"key=N\" where N is uint")
} }
key, err := strconv.ParseUint(keys[1], 10, 64)
if err != nil {
panic("could not parse \"rlp\" key")
}
// typecast key to be of statusOptionKey type // typecast key to be of statusOptionKey type
keyFieldIdx[statusOptionKey(keys[1])] = i keyFieldIdx[statusOptionKey(key)] = i
idxFieldKey[i] = statusOptionKey(keys[1]) idxFieldKey[i] = statusOptionKey(key)
} }
} }
@ -184,12 +189,12 @@ loop:
// Read the rest of the list items and dump peer. // Read the rest of the list items and dump peer.
_, err := s.Raw() _, err := s.Raw()
if err != nil { if err != nil {
return fmt.Errorf("failed to read the value of key %s: %v", key, err) return fmt.Errorf("failed to read the value of key %d: %v", key, err)
} }
continue continue
} }
if err := s.Decode(v.Elem().Field(idx).Addr().Interface()); err != nil { if err := s.Decode(v.Elem().Field(idx).Addr().Interface()); err != nil {
return fmt.Errorf("failed to decode an option %s: %v", key, err) return fmt.Errorf("failed to decode an option %d: %v", key, err)
} }
if err := s.ListEnd(); err != nil { if err := s.ListEnd(); err != nil {
return err return err

View File

@ -38,7 +38,7 @@ func TestEncodeDecodeRLP(t *testing.T) {
func TestBackwardCompatibility(t *testing.T) { func TestBackwardCompatibility(t *testing.T) {
alist := []interface{}{ alist := []interface{}{
[]interface{}{"0", math.Float64bits(2.05)}, []interface{}{uint64(0), math.Float64bits(2.05)},
} }
data, err := rlp.EncodeToBytes(alist) data, err := rlp.EncodeToBytes(alist)
require.NoError(t, err) require.NoError(t, err)
@ -53,8 +53,8 @@ func TestBackwardCompatibility(t *testing.T) {
func TestForwardCompatibility(t *testing.T) { func TestForwardCompatibility(t *testing.T) {
pow := math.Float64bits(2.05) pow := math.Float64bits(2.05)
alist := []interface{}{ alist := []interface{}{
[]interface{}{"0", pow}, []interface{}{uint64(0), pow},
[]interface{}{"99", uint(10)}, // some future option []interface{}{uint64(99), uint(10)}, // some future option
} }
data, err := rlp.EncodeToBytes(alist) data, err := rlp.EncodeToBytes(alist)
require.NoError(t, err) require.NoError(t, err)
@ -67,20 +67,20 @@ func TestForwardCompatibility(t *testing.T) {
func TestInitRLPKeyFields(t *testing.T) { func TestInitRLPKeyFields(t *testing.T) {
ifk := map[int]statusOptionKey{ ifk := map[int]statusOptionKey{
0: "0", 0: 0,
1: "1", 1: 1,
2: "2", 2: 2,
3: "3", 3: 3,
4: "4", 4: 4,
5: "5", 5: 5,
} }
kfi := map[statusOptionKey]int{ kfi := map[statusOptionKey]int{
"0": 0, 0: 0,
"1": 1, 1: 1,
"2": 2, 2: 2,
"3": 3, 3: 3,
"4": 4, 4: 4,
"5": 5, 5: 5,
} }
// Test that the kfi length matches the inited global keyFieldIdx length // Test that the kfi length matches the inited global keyFieldIdx length