waku-org/go-waku

View on GitHub
waku/v2/protocol/rln/onchain_test.go

Summary

Maintainability
A
0 mins
Test Coverage
//go:build include_onchain_tests
// +build include_onchain_tests

package rln

import (
    "context"
    "crypto/ecdsa"
    "crypto/rand"
    "encoding/hex"
    "math/big"
    "os"
    "path/filepath"
    "testing"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/waku-org/go-zerokit-rln/rln"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/stretchr/testify/suite"
    "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
    "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
    "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/dynamic"
    "github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
    "github.com/waku-org/go-waku/waku/v2/protocol/rln/web3"
    "github.com/waku-org/go-waku/waku/v2/timesource"
    "github.com/waku-org/go-waku/waku/v2/utils"
)

const keystorePassword = "test"

func TestWakuRLNRelayDynamicSuite(t *testing.T) {
    suite.Run(t, new(WakuRLNRelayDynamicSuite))
}

type WakuRLNRelayDynamicSuite struct {
    suite.Suite
    web3Config *web3.Config
    u1PrivKey  *ecdsa.PrivateKey
    u2PrivKey  *ecdsa.PrivateKey
}

func TempFileName(prefix, suffix string) string {
    randBytes := make([]byte, 16)
    rand.Read(randBytes)
    return filepath.Join(os.TempDir(), prefix+hex.EncodeToString(randBytes)+suffix)
}

func (s *WakuRLNRelayDynamicSuite) SetupTest() {

    clientAddr := os.Getenv("GANACHE_NETWORK_RPC_URL")
    if clientAddr == "" {
        clientAddr = "ws://localhost:8545"
    }

    backend, err := ethclient.Dial(clientAddr)
    s.Require().NoError(err)
    defer backend.Close()

    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)

    // Deploying contracts
    auth, err := bind.NewKeyedTransactorWithChainID(s.u1PrivKey, chainID)
    s.Require().NoError(err)

    poseidonHasherAddr, _, _, err := contracts.DeployPoseidonHasher(auth, backend)
    s.Require().NoError(err)

    registryAddress, tx, rlnRegistry, err := contracts.DeployRLNRegistry(auth, backend, poseidonHasherAddr)
    s.Require().NoError(err)
    txReceipt, err := bind.WaitMined(context.TODO(), backend, tx)
    s.Require().NoError(err)
    s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful)

    tx, err = rlnRegistry.NewStorage(auth)
    s.Require().NoError(err)
    txReceipt, err = bind.WaitMined(context.TODO(), backend, tx)
    s.Require().NoError(err)
    s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful)

    s.web3Config = web3.NewConfig(clientAddr, registryAddress)
    err = s.web3Config.Build(context.TODO())
    s.Require().NoError(err)
}

func (s *WakuRLNRelayDynamicSuite) generateCredentials(rlnInstance *rln.RLN) *rln.IdentityCredential {
    identityCredential, err := rlnInstance.MembershipKeyGen()
    s.Require().NoError(err)
    return identityCredential
}

func (s *WakuRLNRelayDynamicSuite) register(appKeystore *keystore.AppKeystore, identityCredential *rln.IdentityCredential, privKey *ecdsa.PrivateKey) rln.MembershipIndex {
    membershipFee, err := s.web3Config.RLNContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: context.TODO()})
    s.Require().NoError(err)

    auth, err := bind.NewKeyedTransactorWithChainID(privKey, s.web3Config.ChainID)
    s.Require().NoError(err)

    auth.Value = membershipFee
    auth.Context = context.TODO()
    tx, err := s.web3Config.RegistryContract.Register(auth, s.web3Config.RLNContract.StorageIndex, rln.Bytes32ToBigInt(identityCredential.IDCommitment))
    s.Require().NoError(err)

    txReceipt, err := bind.WaitMined(context.TODO(), s.web3Config.ETHClient, tx)
    s.Require().NoError(err)
    s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful)

    evt, err := s.web3Config.RLNContract.ParseMemberRegistered(*txReceipt.Logs[0])
    s.Require().NoError(err)

    membershipIndex := rln.MembershipIndex(uint(evt.Index.Int64()))

    membershipCredential := keystore.MembershipCredentials{
        IdentityCredential:     identityCredential,
        TreeIndex:              membershipIndex,
        MembershipContractInfo: keystore.NewMembershipContractInfo(s.web3Config.ChainID, s.web3Config.RegistryContract.Address),
    }

    err = appKeystore.AddMembershipCredentials(membershipCredential, keystorePassword)
    s.Require().NoError(err)

    return membershipIndex
}

