test: rln onchain

This commit is contained in:
Richard Ramos 2022-08-10 09:03:25 -04:00
parent 4f71ee6f30
commit c2ce8326ff
3 changed files with 347 additions and 35 deletions

View File

@ -57,7 +57,7 @@ lint:
@golangci-lint --exclude=SA1019 run ./... --deadline=5m
test:
${GOBIN} test ./waku/... -coverprofile=${GO_TEST_OUTFILE}.tmp
${GOBIN} test -timeout 300s ./waku/... -coverprofile=${GO_TEST_OUTFILE}.tmp
cat ${GO_TEST_OUTFILE}.tmp | grep -v ".pb.go" > ${GO_TEST_OUTFILE}
${GOBIN} tool cover -html=${GO_TEST_OUTFILE} -o ${GO_HTML_COV}
@ -77,7 +77,7 @@ generate:
coverage:
${GOBIN} test -count 1 -coverprofile=coverage.out ./...
${GOBIN} test -timeout 300s -count 1 -coverprofile=coverage.out ./...
${GOBIN} tool cover -html=coverage.out -o=coverage.html
# build a docker image for the fleet
@ -149,3 +149,15 @@ install-gomobile: install-xtools
build-linux-pkg:
./scripts/linux/docker-run.sh
ls -la ./build/*.rpm ./build/*.deb
TEST_MNEMONIC="swim relax risk shy chimney please usual search industry board music segment"
start-ganache:
docker run -p 8545:8545 --name ganache-cli --rm -d trufflesuite/ganache-cli:latest -m ${TEST_MNEMONIC}
stop-ganache:
docker stop ganache-cli
test-rln:
go test -timeout 30s -v -count 1 github.com/status-im/go-waku/waku/v2/protocol/rln

View File

@ -0,0 +1,300 @@
package rln
import (
"context"
"crypto/ecdsa"
"crypto/rand"
"errors"
"math/big"
"sync"
"testing"
"time"
r "github.com/status-im/go-rln/rln"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/status-im/go-waku/tests"
"github.com/status-im/go-waku/waku/v2/protocol/relay"
"github.com/status-im/go-waku/waku/v2/protocol/rln/contracts"
"github.com/status-im/go-waku/waku/v2/utils"
"github.com/stretchr/testify/suite"
)
const ETH_CLIENT_ADDRESS = "ws://localhost:8545"
func TestWakuRLNRelayDynamicSuite(t *testing.T) {
suite.Run(t, new(WakuRLNRelayDynamicSuite))
}
type WakuRLNRelayDynamicSuite struct {
suite.Suite
backend *ethclient.Client
chainID *big.Int
rlnAddr common.Address
rlnContract *contracts.RLN
u1PrivKey *ecdsa.PrivateKey
u2PrivKey *ecdsa.PrivateKey
u3PrivKey *ecdsa.PrivateKey
u4PrivKey *ecdsa.PrivateKey
u5PrivKey *ecdsa.PrivateKey
}
func (s *WakuRLNRelayDynamicSuite) SetupTest() {
backend, err := ethclient.Dial(ETH_CLIENT_ADDRESS)
s.Require().NoError(err)
chainID, err := backend.ChainID(context.TODO())
s.Require().NoError(err)
// TODO: obtain account list from ganache mnemonic or from eth_accounts
s.u1PrivKey, err = crypto.ToECDSA(common.FromHex("0x156ec84a451d8a2d0062993242b6c4e863647f5544ff8030f23578d4142f43f8"))
s.Require().NoError(err)
s.u2PrivKey, err = crypto.ToECDSA(common.FromHex("0xa00da43843ad6b5161ddbace48f293ac3f82f8a8257af34de4c32900bb6e9a97"))
s.Require().NoError(err)
s.u3PrivKey, err = crypto.ToECDSA(common.FromHex("0xa4c8d3ed78cd722521fac9d734c45187a4f5e887570be1f707a7bbce054c01ea"))
s.Require().NoError(err)
s.u4PrivKey, err = crypto.ToECDSA(common.FromHex("0x6b11ba548a7fd1958eb156877cc7bdd02d99d876b55381aa9b106c16b0b7a805"))
s.Require().NoError(err)
s.u5PrivKey, err = crypto.ToECDSA(common.FromHex("0x0410196287d0af405e5c16f610de52416bd48be74836dbca93d73e24bffb5a81"))
s.Require().NoError(err)
s.backend = backend
s.chainID = chainID
// Deploying contracts
auth, err := bind.NewKeyedTransactorWithChainID(s.u1PrivKey, chainID)
s.Require().NoError(err)
poseidonHasherAddr, _, _, err := contracts.DeployPoseidonHasher(auth, backend)
s.Require().NoError(err)
rlnAddr, _, rlnContract, err := contracts.DeployRLN(auth, backend, MEMBERSHIP_FEE, big.NewInt(20), poseidonHasherAddr)
s.Require().NoError(err)
s.rlnAddr = rlnAddr
s.rlnContract = rlnContract
}
func (s *WakuRLNRelayDynamicSuite) register(privKey *ecdsa.PrivateKey, commitment *big.Int) {
auth, err := bind.NewKeyedTransactorWithChainID(privKey, s.chainID)
s.Require().NoError(err)
auth.Value = MEMBERSHIP_FEE
auth.Context = context.TODO()
tx, err := s.rlnContract.Register(auth, commitment)
s.Require().NoError(err)
_, err = bind.WaitMined(context.TODO(), s.backend, tx)
s.Require().NoError(err)
}
func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() {
params, err := parametersKeyBytes()
s.Require().NoError(err)
// Create a RLN instance
rlnInstance, err := r.NewRLN(params)
s.Require().NoError(err)
keyPair, err := rlnInstance.MembershipKeyGen()
s.Require().NoError(err)
// initialize the WakuRLNRelay
rlnPeer := &WakuRLNRelay{
ctx: context.TODO(),
membershipIndex: r.MembershipIndex(0),
membershipContractAddress: s.rlnAddr,
ethClientAddress: ETH_CLIENT_ADDRESS,
ethAccountPrivateKey: s.u1PrivKey,
RLN: rlnInstance,
log: utils.Logger(),
nullifierLog: make(map[r.Epoch][]r.ProofMetadata),
membershipKeyPair: *keyPair,
}
// generate another membership key pair
keyPair2, err := rlnInstance.MembershipKeyGen()
s.Require().NoError(err)
wg := &sync.WaitGroup{}
wg.Add(2)
handler := func(pubkey r.IDCommitment, index r.MembershipIndex) error {
if pubkey == keyPair.IDCommitment || pubkey == keyPair2.IDCommitment {
wg.Done()
}
if !rlnInstance.InsertMember(pubkey) {
return errors.New("couldn't insert member")
}
return nil
}
// mount the handler for listening to the contract events
go rlnPeer.HandleGroupUpdates(handler)
// Register first member
s.register(s.u1PrivKey, toBigInt(keyPair.IDCommitment[:]))
// Register second member
s.register(s.u2PrivKey, toBigInt(keyPair2.IDCommitment[:]))
wg.Wait()
}
func (s *WakuRLNRelayDynamicSuite) TestInsertKeyMembershipContract() {
s.register(s.u1PrivKey, big.NewInt(20))
// Batch Register
auth, err := bind.NewKeyedTransactorWithChainID(s.u2PrivKey, s.chainID)
s.Require().NoError(err)
auth.Value = MEMBERSHIP_FEE.Mul(big.NewInt(2), MEMBERSHIP_FEE)
auth.Context = context.TODO()
tx, err := s.rlnContract.RegisterBatch(auth, []*big.Int{big.NewInt(20), big.NewInt(21)})
s.Require().NoError(err)
_, err = bind.WaitMined(context.TODO(), s.backend, tx)
s.Require().NoError(err)
}
func (s *WakuRLNRelayDynamicSuite) TestRegistrationProcedure() {
params, err := parametersKeyBytes()
s.Require().NoError(err)
// Create a RLN instance
rlnInstance, err := r.NewRLN(params)
s.Require().NoError(err)
keyPair, err := rlnInstance.MembershipKeyGen()
s.Require().NoError(err)
// initialize the WakuRLNRelay
rlnPeer := &WakuRLNRelay{
ctx: context.TODO(),
membershipIndex: r.MembershipIndex(0),
membershipContractAddress: s.rlnAddr,
ethClientAddress: ETH_CLIENT_ADDRESS,
ethAccountPrivateKey: s.u1PrivKey,
RLN: rlnInstance,
log: utils.Logger(),
nullifierLog: make(map[r.Epoch][]r.ProofMetadata),
membershipKeyPair: *keyPair,
}
_, err = rlnPeer.Register(context.TODO())
s.Require().NoError(err)
}
func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() {
params, err := parametersKeyBytes()
s.Require().NoError(err)
// Create a RLN instance
rlnInstance, err := r.NewRLN(params)
s.Require().NoError(err)
keyPair1, err := rlnInstance.MembershipKeyGen()
s.Require().NoError(err)
keyPair2, err := rlnInstance.MembershipKeyGen()
s.Require().NoError(err)
r1 := rlnInstance.InsertMember(keyPair1.IDCommitment)
r2 := rlnInstance.InsertMember(keyPair2.IDCommitment)
s.Require().True(r1)
s.Require().True(r2)
// get the Merkle root
expectedRoot, err := rlnInstance.GetMerkleRoot()
s.Require().NoError(err)
// register the members to the contract
s.register(s.u1PrivKey, toBigInt(keyPair1.IDCommitment[:]))
s.register(s.u1PrivKey, toBigInt(keyPair2.IDCommitment[:]))
port, err := tests.FindFreePort(s.T(), "", 5)
s.Require().NoError(err)
host, err := tests.MakeHost(context.TODO(), port, rand.Reader)
s.Require().NoError(err)
relay, err := relay.NewWakuRelay(context.TODO(), host, nil, 0, utils.Logger())
defer relay.Stop()
s.Require().NoError(err)
sub, err := relay.SubscribeToTopic(context.TODO(), RLNRELAY_PUBSUB_TOPIC)
s.Require().NoError(err)
defer sub.Unsubscribe()
// mount the rln relay protocol in the on-chain/dynamic mode
rlnRelay, err := RlnRelayDynamic(context.TODO(), relay, ETH_CLIENT_ADDRESS, nil, s.rlnAddr, keyPair1, r.MembershipIndex(0), RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, utils.Logger())
s.Require().NoError(err)
// wait for the event to reach the group handler
time.Sleep(1 * time.Second)
// rln pks are inserted into the rln peer's Merkle tree and the resulting root
// is expected to be the same as the calculatedRoot i.e., the one calculated outside of the mountRlnRelayDynamic proc
calculatedRoot, err := rlnRelay.RLN.GetMerkleRoot()
s.Require().NoError(err)
s.Require().Equal(expectedRoot, calculatedRoot)
}
func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() {
// Node 1 ============================================================
port1, err := tests.FindFreePort(s.T(), "", 5)
s.Require().NoError(err)
host1, err := tests.MakeHost(context.TODO(), port1, rand.Reader)
s.Require().NoError(err)
relay1, err := relay.NewWakuRelay(context.TODO(), host1, nil, 0, utils.Logger())
defer relay1.Stop()
s.Require().NoError(err)
sub1, err := relay1.SubscribeToTopic(context.TODO(), RLNRELAY_PUBSUB_TOPIC)
s.Require().NoError(err)
defer sub1.Unsubscribe()
// mount the rln relay protocol in the on-chain/dynamic mode
rlnRelay1, err := RlnRelayDynamic(context.TODO(), relay1, ETH_CLIENT_ADDRESS, s.u1PrivKey, s.rlnAddr, nil, r.MembershipIndex(0), RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, utils.Logger())
s.Require().NoError(err)
// Node 2 ============================================================
port2, err := tests.FindFreePort(s.T(), "", 5)
s.Require().NoError(err)
host2, err := tests.MakeHost(context.TODO(), port2, rand.Reader)
s.Require().NoError(err)
relay2, err := relay.NewWakuRelay(context.TODO(), host2, nil, 0, utils.Logger())
defer relay2.Stop()
s.Require().NoError(err)
sub2, err := relay2.SubscribeToTopic(context.TODO(), RLNRELAY_PUBSUB_TOPIC)
s.Require().NoError(err)
defer sub2.Unsubscribe()
// mount the rln relay protocol in the on-chain/dynamic mode
rlnRelay2, err := RlnRelayDynamic(context.TODO(), relay2, ETH_CLIENT_ADDRESS, s.u2PrivKey, s.rlnAddr, nil, r.MembershipIndex(0), RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, utils.Logger())
s.Require().NoError(err)
// ==================================
// the two nodes should be registered into the contract
// since nodes are spun up sequentially
// the first node has index 0 whereas the second node gets index 1
s.Require().Equal(r.MembershipIndex(0), rlnRelay1.membershipIndex)
s.Require().Equal(r.MembershipIndex(1), rlnRelay2.membershipIndex)
}

View File

@ -27,20 +27,20 @@ type WakuRLNRelaySuite struct {
func (s *WakuRLNRelaySuite) TestOffchainMode() {
port, err := tests.FindFreePort(s.T(), "", 5)
s.NoError(err)
s.Require().NoError(err)
host, err := tests.MakeHost(context.Background(), port, rand.Reader)
s.NoError(err)
s.Require().NoError(err)
relay, err := relay.NewWakuRelay(context.Background(), host, nil, 0, utils.Logger())
defer relay.Stop()
s.NoError(err)
s.Require().NoError(err)
params, err := parametersKeyBytes()
s.NoError(err)
s.Require().NoError(err)
groupKeyPairs, root, err := r.CreateMembershipList(100, params)
s.NoError(err)
s.Require().NoError(err)
var groupIDCommitments []r.IDCommitment
for _, c := range groupKeyPairs {
@ -53,16 +53,16 @@ func (s *WakuRLNRelaySuite) TestOffchainMode() {
index := r.MembershipIndex(5)
wakuRLNRelay, err := RlnRelayStatic(context.TODO(), relay, groupIDCommitments, groupKeyPairs[index], index, RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, utils.Logger())
s.NoError(err)
s.Require().NoError(err)
// get the root of Merkle tree which is constructed inside the mountRlnRelay proc
calculatedRoot, err := wakuRLNRelay.RLN.GetMerkleRoot()
s.NoError(err)
s.Require().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[:])
s.Require().Equal(root[:], calculatedRoot[:])
}
func (s *WakuRLNRelaySuite) TestUpdateLogAndHasDuplicate() {
@ -96,37 +96,37 @@ func (s *WakuRLNRelaySuite) TestUpdateLogAndHasDuplicate() {
// 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
s.Require().NoError(err)
s.Require().False(result1) // No duplicate is found
// Add it to the log
added, err := rlnRelay.UpdateLog(wm1)
s.NoError(err)
s.True(added)
s.Require().NoError(err)
s.Require().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
s.Require().NoError(err)
s.Require().False(result2) // No duplicate is found
// Add it to the log
added, err = rlnRelay.UpdateLog(wm2)
s.NoError(err)
s.True(added)
s.Require().NoError(err)
s.Require().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
s.Require().NoError(err)
s.Require().True(result3) // It's a duplicate
}
func (s *WakuRLNRelaySuite) TestValidateMessage() {
params, err := parametersKeyBytes()
s.NoError(err)
s.Require().NoError(err)
groupKeyPairs, _, err := r.CreateMembershipList(100, params)
s.NoError(err)
s.Require().NoError(err)
var groupIDCommitments []r.IDCommitment
for _, c := range groupKeyPairs {
@ -140,10 +140,10 @@ func (s *WakuRLNRelaySuite) TestValidateMessage() {
// Create a RLN instance
rlnInstance, err := r.NewRLN(params)
s.NoError(err)
s.Require().NoError(err)
added := rlnInstance.AddAll(groupIDCommitments)
s.True(added)
s.Require().True(added)
rlnRelay := &WakuRLNRelay{
membershipIndex: index,
@ -160,38 +160,38 @@ func (s *WakuRLNRelaySuite) TestValidateMessage() {
wm1 := &pb.WakuMessage{Payload: []byte("Valid message")}
err = rlnRelay.AppendRLNProof(wm1, now)
s.NoError(err)
s.Require().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)
s.Require().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)
s.Require().NoError(err)
wm4 := &pb.WakuMessage{Payload: []byte("Invalid message")}
// valid message
msgValidate1, err := rlnRelay.ValidateMessage(wm1, &now)
s.NoError(err)
s.Require().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)
s.Require().NoError(err)
// a valid message should be validated successfully
msgValidate3, err := rlnRelay.ValidateMessage(wm3, &now)
s.NoError(err)
s.Require().NoError(err)
// wm4 has no rln proof and should not be validated
msgValidate4, err := rlnRelay.ValidateMessage(wm4, &now)
s.NoError(err)
s.Require().NoError(err)
s.Equal(MessageValidationResult_Valid, msgValidate1)
s.Equal(MessageValidationResult_Spam, msgValidate2)
s.Equal(MessageValidationResult_Valid, msgValidate3)
s.Equal(MessageValidationResult_Invalid, msgValidate4)
s.Require().Equal(MessageValidationResult_Valid, msgValidate1)
s.Require().Equal(MessageValidationResult_Spam, msgValidate2)
s.Require().Equal(MessageValidationResult_Valid, msgValidate3)
s.Require().Equal(MessageValidationResult_Invalid, msgValidate4)
}