From d1ac3994a94ea3ee7b395ffe176906b126fddad3 Mon Sep 17 00:00:00 2001 From: Liju Jose Date: Fri, 3 Feb 2023 00:15:30 +0530 Subject: [PATCH] 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 2f4a55c75..74ec3f228 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 { getLatestTree, @@ -23,6 +24,8 @@ import { saveTransaction, saveBlock, setTransactionHashSiblingInfo, + findDuplicateTransactions, + deleteTransactionsByTransactionHashes, } from '../services/database.mjs'; import { decryptCommitment } from '../services/commitment-sync.mjs'; @@ -55,6 +58,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); @@ -102,6 +106,12 @@ async function blockProposedEventHandler(data, syncing) { ...transaction, isDecrypted, }); + + duplicateTransactions = await findDuplicateTransactions( + nonZeroCommitments, + nonZeroNullifiers, + [transaction.transactionHash], + ); } return Promise.all([ @@ -113,6 +123,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 2c7bf62f2..657b45bc6 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 4ee6dfc78..48a452080 100644 --- a/nightfall-client/src/services/database.mjs +++ b/nightfall-client/src/services/database.mjs @@ -271,3 +271,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 af59a1fcc..957c07b7b 100644 --- a/nightfall-client/src/services/process-calldata.mjs +++ b/nightfall-client/src/services/process-calldata.mjs @@ -10,7 +10,7 @@ import { unpackBlockInfo } from '@polygon-nightfall/common-files/utils/block-uti 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; +}