func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() {
    // Create a RLN instance
    rlnInstance, err := rln.NewRLN()
    s.Require().NoError(err)

    rt := group_manager.NewMerkleRootTracker(5, rlnInstance)

    u1Credentials := s.generateCredentials(rlnInstance)
    appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)

    membershipIndex := s.register(appKeystore, u1Credentials, s.u1PrivKey)

    gm, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, &membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, rlnInstance, rt, utils.Logger())
    s.Require().NoError(err)

    // initialize the WakuRLNRelay
    rlnRelay := &WakuRLNRelay{
        Details: group_manager.Details{
            RootTracker:  rt,
            GroupManager: gm,
            RLN:          rlnInstance,
        },
        log:          utils.Logger(),
        nullifierLog: NewNullifierLog(context.TODO(), utils.Logger()),
    }

    err = rlnRelay.Start(context.TODO())
    s.Require().NoError(err)

    u2Credentials := s.generateCredentials(rlnInstance)
    appKeystore2, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)

    membershipIndex = s.register(appKeystore2, u2Credentials, s.u2PrivKey)

    time.Sleep(1 * time.Second)

    treeCommitment, err := rlnInstance.GetLeaf(membershipIndex)
    s.Require().NoError(err)
    s.Require().Equal(u2Credentials.IDCommitment, treeCommitment)
}

func (s *WakuRLNRelayDynamicSuite) TestInsertKeyMembershipContract() {
    // Create a RLN instance
    rlnInstance, err := rln.NewRLN()
    s.Require().NoError(err)

    credentials1 := s.generateCredentials(rlnInstance)
    credentials2 := s.generateCredentials(rlnInstance)
    credentials3 := s.generateCredentials(rlnInstance)

    appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)

    s.register(appKeystore, credentials1, s.u1PrivKey)

    // Batch Register
    auth, err := bind.NewKeyedTransactorWithChainID(s.u2PrivKey, s.web3Config.ChainID)
    s.Require().NoError(err)

    membershipFee, err := s.web3Config.RLNContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: context.TODO()})
    s.Require().NoError(err)

    auth.Value = membershipFee.Mul(big.NewInt(2), membershipFee)
    auth.Context = context.TODO()

    tx, err := s.web3Config.RegistryContract.Register1(auth, s.web3Config.RLNContract.StorageIndex, []*big.Int{rln.Bytes32ToBigInt(credentials2.IDCommitment), rln.Bytes32ToBigInt(credentials3.IDCommitment)})
    s.Require().NoError(err)

    txReceipt, err := bind.WaitMined(context.TODO(), s.web3Config.ETHClient, tx)
    s.Require().NoError(err)
    s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful)
}

