Skip to content

Commit

Permalink
Add accumulator update helpers and add test
Browse files Browse the repository at this point in the history
Signed-off-by: lovesh <[email protected]>
  • Loading branch information
lovesh committed Jun 17, 2024
1 parent df35df3 commit 8cbce41
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 5 deletions.
45 changes: 44 additions & 1 deletion src/modules/accumulator.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<void>}
*/
// 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);
Expand Down
54 changes: 50 additions & 4 deletions src/utils/chain-ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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) {
Expand All @@ -89,14 +103,46 @@ 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();
}
return headerOrBlock.number.toNumber();
}

/**
* Get the last authored block's number
* @param api
* @returns {Promise<number>}
*/
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<void>}
*/
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
Expand Down
79 changes: 79 additions & 0 deletions tests/integration/anoncreds/prefilled-positive-accumulator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 8cbce41

Please sign in to comment.