diff --git a/src/modules/accumulator.js b/src/modules/accumulator.js index 7332ae804..ea9fd26cb 100644 --- a/src/modules/accumulator.js +++ b/src/modules/accumulator.js @@ -2,7 +2,7 @@ import { hexToU8a, isHex, u8aToHex } from '@polkadot/util'; import { KBUniversalAccumulatorValue, VBWitnessUpdateInfo } from '@docknetwork/crypto-wasm-ts'; -import { getDidNonce, getStateChange } from '../utils/misc'; +import { getDidNonce, getStateChange, inclusiveRange } from '../utils/misc'; import WithParamsAndPublicKeys from './WithParamsAndPublicKeys'; import { getAllExtrinsicsFromBlock } from '../utils/chain-ops'; import { createDidSig, typedHexDID } from '../utils/did'; @@ -799,40 +799,66 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys { blockNoOrBlockHash, false, ); - const updates = []; - extrinsics.forEach((ext) => { - if ( - ext.method - && ext.method.section === 'accumulator' - && ext.method.method === 'updateAccumulator' - ) { - const update = this.api.createType( - 'UpdateAccumulator', - ext.method.args[0], - ); - if (u8aToHex(update.id) === accumulatorId) { - // The following commented line produces truncated hex strings. Don't know why - // const additions = update.additions.isSome ? update.additions.unwrap().map(u8aToHex) : null; - const additions = update.additions.isSome - ? update.additions.unwrap().map((i) => u8aToHex(i)) - : null; - const removals = update.removals.isSome - ? update.removals.unwrap().map((i) => u8aToHex(i)) - : null; - const witnessUpdateInfo = update.witnessUpdateInfo.isSome - ? u8aToHex(update.witnessUpdateInfo.unwrap()) - : null; - - updates.push({ - newAccumulated: u8aToHex(update.newAccumulated), - additions, - removals, - witnessUpdateInfo, - }); - } + return extrinsics.map((e) => this.getUpdatesFromExtrinsic(e, accumulatorId)).filter((u) => u !== undefined); + } + + /** + * Fetch blocks corresponding to the given block numbers or hashes and get all accumulator updates made in those blocks' extrinsics corresponding to accumulator id `accumulatorId` + * @param accumulatorId + * @param blockNosOrBlockHashes {number[]|string[]} + * @returns {Promise} - Resolves to an array of `update`s where each `update` is an object with keys + * `newAccumulated`, `additions`, `removals` and `witnessUpdateInfo`. The last keys have value null if they were + * not provided in the extrinsic. + */ + async getUpdatesFromBlocks(accumulatorId, blockNosOrBlockHashes) { + // NOTE: polkadot-js doesn't allow to fetch more than one block in 1 RPC call. + const extrinsics = await Promise.all(blockNosOrBlockHashes.map(async (b) => await getAllExtrinsicsFromBlock( + this.api, + b, + false, + ))); + return extrinsics.flat().map((e) => this.getUpdatesFromExtrinsic(e, accumulatorId)).filter((u) => u !== undefined); + } + + /** + * Get accumulator updates corresponding to accumulator id `accumulatorId` + * @param ext + * @param accumulatorId + * @returns {Promise} - Resolves to an `update` object with keys `newAccumulated`, `additions`, `removals` and `witnessUpdateInfo`. + * The last keys have value null if they were not provided in the extrinsic. + */ + getUpdatesFromExtrinsic(ext, accumulatorId) { + if ( + ext.method + && ext.method.section === 'accumulator' + && ext.method.method === 'updateAccumulator' + ) { + const update = this.api.createType( + 'UpdateAccumulator', + ext.method.args[0], + ); + if (u8aToHex(update.id) === accumulatorId) { + // The following commented line produces truncated hex strings. Don't know why + // const additions = update.additions.isSome ? update.additions.unwrap().map(u8aToHex) : null; + const additions = update.additions.isSome + ? update.additions.unwrap().map((i) => u8aToHex(i)) + : null; + const removals = update.removals.isSome + ? update.removals.unwrap().map((i) => u8aToHex(i)) + : null; + const witnessUpdateInfo = update.witnessUpdateInfo.isSome + ? u8aToHex(update.witnessUpdateInfo.unwrap()) + : null; + + return { + newAccumulated: u8aToHex(update.newAccumulated), + additions, + removals, + witnessUpdateInfo, + }; } - }); - return updates; + } + return undefined; } /** @@ -934,20 +960,24 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys { * @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. + * @param batchSize - the number of blocks to fetch in one go * @returns {Promise} */ // eslint-disable-next-line sonarjs/cognitive-complexity - async updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, witness, startBlock, endBlock = undefined) { + async updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, witness, startBlock, endBlock = undefined, batchSize = 10) { if (endBlock === undefined) { const accum = await this.accumulatorModule.getAccumulator(accumulatorId, false); // eslint-disable-next-line no-param-reassign endBlock = accum.lastModified; } + // If endBlock < startBlock, it won't throw an error but won't fetch any updates and witness won't be updated. console.debug(`Will start updating witness from block ${startBlock} to ${endBlock}`); let current = startBlock; while (current <= endBlock) { + const till = (current + batchSize) <= endBlock ? (current + batchSize) : endBlock; + // Get updates from blocks [current, current + 1, current + 2, ..., till] // eslint-disable-next-line no-await-in-loop - const updates = await this.getUpdatesFromBlock(accumulatorId, current); + const updates = await this.getUpdatesFromBlocks(accumulatorId, inclusiveRange(current, till, 1)); for (const update of updates) { const additions = []; const removals = []; @@ -966,7 +996,7 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys { witness.updateUsingPublicInfoPostBatchUpdate(member, additions, removals, queriedWitnessInfo); } - current++; + current = till + 1; } } diff --git a/src/utils/misc.js b/src/utils/misc.js index 27ae3166d..d4ea6949f 100644 --- a/src/utils/misc.js +++ b/src/utils/misc.js @@ -265,3 +265,12 @@ export async function getDidNonce( * @param value */ export const ensureMatchesPattern = (pattern, value) => new PatternMatcher().check(pattern, value); + +/** + * Get a list of numbers in the range [start, stop], i.e. both are inclusive. Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range + * @param start + * @param stop + * @param step + * @returns {number[]} + */ +export const inclusiveRange = (start, stop, step) => Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step); diff --git a/tests/integration/anoncreds/prefilled-positive-accumulator.test.js b/tests/integration/anoncreds/prefilled-positive-accumulator.test.js index 0545e6cef..5e99e7ee0 100644 --- a/tests/integration/anoncreds/prefilled-positive-accumulator.test.js +++ b/tests/integration/anoncreds/prefilled-positive-accumulator.test.js @@ -5,6 +5,7 @@ import { AccumulatorPublicKey, PositiveAccumulator, VBWitnessUpdateInfo, + VBMembershipWitness, } from '@docknetwork/crypto-wasm-ts'; import { randomAsHex } from '@polkadot/util-crypto'; import { InMemoryState } from '@docknetwork/crypto-wasm-ts/lib/accumulator/in-memory-persistence'; @@ -229,11 +230,26 @@ describe('Prefilled positive accumulator', () => { // Old witness doesn't verify with new accumulator expect(verifAccumulator.verifyMembershipWitness(member, witness, accumPk, accumParams)).toEqual(false); + const oldWitness1 = new VBMembershipWitness(witness.value); + const oldWitness2 = new VBMembershipWitness(witness.value); + const oldWitness3 = new VBMembershipWitness(witness.value); + // 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); + + // Test again with a batch size bigger than the total number of blocks + await dock.accumulatorModule.updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, oldWitness1, blockNoToUpdateFrom, queriedAccum.lastModified, queriedAccum.lastModified - blockNoToUpdateFrom + 10); + expect(verifAccumulator.verifyMembershipWitness(member, oldWitness1, accumPk, accumParams)).toEqual(true); + + // Test again with few other batch sizes + await dock.accumulatorModule.updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, oldWitness2, blockNoToUpdateFrom, queriedAccum.lastModified, 3); + expect(verifAccumulator.verifyMembershipWitness(member, oldWitness2, accumPk, accumParams)).toEqual(true); + + await dock.accumulatorModule.updateVbAccumulatorWitnessFromUpdatesInBlocks(accumulatorId, member, oldWitness3, blockNoToUpdateFrom, queriedAccum.lastModified, 4); + expect(verifAccumulator.verifyMembershipWitness(member, oldWitness3, accumPk, accumParams)).toEqual(true); }, 60000); afterAll(async () => {