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
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
const (
Version = uint64(0) // Peer version number
VersionStr = "0" // The same, as a string
Version = uint64(1) // Peer version number
VersionStr = "1" // The same, as a string
Name = "waku" // Nickname of the protocol
// 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)
opts := StatusOptionsFromHost(p.host)
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
@ -401,34 +401,21 @@ func (p *Peer) handshake() error {
return fmt.Errorf("p [%x] sent packet %x before status packet", p.ID(), packet.Code)
}
var (
peerProtocolVersion uint64
peerOptions StatusOptions
)
var peerOptions StatusOptions
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.
if err := s.Decode(&peerOptions); err != nil {
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 {
return fmt.Errorf("p [%x]: failed to set options: %v", p.ID(), err)
}
if err := <-errc; err != nil {
return fmt.Errorf("p [%x] failed to send status packet: %v", p.ID(), err)
}
_ = packet.Discard()
return nil
}

View File

@ -19,11 +19,25 @@
package v1
import (
mrand "math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/crypto"
"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 wrongTopic = common.TopicType{0, 0, 0, 0}
@ -58,3 +72,54 @@ func TestTopicOrBloomMatchFullNode(t *testing.T) {
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"
"math"
"reflect"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/rlp"
@ -14,7 +15,7 @@ import (
)
// statusOptionKey is a current type used in StatusOptions as a key.
type statusOptionKey string
type statusOptionKey uint64
var (
defaultMinPoW = math.Float64bits(0.001)
@ -84,10 +85,14 @@ func initRLPKeyFields() {
if len(keys) != 2 || keys[0] != "key" {
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
keyFieldIdx[statusOptionKey(keys[1])] = i
idxFieldKey[i] = statusOptionKey(keys[1])
keyFieldIdx[statusOptionKey(key)] = i
idxFieldKey[i] = statusOptionKey(key)
}
}
@ -184,12 +189,12 @@ loop:
// Read the rest of the list items and dump peer.
_, err := s.Raw()
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
}
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 {
return err

View File

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