mirror of
https://github.com/logos-messaging/logos-messaging-go.git
synced 2026-01-04 06:53:06 +00:00
* fix: and optimising fetching membership events * fix: start from lastProcessedBlock+1 * test: fetching membership logic * refactor: usage of rlnInstance,rootTracker,groupManager rlnInstance, rootTrack were previously created while creating rlnRelay but were assigned to groupManager on Start of rlnRelay. This created unncessary dependency of passing them to static and dynamic group manager. Web3Config uses interface EthClientI for client, so that we can pass mock client for testing MembershipFetcher. * fix: failing test * fix: lint error * fix: account for PR suggestions * fix: failing race test * fix: dont' increase fromBlock on error * nit: fix naming and add comments
335 lines
12 KiB
Go
335 lines
12 KiB
Go
//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, err := group_manager.NewMerkleRootTracker(5, rlnInstance)
|
|
s.Require().NoError(err)
|
|
|
|
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: make(map[rln.MerkleNode][]rln.ProofMetadata),
|
|
}
|
|
|
|
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
|
|
}
|