Source: modules/accumulator.js

/* eslint-disable camelcase */

import { hexToU8a, isHex, u8aToHex } from '@polkadot/util';
import { KBUniversalAccumulatorValue } from '@docknetwork/crypto-wasm-ts';
import { getDidNonce, getStateChange } from '../utils/misc';
import WithParamsAndPublicKeys from './WithParamsAndPublicKeys';
import { getAllExtrinsicsFromBlock } from '../utils/chain-ops';
import { createDidSig, typedHexDID } from '../utils/did';

export const AccumulatorType = {
  VBPos: 0,
  VBUni: 1,
  KBUni: 2,
};

/** Class to manage accumulators on chain */
export default class AccumulatorModule extends WithParamsAndPublicKeys {
  constructor(api, signAndSend) {
    super();
    this.api = api;
    this.moduleName = 'accumulator';
    this.module = api.tx[this.moduleName];
    this.signAndSend = signAndSend;
  }

  static prepareAddPositiveAccumulator(api, id, accumulated, publicKeyRef) {
    const keyRef = AccumulatorModule.parseRef(api, publicKeyRef);
    return {
      id,
      accumulator: {
        Positive: {
          accumulated,
          keyRef,
        },
      },
    };
  }

  static prepareAddUniversalAccumulator(
    api,
    id,
    accumulated,
    publicKeyRef,
    maxSize,
  ) {
    const keyRef = AccumulatorModule.parseRef(api, publicKeyRef);
    return {
      id,
      accumulator: {
        Universal: {
          common: {
            accumulated,
            keyRef,
          },
          maxSize,
        },
      },
    };
  }

  static prepareAddKBUniversalAccumulator(api, id, accumulated, publicKeyRef) {
    const keyRef = AccumulatorModule.parseRef(api, publicKeyRef);
    return {
      id,
      accumulator: {
        KBUniversal: {
          accumulated,
          keyRef,
        },
      },
    };
  }

  static ensureArrayOfBytearrays(arr) {
    if (!(typeof arr === 'object' && arr instanceof Array)) {
      throw new Error(`Require an array but got ${arr}`);
    }
    for (let i = 0; i < arr.length; i++) {
      if (!isHex(arr[i])) {
        throw new Error(`Require a hex string but got ${arr[0]}`);
      }
    }
  }

  /**
   * Accepts an event and returns the accumulator id and accumulated value if the event was
   * `accumulator:AccumulatorUpdated`
   * @param event - The event. This is the `event` key in the `event` object, i.e. for the `event` object got in response
   * of `api.query.system.events`, the argument to this function is `event.event`.
   * @returns {null|string[]} - null if the event is not `accumulator:AccumulatorUpdated` else [accumulatorId, accumulated]
   */
  static parseEventAsAccumulatorUpdate(event) {
    if (
      event.section === 'accumulator'
      && event.method === 'AccumulatorUpdated'
    ) {
      return [u8aToHex(event.data[0]), u8aToHex(event.data[1])];
    }
    return null;
  }

  /**
   * Return the accumulated value as hex
   * @param accumulated {Uint8Array|KBUniversalAccumulatorValue}
   * @param typ {number} - Type of the accumulator
   * @returns {string}
   */
  static accumulatedAsHex(accumulated, typ = AccumulatorType.VBPos) {
    if (typ === AccumulatorType.VBPos || typ === AccumulatorType.VBUni) {
      return u8aToHex(accumulated);
    } else if (typ === AccumulatorType.KBUni) {
      // Create a single Uint8Array and convert it to hex. The 2 are guaranteed to be of the same length
      const merged = new Uint8Array(accumulated.mem.length + accumulated.nonMem.length);
      merged.set(accumulated.mem);
      merged.set(accumulated.nonMem, accumulated.mem.length);
      return u8aToHex(merged);
    } else {
      throw new Error(`Unknown accumulator type ${typ}`);
    }
  }

