Repository: thehubbleproject/hubble-contracts (ts)

Issue:
Implement BatchPubkeyRegisteredEventSyncer
### Problem

Syncing of batch pubkey registrations to the client is currently not implemented. See `BatchPubkeyRegisteredEventSyncer`.

### Solution

Implement batch pubkey registration syncing in client. See `SinglePubkeyRegisteredEventSyncer` as a reference.

Pull request description:
implemented event listener for `BatchPubkeyRegistered` event
closes #625 

Patch:
diff --git a/ts/client/services/events/batchPubkeyRegistered.ts b/ts/client/services/events/batchPubkeyRegistered.ts
index 87af8af..25e93ff 100644
--- a/ts/client/services/events/batchPubkeyRegistered.ts
+++ b/ts/client/services/events/batchPubkeyRegistered.ts
@@ -1,11 +1,14 @@
 import { Event } from "ethers";
+import { chunk } from "lodash";
+import { PRODUCTION_PARAMS } from "../../../constants";
+import { solG2 } from "../../../mcl";
+import { Pubkey } from "../../../pubkey";
 import { CoreAPI } from "../../coreAPI";
 import { PubkeyStorageEngine } from "../../storageEngine";
 import { ContractEventSyncer } from "./contractEventSyncer";
 
 /**
- * Syncs batchPubkeyRegistered events from the blsAccountRegistry contract
- * TODO Implement https://github.com/thehubbleproject/hubble-contracts/issues/625
+ * Syncs BatchPubkeyRegistered events from the blsAccountRegistry contract
  */
 export class BatchPubkeyRegisteredEventSyncer extends ContractEventSyncer {
     private readonly pubkeyStorage: PubkeyStorageEngine;
@@ -13,10 +16,7 @@ export class BatchPubkeyRegisteredEventSyncer extends ContractEventSyncer {
     constructor(api: CoreAPI) {
         super(
             api.contracts.blsAccountRegistry,
-            api.contracts.blsAccountRegistry.filters.BatchPubkeyRegistered(
-                null,
-                null
-            )
+            api.contracts.blsAccountRegistry.filters.BatchPubkeyRegistered()
         );
         this.eventListener = this.batchPubkeyRegisteredListener;
 
@@ -24,18 +24,48 @@ export class BatchPubkeyRegisteredEventSyncer extends ContractEventSyncer {
     }
 
     public async initialSync(
-        _startBlock: number,
-        _endBlock: number
+        startBlock: number,
+        endBlock: number
     ): Promise<void> {
-        console.error(
-            "BatchPubkeyRegisteredEventSyncer: initialSync not implemented."
+        const events = await this.getEvents(startBlock, endBlock);
+        console.info(
+            `Block ${startBlock} -- ${endBlock}\t${events.length} new batch public key registrations`
         );
+
+        await chunk(events, 10).reduce(async (prev, eventsChunk) => {
+            await prev;
+            await Promise.all(
+                eventsChunk.map(e => this.handleBatchPubkeyRegistered(e))
+            );
+            await this.commitUpdate();
+        }, Promise.resolve());
     }
 
-    private async handleBatchPubkeyRegistered(_event: Event) {
-        console.error(
-            "BatchPubkeyRegisteredEventSyncer: handleBatchPubkeyRegistered not implemented."
-        );
+    private async commitUpdate() {
+        // unused with db engine
+        await this.pubkeyStorage.commit();
+    }
+
+    private getPubkeysFromTxn(txn: { data: string }): Pubkey[] {
+        // Get public key from registration call data
+        const { args } = this.contract.interface.parseTransaction(txn);
+        return args.pubkeys.map((pubkey: solG2) => new Pubkey(pubkey));
+    }
+
+    private async handleBatchPubkeyRegistered(event: Event) {
+        let startID = event.args?.startID;
+
+        const txn = await event.getTransaction();
+        const keysBatch = this.getPubkeysFromTxn(txn);
+
+        const baseIndex = 2 ** (PRODUCTION_PARAMS.MAX_DEPTH - 1);
+        let nextIndex = baseIndex + Number(startID);
+
+        for (const key of keysBatch) {
+            await this.pubkeyStorage.update(nextIndex, key);
+            console.info(`Pubkey added ID ${nextIndex} ${key.toString()}`);
+            nextIndex++;
+        }
     }
 
     batchPubkeyRegisteredListener = async (
@@ -44,5 +74,7 @@ export class BatchPubkeyRegisteredEventSyncer extends ContractEventSyncer {
         event: Event
     ) => {
         await this.handleBatchPubkeyRegistered(event);
+        // unused with db engine
+        await this.commitUpdate();
     };
 }
diff --git a/ts/factory.ts b/ts/factory.ts
index 7ced779..0d0375e 100644
--- a/ts/factory.ts
+++ b/ts/factory.ts
@@ -47,7 +47,7 @@ export class User {
     }
     public addStateID(tokenID: number, stateID: number) {
         if (this.tokenIDtoStateID[tokenID] !== undefined) {
-            throw new Error(`stateID alreadt set for tokenID ${tokenID}`);
+            throw new Error(`stateID already set for tokenID ${tokenID}`);
         }
 
         this.tokenIDtoStateID[tokenID] = stateID;
@@ -62,6 +62,9 @@ export class User {
     public clearStateIDs() {
         this.tokenIDtoStateID = {};
     }
+    public changePubkeyID(pubkeyID: number) {
+        this.pubkeyID = pubkeyID;
+    }
 
     get stateID() {
         return this.getStateID(0);


Test patch:
diff --git a/test/client/integration.test.ts b/test/client/integration.test.ts
index 9f15b38..21cb4cc 100644
--- a/test/client/integration.test.ts
+++ b/test/client/integration.test.ts
@@ -80,6 +80,19 @@ describe("Client Integration", function() {
         const domainSeparator = await contracts.rollup.domainSeparator();
         group.setupSigners(arrayify(domainSeparator));
 
+        const pubkeyBatch: any = [];
+        let batchBasePubkeyID = 2 ** (parameters.MAX_DEPTH - 1);
+        for (const user of group.userIterator()) {
+            // Pubkey
+            if (user.pubkeyID < 16) {
+                await contracts.blsAccountRegistry.register(user.pubkey);
+            } else {
+                user.changePubkeyID(batchBasePubkeyID++);
+                pubkeyBatch.push(user.pubkey);
+            }
+        }
+        await contracts.blsAccountRegistry.registerBatch(pubkeyBatch);
+
         let numDeposits = 0;
         let currentStateID = 0;
         // Split users in subtree sized chunks
@@ -89,8 +102,6 @@ describe("Client Integration", function() {
             for (const user of subGroup.userIterator()) {
                 // Clear out default stateIDs
                 user.clearStateIDs();
-                // Pubkey
-                await contracts.blsAccountRegistry.register(user.pubkey);
                 // Deposit tokens
                 for (let tokenID = 0; tokenID < allTokens.length; tokenID++) {
                     const token = allTokens[tokenID];