func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() {
    // Create a RLN instance
    rlnInstance, err := rln.NewRLN()
    s.Require().NoError(err)

    credentials1 := s.generateCredentials(rlnInstance)
    credentials2 := s.generateCredentials(rlnInstance)

    err = rlnInstance.InsertMembers(0, []rln.IDCommitment{credentials1.IDCommitment, credentials2.IDCommitment})
    s.Require().NoError(err)

    //  get the Merkle root
    expectedRoot, err := rlnInstance.GetMerkleRoot()
    s.Require().NoError(err)

    // register the members to the contract
    appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)

    membershipIndex := s.register(appKeystore, credentials1, s.u1PrivKey)
    membershipIndex = s.register(appKeystore, credentials2, s.u1PrivKey)

    rlnInstance, rootTracker, err := GetRLNInstanceAndRootTracker(s.tmpRLNDBPath())
    s.Require().NoError(err)
    // mount the rln relay protocol in the on-chain/dynamic mode
    gm, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, &membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, rlnInstance, rootTracker, utils.Logger())
    s.Require().NoError(err)

    rlnRelay := New(group_manager.Details{
        RLN:          rlnInstance,
        RootTracker:  rootTracker,
        GroupManager: gm,
    }, timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger())
    s.Require().NoError(err)

    err = rlnRelay.Start(context.TODO())
    s.Require().NoError(err)

    // wait for the event to reach the group handler
    time.Sleep(2 * 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() {
    // Creating an RLN instance (just for generating membership keys)
    rlnInstance, err := rln.NewRLN()
    s.Require().NoError(err)

    // Node 1 ============================================================

    // Register credentials1 in contract and keystore1
    credentials1 := s.generateCredentials(rlnInstance)
    appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)
    membershipGroupIndex := s.register(appKeystore, credentials1, s.u1PrivKey)

    // mount the rln relay protocol in the on-chain/dynamic mode
    rootInstance, rootTracker, err := GetRLNInstanceAndRootTracker(s.tmpRLNDBPath())
    s.Require().NoError(err)
    gm1, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, &membershipGroupIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, rootInstance, rootTracker, utils.Logger())
    s.Require().NoError(err)

    rlnRelay1 := New(group_manager.Details{
        GroupManager: gm1,
        RootTracker:  rootTracker,
        RLN:          rootInstance,
    }, timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger())

    err = rlnRelay1.Start(context.TODO())
    s.Require().NoError(err)

    // Node 2 ============================================================

    // Register credentials2 in contract and keystore2
    credentials2 := s.generateCredentials(rlnInstance)
    appKeystore2, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)

    membershipGroupIndex = s.register(appKeystore2, credentials2, s.u2PrivKey)

    // mount the rln relay protocol in the on-chain/dynamic mode
    rootInstance, rootTracker, err = GetRLNInstanceAndRootTracker(s.tmpRLNDBPath())
    s.Require().NoError(err)
    gm2, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, &membershipGroupIndex, appKeystore2, keystorePassword, prometheus.DefaultRegisterer, rootInstance, rootTracker, utils.Logger())
    s.Require().NoError(err)

    rlnRelay2 := New(group_manager.Details{
        GroupManager: gm2,
        RootTracker:  rootTracker,
        RLN:          rootInstance,
    }, timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger())
    err = rlnRelay2.Start(context.TODO())
    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
    idx1 := rlnRelay1.GroupManager.MembershipIndex()
    idx2 := rlnRelay2.GroupManager.MembershipIndex()
    s.Require().NoError(err)
    s.Require().Equal(rln.MembershipIndex(0), idx1)
    s.Require().Equal(rln.MembershipIndex(1), idx2)
}

func (s *WakuRLNRelayDynamicSuite) tmpKeystorePath() string {
    keystoreDir, err := os.MkdirTemp("", "keystore_dir")
    s.Require().NoError(err)
    return filepath.Join(keystoreDir, "keystore.json")
}

func (s *WakuRLNRelayDynamicSuite) tmpRLNDBPath() string {
    dbPath, err := os.MkdirTemp("", "rln_db")
    s.Require().NoError(err)
    return dbPath
}

func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagerGetters() {
    // Create a RLN instance
    rlnInstance, err := rln.NewRLN()
    s.Require().NoError(err)

    ctx := context.Background()

    rt := group_manager.NewMerkleRootTracker(5, rlnInstance)

    u1Credentials := s.generateCredentials(rlnInstance)
    appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger())
    s.Require().NoError(err)

    membershipIndex := s.register(appKeystore, u1Credentials, s.u1PrivKey)

    gm, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, &membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, rlnInstance, rt, utils.Logger())
    s.Require().NoError(err)

    // initialize the WakuRLNRelay
    rlnRelay := &WakuRLNRelay{
        Details: group_manager.Details{
            RootTracker:  rt,
            GroupManager: gm,
            RLN:          rlnInstance,
        },
        log:          utils.Logger(),
        nullifierLog: NewNullifierLog(ctx, utils.Logger()),
    }

    err = rlnRelay.Start(ctx)
    s.Require().NoError(err)

    // Test IdentityCredentials
    _, err = gm.IdentityCredentials()
    s.Require().NoError(err)

    // Test MembershipIndex
    mIndex := gm.MembershipIndex()
    s.Require().Equal(mIndex, uint(membershipIndex))

    // Test IsReady
    isReady, err := gm.IsReady(ctx)
    s.Require().NoError(err)
    s.Require().True(isReady)

    // Test Stop / gm.Start happened as a part of rlnRelay.Start
    err = gm.Stop()
    s.Require().NoError(err)

}