From 8cbce412ad5ea47e2ed5dd908f457e8870c51023 Mon Sep 17 00:00:00 2001 From: lovesh Date: Mon, 10 Jun 2024 21:34:19 +0530 Subject: [PATCH] Add accumulator update helpers and add test Signed-off-by: lovesh --- src/modules/accumulator.js | 45 ++++++++++- src/utils/chain-ops.js | 54 ++++++++++++- .../prefilled-positive-accumulator.test.js | 79 +++++++++++++++++++ 3 files changed, 173 insertions(+), 5 deletions(-) diff --git a/src/modules/accumulator.js b/src/modules/accumulator.js index 65c279104..7332ae804 100644 --- a/src/modules/accumulator.js +++ b/src/modules/accumulator.js @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import { hexToU8a, isHex, u8aToHex } from '@polkadot/util'; -import { KBUniversalAccumulatorValue } from '@docknetwork/crypto-wasm-ts'; +import { KBUniversalAccumulatorValue, VBWitnessUpdateInfo } from '@docknetwork/crypto-wasm-ts'; import { getDidNonce, getStateChange } from '../utils/misc'; import WithParamsAndPublicKeys from './WithParamsAndPublicKeys'; import { getAllExtrinsicsFromBlock } from '../utils/chain-ops'; @@ -927,6 +927,49 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys { } } + /** + * Update given witness by downloading necessary blocks and applying the updates if found. Both start and end are inclusive + * @param accumulatorId + * @param member + * @param witness - this will be updated to the latest witness + * @param startBlock - block number to start from + * @param endBlock - block number to end at. If not specified, it will pick the `lastUpdated` field of the accumulator. + * @returns {Promise} + */ + // eslint-disable-next-line sonarjs/cognitive-complexity + async updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, witness, startBlock, endBlock = undefined) { + if (endBlock === undefined) { + const accum = await this.accumulatorModule.getAccumulator(accumulatorId, false); + // eslint-disable-next-line no-param-reassign + endBlock = accum.lastModified; + } + console.debug(`Will start updating witness from block ${startBlock} to ${endBlock}`); + let current = startBlock; + while (current <= endBlock) { + // eslint-disable-next-line no-await-in-loop + const updates = await this.getUpdatesFromBlock(accumulatorId, current); + for (const update of updates) { + const additions = []; + const removals = []; + if (update.additions !== null) { + for (const a of update.additions) { + additions.push(hexToU8a(a)); + } + } + if (update.removals !== null) { + for (const a of update.removals) { + removals.push(hexToU8a(a)); + } + } + console.debug(`Found ${additions.length} additions and ${removals.length} removals in block no ${current}`); + const queriedWitnessInfo = new VBWitnessUpdateInfo(hexToU8a(update.witnessUpdateInfo)); + + witness.updateUsingPublicInfoPostBatchUpdate(member, additions, removals, queriedWitnessInfo); + } + current++; + } + } + signAddParams(signingKeyRef, params) { const serialized = getStateChange(this.api, 'AddAccumulatorParams', params); return signingKeyRef.sign(serialized); diff --git a/src/utils/chain-ops.js b/src/utils/chain-ops.js index 2f289c37c..979c5efcd 100644 --- a/src/utils/chain-ops.js +++ b/src/utils/chain-ops.js @@ -57,13 +57,21 @@ export async function generateAccount({ secretUri, type = 'sr25519', network = ' return [secretUri, keypair.address, keypair]; } -// Get the last authored block +/** + * Get the last authored block + * @param api + * @returns {Promise<*>} + */ export async function getLastBlock(api) { const { block } = await api.rpc.chain.getBlock(); return block; } -// Get the last finalized block +/** + * Get the last finalized block + * @param api + * @returns {Promise<*|{author: *, block: *}>} + */ export async function getLastFinalizedBlock(api) { const h = await api.rpc.chain.getFinalizedHead(); return getBlock(api, u8aToHex(h)); @@ -78,7 +86,13 @@ export async function blockNumberToHash(api, number) { throw new Error(`${number} is not a valid block number`); } -// Fetch a block by block number or block hash +/** + * Fetch a block by block number or block hash + * @param api + * @param numberOrHash + * @param withAuthor + * @returns {Promise<*|{author: *, block: *}>} + */ export async function getBlock(api, numberOrHash, withAuthor = false) { const hash = isHexWithGivenByteSize(numberOrHash, 32) ? numberOrHash : (await blockNumberToHash(api, numberOrHash)); if (withAuthor) { @@ -89,7 +103,11 @@ export async function getBlock(api, numberOrHash, withAuthor = false) { return block; } -// Given a block header or block, extract the block number +/** + * Given a block header or block, extract the block number + * @param headerOrBlock + * @returns {*} + */ export function getBlockNo(headerOrBlock) { if ((typeof headerOrBlock.header) === 'object') { return headerOrBlock.header.number.toNumber(); @@ -97,6 +115,34 @@ export function getBlockNo(headerOrBlock) { return headerOrBlock.number.toNumber(); } +/** + * Get the last authored block's number + * @param api + * @returns {Promise} + */ +export async function getLastBlockNo(api) { + const { block } = await api.rpc.chain.getBlock(); + return block.header.number.toNumber(); +} + +/** + * Wait for some blocks to be produced + * @param api + * @param n - the number of blocks to wait for + * @returns {Promise} + */ +export async function waitForBlocks(api, n) { + let currentBlockNo = await getLastBlockNo(api); + const targetBlockNo = currentBlockNo + n; + const blockTime = api.consts.babe.expectedBlockTime.toNumber(); + while (currentBlockNo < targetBlockNo) { + // eslint-disable-next-line no-await-in-loop,no-loop-func + await new Promise((r) => setTimeout(r, blockTime * (targetBlockNo - currentBlockNo))); + // eslint-disable-next-line no-await-in-loop + currentBlockNo = await getLastBlockNo(api); + } +} + /** * Given a block number or block hash, get all extrinsics. * @param {*} api diff --git a/tests/integration/anoncreds/prefilled-positive-accumulator.test.js b/tests/integration/anoncreds/prefilled-positive-accumulator.test.js index fa85e1079..0545e6cef 100644 --- a/tests/integration/anoncreds/prefilled-positive-accumulator.test.js +++ b/tests/integration/anoncreds/prefilled-positive-accumulator.test.js @@ -13,6 +13,7 @@ import AccumulatorModule, { AccumulatorType } from '../../../src/modules/accumul import { FullNodeEndpoint, TestAccountURI, TestKeyringOpts } from '../../test-constants'; import { createNewDockDID, DidKeypair } from '../../../src/utils/did'; import { registerNewDIDUsingPair } from '../helpers'; +import { getLastBlockNo, waitForBlocks } from '../../../src/utils/chain-ops'; describe('Prefilled positive accumulator', () => { // Incase updating an accumulator is expensive like making a blockchain txn, a cheaper strategy @@ -157,6 +158,84 @@ describe('Prefilled positive accumulator', () => { expect(verifAccumulator.verifyMembershipWitness(member3, witness3, accumPk, accumParams)).toEqual(true); }); + test('Witness update after several batch upgrades', async () => { + let queriedAccum = await dock.accumulatorModule.getAccumulator(accumulatorId, true); + let verifAccumulator = PositiveAccumulator.fromAccumulated(AccumulatorModule.accumulatedFromHex(queriedAccum.accumulated, AccumulatorType.VBPos)); + const member = members[10]; + let witness = await accumulator.membershipWitness(member, keypair.secretKey, accumState); + + const accumPk = new AccumulatorPublicKey(hexToU8a(queriedAccum.publicKey.bytes)); + const accumParams = new AccumulatorParams(hexToU8a(queriedAccum.publicKey.params.bytes)); + expect(verifAccumulator.verifyMembershipWitness(member, witness, accumPk, accumParams)).toEqual(true); + + await waitForBlocks(dock.api, 2); + + // Do some updates to the accumulator + + const removals1 = [members[85], members[86]]; + const witnessUpdInfo1 = VBWitnessUpdateInfo.new(accumulator.accumulated, [], removals1, keypair.secretKey); + await accumulator.removeBatch(removals1, keypair.secretKey, accumState); + const accumulated1 = AccumulatorModule.accumulatedAsHex(accumulator.accumulated, AccumulatorType.VBPos); + const witUpdBytes1 = u8aToHex(witnessUpdInfo1.value); + await dock.accumulatorModule.updateAccumulator(accumulatorId, accumulated1, { removals: removals1.map((r) => u8aToHex(r)), witnessUpdateInfo: witUpdBytes1 }, did, pair, { didModule: dock.didModule }, false); + + await waitForBlocks(dock.api, 5); + + const removals2 = [members[87], members[88]]; + const witnessUpdInfo2 = VBWitnessUpdateInfo.new(accumulator.accumulated, [], removals2, keypair.secretKey); + await accumulator.removeBatch(removals2, keypair.secretKey, accumState); + const accumulated2 = AccumulatorModule.accumulatedAsHex(accumulator.accumulated, AccumulatorType.VBPos); + const witUpdBytes2 = u8aToHex(witnessUpdInfo2.value); + await dock.accumulatorModule.updateAccumulator(accumulatorId, accumulated2, { removals: removals2.map((r) => u8aToHex(r)), witnessUpdateInfo: witUpdBytes2 }, did, pair, { didModule: dock.didModule }, false); + + await waitForBlocks(dock.api, 5); + + const removals3 = [members[89], members[90], members[91]]; + const witnessUpdInfo3 = VBWitnessUpdateInfo.new(accumulator.accumulated, [], removals3, keypair.secretKey); + await accumulator.removeBatch(removals3, keypair.secretKey, accumState); + const accumulated3 = AccumulatorModule.accumulatedAsHex(accumulator.accumulated, AccumulatorType.VBPos); + const witUpdBytes3 = u8aToHex(witnessUpdInfo3.value); + await dock.accumulatorModule.updateAccumulator(accumulatorId, accumulated3, { removals: removals3.map((r) => u8aToHex(r)), witnessUpdateInfo: witUpdBytes3 }, did, pair, { didModule: dock.didModule }, false); + + // Get a witness from the accumulator manager. This will be updated after the following updates. + witness = await accumulator.membershipWitness(member, keypair.secretKey, accumState); + // The user should be told the block number from which he is supposed to update the witnesses from. It will be one block + // ahead from where the last update was posted. + const blockNoToUpdateFrom = (await getLastBlockNo(dock.api)) + 1; + + await waitForBlocks(dock.api, 5); + + // Do some more updates to the accumulator + const removals4 = [members[92], members[93]]; + const witnessUpdInfo4 = VBWitnessUpdateInfo.new(accumulator.accumulated, [], removals4, keypair.secretKey); + await accumulator.removeBatch(removals4, keypair.secretKey, accumState); + const accumulated4 = AccumulatorModule.accumulatedAsHex(accumulator.accumulated, AccumulatorType.VBPos); + const witUpdBytes4 = u8aToHex(witnessUpdInfo4.value); + await dock.accumulatorModule.updateAccumulator(accumulatorId, accumulated4, { removals: removals4.map((r) => u8aToHex(r)), witnessUpdateInfo: witUpdBytes4 }, did, pair, { didModule: dock.didModule }, false); + console.log(`Updated witness at block ${(await getLastBlockNo(dock.api))}`); + await waitForBlocks(dock.api, 5); + + const removals5 = [members[94], members[95], members[96]]; + const witnessUpdInfo5 = VBWitnessUpdateInfo.new(accumulator.accumulated, [], removals5, keypair.secretKey); + await accumulator.removeBatch(removals5, keypair.secretKey, accumState); + const accumulated5 = AccumulatorModule.accumulatedAsHex(accumulator.accumulated, AccumulatorType.VBPos); + const witUpdBytes5 = u8aToHex(witnessUpdInfo5.value); + await dock.accumulatorModule.updateAccumulator(accumulatorId, accumulated5, { removals: removals5.map((r) => u8aToHex(r)), witnessUpdateInfo: witUpdBytes5 }, did, pair, { didModule: dock.didModule }, false); + console.log(`Updated witness at block ${(await getLastBlockNo(dock.api))}`); + await waitForBlocks(dock.api, 5); + + queriedAccum = await dock.accumulatorModule.getAccumulator(accumulatorId, true); + verifAccumulator = PositiveAccumulator.fromAccumulated(AccumulatorModule.accumulatedFromHex(queriedAccum.accumulated, AccumulatorType.VBPos)); + // Old witness doesn't verify with new accumulator + expect(verifAccumulator.verifyMembershipWitness(member, witness, accumPk, accumParams)).toEqual(false); + + // Update witness by downloading necessary blocks and applying the updates if found + await dock.accumulatorModule.updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, witness, blockNoToUpdateFrom, queriedAccum.lastModified); + + // Updated witness verifies with new accumulator + expect(verifAccumulator.verifyMembershipWitness(member, witness, accumPk, accumParams)).toEqual(true); + }, 60000); + afterAll(async () => { await dock.disconnect(); }, 10000);