From 5fcfbb989765e9fd34c1642e4fa1306e83dd804f Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Thu, 31 Aug 2023 11:49:38 -0400 Subject: [PATCH] feat: use rln registry contract --- cmd/waku/rlngenerate/command_rln.go | 21 +-- cmd/waku/rlngenerate/web3.go | 19 ++- .../rln/group_manager/dynamic/dynamic.go | 51 ++---- .../rln/group_manager/dynamic/handler_test.go | 13 +- .../rln/group_manager/dynamic/web3.go | 18 +-- waku/v2/protocol/rln/onchain_test.go | 146 +++++++++--------- waku/v2/protocol/rln/web3/web3.go | 122 +++++++++++++++ 7 files changed, 242 insertions(+), 148 deletions(-) create mode 100644 waku/v2/protocol/rln/web3/web3.go diff --git a/cmd/waku/rlngenerate/command_rln.go b/cmd/waku/rlngenerate/command_rln.go index 2ffc39a7..7e580d8f 100644 --- a/cmd/waku/rlngenerate/command_rln.go +++ b/cmd/waku/rlngenerate/command_rln.go @@ -9,12 +9,11 @@ import ( "fmt" "math/big" - "github.com/ethereum/go-ethereum/ethclient" cli "github.com/urfave/cli/v2" "github.com/waku-org/go-waku/logging" - "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts" "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/utils" "github.com/waku-org/go-zerokit-rln/rln" "go.uber.org/zap" @@ -46,22 +45,12 @@ var Command = cli.Command{ } func execute(ctx context.Context) error { - ethClient, err := ethclient.Dial(options.ETHClientAddress) - if err != nil { - return err - } - rlnInstance, err := rln.NewRLN() if err != nil { return err } - chainID, err := ethClient.ChainID(ctx) - if err != nil { - return err - } - - rlnContract, err := contracts.NewRLN(options.MembershipContractAddress, ethClient) + web3Config, err := web3.BuildConfig(ctx, options.ETHClientAddress, options.MembershipContractAddress) if err != nil { return err } @@ -74,14 +63,14 @@ func execute(ctx context.Context) error { } // register the rln-relay peer to the membership contract - membershipIndex, err := register(ctx, ethClient, rlnContract, identityCredential.IDCommitment, chainID) + membershipIndex, err := register(ctx, web3Config, identityCredential.IDCommitment) if err != nil { return err } // TODO: clean private key from memory - err = persistCredentials(identityCredential, membershipIndex, chainID) + err = persistCredentials(identityCredential, membershipIndex, web3Config.ChainID) if err != nil { return err } @@ -98,7 +87,7 @@ func execute(ctx context.Context) error { logger.Info("registered credentials into the membership contract", logging.HexString("idCommitment", identityCredential.IDCommitment[:]), zap.Uint("index", membershipIndex)) } - ethClient.Close() + web3Config.ETHClient.Close() return nil } diff --git a/cmd/waku/rlngenerate/web3.go b/cmd/waku/rlngenerate/web3.go index f817dc48..535311fe 100644 --- a/cmd/waku/rlngenerate/web3.go +++ b/cmd/waku/rlngenerate/web3.go @@ -11,15 +11,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/waku-org/go-waku/logging" - "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts" + "github.com/waku-org/go-waku/waku/v2/protocol/rln/web3" "github.com/waku-org/go-zerokit-rln/rln" "go.uber.org/zap" ) -func getMembershipFee(ctx context.Context, rlnContract *contracts.RLN) (*big.Int, error) { +func getMembershipFee(ctx context.Context, rlnContract web3.RLNContract) (*big.Int, error) { return rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx}) } @@ -70,14 +69,14 @@ func buildTransactor(ctx context.Context, membershipFee *big.Int, chainID *big.I return auth, nil } -func register(ctx context.Context, ethClient *ethclient.Client, rlnContract *contracts.RLN, idComm rln.IDCommitment, chainID *big.Int) (rln.MembershipIndex, error) { +func register(ctx context.Context, web3Config *web3.Config, idComm rln.IDCommitment) (rln.MembershipIndex, error) { // check if the contract exists by calling a static function - membershipFee, err := getMembershipFee(ctx, rlnContract) + membershipFee, err := getMembershipFee(ctx, web3Config.RLNContract) if err != nil { return 0, err } - auth, err := buildTransactor(ctx, membershipFee, chainID) + auth, err := buildTransactor(ctx, membershipFee, web3Config.ChainID) if err != nil { return 0, err } @@ -85,13 +84,13 @@ func register(ctx context.Context, ethClient *ethclient.Client, rlnContract *con log.Debug("registering an id commitment", zap.Binary("idComm", idComm[:])) // registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress - tx, err := rlnContract.Register(auth, rln.Bytes32ToBigInt(idComm)) + tx, err := web3Config.RegistryContract.Register(auth, web3Config.RLNContract.StorageIndex, rln.Bytes32ToBigInt(idComm)) if err != nil { return 0, fmt.Errorf("transaction error: %w", err) } explorerURL := "" - switch chainID.Int64() { + switch web3Config.ChainID.Int64() { case 1: explorerURL = "https://etherscan.io" case 5: @@ -108,7 +107,7 @@ func register(ctx context.Context, ethClient *ethclient.Client, rlnContract *con logger.Warn("waiting for transaction to be mined...") - txReceipt, err := bind.WaitMined(ctx, ethClient, tx) + txReceipt, err := bind.WaitMined(ctx, web3Config.ETHClient, tx) if err != nil { return 0, fmt.Errorf("transaction error: %w", err) } @@ -118,7 +117,7 @@ func register(ctx context.Context, ethClient *ethclient.Client, rlnContract *con } // the receipt topic holds the hash of signature of the raised events - evt, err := rlnContract.ParseMemberRegistered(*txReceipt.Logs[0]) + evt, err := web3Config.RLNContract.ParseMemberRegistered(*txReceipt.Logs[0]) if err != nil { return 0, err } diff --git a/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go b/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go index ab6852aa..58a0915a 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go @@ -10,12 +10,12 @@ import ( "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/ethclient" "github.com/prometheus/client_golang/prometheus" "github.com/waku-org/go-waku/logging" "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/keystore" + "github.com/waku-org/go-waku/waku/v2/protocol/rln/web3" "github.com/waku-org/go-zerokit-rln/rln" om "github.com/wk8/go-ordered-map" "go.uber.org/zap" @@ -38,17 +38,11 @@ type DynamicGroupManager struct { identityCredential *rln.IdentityCredential membershipIndex rln.MembershipIndex - membershipContractAddress common.Address - ethClientAddress string - ethClient *ethclient.Client - + web3Config *web3.Config lastBlockProcessed uint64 eventHandler RegistrationEventHandler - chainId *big.Int - rlnContract *contracts.RLN - appKeystore *keystore.AppKeystore keystorePassword string @@ -99,14 +93,14 @@ func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) e gm.lastBlockProcessed = lastBlockProcessed err = gm.SetMetadata(RLNMetadata{ LastProcessedBlock: gm.lastBlockProcessed, - ChainID: gm.chainId, - ContractAddress: gm.membershipContractAddress, + ChainID: gm.web3Config.ChainID, + ContractAddress: gm.web3Config.RegistryContract.Address, }) if err != nil { // this is not a fatal error, hence we don't raise an exception gm.log.Warn("failed to persist rln metadata", zap.Error(err)) } else { - gm.log.Debug("rln metadata persisted", zap.Uint64("lastProcessedBlock", gm.lastBlockProcessed), zap.Uint64("chainID", gm.chainId.Uint64()), logging.HexBytes("contractAddress", gm.membershipContractAddress[:])) + gm.log.Debug("rln metadata persisted", zap.Uint64("lastProcessedBlock", gm.lastBlockProcessed), zap.Uint64("chainID", gm.web3Config.ChainID.Uint64()), logging.HexBytes("contractAddress", gm.web3Config.RegistryContract.Address.Bytes())) } return nil @@ -126,19 +120,18 @@ func NewDynamicGroupManager( log = log.Named("rln-dynamic") return &DynamicGroupManager{ - membershipIndex: membershipIndex, - membershipContractAddress: memContractAddr, - ethClientAddress: ethClientAddr, - eventHandler: handler, - appKeystore: appKeystore, - keystorePassword: keystorePassword, - log: log, - metrics: newMetrics(reg), + membershipIndex: membershipIndex, + web3Config: web3.NewConfig(ethClientAddr, memContractAddr), + eventHandler: handler, + appKeystore: appKeystore, + keystorePassword: keystorePassword, + log: log, + metrics: newMetrics(reg), }, nil } func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) { - return gm.rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx}) + return gm.web3Config.RLNContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx}) } func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error { @@ -151,25 +144,14 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, gm.log.Info("mounting rln-relay in on-chain/dynamic mode") - backend, err := ethclient.Dial(gm.ethClientAddress) + err := gm.web3Config.Build(ctx) if err != nil { return err } - gm.ethClient = backend gm.rln = rlnInstance gm.rootTracker = rootTracker - gm.chainId, err = backend.ChainID(ctx) - if err != nil { - return err - } - - gm.rlnContract, err = contracts.NewRLN(gm.membershipContractAddress, backend) - if err != nil { - return err - } - // check if the contract exists by calling a static function _, err = gm.getMembershipFee(ctx) if err != nil { @@ -194,7 +176,7 @@ func (gm *DynamicGroupManager) loadCredential() error { credentials, err := gm.appKeystore.GetMembershipCredentials( gm.keystorePassword, gm.membershipIndex, - keystore.NewMembershipContractInfo(gm.chainId, gm.membershipContractAddress)) + keystore.NewMembershipContractInfo(gm.web3Config.ChainID, gm.web3Config.RegistryContract.Address)) if err != nil { return err } @@ -281,7 +263,8 @@ func (gm *DynamicGroupManager) Stop() error { if err != nil { return err } - gm.ethClient.Close() + + gm.web3Config.ETHClient.Close() gm.wg.Wait() diff --git a/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go b/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go index 50c461b6..1479b495 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "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/web3" "github.com/waku-org/go-waku/waku/v2/utils" "github.com/waku-org/go-zerokit-rln/rln" ) @@ -40,11 +41,13 @@ func TestHandler(t *testing.T) { _ = ctx gm := &DynamicGroupManager{ - rln: rlnInstance, - log: utils.Logger(), - cancel: cancel, - wg: sync.WaitGroup{}, - chainId: big.NewInt(1), + rln: rlnInstance, + log: utils.Logger(), + cancel: cancel, + wg: sync.WaitGroup{}, + web3Config: &web3.Config{ + ChainID: big.NewInt(1), + }, rootTracker: rootTracker, metrics: newMetrics(prometheus.DefaultRegisterer), } diff --git a/waku/v2/protocol/rln/group_manager/dynamic/web3.go b/waku/v2/protocol/rln/group_manager/dynamic/web3.go index 4e8509f0..d994c010 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/web3.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/web3.go @@ -26,11 +26,11 @@ func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler R if err != nil { gm.log.Warn("could not load last processed block from metadata. Starting onchain sync from scratch", zap.Error(err)) } else { - if gm.chainId.Uint64() != metadata.ChainID.Uint64() { + if gm.web3Config.ChainID.Cmp(metadata.ChainID) != 0 { return errors.New("persisted data: chain id mismatch") } - if !bytes.Equal(gm.membershipContractAddress[:], metadata.ContractAddress[:]) { + if !bytes.Equal(gm.web3Config.RegistryContract.Address.Bytes(), metadata.ContractAddress.Bytes()) { return errors.New("persisted data: contract address mismatch") } @@ -38,7 +38,7 @@ func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler R gm.log.Info("resuming onchain sync", zap.Uint64("fromBlock", fromBlock)) } - err = gm.loadOldEvents(ctx, gm.rlnContract, fromBlock, handler) + err = gm.loadOldEvents(ctx, fromBlock, handler) if err != nil { return err } @@ -46,11 +46,11 @@ func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler R errCh := make(chan error) gm.wg.Add(1) - go gm.watchNewEvents(ctx, gm.rlnContract, handler, gm.log, errCh) + go gm.watchNewEvents(ctx, handler, gm.log, errCh) return <-errCh } -func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, rlnContract *contracts.RLN, fromBlock uint64, handler RegistrationEventHandler) error { +func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, fromBlock uint64, handler RegistrationEventHandler) error { events, err := gm.getEvents(ctx, fromBlock, nil) if err != nil { return err @@ -58,14 +58,14 @@ func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, rlnContract *c return handler(gm, events) } -func (gm *DynamicGroupManager) watchNewEvents(ctx context.Context, rlnContract *contracts.RLN, handler RegistrationEventHandler, log *zap.Logger, errCh chan<- error) { +func (gm *DynamicGroupManager) watchNewEvents(ctx context.Context, handler RegistrationEventHandler, log *zap.Logger, errCh chan<- error) { defer gm.wg.Done() // Watch for new events firstErr := true headerCh := make(chan *types.Header) subs := event.Resubscribe(2*time.Second, func(ctx context.Context) (event.Subscription, error) { - s, err := gm.ethClient.SubscribeNewHead(ctx, headerCh) + s, err := gm.web3Config.ETHClient.SubscribeNewHead(ctx, headerCh) if err != nil { if err == rpc.ErrNotificationsUnsupported { err = errors.New("notifications not supported. The node must support websockets") @@ -123,7 +123,7 @@ func (gm *DynamicGroupManager) getEvents(ctx context.Context, from uint64, to *u toBlock := to if to == nil { - block, err := gm.ethClient.BlockByNumber(ctx, nil) + block, err := gm.web3Config.ETHClient.BlockByNumber(ctx, nil) if err != nil { return nil, err } @@ -179,7 +179,7 @@ func (gm *DynamicGroupManager) getEvents(ctx context.Context, from uint64, to *u } func (gm *DynamicGroupManager) fetchEvents(ctx context.Context, from uint64, to *uint64) ([]*contracts.RLNMemberRegistered, error) { - logIterator, err := gm.rlnContract.FilterMemberRegistered(&bind.FilterOpts{Start: from, End: to, Context: ctx}) + logIterator, err := gm.web3Config.RLNContract.FilterMemberRegistered(&bind.FilterOpts{Start: from, End: to, Context: ctx}) if err != nil { return nil, err } diff --git a/waku/v2/protocol/rln/onchain_test.go b/waku/v2/protocol/rln/onchain_test.go index ab9f6950..000a780b 100644 --- a/waku/v2/protocol/rln/onchain_test.go +++ b/waku/v2/protocol/rln/onchain_test.go @@ -6,14 +6,16 @@ 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" - "go.uber.org/zap" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -25,11 +27,11 @@ import ( "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" ) -var membershipFee = big.NewInt(1000000000000000) // wei - 0.001 eth const keystorePassword = "test" func TestWakuRLNRelayDynamicSuite(t *testing.T) { @@ -38,29 +40,27 @@ func TestWakuRLNRelayDynamicSuite(t *testing.T) { type WakuRLNRelayDynamicSuite struct { suite.Suite - - clientAddr string - - backend *ethclient.Client - chainID *big.Int - rlnAddr common.Address - rlnContract *contracts.RLN - - u1PrivKey *ecdsa.PrivateKey - u2PrivKey *ecdsa.PrivateKey + web3Config *web3.Config + u1PrivKey *ecdsa.PrivateKey + u2PrivKey *ecdsa.PrivateKey } -// TODO: on teardown, remove credentials +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() { - s.clientAddr = os.Getenv("GANACHE_NETWORK_RPC_URL") - if s.clientAddr == "" { - s.clientAddr = "ws://localhost:8545" + clientAddr := os.Getenv("GANACHE_NETWORK_RPC_URL") + if clientAddr == "" { + clientAddr = "ws://localhost:8545" } - backend, err := ethclient.Dial(s.clientAddr) + backend, err := ethclient.Dial(clientAddr) s.Require().NoError(err) + defer backend.Close() chainID, err := backend.ChainID(context.TODO()) s.Require().NoError(err) @@ -72,9 +72,6 @@ func (s *WakuRLNRelayDynamicSuite) SetupTest() { s.u2PrivKey, err = crypto.ToECDSA(common.FromHex("0xa00da43843ad6b5161ddbace48f293ac3f82f8a8257af34de4c32900bb6e9a97")) s.Require().NoError(err) - s.backend = backend - s.chainID = chainID - // Deploying contracts auth, err := bind.NewKeyedTransactorWithChainID(s.u1PrivKey, chainID) s.Require().NoError(err) @@ -82,18 +79,21 @@ func (s *WakuRLNRelayDynamicSuite) SetupTest() { poseidonHasherAddr, _, _, err := contracts.DeployPoseidonHasher(auth, backend) s.Require().NoError(err) - rlnAddr, _, rlnContract, err := contracts.DeployRLN(auth, backend, membershipFee, big.NewInt(20), poseidonHasherAddr) + 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) - s.rlnAddr = rlnAddr - s.rlnContract = rlnContract -} + 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) -func (s *WakuRLNRelayDynamicSuite) removeCredentials(path string) { - err := os.Remove(path) - if err != nil { - utils.Logger().Warn("could not remove credentials", zap.String("path", path)) - } + 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 { @@ -103,21 +103,22 @@ func (s *WakuRLNRelayDynamicSuite) generateCredentials(rlnInstance *rln.RLN) *rl } 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.chainID) + auth, err := bind.NewKeyedTransactorWithChainID(privKey, s.web3Config.ChainID) s.Require().NoError(err) auth.Value = membershipFee auth.Context = context.TODO() - - tx, err := s.rlnContract.Register(auth, rln.Bytes32ToBigInt(identityCredential.IDCommitment)) - s.Require().NoError(err) - txReceipt, err := bind.WaitMined(context.TODO(), s.backend, tx) + 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.rlnContract.ParseMemberRegistered(*txReceipt.Logs[0]) + evt, err := s.web3Config.RLNContract.ParseMemberRegistered(*txReceipt.Logs[0]) s.Require().NoError(err) membershipIndex := rln.MembershipIndex(uint(evt.Index.Int64())) @@ -125,7 +126,7 @@ func (s *WakuRLNRelayDynamicSuite) register(appKeystore *keystore.AppKeystore, i membershipCredential := keystore.MembershipCredentials{ IdentityCredential: identityCredential, TreeIndex: membershipIndex, - MembershipContractInfo: keystore.NewMembershipContractInfo(s.chainID, s.rlnAddr), + MembershipContractInfo: keystore.NewMembershipContractInfo(s.web3Config.ChainID, s.web3Config.RegistryContract.Address), } err = appKeystore.AddMembershipCredentials(membershipCredential, keystorePassword) @@ -143,14 +144,12 @@ func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() { s.Require().NoError(err) u1Credentials := s.generateCredentials(rlnInstance) - keystorePath1 := "./test_onchain.json" - appKeystore, err := keystore.New(keystorePath1, dynamic.RLNAppInfo, utils.Logger()) + appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger()) s.Require().NoError(err) membershipIndex := s.register(appKeystore, u1Credentials, s.u1PrivKey) - defer s.removeCredentials(keystorePath1) - gm, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) + gm, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) // initialize the WakuRLNRelay @@ -166,12 +165,10 @@ func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() { s.Require().NoError(err) u2Credentials := s.generateCredentials(rlnInstance) - keystorePath2 := "./test_onchain2.json" - appKeystore2, err := keystore.New(keystorePath2, dynamic.RLNAppInfo, utils.Logger()) + appKeystore2, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger()) s.Require().NoError(err) membershipIndex = s.register(appKeystore2, u2Credentials, s.u2PrivKey) - defer s.removeCredentials(keystorePath2) time.Sleep(1 * time.Second) @@ -189,24 +186,25 @@ func (s *WakuRLNRelayDynamicSuite) TestInsertKeyMembershipContract() { credentials2 := s.generateCredentials(rlnInstance) credentials3 := s.generateCredentials(rlnInstance) - keystorePath1 := "./test_onchain.json" - appKeystore, err := keystore.New(keystorePath1, dynamic.RLNAppInfo, utils.Logger()) + appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger()) s.Require().NoError(err) s.register(appKeystore, credentials1, s.u1PrivKey) - defer s.removeCredentials(keystorePath1) // Batch Register - auth, err := bind.NewKeyedTransactorWithChainID(s.u2PrivKey, s.chainID) + 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.rlnContract.RegisterBatch(auth, []*big.Int{rln.Bytes32ToBigInt(credentials2.IDCommitment), rln.Bytes32ToBigInt(credentials3.IDCommitment)}) + 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.backend, tx) + txReceipt, err := bind.WaitMined(context.TODO(), s.web3Config.ETHClient, tx) s.Require().NoError(err) s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful) } @@ -219,10 +217,7 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { credentials1 := s.generateCredentials(rlnInstance) credentials2 := s.generateCredentials(rlnInstance) - err = rlnInstance.InsertMember(credentials1.IDCommitment) - s.Require().NoError(err) - - err = rlnInstance.InsertMember(credentials2.IDCommitment) + err = rlnInstance.InsertMembers(1, []rln.IDCommitment{credentials1.IDCommitment, credentials2.IDCommitment}) s.Require().NoError(err) // get the Merkle root @@ -230,21 +225,17 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { s.Require().NoError(err) // register the members to the contract - keystorePath1 := "./test_onchain.json" - appKeystore, err := keystore.New(keystorePath1, dynamic.RLNAppInfo, utils.Logger()) + 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) - defer s.removeCredentials(keystorePath1) - // mount the rln relay protocol in the on-chain/dynamic mode - gm, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) - + gm, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, membershipIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) - rlnRelay, err := New(gm, "test-merkle-tree.db", timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger()) + rlnRelay, err := New(gm, s.tmpRLNDBPath(), timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) err = rlnRelay.Start(context.TODO()) @@ -257,7 +248,6 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { // 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) } @@ -270,17 +260,15 @@ func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() { // Register credentials1 in contract and keystore1 credentials1 := s.generateCredentials(rlnInstance) - keystorePath1 := "./test_onchain.json" - appKeystore, err := keystore.New(keystorePath1, dynamic.RLNAppInfo, utils.Logger()) + appKeystore, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger()) s.Require().NoError(err) membershipGroupIndex := s.register(appKeystore, credentials1, s.u1PrivKey) - defer s.removeCredentials(keystorePath1) // mount the rln relay protocol in the on-chain/dynamic mode - gm1, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipGroupIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) + gm1, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, membershipGroupIndex, appKeystore, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) - rlnRelay1, err := New(gm1, "test-correct-registration-1.db", timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger()) + rlnRelay1, err := New(gm1, s.tmpRLNDBPath(), timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) err = rlnRelay1.Start(context.TODO()) s.Require().NoError(err) @@ -289,17 +277,16 @@ func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() { // Register credentials2 in contract and keystore2 credentials2 := s.generateCredentials(rlnInstance) - keystorePath2 := "./test_onchain2.json" - appKeystore2, err := keystore.New(keystorePath2, dynamic.RLNAppInfo, utils.Logger()) + appKeystore2, err := keystore.New(s.tmpKeystorePath(), dynamic.RLNAppInfo, utils.Logger()) s.Require().NoError(err) + membershipGroupIndex = s.register(appKeystore2, credentials2, s.u2PrivKey) - defer s.removeCredentials(keystorePath2) // mount the rln relay protocol in the on-chain/dynamic mode - gm2, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipGroupIndex, appKeystore2, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) + gm2, err := dynamic.NewDynamicGroupManager(s.web3Config.ETHClientAddress, s.web3Config.RegistryContract.Address, membershipGroupIndex, appKeystore2, keystorePassword, prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) - rlnRelay2, err := New(gm2, "test-correct-registration-2.db", timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger()) + rlnRelay2, err := New(gm2, s.tmpRLNDBPath(), timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger()) s.Require().NoError(err) err = rlnRelay2.Start(context.TODO()) s.Require().NoError(err) @@ -311,7 +298,18 @@ func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() { 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) + s.Require().Equal(rln.MembershipIndex(1), idx1) + s.Require().Equal(rln.MembershipIndex(2), 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 } diff --git a/waku/v2/protocol/rln/web3/web3.go b/waku/v2/protocol/rln/web3/web3.go new file mode 100644 index 00000000..7a473c2e --- /dev/null +++ b/waku/v2/protocol/rln/web3/web3.go @@ -0,0 +1,122 @@ +package web3 + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts" +) + +// RegistryContract contains an instance of the RLN Registry contract and its address +type RegistryContract struct { + *contracts.RLNRegistry + Address common.Address +} + +// RLNContract contains an instance of the RLN contract, its address and the storage index within the registry +// that represents this contract +type RLNContract struct { + *contracts.RLN + Address common.Address + StorageIndex uint16 +} + +// Config is a helper struct that contains attributes for interaction with RLN smart contracts +type Config struct { + configured bool + + ETHClientAddress string + ETHClient *ethclient.Client + ChainID *big.Int + RegistryContract RegistryContract + RLNContract RLNContract +} + +// NewConfig creates an instance of web3 Config +func NewConfig(ethClientAddress string, registryAddress common.Address) *Config { + return &Config{ + ETHClientAddress: ethClientAddress, + RegistryContract: RegistryContract{ + Address: registryAddress, + }, + } +} + +// BuildConfig returns an instance of Web3Config with all the required elements for interaction with the RLN smart contracts +func BuildConfig(ctx context.Context, ethClientAddress string, registryAddress common.Address) (*Config, error) { + ethClient, err := ethclient.Dial(ethClientAddress) + if err != nil { + return nil, err + } + + chainID, err := ethClient.ChainID(ctx) + if err != nil { + return nil, err + } + + rlnRegistry, err := contracts.NewRLNRegistry(registryAddress, ethClient) + if err != nil { + return nil, err + } + + storageIndex, err := rlnRegistry.UsingStorageIndex(&bind.CallOpts{Context: ctx}) + if err != nil { + return nil, err + } + + rlnContractAddress, err := rlnRegistry.Storages(&bind.CallOpts{Context: ctx}, storageIndex) + if err != nil { + return nil, err + } + + rlnContract, err := contracts.NewRLN(rlnContractAddress, ethClient) + if err != nil { + return nil, err + } + + return &Config{ + configured: true, + ETHClientAddress: ethClientAddress, + ETHClient: ethClient, + ChainID: chainID, + RegistryContract: RegistryContract{ + RLNRegistry: rlnRegistry, + Address: registryAddress, + }, + RLNContract: RLNContract{ + RLN: rlnContract, + Address: rlnContractAddress, + StorageIndex: storageIndex, + }, + }, nil +} + +// Build sets up the Config object by instantiating the eth client and contracts +func (w *Config) Build(ctx context.Context) error { + if w.configured { + return errors.New("already configured") + } + + if w.ETHClientAddress == "" { + return errors.New("no eth client address") + } + + var zeroAddr common.Address + if w.RegistryContract.Address == zeroAddr { + return errors.New("no registry contract address") + } + + newW, err := BuildConfig(ctx, w.ETHClientAddress, w.RegistryContract.Address) + if err != nil { + return err + } + + *w = *newW + w.configured = true + + return nil +}