From 2e34fd15f22d0494a2e30d8eae1982ae0e5b9104 Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Fri, 3 Feb 2023 00:15:30 +0530 Subject: [PATCH 1/9] fix: add transaction submit eventHandler for nightfall-client --- .../src/event-handlers/block-proposed.mjs | 19 ++++++- nightfall-client/src/event-handlers/index.mjs | 3 + .../event-handlers/transaction-submitted.mjs | 57 +++++++++++++++++++ .../src/services/commitment-storage.mjs | 8 +++ nightfall-client/src/services/database.mjs | 15 +++++ .../src/services/process-calldata.mjs | 47 ++++++++++++++- 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 nightfall-client/src/event-handlers/transaction-submitted.mjs diff --git a/nightfall-client/src/event-handlers/block-proposed.mjs b/nightfall-client/src/event-handlers/block-proposed.mjs index 60a662184..2f6d62153 100644 --- a/nightfall-client/src/event-handlers/block-proposed.mjs +++ b/nightfall-client/src/event-handlers/block-proposed.mjs @@ -14,8 +14,9 @@ import { setSiblingInfo, countCircuitTransactions, isTransactionHashBelongCircuit, + deleteNonNullifiedCommitments, } from '../services/commitment-storage.mjs'; -import getProposeBlockCalldata from '../services/process-calldata.mjs'; +import { getProposeBlockCalldata } from '../services/process-calldata.mjs'; import { zkpPrivateKeys, nullifierKeys } from '../services/keys.mjs'; import { getTreeByBlockNumberL2, @@ -26,6 +27,8 @@ import { getNumberOfL2Blocks, getTransactionByTransactionHash, updateTransaction, + findDuplicateTransactions, + deleteTransactionsByTransactionHashes, } from '../services/database.mjs'; import { decryptCommitment } from '../services/commitment-sync.mjs'; import { syncState } from '../services/state-sync.mjs'; @@ -81,6 +84,7 @@ async function blockProposedEventHandler(data, syncing) { const dbUpdates = transactions.map(async transaction => { let saveTxToDb = false; + let duplicateTransactions = []; // duplicate tx holding same commitments or nullifiers // filter out non zero commitments and nullifiers const nonZeroCommitments = transaction.commitments.filter(c => c !== ZERO); @@ -143,6 +147,12 @@ async function blockProposedEventHandler(data, syncing) { } else logger.warn(`Duplicate transaction in Proposed Block has been dropped`); } else throw new Error(err); } + + duplicateTransactions = await findDuplicateTransactions( + nonZeroCommitments, + nonZeroNullifiers, + [transaction.transactionHash], + ); } return Promise.all([ @@ -154,6 +164,13 @@ async function blockProposedEventHandler(data, syncing) { data.blockNumber, data.transactionHash, ), + deleteTransactionsByTransactionHashes([...duplicateTransactions.map(t => t.transactionHash)]), + deleteNonNullifiedCommitments([ + ...duplicateTransactions + .map(t => t.commitments) + .flat() + .filter(c => c !== ZERO), + ]), ]); }); diff --git a/nightfall-client/src/event-handlers/index.mjs b/nightfall-client/src/event-handlers/index.mjs index 0c7a8d2a9..af6e0ceaa 100644 --- a/nightfall-client/src/event-handlers/index.mjs +++ b/nightfall-client/src/event-handlers/index.mjs @@ -2,15 +2,18 @@ import { startEventQueue } from './subscribe.mjs'; import blockProposedEventHandler from './block-proposed.mjs'; import rollbackEventHandler from './rollback.mjs'; import removeBlockProposedEventHandler from './chain-reorg.mjs'; +import transactionSubmittedEventHandler from './transaction-submitted.mjs'; const eventHandlers = { BlockProposed: blockProposedEventHandler, + TransactionSubmitted: transactionSubmittedEventHandler, Rollback: rollbackEventHandler, removers: { BlockProposed: removeBlockProposedEventHandler, }, priority: { BlockProposed: 0, + TransactionSubmitted: 1, Rollback: 0, }, }; diff --git a/nightfall-client/src/event-handlers/transaction-submitted.mjs b/nightfall-client/src/event-handlers/transaction-submitted.mjs new file mode 100644 index 000000000..243dee303 --- /dev/null +++ b/nightfall-client/src/event-handlers/transaction-submitted.mjs @@ -0,0 +1,57 @@ +import logger from '@polygon-nightfall/common-files/utils/logger.mjs'; +import constants from '@polygon-nightfall/common-files/constants/index.mjs'; +import { getTransactionSubmittedCalldata } from '../services/process-calldata.mjs'; +import { countCommitments, countNullifiers } from '../services/commitment-storage.mjs'; +import { saveTransaction } from '../services/database.mjs'; + +const { ZERO } = constants; + +async function doesAnyOfCommitmentsExistInDB(commitments) { + const count = await countCommitments(commitments); + return Boolean(count); +} + +async function doesAnyOfNullifiersExistInDB(nullifiers) { + const count = await countNullifiers(nullifiers); + return Boolean(count); +} + +/** + * This handler runs whenever a new transaction is submitted to the blockchain + */ +async function transactionSubmittedEventHandler(eventParams) { + const { offchain = false, ...data } = eventParams; + let saveTxInDb = false; + + const transaction = await getTransactionSubmittedCalldata(data); + transaction.blockNumber = data.blockNumber; + transaction.transactionHashL1 = data.transactionHash; + + // logic: if any of non zero commitment in transaction alraedy exist in db + // i.e transaction belong to user using this nightfall-client. + // for example: for deposit we store commitment while transaction submit, + // similarly for transfer we store change commitment while transaction submit + + // filter out non zero commitments and nullifiers + const nonZeroCommitments = transaction.commitments.filter(c => c !== ZERO); + const nonZeroNullifiers = transaction.nullifiers.filter(n => n !== ZERO); + + if (await doesAnyOfCommitmentsExistInDB(nonZeroCommitments)) { + saveTxInDb = true; + } else if (doesAnyOfNullifiersExistInDB(nonZeroNullifiers)) { + saveTxInDb = true; + } + + if (saveTxInDb) { + await saveTransaction({ ...transaction }); + } + + logger.info({ + msg: 'Client Transaction Handler - New transaction received.', + transaction, + offchain, + saveTxInDb, + }); +} + +export default transactionSubmittedEventHandler; diff --git a/nightfall-client/src/services/commitment-storage.mjs b/nightfall-client/src/services/commitment-storage.mjs index e532f8f4e..53c88bcea 100644 --- a/nightfall-client/src/services/commitment-storage.mjs +++ b/nightfall-client/src/services/commitment-storage.mjs @@ -1072,3 +1072,11 @@ export async function getCommitmentsDepositedRollbacked(compressedZkpPublicKey) return db.collection(COMMITMENTS_COLLECTION).find(query).toArray(); } + +// function to delete non nullified commitments +export async function deleteNonNullifiedCommitments(commitments) { + const connection = await mongo.connection(MONGO_URL); + const query = { _id: { $in: commitments }, isNullifiedOnChain: -1 }; + const db = connection.db(COMMITMENTS_DB); + return db.collection(COMMITMENTS_COLLECTION).deleteMany(query); +} diff --git a/nightfall-client/src/services/database.mjs b/nightfall-client/src/services/database.mjs index 237409571..4678180b4 100644 --- a/nightfall-client/src/services/database.mjs +++ b/nightfall-client/src/services/database.mjs @@ -304,3 +304,18 @@ export async function getTransactionsByTransactionHashesByL2Block(transactionHas ); return transactions; } + +/** + * Function to find duplicate transactions for an array of commitments or nullifiers + * this function is used in blockProposedEventHandler + */ +export async function findDuplicateTransactions(commitments, nullifiers, transactionHashes = []) { + const connection = await mongo.connection(MONGO_URL); + const db = connection.db(COMMITMENTS_DB); + const query = { + $or: [{ commitments: { $in: commitments } }, { nullifiers: { $in: nullifiers } }], + transactionHash: { $nin: transactionHashes }, + blockNumberL2: { $exists: false }, + }; + return db.collection(TRANSACTIONS_COLLECTION).find(query).toArray(); +} diff --git a/nightfall-client/src/services/process-calldata.mjs b/nightfall-client/src/services/process-calldata.mjs index 844742f85..32f518f78 100644 --- a/nightfall-client/src/services/process-calldata.mjs +++ b/nightfall-client/src/services/process-calldata.mjs @@ -10,7 +10,7 @@ import { unpackBlockInfo } from 'common-files/utils/block-utils.mjs'; const { SIGNATURES } = config; -async function getProposeBlockCalldata(eventData) { +export async function getProposeBlockCalldata(eventData) { const web3 = Web3.connection(); const { transactionHash } = eventData; const tx = await web3.eth.getTransaction(transactionHash); @@ -76,4 +76,47 @@ async function getProposeBlockCalldata(eventData) { return { transactions, block }; } -export default getProposeBlockCalldata; +export async function getTransactionSubmittedCalldata(eventData) { + const web3 = Web3.connection(); + const { transactionHash } = eventData; + const tx = await web3.eth.getTransaction(transactionHash); + // Remove the '0x' and function signature to recove rhte abi bytecode + const abiBytecode = `0x${tx.input.slice(10)}`; + const transactionData = web3.eth.abi.decodeParameter(SIGNATURES.SUBMIT_TRANSACTION, abiBytecode); + const [ + packedTransactionInfo, + historicRootBlockNumberL2Packed, + tokenId, + ercAddress, + recipientAddress, + commitments, + nullifiers, + compressedSecrets, + proof, + ] = transactionData; + + const { value, fee, circuitHash, tokenType } = + Transaction.unpackTransactionInfo(packedTransactionInfo); + + const historicRootBlockNumberL2 = Transaction.unpackHistoricRoot( + nullifiers.length, + historicRootBlockNumberL2Packed, + ); + + const transaction = { + value, + fee, + circuitHash, + tokenType, + historicRootBlockNumberL2, + tokenId, + ercAddress, + recipientAddress, + commitments, + nullifiers, + compressedSecrets, + proof, + }; + transaction.transactionHash = Transaction.calcHash(transaction); + return transaction; +} From b705a613ec803212b21534ac28adf5bc378cb894 Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Tue, 28 Mar 2023 23:42:37 +1100 Subject: [PATCH 2/9] fix: handle case for TransactionSubmit in state-sync code --- .../src/event-handlers/transaction-submitted.mjs | 1 + nightfall-client/src/services/state-sync.mjs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/nightfall-client/src/event-handlers/transaction-submitted.mjs b/nightfall-client/src/event-handlers/transaction-submitted.mjs index 243dee303..19238c7bd 100644 --- a/nightfall-client/src/event-handlers/transaction-submitted.mjs +++ b/nightfall-client/src/event-handlers/transaction-submitted.mjs @@ -1,3 +1,4 @@ +/* eslint-disable import/no-cycle */ import logger from '@polygon-nightfall/common-files/utils/logger.mjs'; import constants from '@polygon-nightfall/common-files/constants/index.mjs'; import { getTransactionSubmittedCalldata } from '../services/process-calldata.mjs'; diff --git a/nightfall-client/src/services/state-sync.mjs b/nightfall-client/src/services/state-sync.mjs index 89d36f599..66fe6cda4 100644 --- a/nightfall-client/src/services/state-sync.mjs +++ b/nightfall-client/src/services/state-sync.mjs @@ -12,8 +12,9 @@ import { unpauseQueue } from 'common-files/utils/event-queue.mjs'; import constants from 'common-files/constants/index.mjs'; import blockProposedEventHandler from '../event-handlers/block-proposed.mjs'; import rollbackEventHandler from '../event-handlers/rollback.mjs'; +import transactionSubmittedEventHandler from '../event-handlers/transaction-submitted.mjs'; -const { STATE_CONTRACT_NAME, CHALLENGES_CONTRACT_NAME } = constants; +const { STATE_CONTRACT_NAME, SHIELD_CONTRACT_NAME, CHALLENGES_CONTRACT_NAME } = constants; const { MONGO_URL, COMMITMENTS_DB, COMMITMENTS_COLLECTION, STATE_GENESIS_BLOCK } = config; export const syncState = async ( @@ -24,6 +25,7 @@ export const syncState = async ( logger.info({ msg: 'SyncState parameters', fromBlock, toBlock, eventFilter }); const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); // BlockProposed + const shieldContractInstance = await waitForContract(SHIELD_CONTRACT_NAME); // TransactionSubmitted const challengesContractInstance = await waitForContract(CHALLENGES_CONTRACT_NAME); // Rollback const pastStateEvents = await stateContractInstance.getPastEvents(eventFilter, { @@ -31,6 +33,11 @@ export const syncState = async ( toBlock, }); + const pastShieldEvents = await shieldContractInstance.getPastEvents(eventFilter, { + fromBlock, + toBlock, + }); + const pastChallengeEvents = await challengesContractInstance.getPastEvents(eventFilter, { fromBlock, toBlock, @@ -38,6 +45,7 @@ export const syncState = async ( // Put all events together and sort chronologically as they appear on Ethereum const splicedList = pastStateEvents + .concat(pastShieldEvents) .concat(pastChallengeEvents) .sort((a, b) => a.blockNumber - b.blockNumber); @@ -52,6 +60,10 @@ export const syncState = async ( // eslint-disable-next-line no-await-in-loop await rollbackEventHandler(pastEvent); break; + case 'TransactionSubmitted': + // eslint-disable-next-line no-await-in-loop + await transactionSubmittedEventHandler(pastEvent); + break; default: break; } From 1fae5e8382d4cd3d3d35d63c161726f3a5556c48 Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Wed, 29 Mar 2023 15:00:55 +1100 Subject: [PATCH 3/9] fix: comment corrected --- .../src/event-handlers/transaction-submitted.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nightfall-client/src/event-handlers/transaction-submitted.mjs b/nightfall-client/src/event-handlers/transaction-submitted.mjs index 19238c7bd..78796fe16 100644 --- a/nightfall-client/src/event-handlers/transaction-submitted.mjs +++ b/nightfall-client/src/event-handlers/transaction-submitted.mjs @@ -29,9 +29,10 @@ async function transactionSubmittedEventHandler(eventParams) { transaction.transactionHashL1 = data.transactionHash; // logic: if any of non zero commitment in transaction alraedy exist in db - // i.e transaction belong to user using this nightfall-client. - // for example: for deposit we store commitment while transaction submit, - // similarly for transfer we store change commitment while transaction submit + // That means this transaction belong to user using this nightfall-client + // Hence, proceed and save tx in db. + // Note: for deposit we store commitment in transaction submit event handler, + // similarly for transfer we store change commitment in transaction submit event handler. // filter out non zero commitments and nullifiers const nonZeroCommitments = transaction.commitments.filter(c => c !== ZERO); From 997b2fe5bb6550e0e4a341f89c9c9f6fd61e772b Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Wed, 29 Mar 2023 16:59:59 +1100 Subject: [PATCH 4/9] fix: cyclic dependency fixed for transactionSubmitEventHandler --- .../src/event-handlers/transaction-submitted.mjs | 1 - nightfall-client/src/routes/commitment.mjs | 6 ++++-- nightfall-client/src/services/commitment-storage.mjs | 9 ++------- nightfall-client/src/services/state-sync.mjs | 3 +-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/nightfall-client/src/event-handlers/transaction-submitted.mjs b/nightfall-client/src/event-handlers/transaction-submitted.mjs index 78796fe16..648068dbe 100644 --- a/nightfall-client/src/event-handlers/transaction-submitted.mjs +++ b/nightfall-client/src/event-handlers/transaction-submitted.mjs @@ -1,4 +1,3 @@ -/* eslint-disable import/no-cycle */ import logger from '@polygon-nightfall/common-files/utils/logger.mjs'; import constants from '@polygon-nightfall/common-files/constants/index.mjs'; import { getTransactionSubmittedCalldata } from '../services/process-calldata.mjs'; diff --git a/nightfall-client/src/routes/commitment.mjs b/nightfall-client/src/routes/commitment.mjs index 6cd953a04..45e1e17dc 100644 --- a/nightfall-client/src/routes/commitment.mjs +++ b/nightfall-client/src/routes/commitment.mjs @@ -15,10 +15,11 @@ import { getWalletPendingSpentBalance, getCommitments, getCommitmentsByCompressedZkpPublicKeyList, - insertCommitmentsAndResync, + insertCommitments, getCommitmentsByCircuitHash, getCommitmentsDepositedRollbacked, } from '../services/commitment-storage.mjs'; +import { syncState } from '../services/state-sync.mjs'; const router = express.Router(); @@ -84,7 +85,8 @@ router.get('/commitments', async (req, res, next) => { router.post('/save', async (req, res, next) => { const listOfCommitments = req.body; try { - const response = await insertCommitmentsAndResync(listOfCommitments); + const response = await insertCommitments(listOfCommitments); + await syncState(); // Sycronize from beggining res.json(response); } catch (err) { next(err); diff --git a/nightfall-client/src/services/commitment-storage.mjs b/nightfall-client/src/services/commitment-storage.mjs index 53c88bcea..cbc99f51c 100644 --- a/nightfall-client/src/services/commitment-storage.mjs +++ b/nightfall-client/src/services/commitment-storage.mjs @@ -16,7 +16,6 @@ import { getTransactionByTransactionHash, getTransactionHashSiblingInfo, } from './database.mjs'; -import { syncState } from './state-sync.mjs'; const { MONGO_URL, COMMITMENTS_DB, COMMITMENTS_COLLECTION } = config; const { generalise } = gen; @@ -976,13 +975,13 @@ export async function findUsableCommitmentsMutex( /** * - * @function insertCommitmentsAndResync save a list of commitments in the database + * @function insertCommitments save a list of commitments in the database * @param {[]} listOfCommitments a list of commitments to be saved in the database * @throws if all the commitments in the list already exists in the database * throw an error * @returns return a success message. */ -export async function insertCommitmentsAndResync(listOfCommitments) { +export async function insertCommitments(listOfCommitments) { const connection = await mongo.connection(MONGO_URL); const db = connection.db(COMMITMENTS_DB); @@ -1005,10 +1004,6 @@ export async function insertCommitmentsAndResync(listOfCommitments) { if (onlyNewCommitments.length > 0) { // 4. Insert all await db.collection(COMMITMENTS_COLLECTION).insertMany(onlyNewCommitments); - - // 5. Sycronize from beggining - await syncState(); - return { successMessage: 'Commitments have been saved successfully!' }; } diff --git a/nightfall-client/src/services/state-sync.mjs b/nightfall-client/src/services/state-sync.mjs index 66fe6cda4..cd73152c7 100644 --- a/nightfall-client/src/services/state-sync.mjs +++ b/nightfall-client/src/services/state-sync.mjs @@ -80,8 +80,7 @@ const genGetCommitments = async (query = {}, proj = {}) => { export const initialClientSync = async () => { const allCommitments = await genGetCommitments(); const commitmentBlockNumbers = allCommitments.map(a => a.blockNumber).filter(n => n >= 0); - - logger.info(`commitmentBlockNumbers: ${commitmentBlockNumbers}`); + logger.info({ msg: 'commitmentBlockNumbers', commitmentBlockNumbers }); const firstSeenBlockNumber = Math.min(...commitmentBlockNumbers); From 023b3a7d1ee4bd78479415ce0e56a7d77fbfb13e Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Wed, 29 Mar 2023 22:54:36 +1100 Subject: [PATCH 5/9] fix: filter moved in mongo query --- nightfall-client/src/services/state-sync.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nightfall-client/src/services/state-sync.mjs b/nightfall-client/src/services/state-sync.mjs index cd73152c7..cd3c0f2ac 100644 --- a/nightfall-client/src/services/state-sync.mjs +++ b/nightfall-client/src/services/state-sync.mjs @@ -78,8 +78,8 @@ const genGetCommitments = async (query = {}, proj = {}) => { // eslint-disable-next-line import/prefer-default-export export const initialClientSync = async () => { - const allCommitments = await genGetCommitments(); - const commitmentBlockNumbers = allCommitments.map(a => a.blockNumber).filter(n => n >= 0); + const allCommitments = await genGetCommitments({blockNumber: {$gte: 0}}); + const commitmentBlockNumbers = allCommitments.map(a => a.blockNumber); logger.info({ msg: 'commitmentBlockNumbers', commitmentBlockNumbers }); const firstSeenBlockNumber = Math.min(...commitmentBlockNumbers); From d3cb95ffaf047e5727c1732d37f142aa413a8c2d Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Thu, 30 Mar 2023 10:50:42 +1100 Subject: [PATCH 6/9] fix: array filter moved to mongo query --- nightfall-client/src/services/state-sync.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nightfall-client/src/services/state-sync.mjs b/nightfall-client/src/services/state-sync.mjs index cd3c0f2ac..b26307544 100644 --- a/nightfall-client/src/services/state-sync.mjs +++ b/nightfall-client/src/services/state-sync.mjs @@ -78,7 +78,7 @@ const genGetCommitments = async (query = {}, proj = {}) => { // eslint-disable-next-line import/prefer-default-export export const initialClientSync = async () => { - const allCommitments = await genGetCommitments({blockNumber: {$gte: 0}}); + const allCommitments = await genGetCommitments({ blockNumber: { $gte: 0 } }); const commitmentBlockNumbers = allCommitments.map(a => a.blockNumber); logger.info({ msg: 'commitmentBlockNumbers', commitmentBlockNumbers }); From aa2c35c86a960b1fe67e5fe97b7b57deb6d8fb8e Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Wed, 10 May 2023 22:52:55 +1000 Subject: [PATCH 7/9] fix: adding missed code logic for master rebase --- .github/workflows/on-pull-request-master.yml | 54 ++++++ cli/lib/nf3.mjs | 4 +- nightfall-client/src/routes/commitment.mjs | 10 ++ package.json | 1 + test/client-resync.test.mjs | 173 +++++++++++++++++++ test/utils.mjs | 34 ++++ 6 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 test/client-resync.test.mjs diff --git a/.github/workflows/on-pull-request-master.yml b/.github/workflows/on-pull-request-master.yml index 069aef685..9c566185a 100644 --- a/.github/workflows/on-pull-request-master.yml +++ b/.github/workflows/on-pull-request-master.yml @@ -895,3 +895,57 @@ jobs: with: name: periodic-payment-test-logs path: ./periodic-payment-test.log + + test-client-sync: + env: + CONFIRMATIONS: 1 + NF_SERVICES_TO_START: blockchain,client,deployer,mongodb,optimist,rabbitmq,worker + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v1 + with: + node-version: '16.17.0' + + - name: Perform npm link + run: | + cd common-files + npm link + cd .. + npm link @polygon-nightfall/common-files + cd cli + npm link @polygon-nightfall/common-files + cd .. + - name: Start Containers + run: | + ./bin/setup-nightfall + ./bin/start-nightfall -g -d &> test-client-sync.log &disown + - name: Wait for images to be ready + uses: Wandalen/wretry.action@v1.0.11 + with: + command: | + docker wait nightfall_3_deployer_1 + attempt_limit: 100 + attempt_delay: 20000 + + - name: Debug logs - after image builds + if: always() + run: cat test-client-sync.log + + - name: Run integration test + run: | + npm run test-client-sync + - name: Debug logs - after integration test run + if: always() + run: cat test-client-sync.log + + - name: If integration test failed, shutdown the Containers + if: failure() + run: npm run nightfall-down + + - name: If integration test failed, upload logs files as artifacts + if: failure() + uses: actions/upload-artifact@master + with: + name: test-client-sync-logs + path: ./test-client-sync.log diff --git a/cli/lib/nf3.mjs b/cli/lib/nf3.mjs index e3bd0f160..01f72b48e 100644 --- a/cli/lib/nf3.mjs +++ b/cli/lib/nf3.mjs @@ -677,7 +677,7 @@ class Nf3 { this.shieldContractAddress, 0, ); - resolve(receipt); + resolve({ ...receipt, transactionHashL2: res.data.transaction.transactionHash }); } catch (err) { reject(err); } @@ -740,7 +740,7 @@ class Nf3 { this.shieldContractAddress, 0, ); - resolve(receipt); + resolve({ ...receipt, transactionHashL2: res.data.transaction.transactionHash }); } catch (err) { reject(err); } diff --git a/nightfall-client/src/routes/commitment.mjs b/nightfall-client/src/routes/commitment.mjs index 45e1e17dc..b2d98966a 100644 --- a/nightfall-client/src/routes/commitment.mjs +++ b/nightfall-client/src/routes/commitment.mjs @@ -20,6 +20,7 @@ import { getCommitmentsDepositedRollbacked, } from '../services/commitment-storage.mjs'; import { syncState } from '../services/state-sync.mjs'; +import { getAllTransactions } from '../services/database.mjs'; const router = express.Router(); @@ -149,4 +150,13 @@ router.get('/commitmentsRollbacked', async (req, res, next) => { } }); +router.get('/transactions', async (req, res, next) => { + try { + const transactions = await getAllTransactions(); + res.json({ transactions }); + } catch (err) { + next(err); + } +}); + export default router; diff --git a/package.json b/package.json index c2f35f729..6a58e2c5f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "ping-pong": ". ./bin/export-multiproposer-test-env && npx hardhat test --bail --no-compile test/ping-pong/ping-pong.test.mjs", "test-administrator": "npx hardhat test --bail --no-compile test/multisig/administrator.test.mjs ", "test-optimist-sync": "npx hardhat test --no-compile --bail test/optimist-resync.test.mjs", + "test-client-sync": "npx hardhat test --no-compile --bail test/client-resync.test.mjs", "test-adversary": "npx hardhat test --no-compile --bail test/adversary.test.mjs", "test-general-stuff": "npx hardhat test --bail --no-compile test/kem-dem.test.mjs test/timber.test.mjs", "test-x509": "LOG_LEVEL=debug npx hardhat test --bail --no-compile test/x509.test.mjs", diff --git a/test/client-resync.test.mjs b/test/client-resync.test.mjs new file mode 100644 index 000000000..a49031028 --- /dev/null +++ b/test/client-resync.test.mjs @@ -0,0 +1,173 @@ +/* eslint-disable no-await-in-loop */ +import chai, { expect } from 'chai'; +// import gen from 'general-number'; +import chaiHttp from 'chai-http'; +import chaiAsPromised from 'chai-as-promised'; +import config from 'config'; +import logger from '@polygon-nightfall/common-files/utils/logger.mjs'; +import Nf3 from '../cli/lib/nf3.mjs'; +import { + getLayer2Balances, + expectTransaction, + Web3Client, + getUserCommitments, + getClientTransactions, + restartClient, +} from './utils.mjs'; + +chai.use(chaiHttp); +chai.use(chaiAsPromised); + +// const { generalise } = gen; +const environment = config.ENVIRONMENTS[process.env.ENVIRONMENT] || config.ENVIRONMENTS.localhost; +const { + fee, + transferValue, + tokenConfigs: { tokenType, tokenId }, + mnemonics, + signingKeys, +} = config.TEST_OPTIONS; + +const web3Client = new Web3Client(); +const eventLogs = []; +let rollbackCount = 0; + +const nf3User = new Nf3(signingKeys.user1, environment); +const nf3User2 = new Nf3(signingKeys.user2, environment); + +const nf3Proposer = new Nf3(signingKeys.proposer1, environment); + +async function makeBlock() { + logger.debug(`Make block...`); + await nf3Proposer.makeBlockNow(); + await web3Client.waitForEvent(eventLogs, ['blockProposed']); +} + +describe('Client synchronisation tests', () => { + let erc20Address; + + before(async () => { + await nf3User.init(mnemonics.user1); + await nf3User2.init(mnemonics.user2); + + await nf3Proposer.init(mnemonics.proposer); + await nf3Proposer.registerProposer('http://optimist', await nf3Proposer.getMinimumStake()); + + // Proposer listening for incoming events + const newGasBlockEmitter = await nf3Proposer.startProposer(); + newGasBlockEmitter.on('rollback', () => { + rollbackCount += 1; + logger.debug( + `Proposer received a signalRollback complete, Now no. of rollbacks are ${rollbackCount}`, + ); + }); + + erc20Address = await nf3User.getContractAddress('ERC20Mock'); + web3Client.subscribeTo('logs', eventLogs, { address: nf3User.stateContractAddress }); + web3Client.subscribeTo('logs', eventLogs, { address: nf3User.shieldContractAddress }); + }); + + describe('Test nightfall-client', () => { + it('Should do two deposit successfully', async function () { + const userL2BalanceBefore = await getLayer2Balances(nf3User, erc20Address); + + // first deposit + const res = await nf3User.deposit(erc20Address, tokenType, transferValue, tokenId, fee); + expectTransaction(res); + + await web3Client.waitForEvent(eventLogs, ['TransactionSubmitted']); + const transactions = await getClientTransactions(environment.clientApiUrl); + + // passing of below expect proves that transaction are save in + // transactionEventHandler + expect(transactions.length).to.be.equal(1); + expect(res.transactionHashL2).to.be.equal(transactions[0].transactionHash); + + await makeBlock(); + + // second deposit + await nf3User.deposit(erc20Address, tokenType, transferValue, tokenId, fee); + + await makeBlock(); + + const userL2BalanceAfter = await getLayer2Balances(nf3User, erc20Address); + expect(userL2BalanceAfter - userL2BalanceBefore).to.be.equal(transferValue * 2 - fee * 2); + }); + + // this test is to check nightfall-client behaviour in a case + // where two same transfer transactions is created but second one with higher fee + context('Test nightfall-client duplicate transaction deletion logic', () => { + let userCommitments; + let firstTransfer; + let userL2BalanceBefore; + before(async () => { + userCommitments = await getUserCommitments( + environment.clientApiUrl, + nf3User.zkpKeys.compressedZkpPublicKey, + ); + userL2BalanceBefore = await getLayer2Balances(nf3User, erc20Address); + }); + + it('Should successfully create a transfer transaction', async function () { + const res = await nf3User.transfer( + false, + erc20Address, + tokenType, + transferValue, + tokenId, + nf3User2.zkpKeys.compressedZkpPublicKey, + fee, + userCommitments.map(c => c.commitmentHash), + ); + expectTransaction(res); + firstTransfer = res.transactionHashL2; + await web3Client.waitForEvent(eventLogs, ['TransactionSubmitted']); + const transactions = await getClientTransactions(environment.clientApiUrl); + + expect(transactions.length).to.be.equal(3); + expect(res.transactionHashL2).to.be.equal(transactions[2].transactionHash); + }); + + it('Should successfully do a transfer with higher fee with create block', async function () { + let transactions; + const res = await nf3User.transfer( + false, + erc20Address, + tokenType, + transferValue, + tokenId, + nf3User2.zkpKeys.compressedZkpPublicKey, + fee + 1, + userCommitments.map(c => c.commitmentHash), + ); + expectTransaction(res); + + transactions = await getClientTransactions(environment.clientApiUrl); + expect(transactions.length).to.be.equal(3); + expect(firstTransfer).to.be.equal(transactions[2].transactionHash); + + // here we will also test client resync atleast for TransactionSubmitEvent Handler + await restartClient(nf3User); + + transactions = await getClientTransactions(environment.clientApiUrl); + // if below expect passes it proves client resync is working. + expect(transactions.length).to.be.equal(4); + expect(res.transactionHashL2).to.be.equal(transactions[3].transactionHash); + + // client re-sync has made sure higer fee transfer transaction is received in + // transaction submit eventHandler, infact that what passing of above expect proves + // But for optimist to receive same transaction lets wait for TransactionSubmitted + // event trigger, needed before proceeding to makeBLock + await web3Client.waitForEvent(eventLogs, ['TransactionSubmitted']); + await makeBlock(); + const userL2BalanceAfter = await getLayer2Balances(nf3User, erc20Address); + expect(userL2BalanceAfter - userL2BalanceBefore).to.be.equal(-(transferValue + fee + 1)); + + transactions = await getClientTransactions(environment.clientApiUrl); + // if below expect passes it proves blockEventHandler delete duplicate transaction is working. + expect(transactions.length).to.be.equal(3); + expect(res.transactionHashL2).to.be.equal(transactions[2].transactionHash); + }); + }); + }); +}); diff --git a/test/utils.mjs b/test/utils.mjs index dc669e4f7..44c4d9d3d 100644 --- a/test/utils.mjs +++ b/test/utils.mjs @@ -571,6 +571,14 @@ const healthy = async nf3Proposer => { logger.debug('optimist is healthy'); }; +const healthyClient = async nf3User => { + while (!(await nf3User.healthcheck('client'))) { + await waitForTimeout(1000); + } + + logger.debug('client is healthy'); +}; + const dropOptimistMongoDatabase = async () => { logger.debug(`Dropping Optimist's Mongo database`); let mongoConn; @@ -717,3 +725,29 @@ export async function restartOptimist(nf3Proposer, dropDb = true) { await healthy(nf3Proposer); } + +// unlike optimist, client cannot drop its database. +// because of commitments stored in database. +// restartClient function is only used in client-resync.test.mjs +export async function restartClient(nf3User) { + const options = { + config: [ + 'docker/docker-compose.yml', + 'docker/docker-compose.dev.yml', + 'docker/docker-compose.ganache.yml', + ], + log: process.env.LOG_LEVEL || 'silent', + composeOptions: [['-p', 'nightfall_3']], + }; + + await compose.stopOne('client', options); + await compose.rm(options, 'client'); + + await compose.upOne('client', options); + await healthyClient(nf3User); +} + +export async function getClientTransactions(clientApiUrl) { + const { transactions } = (await axios.get(`${clientApiUrl}/commitment/transactions`)).data; + return transactions; +} From 9566e0c3db731bd22e89e620144f082db5f2d088 Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Wed, 10 May 2023 23:29:33 +1000 Subject: [PATCH 8/9] fix: fix common-file module error from missing code --- .github/workflows/on-pull-request-master.yml | 9 --------- .../src/event-handlers/transaction-submitted.mjs | 4 ++-- test/client-resync.test.mjs | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/on-pull-request-master.yml b/.github/workflows/on-pull-request-master.yml index 9c566185a..687da963c 100644 --- a/.github/workflows/on-pull-request-master.yml +++ b/.github/workflows/on-pull-request-master.yml @@ -907,15 +907,6 @@ jobs: with: node-version: '16.17.0' - - name: Perform npm link - run: | - cd common-files - npm link - cd .. - npm link @polygon-nightfall/common-files - cd cli - npm link @polygon-nightfall/common-files - cd .. - name: Start Containers run: | ./bin/setup-nightfall diff --git a/nightfall-client/src/event-handlers/transaction-submitted.mjs b/nightfall-client/src/event-handlers/transaction-submitted.mjs index 648068dbe..87683a2e9 100644 --- a/nightfall-client/src/event-handlers/transaction-submitted.mjs +++ b/nightfall-client/src/event-handlers/transaction-submitted.mjs @@ -1,5 +1,5 @@ -import logger from '@polygon-nightfall/common-files/utils/logger.mjs'; -import constants from '@polygon-nightfall/common-files/constants/index.mjs'; +import logger from 'common-files/utils/logger.mjs'; +import constants from 'common-files/constants/index.mjs'; import { getTransactionSubmittedCalldata } from '../services/process-calldata.mjs'; import { countCommitments, countNullifiers } from '../services/commitment-storage.mjs'; import { saveTransaction } from '../services/database.mjs'; diff --git a/test/client-resync.test.mjs b/test/client-resync.test.mjs index a49031028..b80cc1121 100644 --- a/test/client-resync.test.mjs +++ b/test/client-resync.test.mjs @@ -4,7 +4,7 @@ import chai, { expect } from 'chai'; import chaiHttp from 'chai-http'; import chaiAsPromised from 'chai-as-promised'; import config from 'config'; -import logger from '@polygon-nightfall/common-files/utils/logger.mjs'; +import logger from 'common-files/utils/logger.mjs'; import Nf3 from '../cli/lib/nf3.mjs'; import { getLayer2Balances, From b5f8862081850eba1e1e3a39969976d3aeb50ccc Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Thu, 11 May 2023 22:18:13 +1000 Subject: [PATCH 9/9] fix: fixing client-resync test --- .../src/event-handlers/transaction-submitted.mjs | 7 ++++++- nightfall-client/src/services/database.mjs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/nightfall-client/src/event-handlers/transaction-submitted.mjs b/nightfall-client/src/event-handlers/transaction-submitted.mjs index 87683a2e9..1cf1766fb 100644 --- a/nightfall-client/src/event-handlers/transaction-submitted.mjs +++ b/nightfall-client/src/event-handlers/transaction-submitted.mjs @@ -44,7 +44,12 @@ async function transactionSubmittedEventHandler(eventParams) { } if (saveTxInDb) { - await saveTransaction({ ...transaction }); + await saveTransaction({ ...transaction }).catch(err => + logger.error({ + msg: 'error while saving transaction in transactionSubmittedEventHandler', + err, + }), + ); } logger.info({ diff --git a/nightfall-client/src/services/database.mjs b/nightfall-client/src/services/database.mjs index 4678180b4..c1ad432ce 100644 --- a/nightfall-client/src/services/database.mjs +++ b/nightfall-client/src/services/database.mjs @@ -315,7 +315,7 @@ export async function findDuplicateTransactions(commitments, nullifiers, transac const query = { $or: [{ commitments: { $in: commitments } }, { nullifiers: { $in: nullifiers } }], transactionHash: { $nin: transactionHashes }, - blockNumberL2: { $exists: false }, + blockNumberL2: { $eq: -1 }, }; return db.collection(TRANSACTIONS_COLLECTION).find(query).toArray(); }