  /**
   * Parse the given accumulated value in hex form
   * @param accumulated {string}
   * @param typ {number} - Type of the accumulator
   * @returns {Uint8Array|KBUniversalAccumulatorValue}
   */
  static accumulatedFromHex(accumulated, typ = AccumulatorType.VBPos) {
    if (typ === AccumulatorType.VBPos || typ === AccumulatorType.VBUni) {
      return hexToU8a(accumulated);
    } else if (typ === AccumulatorType.KBUni) {
      // Create 2 Uint8Array from this hex. The 2 are guaranteed to be of the same length
      const merged = hexToU8a(accumulated);
      const mem = merged.subarray(0, merged.length / 2);
      const nonMem = merged.subarray(merged.length / 2);
      return new KBUniversalAccumulatorValue(mem, nonMem);
    } else {
      throw new Error(`Unknown accumulator type ${typ}`);
    }
  }

  /**
   * Create transaction to add accumulator public key
   * @param publicKey - Accumulator public key
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<*>}
   */
  async createAddPublicKeyTx(
    publicKey,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [addPk, signature] = await this.createSignedAddPublicKey(
      publicKey,
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.addPublicKey(addPk, signature);
  }

  /**
   * Create transaction to remove accumulator public key
   * @param removeKeyId - Index of the accumulator public key
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<*>}
   */
  async createRemovePublicKeyTx(
    removeKeyId,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [remPk, signature] = await this.createSignedRemovePublicKey(
      removeKeyId,
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.removePublicKey(remPk, signature);
  }

  /**
   * Create a transaction to add positive (add-only) accumulator
   * @param id - Unique accumulator id
   * @param accumulated - Current accumulated value.
   * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not
   * have any public key on the chain. This is useful for KVAC.
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<*>}
   */
  async createAddPositiveAccumulatorTx(
    id,
    accumulated,
    publicKeyRef,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [addAccumulator, signature] = await this.createSignedAddPositiveAccumulator(
      id,
      accumulated,
      publicKeyRef,
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.addAccumulator(addAccumulator, signature);
  }

  /**
   * Create a transaction to add universal (supports add/remove) accumulator
   * @param id - Unique accumulator id
   * @param accumulated - Current accumulated value.
   * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not
   * have any public key on the chain. This is useful for KVAC.
   * @param maxSize - Maximum size of the accumulator
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<*>}
   */
  async createAddUniversalAccumulatorTx(
    id,
    accumulated,
    publicKeyRef,
    maxSize,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [addAccumulator, signature] = await this.createSignedAddUniversalAccumulator(
      id,
      accumulated,
      publicKeyRef,
      maxSize,
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.addAccumulator(addAccumulator, signature);
  }

  /**
   * Create a transaction to add KB universal (supports add/remove) accumulator
   * @param id - Unique accumulator id
   * @param accumulated - Current accumulated value.
   * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not
   * have any public key on the chain. This is useful for KVAC.
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<*>}
   */
  async createAddKBUniversalAccumulatorTx(
    id,
    accumulated,
    publicKeyRef,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [addAccumulator, signature] = await this.createSignedAddKBUniversalAccumulator(
      id,
      accumulated,
      publicKeyRef,
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.addAccumulator(addAccumulator, signature);
  }

  /**
   * Create a transaction to update accumulator
   * @param id - Unique accumulator id
   * @param newAccumulated - Accumulated value after the update
   * @param additions
   * @param removals
   * @param witnessUpdateInfo
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<object>}
   */
  async updateAccumulatorTx(
    id,
    newAccumulated,
    {
      additions = undefined,
      removals = undefined,
      witnessUpdateInfo = undefined,
    },
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [update, signature] = await this.createSignedUpdateAccumulator(
      id,
      newAccumulated,
      { additions, removals, witnessUpdateInfo },
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.updateAccumulator(update, signature);
  }

  /**
   * Create transaction to remove accumulator
   * @param id - id to remove
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @returns {Promise<object>}
   */
  async removeAccumulatorTx(
    id,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    const signerHexDid = typedHexDID(this.api, signerDid);
    const [removal, signature] = await this.createSignedRemoveAccumulator(
      id,
      signerHexDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.module.removeAccumulator(removal, signature);
  }

  /**
   * Add accumulator public key
   * @param publicKey - Accumulator public key
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise<*>}
   */
  async addPublicKey(
    publicKey,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.createAddPublicKeyTx(
      publicKey,
      signerDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.signAndSend(tx, waitForFinalization, params);
  }

  /**
   * Remove a public key
   * @param removeKeyId - Index of the accumulator public key
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise<*>}
   */
  async removePublicKey(
    removeKeyId,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.createRemovePublicKeyTx(
      removeKeyId,
      signerDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.signAndSend(tx, waitForFinalization, params);
  }

  /**
   * Add a positive (add-only) accumulator
   * @param id - Unique accumulator id
   * @param accumulated - Current accumulated value.
   * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not
   * have any public key on the chain. This is useful for KVAC.
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise<*>}
   */
  async addPositiveAccumulator(
    id,
    accumulated,
    publicKeyRef,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.createAddPositiveAccumulatorTx(
      id,
      accumulated,
      publicKeyRef,
      signerDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.signAndSend(tx, waitForFinalization, params);
  }

  /**
   * Add universal (supports add/remove) accumulator
   * @param id - Unique accumulator id
   * @param accumulated - Current accumulated value.
   * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not
   * have any public key on the chain. This is useful for KVAC.
   * @param maxSize - Maximum size of the accumulator
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise<*>}
   */
  async addUniversalAccumulator(
    id,
    accumulated,
    publicKeyRef,
    maxSize,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.createAddUniversalAccumulatorTx(
      id,
      accumulated,
      publicKeyRef,
      maxSize,
      signerDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.signAndSend(tx, waitForFinalization, params);
  }

  /**
   * Add KB universal (supports add/remove) accumulator
   * @param id - Unique accumulator id
   * @param accumulated - Current accumulated value.
   * @param publicKeyRef - Reference to accumulator public key. If the reference contains the key id 0, it means the accumulator does not
   * have any public key on the chain. This is useful for KVAC.
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise<*>}
   */
  async addKBUniversalAccumulator(
    id,
    accumulated,
    publicKeyRef,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.createAddKBUniversalAccumulatorTx(
      id,
      accumulated,
      publicKeyRef,
      signerDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.signAndSend(tx, waitForFinalization, params);
  }

  /**
   * Update existing accumulator
   * @param id
   * @param newAccumulated - Accumulated value after the update
   * @param additions
   * @param removals
   * @param witnessUpdateInfo
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise< object>}
   */
  async updateAccumulator(
    id,
    newAccumulated,
    {
      additions = undefined,
      removals = undefined,
      witnessUpdateInfo = undefined,
    },
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.updateAccumulatorTx(
      id,
      newAccumulated,
      { additions, removals, witnessUpdateInfo },
      signerDid,
      signingKeyRef,
      { nonce, didModule },
    );
    return this.signAndSend(tx, waitForFinalization, params);
  }

  /**
   * Remove the accumulator from chain. This frees up the id for reuse.
   * @param id - id to remove
   * @param signerDid - Signer of the transaction payload
   * @param signingKeyRef - Signer's keypair reference
   * @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
   * @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
   * using this
   * @param waitForFinalization
   * @param params
   * @returns {Promise<*>}
   */
  async removeAccumulator(
    id,
    signerDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
    waitForFinalization = true,
    params = {},
  ) {
    const tx = await this.removeAccumulatorTx(id, signerDid, signingKeyRef, {
      nonce,
      didModule,
    });
    return this.signAndSend(tx, waitForFinalization, params);
  }

  async createSignedAddPublicKey(
    publicKey,
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    const addPk = { publicKey, nonce };
    const signature = this.signAddPublicKey(signingKeyRef, addPk);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [addPk, didSig];
  }

  async createSignedRemovePublicKey(
    removeKeyId,
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    const removeKey = { keyRef: [signerHexDid, removeKeyId], nonce };
    const signature = this.signRemovePublicKey(signingKeyRef, removeKey);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [removeKey, didSig];
  }

  async createSignedAddPositiveAccumulator(
    id,
    accumulated,
    publicKeyRef,
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    const accum = AccumulatorModule.prepareAddPositiveAccumulator(
      this.api,
      id,
      accumulated,
      publicKeyRef,
    );
    const addAccum = { ...accum, nonce };
    const signature = this.signAddAccumulator(signingKeyRef, addAccum);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [addAccum, didSig];
  }

  async createSignedAddUniversalAccumulator(
    id,
    accumulated,
    publicKeyRef,
    maxSize,
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    const accum = AccumulatorModule.prepareAddUniversalAccumulator(
      this.api,
      id,
      accumulated,
      publicKeyRef,
      maxSize,
    );
    const addAccum = { ...accum, nonce };
    const signature = this.signAddAccumulator(signingKeyRef, addAccum);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [addAccum, didSig];
  }

  async createSignedAddKBUniversalAccumulator(
    id,
    accumulated,
    publicKeyRef,
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    const accum = AccumulatorModule.prepareAddKBUniversalAccumulator(
      this.api,
      id,
      accumulated,
      publicKeyRef,
    );
    const addAccum = { ...accum, nonce };
    const signature = this.signAddAccumulator(signingKeyRef, addAccum);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [addAccum, didSig];
  }

  async createSignedUpdateAccumulator(
    id,
    newAccumulated,
    {
      additions = undefined,
      removals = undefined,
      witnessUpdateInfo = undefined,
    },
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    if (additions !== undefined) {
      AccumulatorModule.ensureArrayOfBytearrays(additions);
    }
    if (removals !== undefined) {
      AccumulatorModule.ensureArrayOfBytearrays(removals);
    }
    if (witnessUpdateInfo !== undefined && !isHex(witnessUpdateInfo)) {
      throw new Error(`Require a hex string but got ${witnessUpdateInfo}`);
    }
    const updateAccum = {
      id,
      new_accumulated: newAccumulated,
      additions,
      removals,
      witness_update_info: witnessUpdateInfo,
      nonce,
    };
    const signature = this.signUpdateAccumulator(signingKeyRef, updateAccum);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [updateAccum, didSig];
  }

  async createSignedRemoveAccumulator(
    id,
    signerHexDid,
    signingKeyRef,
    { nonce = undefined, didModule = undefined },
  ) {
    // eslint-disable-next-line no-param-reassign
    nonce = await getDidNonce(signerHexDid, nonce, didModule);
    const remAccum = { id, nonce };
    const signature = this.signRemoveAccumulator(signingKeyRef, remAccum);
    const didSig = createDidSig(signerHexDid, signingKeyRef, signature);
    return [remAccum, didSig];
  }

  /**
   * Get the accumulator as an object. The field `type` in object specifies whether it is "positive" or "universal".
   * Fields `created` and `lastModified` are block nos where the accumulator was created and last updated respectively.
   * Field `nonce` is the last accepted nonce by the chain, the next write to the accumulator should increment the nonce by 1.
   * Field `accumulated` contains the current accumulated value.
   * @param id
   * @param withKeyAndParams - Fetch both keys and params.
   * @param withKeyOnly - Fetch key only. This is useful when default params are used.
   * @returns {Promise<{created: *, lastModified: *}|null>}
   */
  async getAccumulator(id, withKeyAndParams = false, withKeyOnly = false) {
    const resp = await this.api.query[this.moduleName].accumulators(id);
    if (resp.isSome) {
      const accumInfo = resp.unwrap();
      const accumulatorObj = {
        created: accumInfo.createdAt.toNumber(),
        lastModified: accumInfo.lastUpdatedAt.toNumber(),
      };
      let common;
      if (accumInfo.accumulator.isPositive) {
        accumulatorObj.type = 'positive';
        common = accumInfo.accumulator.asPositive;
      } else if (accumInfo.accumulator.isUniversal) {
        accumulatorObj.type = 'universal';
        common = accumInfo.accumulator.asUniversal.common;
        accumulatorObj.maxSize = accumInfo.accumulator.asUniversal.maxSize.toNumber();
      } else {
        accumulatorObj.type = 'kb-universal';
        common = accumInfo.accumulator.asKbUniversal;
      }
      accumulatorObj.accumulated = u8aToHex(common.accumulated);
      const owner = common.keyRef[0];
      const keyId = common.keyRef[1].toNumber();
      accumulatorObj.keyRef = [typedHexDID(this.api, owner), keyId];

      if (withKeyAndParams || withKeyOnly) {
        if (keyId === 0) {
          throw new Error('Key id is 0 which means no public key exists for the accumulator on chain');
        }
        const pk = await this.getPublicKeyByHexDid(
          owner,
          keyId,
          withKeyAndParams,
        );
        if (pk !== null) {
          accumulatorObj.publicKey = pk;
        }
      }
      return accumulatorObj;
    }
    return null;
  }

  /**
   * Fetch a block and get all accumulator updates made in that block's extrinsics corresponding to accumulator id `accumulatorId`
   * @param accumulatorId
   * @param blockNoOrBlockHash
   * @returns {Promise<object[]>} - 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 getUpdatesFromBlock(accumulatorId, blockNoOrBlockHash) {
    const extrinsics = await getAllExtrinsicsFromBlock(
      this.api,
      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 updates;
  }

  /**
   * Get last params written by this DID
   * @param did
   * @returns {Promise<{bytes: string}|null>}
   */
  async getLastParamsWritten(did) {
    const hexId = typedHexDID(this.api, did);

    const counters = await this.api.query[this.moduleName].accumulatorOwnerCounters(hexId);
    const counter = counters.paramsCounter.toNumber();
    if (counter > 0) {
      const resp = await this.queryParamsFromChain(hexId, counter);
      if (resp) {
        return this.createParamsObjFromChainResponse(resp);
      }
    }
    return null;
  }

  /**
   * Get all params written by a DID
   * @param did
   * @returns {Promise<object[]>}
   */
  async getAllParamsByDid(did) {
    const hexId = typedHexDID(this.api, did);

    const params = [];
    const counters = await this.api.query[this.moduleName].accumulatorOwnerCounters(hexId);
    const counter = counters.paramsCounter.toNumber();
    if (counter > 0) {
      for (let i = 1; i <= counter; i++) {
        // eslint-disable-next-line no-await-in-loop
        const param = await this.getParamsByHexDid(hexId, i);
        if (param !== null) {
          params.push(param);
        }
      }
    }
    return params;
  }

  /**
   * Get all public keys written by a DID
   * @param did
   * @param withParams
   * @returns {Promise< object[]>}
   */
  async getAllPublicKeysByDid(did, withParams = false) {
    const hexId = typedHexDID(this.api, did);

    const pks = [];
    const counters = await this.api.query[this.moduleName].accumulatorOwnerCounters(hexId);
    const counter = counters.keyCounter.toNumber();
    if (counter > 0) {
      for (let i = 1; i <= counter; i++) {
        // eslint-disable-next-line no-await-in-loop
        const pk = await this.getPublicKeyByHexDid(hexId, i, withParams);
        if (pk !== null) {
          pks.push(pk);
        }
      }
    }
    return pks;
  }

  async queryParamsFromChain(hexDid, counter) {
    const params = await this.api.query[this.moduleName].accumulatorParams(
      hexDid,
      counter,
    );

    if (params.isSome) {
      return params.unwrap();
    } else {
      return null;
    }
  }

  async queryPublicKeyFromChain(hexDid, counter) {
    const key = await this.api.query[this.moduleName].accumulatorKeys(
      hexDid,
      counter,
    );

    if (key.isSome) {
      return key.unwrap();
    } else {
      return null;
    }
  }

  signAddParams(signingKeyRef, params) {
    const serialized = getStateChange(this.api, 'AddAccumulatorParams', params);
    return signingKeyRef.sign(serialized);
  }

  signAddPublicKey(signingKeyRef, pk) {
    const serialized = getStateChange(this.api, 'AddAccumulatorPublicKey', pk);
    return signingKeyRef.sign(serialized);
  }

  signRemoveParams(signingKeyRef, ref) {
    const serialized = getStateChange(this.api, 'RemoveAccumulatorParams', ref);
    return signingKeyRef.sign(serialized);
  }

  signRemovePublicKey(signingKeyRef, ref) {
    const serialized = getStateChange(
      this.api,
      'RemoveAccumulatorPublicKey',
      ref,
    );
    return signingKeyRef.sign(serialized);
  }

  signAddAccumulator(signingKeyRef, addAccumulator) {
    const serialized = getStateChange(
      this.api,
      'AddAccumulator',
      addAccumulator,
    );
    return signingKeyRef.sign(serialized);
  }

  signUpdateAccumulator(signingKeyRef, update) {
    const serialized = getStateChange(this.api, 'UpdateAccumulator', update);
    return signingKeyRef.sign(serialized);
  }

  signRemoveAccumulator(signingKeyRef, removal) {
    const serialized = getStateChange(this.api, 'RemoveAccumulator', removal);
    return signingKeyRef.sign(serialized);
  }
}