mirror of
https://github.com/logos-messaging/logos-messaging-go.git
synced 2026-01-03 14:33:09 +00:00
feat: test unit for RLN (static)
This commit is contained in:
parent
1636a33835
commit
67a43b8ba7
2
go.mod
2
go.mod
@ -8,7 +8,7 @@ replace github.com/ethereum/go-ethereum v1.10.18 => github.com/status-im/go-ethe
|
||||
|
||||
replace github.com/flynn/noise v1.0.0 => github.com/status-im/noise v1.0.1-handshakeMessages
|
||||
|
||||
replace github.com/decanus/go-rln => github.com/status-im/go-rln v0.0.4
|
||||
replace github.com/decanus/go-rln => github.com/status-im/go-rln v0.0.6
|
||||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.1
|
||||
|
||||
4
go.sum
4
go.sum
@ -1687,8 +1687,8 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
|
||||
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
|
||||
github.com/status-im/go-discover v0.0.0-20220406135310-85a2ce36f63e h1:fDm8hqKGFy8LMNV8zedT3W+QYVPVDfb0F9Fr7fVf9rQ=
|
||||
github.com/status-im/go-discover v0.0.0-20220406135310-85a2ce36f63e/go.mod h1:u1s0ACIlweIjmJrgXyljRPSOflZLaS6ezb044+92W3c=
|
||||
github.com/status-im/go-rln v0.0.4 h1:AiBngoSSy2DI98EgTZquWqNHrRcAScC34UM36TpiPMw=
|
||||
github.com/status-im/go-rln v0.0.4/go.mod h1:X8j1STGa0vtbvZ4AZibPmk9cCI4FafO0+6zhaomgfNI=
|
||||
github.com/status-im/go-rln v0.0.6 h1:/9IKDEIJAUEpIRDNP65/mSfHGjpAxPfghKFb1U2O99o=
|
||||
github.com/status-im/go-rln v0.0.6/go.mod h1:X8j1STGa0vtbvZ4AZibPmk9cCI4FafO0+6zhaomgfNI=
|
||||
github.com/status-im/go-waku-rendezvous v0.0.0-20211018070416-a93f3b70c432 h1:cbNFU38iimo9fY4B7CdF/fvIF6tNPJIZjBbpfmW2EY4=
|
||||
github.com/status-im/go-waku-rendezvous v0.0.0-20211018070416-a93f3b70c432/go.mod h1:A8t3i0CUGtXCA0aiLsP7iyikmk/KaD/2XVvNJqGCU20=
|
||||
github.com/status-im/go-watchdog v1.2.0-ios-nolibproc h1:BJwZEF7OVKaXc2zErBUAolFSGzwrTBbWnN8e/6MER5E=
|
||||
|
||||
197
waku/v2/protocol/rln/rln_relay_test.go
Normal file
197
waku/v2/protocol/rln/rln_relay_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
package rln
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
r "github.com/decanus/go-rln/rln"
|
||||
"github.com/status-im/go-waku/tests"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/relay"
|
||||
"github.com/status-im/go-waku/waku/v2/utils"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const RLNRELAY_PUBSUB_TOPIC = "waku/2/rlnrelay/proto"
|
||||
const RLNRELAY_CONTENT_TOPIC = "waku/2/rlnrelay/proto"
|
||||
|
||||
func TestWakuRLNRelaySuite(t *testing.T) {
|
||||
suite.Run(t, new(WakuRLNRelaySuite))
|
||||
}
|
||||
|
||||
type WakuRLNRelaySuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (s *WakuRLNRelaySuite) TestOffchainMode() {
|
||||
port, err := tests.FindFreePort(s.T(), "", 5)
|
||||
s.NoError(err)
|
||||
|
||||
host, err := tests.MakeHost(context.Background(), port, rand.Reader)
|
||||
s.NoError(err)
|
||||
|
||||
relay, err := relay.NewWakuRelay(context.Background(), host, nil, 0, utils.Logger())
|
||||
defer relay.Stop()
|
||||
s.NoError(err)
|
||||
|
||||
params, err := parametersKeyBytes()
|
||||
s.NoError(err)
|
||||
|
||||
groupKeyPairs, root, err := r.CreateMembershipList(100, params)
|
||||
s.NoError(err)
|
||||
|
||||
var groupIDCommitments []r.IDCommitment
|
||||
for _, c := range groupKeyPairs {
|
||||
groupIDCommitments = append(groupIDCommitments, c.IDCommitment)
|
||||
}
|
||||
|
||||
// index indicates the position of a membership key pair in the static list of group keys i.e., groupKeyPairs
|
||||
// the corresponding key pair will be used to mount rlnRelay on the current node
|
||||
// index also represents the index of the leaf in the Merkle tree that contains node's commitment key
|
||||
index := r.MembershipIndex(5)
|
||||
|
||||
wakuRLNRelay, err := RlnRelayStatic(relay, groupIDCommitments, groupKeyPairs[index], index, RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, utils.Logger())
|
||||
s.NoError(err)
|
||||
|
||||
// get the root of Merkle tree which is constructed inside the mountRlnRelay proc
|
||||
calculatedRoot, err := wakuRLNRelay.RLN.GetMerkleRoot()
|
||||
s.NoError(err)
|
||||
|
||||
// Checks whether the Merkle tree is constructed correctly inside the mountRlnRelay func
|
||||
// this check is done by comparing the tree root resulted from mountRlnRelay i.e., calculatedRoot
|
||||
// against the root which is the expected root
|
||||
s.Equal(root[:], calculatedRoot[:])
|
||||
}
|
||||
|
||||
func (s *WakuRLNRelaySuite) TestUpdateLogAndHasDuplicate() {
|
||||
|
||||
rlnRelay := &WakuRLNRelay{
|
||||
nullifierLog: make(map[r.Epoch][]r.ProofMetadata),
|
||||
}
|
||||
|
||||
epoch := r.GetCurrentEpoch()
|
||||
|
||||
// create some dummy nullifiers and secret shares
|
||||
var nullifier1, nullifier2, nullifier3 r.Nullifier
|
||||
var shareX1, shareX2, shareX3 r.MerkleNode
|
||||
var shareY1, shareY2, shareY3 r.MerkleNode
|
||||
for i := 0; i < 32; i++ {
|
||||
nullifier1[i] = 1
|
||||
nullifier2[i] = 2
|
||||
nullifier3[i] = nullifier1[i]
|
||||
shareX1[i] = 1
|
||||
shareX2[i] = 2
|
||||
shareX3[i] = 3
|
||||
shareY1[i] = 1
|
||||
shareY2[i] = shareX2[i]
|
||||
shareY3[i] = shareX3[i]
|
||||
}
|
||||
|
||||
wm1 := &pb.WakuMessage{RateLimitProof: &pb.RateLimitProof{Epoch: epoch[:], Nullifier: nullifier1[:], ShareX: shareX1[:], ShareY: shareY1[:]}}
|
||||
wm2 := &pb.WakuMessage{RateLimitProof: &pb.RateLimitProof{Epoch: epoch[:], Nullifier: nullifier2[:], ShareX: shareX2[:], ShareY: shareY2[:]}}
|
||||
wm3 := &pb.WakuMessage{RateLimitProof: &pb.RateLimitProof{Epoch: epoch[:], Nullifier: nullifier3[:], ShareX: shareX3[:], ShareY: shareY3[:]}}
|
||||
|
||||
// check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares
|
||||
// no duplicate for wm1 should be found, since the log is empty
|
||||
result1, err := rlnRelay.HasDuplicate(wm1)
|
||||
s.NoError(err)
|
||||
s.False(result1) // No duplicate is found
|
||||
|
||||
// Add it to the log
|
||||
added, err := rlnRelay.UpdateLog(wm1)
|
||||
s.NoError(err)
|
||||
s.True(added)
|
||||
|
||||
// no duplicate for wm2 should be found, its nullifier differs from wm1
|
||||
result2, err := rlnRelay.HasDuplicate(wm2)
|
||||
s.NoError(err)
|
||||
s.False(result2) // No duplicate is found
|
||||
|
||||
// Add it to the log
|
||||
added, err = rlnRelay.UpdateLog(wm2)
|
||||
s.NoError(err)
|
||||
s.True(added)
|
||||
|
||||
// wm3 has the same nullifier as wm1 but different secret shares, it should be detected as duplicate
|
||||
result3, err := rlnRelay.HasDuplicate(wm3)
|
||||
s.NoError(err)
|
||||
s.True(result3) // It's a duplicate
|
||||
|
||||
}
|
||||
|
||||
func (s *WakuRLNRelaySuite) TestValidateMessage() {
|
||||
params, err := parametersKeyBytes()
|
||||
s.NoError(err)
|
||||
|
||||
groupKeyPairs, _, err := r.CreateMembershipList(100, params)
|
||||
s.NoError(err)
|
||||
|
||||
var groupIDCommitments []r.IDCommitment
|
||||
for _, c := range groupKeyPairs {
|
||||
groupIDCommitments = append(groupIDCommitments, c.IDCommitment)
|
||||
}
|
||||
|
||||
// index indicates the position of a membership key pair in the static list of group keys i.e., groupKeyPairs
|
||||
// the corresponding key pair will be used to mount rlnRelay on the current node
|
||||
// index also represents the index of the leaf in the Merkle tree that contains node's commitment key
|
||||
index := r.MembershipIndex(5)
|
||||
|
||||
// Create a RLN instance
|
||||
rlnInstance, err := r.NewRLN(params)
|
||||
s.NoError(err)
|
||||
|
||||
added := rlnInstance.AddAll(groupIDCommitments)
|
||||
s.True(added)
|
||||
|
||||
rlnRelay := &WakuRLNRelay{
|
||||
membershipIndex: index,
|
||||
membershipKeyPair: groupKeyPairs[index],
|
||||
RLN: rlnInstance,
|
||||
nullifierLog: make(map[r.Epoch][]r.ProofMetadata),
|
||||
log: utils.Logger(),
|
||||
}
|
||||
|
||||
//get the current epoch time
|
||||
now := time.Now()
|
||||
|
||||
// create some messages from the same peer and append rln proof to them, except wm4
|
||||
|
||||
wm1 := &pb.WakuMessage{Payload: []byte("Valid message")}
|
||||
err = rlnRelay.AppendRLNProof(wm1, now)
|
||||
s.NoError(err)
|
||||
|
||||
// another message in the same epoch as wm1, it will break the messaging rate limit
|
||||
wm2 := &pb.WakuMessage{Payload: []byte("Spam")}
|
||||
err = rlnRelay.AppendRLNProof(wm2, now)
|
||||
s.NoError(err)
|
||||
|
||||
// wm3 points to the next epoch
|
||||
wm3 := &pb.WakuMessage{Payload: []byte("Valid message")}
|
||||
err = rlnRelay.AppendRLNProof(wm3, now.Add(time.Second*time.Duration(r.EPOCH_UNIT_SECONDS)))
|
||||
s.NoError(err)
|
||||
|
||||
wm4 := &pb.WakuMessage{Payload: []byte("Invalid message")}
|
||||
|
||||
// valid message
|
||||
msgValidate1, err := rlnRelay.ValidateMessage(wm1, &now)
|
||||
s.NoError(err)
|
||||
|
||||
// wm2 is published within the same Epoch as wm1 and should be found as spam
|
||||
msgValidate2, err := rlnRelay.ValidateMessage(wm2, &now)
|
||||
s.NoError(err)
|
||||
|
||||
// a valid message should be validated successfully
|
||||
msgValidate3, err := rlnRelay.ValidateMessage(wm3, &now)
|
||||
s.NoError(err)
|
||||
|
||||
// wm4 has no rln proof and should not be validated
|
||||
msgValidate4, err := rlnRelay.ValidateMessage(wm4, &now)
|
||||
s.NoError(err)
|
||||
|
||||
s.Equal(MessageValidationResult_Valid, msgValidate1)
|
||||
s.Equal(MessageValidationResult_Spam, msgValidate2)
|
||||
s.Equal(MessageValidationResult_Valid, msgValidate3)
|
||||
s.Equal(MessageValidationResult_Invalid, msgValidate4)
|
||||
}
|
||||
@ -19,6 +19,14 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// the rln-relay epoch length in seconds
|
||||
|
||||
// the maximum clock difference between peers in seconds
|
||||
const MAX_CLOCK_GAP_SECONDS = 20
|
||||
|
||||
// maximum allowed gap between the epochs of messages' RateLimitProofs
|
||||
const MAX_EPOCH_GAP = int64(MAX_CLOCK_GAP_SECONDS / r.EPOCH_UNIT_SECONDS)
|
||||
|
||||
type WakuRLNRelay struct {
|
||||
membershipKeyPair r.MembershipKeyPair
|
||||
|
||||
@ -156,7 +164,7 @@ func (rln *WakuRLNRelay) UpdateLog(msg *pb.WakuMessage) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time.Duration) (MessageValidationResult, error) {
|
||||
func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time.Time) (MessageValidationResult, error) {
|
||||
// validate the supplied `msg` based on the waku-rln-relay routing protocol i.e.,
|
||||
// the `msg`'s epoch is within MAX_EPOCH_GAP of the current epoch
|
||||
// the `msg` has valid rate limit proof
|
||||
@ -179,15 +187,18 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time
|
||||
}
|
||||
|
||||
msgProof := ToRateLimitProof(msg)
|
||||
if msgProof == nil {
|
||||
// message does not contain a proof
|
||||
rln.log.Debug("invalid message: message does not contain a proof")
|
||||
return MessageValidationResult_Invalid, nil
|
||||
}
|
||||
|
||||
// calculate the gaps
|
||||
// calculate the gaps and validate the epoch
|
||||
gap := r.Diff(epoch, msgProof.Epoch)
|
||||
|
||||
// validate the epoch
|
||||
if int64(math.Abs(float64(gap))) >= r.MAX_EPOCH_GAP {
|
||||
if int64(math.Abs(float64(gap))) >= MAX_EPOCH_GAP {
|
||||
// message's epoch is too old or too ahead
|
||||
// accept messages whose epoch is within +-MAX_EPOCH_GAP from the current epoch
|
||||
//debug "invalid message: epoch gap exceeds a threshold", gap = gap, payload = string.fromBytes(msg.payload)
|
||||
rln.log.Debug("invalid message: epoch gap exceeds a threshold", zap.Int64("gap", gap))
|
||||
return MessageValidationResult_Invalid, nil
|
||||
}
|
||||
|
||||
@ -196,18 +207,19 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time
|
||||
input := append(msg.Payload, contentTopicBytes...)
|
||||
if !rln.RLN.Verify(input, *msgProof) {
|
||||
// invalid proof
|
||||
//debug "invalid message: invalid proof", payload = string.fromBytes(msg.payload)
|
||||
rln.log.Debug("invalid message: invalid proof")
|
||||
return MessageValidationResult_Invalid, nil
|
||||
}
|
||||
|
||||
// check if double messaging has happened
|
||||
hasDup, err := rln.HasDuplicate(msg)
|
||||
if err != nil {
|
||||
rln.log.Debug("validation error", zap.Error(err))
|
||||
return MessageValidationResult_Unknown, err
|
||||
}
|
||||
|
||||
if hasDup {
|
||||
// debug "invalid message: message is a spam", payload = string.fromBytes(msg.payload)
|
||||
rln.log.Debug("spam received")
|
||||
return MessageValidationResult_Spam, nil
|
||||
}
|
||||
|
||||
@ -219,11 +231,11 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time
|
||||
return MessageValidationResult_Unknown, err
|
||||
}
|
||||
|
||||
//debug "message is valid", payload = string.fromBytes(msg.payload)
|
||||
rln.log.Debug("message is valid")
|
||||
return MessageValidationResult_Valid, nil
|
||||
}
|
||||
|
||||
func (rln *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Duration) error {
|
||||
func (rln *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Time) error {
|
||||
// returns error if it could not create and append a `RateLimitProof` to the supplied `msg`
|
||||
// `senderEpochTime` indicates the number of seconds passed since Unix epoch. The fractional part holds sub-seconds.
|
||||
// The `epoch` field of `RateLimitProof` is derived from the provided `senderEpochTime` (using `calcEpoch()`)
|
||||
@ -367,7 +379,7 @@ func toRLNSignal(wakuMessage *pb.WakuMessage) []byte {
|
||||
}
|
||||
|
||||
func ToRateLimitProof(msg *pb.WakuMessage) *r.RateLimitProof {
|
||||
if msg == nil {
|
||||
if msg == nil || msg.RateLimitProof == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user