From 28a92f603467bfb3615d960065030a8b110af6ee Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 28 Jul 2021 08:52:01 +0200 Subject: [PATCH 01/21] chore: update Delegation interfaces to match latest version --- packages/types/src/Delegation.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/types/src/Delegation.ts b/packages/types/src/Delegation.ts index a3489cfb3..ceab81a31 100644 --- a/packages/types/src/Delegation.ts +++ b/packages/types/src/Delegation.ts @@ -18,18 +18,16 @@ export enum Permission { DELEGATE = 1 << 1, // 0010 } -export interface IDelegationBaseNode { +export interface IDelegationNode { id: string + rootId: IDelegationNode['id'] + parentId?: IDelegationNode['id'] + childrenIds: Array account: IPublicIdentity['address'] + permissions: Permission[] revoked: boolean } -export interface IDelegationRootNode extends IDelegationBaseNode { +export interface IDelegationHierarchyDetails { cTypeHash: ICType['hash'] } - -export interface IDelegationNode extends IDelegationBaseNode { - rootId: IDelegationBaseNode['id'] - parentId?: IDelegationBaseNode['id'] - permissions: Permission[] -} From 6630e8c2ca030b8b0c76376e44a9107d27574980 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 28 Jul 2021 14:22:45 +0200 Subject: [PATCH 02/21] feat: replace DelegationRoot with DelegationHierarchyDetails and DelegationBaseNode with DelegationNode --- .../core/src/delegation/Delegation.chain.ts | 76 ------ .../core/src/delegation/Delegation.spec.ts | 199 ---------------- packages/core/src/delegation/Delegation.ts | 160 ------------- .../core/src/delegation/Delegation.utils.ts | 31 --- .../core/src/delegation/DelegationDecoder.ts | 91 +++---- .../DelegationHierarchyDetails.chain.ts | 59 +++++ ....ts => DelegationHierarchyDetails.spec.ts} | 22 +- .../delegation/DelegationHierarchyDetails.ts | 62 +++++ .../src/delegation/DelegationNode.chain.ts | 115 +++++---- .../src/delegation/DelegationNode.spec.ts | 130 +++++----- .../core/src/delegation/DelegationNode.ts | 223 +++++++++++++----- .../src/delegation/DelegationNode.utils.ts | 2 +- .../delegation/DelegationRootNode.chain.ts | 85 ------- .../core/src/delegation/DelegationRootNode.ts | 102 -------- .../delegation/DelegationRootNode.utils.ts | 19 -- packages/types/src/Delegation.ts | 3 +- packages/utils/src/SDKErrors.ts | 6 +- 17 files changed, 496 insertions(+), 889 deletions(-) delete mode 100644 packages/core/src/delegation/Delegation.chain.ts delete mode 100644 packages/core/src/delegation/Delegation.spec.ts delete mode 100644 packages/core/src/delegation/Delegation.ts delete mode 100644 packages/core/src/delegation/Delegation.utils.ts create mode 100644 packages/core/src/delegation/DelegationHierarchyDetails.chain.ts rename packages/core/src/delegation/{DelegationRootNode.spec.ts => DelegationHierarchyDetails.spec.ts} (88%) create mode 100644 packages/core/src/delegation/DelegationHierarchyDetails.ts delete mode 100644 packages/core/src/delegation/DelegationRootNode.chain.ts delete mode 100644 packages/core/src/delegation/DelegationRootNode.ts delete mode 100644 packages/core/src/delegation/DelegationRootNode.utils.ts diff --git a/packages/core/src/delegation/Delegation.chain.ts b/packages/core/src/delegation/Delegation.chain.ts deleted file mode 100644 index 2fc3a054a..000000000 --- a/packages/core/src/delegation/Delegation.chain.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * @packageDocumentation - * @module DelegationBaseNode - */ - -import type { Option, Vec } from '@polkadot/types' -import type { Hash } from '@polkadot/types/interfaces' -import type { IDelegationBaseNode } from '@kiltprotocol/types' -import { DecoderUtils } from '@kiltprotocol/utils' -import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' -import type { CodecWithId, IChainDelegationNode } from './DelegationDecoder' - -function decodeDelegatedAttestations(queryResult: Option>): string[] { - DecoderUtils.assertCodecIsType(queryResult, ['Option>']) - return queryResult.unwrapOrDefault().map((hash) => hash.toHex()) -} - -/** - * @param id - * @internal - */ -export async function getAttestationHashes( - id: IDelegationBaseNode['id'] -): Promise { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const encodedHashes = await blockchain.api.query.attestation.delegatedAttestations< - Option> - >(id) - return decodeDelegatedAttestations(encodedHashes) -} - -/** - * @param id - * @internal - */ -export async function getChildIds( - id: IDelegationBaseNode['id'] -): Promise { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const childIds = await blockchain.api.query.delegation.children< - Option> - >(id) - DecoderUtils.assertCodecIsType(childIds, ['Option>']) - return childIds.unwrapOrDefault().map((hash) => hash.toHex()) -} - -/** - * @param childIds - * @internal - */ -export async function fetchChildren( - childIds: string[] -): Promise>>> { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const val: Array - >> = await Promise.all( - childIds.map(async (childId: string) => { - const queryResult = await blockchain.api.query.delegation.delegations< - Option - >(childId) - return { - id: childId, - codec: queryResult, - } - }) - ) - return val -} diff --git a/packages/core/src/delegation/Delegation.spec.ts b/packages/core/src/delegation/Delegation.spec.ts deleted file mode 100644 index d980ab9d2..000000000 --- a/packages/core/src/delegation/Delegation.spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * @group unit/delegation - */ - -/* eslint-disable @typescript-eslint/ban-ts-comment */ - -import { Permission } from '@kiltprotocol/types' -import type { ICType, IDelegationBaseNode } from '@kiltprotocol/types' -import { Crypto, SDKErrors } from '@kiltprotocol/utils' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' -import { Identity } from '..' -import DelegationNode from './DelegationNode' -import Kilt from '../kilt/Kilt' -import errorCheck from './Delegation.utils' - -jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' -) - -const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') - .__mocked_api - -Kilt.config({ address: 'ws://testString' }) - -describe('Delegation', () => { - let identityAlice: Identity - let rootId: IDelegationBaseNode['id'] - let nodeId: IDelegationBaseNode['id'] - let cTypeHash: ICType['hash'] - let myDelegation: DelegationNode - let children: DelegationNode[] - let attestationHashes: string[] - beforeAll(async () => { - identityAlice = Identity.buildFromURI('//Alice') - rootId = Crypto.hashStr('rootId') - nodeId = Crypto.hashStr('myNodeId') - cTypeHash = - 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' - - blockchainApi.query.attestation.delegatedAttestations.mockReturnValue( - mockChainQueryReturn('attestation', 'delegatedAttestations', [ - cTypeHash, - Crypto.hashStr('secondTest'), - Crypto.hashStr('thirdTest'), - ]) - ) - blockchainApi.query.delegation.root.mockReturnValue( - mockChainQueryReturn('delegation', 'root', [ - cTypeHash, - identityAlice.address, - false, - ]) - ) - - blockchainApi.query.delegation.delegations - // first call - .mockResolvedValueOnce( - mockChainQueryReturn('delegation', 'delegations', [ - rootId, - nodeId, - identityAlice.getPublicIdentity().address, - 2, - false, - ]) - ) - // second call - .mockResolvedValueOnce( - mockChainQueryReturn('delegation', 'delegations', [ - rootId, - nodeId, - identityAlice.getPublicIdentity().address, - 1, - false, - ]) - ) - // third call - .mockResolvedValueOnce( - mockChainQueryReturn('delegation', 'delegations', [ - rootId, - nodeId, - identityAlice.getPublicIdentity().address, - 1, - false, - ]) - ) - // default (any further calls) - .mockResolvedValue( - // Delegation: delegation-id -> (root-id, parent-id?, account, permissions, revoked) - mockChainQueryReturn('delegation', 'delegations') - ) - - blockchainApi.query.delegation.children.mockResolvedValue( - mockChainQueryReturn('delegation', 'children', [ - Crypto.hashStr('firstChild'), - Crypto.hashStr('secondChild'), - Crypto.hashStr('thirdChild'), - ]) - ) - }) - - it('get children', async () => { - myDelegation = new DelegationNode({ - id: nodeId, - rootId, - account: identityAlice.getPublicIdentity().address, - permissions: [Permission.ATTEST], - parentId: undefined, - revoked: false, - }) - children = await myDelegation.getChildren() - expect(children).toHaveLength(3) - expect(children[0]).toEqual({ - id: Crypto.hashStr('firstChild'), - rootId, - parentId: nodeId, - account: identityAlice.getPublicIdentity().address, - permissions: [Permission.DELEGATE], - revoked: false, - }) - expect(children[1]).toEqual({ - id: Crypto.hashStr('secondChild'), - rootId, - parentId: nodeId, - account: identityAlice.getPublicIdentity().address, - permissions: [Permission.ATTEST], - revoked: false, - }) - expect(children[2]).toEqual({ - id: Crypto.hashStr('thirdChild'), - rootId, - parentId: nodeId, - account: identityAlice.getPublicIdentity().address, - permissions: [Permission.ATTEST], - revoked: false, - }) - }) - it('get attestation hashes', async () => { - attestationHashes = await myDelegation.getAttestationHashes() - expect(attestationHashes).toHaveLength(3) - }) - - it('error check should throw errors on faulty delegation', async () => { - const malformedIdDelegation = { - id: nodeId.slice(13) + nodeId.slice(15), - account: identityAlice.address, - revoked: false, - } as IDelegationBaseNode - - const missingIdDelegation = { - id: nodeId, - account: identityAlice.address, - revoked: false, - } as IDelegationBaseNode - - // @ts-expect-error - delete missingIdDelegation.id - - const missingAccountDelegation = { - id: nodeId, - account: identityAlice.address, - revoked: false, - } as IDelegationBaseNode - - // @ts-expect-error - delete missingAccountDelegation.account - - const missingRevokedStatusDelegation = { - id: nodeId, - account: identityAlice.address, - revoked: false, - } as IDelegationBaseNode - - // @ts-expect-error - delete missingRevokedStatusDelegation.revoked - - expect(() => errorCheck(malformedIdDelegation)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_TYPE() - ) - - expect(() => errorCheck(missingIdDelegation)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_MISSING() - ) - - expect(() => errorCheck(missingAccountDelegation)).toThrowError( - SDKErrors.ERROR_OWNER_NOT_PROVIDED() - ) - - expect(() => errorCheck(missingRevokedStatusDelegation)).toThrowError( - new TypeError('revoked is expected to be a boolean') - ) - }) -}) diff --git a/packages/core/src/delegation/Delegation.ts b/packages/core/src/delegation/Delegation.ts deleted file mode 100644 index 4618d7238..000000000 --- a/packages/core/src/delegation/Delegation.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * Delegations are the building blocks of top-down trust structures in KILT. An Attester can inherit trust through delegation from another attester ("top-down"). - * In order to model these trust hierarchies, a delegation is represented as a **node** in a **delegation tree**. - * - * A delegation object is stored on-chain, and can be revoked. A base node is created, a ID which may be used in a [[RequestForAttestation]]. - * A delegation can and may restrict permissions. - * - * Permissions: - * * Delegate. - * * Attest. - * - * @packageDocumentation - * @module DelegationBaseNode - * @preferred - */ - -import type { - IDelegationBaseNode, - SubmittableExtrinsic, -} from '@kiltprotocol/types' -import Attestation from '../attestation/Attestation' -import { query } from '../attestation/Attestation.chain' -import Identity from '../identity/Identity' -import { getAttestationHashes } from './Delegation.chain' -import DelegationNode from './DelegationNode' -import DelegationRootNode from './DelegationRootNode' -import errorCheck from './Delegation.utils' - -export default abstract class DelegationBaseNode - implements IDelegationBaseNode { - public id: IDelegationBaseNode['id'] - public account: IDelegationBaseNode['account'] - public revoked: IDelegationBaseNode['revoked'] = false - - /** - * Builds a new [DelegationBaseNode] instance. - * - * @param delegationBaseNodeInput - The base object from which to create the delegation base node. - */ - public constructor(delegationBaseNodeInput: IDelegationBaseNode) { - this.account = delegationBaseNodeInput.account - this.id = delegationBaseNodeInput.id - this.revoked = delegationBaseNodeInput.revoked - errorCheck(this) - } - - /** - * Fetches the root of the delegation tree. - * - * @returns Promise containing [[DelegationRootNode]]. - */ - public abstract getRoot(): Promise - - /** - * Fetches the parent delegation node. If the parent node is [null] this node is a direct child of the root node. - * - * @returns Promise containing the parent node or [null]. - */ - public abstract getParent(): Promise - - /** - * Fetches the children nodes of the current node. - * - * @returns Promise containing the resolved children nodes. - */ - public abstract getChildren(): Promise - - /** - * Fetches and resolves all attestations attested with this delegation node. - * - * @returns Promise containing all resolved attestations attested with this node. - */ - public async getAttestations(): Promise { - const attestationHashes = await this.getAttestationHashes() - const attestations = await Promise.all( - attestationHashes.map((claimHash: string) => { - return query(claimHash) - }) - ) - - return attestations.filter((value): value is Attestation => !!value) - } - - /** - * Fetches all hashes of attestations attested with this delegation node. - * - * @returns Promise containing all attestation hashes attested with this node. - */ - public async getAttestationHashes(): Promise { - return getAttestationHashes(this.id) - } - - /** - * Verifies this delegation node by querying it from chain and checking its [revoked] status. - * - * @returns Promise containing a boolean flag indicating if the verification succeeded. - */ - public abstract verify(): Promise - - /** - * Revokes this delegation node on chain. - * - * @param address The address of the identity used to revoke the delegation. - * @returns Promise containing a unsigned submittable transaction. - */ - public abstract revoke(address: string): Promise - - /** - * Checks on chain whether a identity with the given address is delegating to the current node. - * - * @param address The address of the identity. - * @returns An object containing a `node` owned by the identity if it is delegating, plus the number of `steps` traversed. `steps` is 0 if the address is owner of the current node. - */ - public async findAncestorOwnedBy( - address: Identity['address'] - ): Promise<{ steps: number; node: DelegationBaseNode | null }> { - if (this.account === address) { - return { - steps: 0, - node: this, - } - } - const parent = await this.getParent() - if (parent) { - const result = await parent.findAncestorOwnedBy(address) - result.steps += 1 - return result - } - return { - steps: 0, - node: null, - } - } - - /** - * Recursively counts all nodes in the branches below the current node (excluding the current node). - * - * @returns Promise resolving to the node count. - */ - public async subtreeNodeCount(): Promise { - const children = await this.getChildren() - if (children.length > 0) { - const childrensChildCounts = await Promise.all( - children.map((child) => child.subtreeNodeCount()) - ) - return ( - children.length + - childrensChildCounts.reduce((previous, current) => previous + current) - ) - } - return 0 - } -} diff --git a/packages/core/src/delegation/Delegation.utils.ts b/packages/core/src/delegation/Delegation.utils.ts deleted file mode 100644 index 3c5c8cef9..000000000 --- a/packages/core/src/delegation/Delegation.utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -import type { IDelegationBaseNode } from '@kiltprotocol/types' -import { SDKErrors, DataUtils } from '@kiltprotocol/utils' -import { isHex } from '@polkadot/util' - -export default function errorCheck( - delegationBaseNode: IDelegationBaseNode -): void { - const { id, account, revoked } = delegationBaseNode - if (!id) { - throw SDKErrors.ERROR_DELEGATION_ID_MISSING() - } else if (typeof id !== 'string') { - throw SDKErrors.ERROR_DELEGATION_ID_TYPE() - } else if (!isHex(id)) { - throw SDKErrors.ERROR_DELEGATION_ID_TYPE() - } - - if (!account) { - throw SDKErrors.ERROR_OWNER_NOT_PROVIDED() - } else DataUtils.validateAddress(account, 'delegationNode owner') - - if (typeof revoked !== 'boolean') { - throw new TypeError('revoked is expected to be a boolean') - } -} diff --git a/packages/core/src/delegation/DelegationDecoder.ts b/packages/core/src/delegation/DelegationDecoder.ts index 1c41bfe2c..af321c956 100644 --- a/packages/core/src/delegation/DelegationDecoder.ts +++ b/packages/core/src/delegation/DelegationDecoder.ts @@ -6,7 +6,7 @@ */ /** - * When [[DelegationNode]]s or [[DelegationRootNode]]s are written on the blockchain, they're encoded. + * When a [[DelegationNode]] or a [[DelegationHierarchy]] is written on the blockchain, it is encoded. * DelegationDecoder helps to decode them when they're queried from the chain. * * The DelegationDecoder methods transform a Codec type into an object of a KILT type. @@ -18,45 +18,43 @@ /** * Dummy comment needed for correct doc display, do not remove. */ -import { Permission } from '@kiltprotocol/types' +import { IDelegationHierarchyDetails, Permission } from '@kiltprotocol/types' import type { Option } from '@polkadot/types' -import type { IDelegationRootNode } from '@kiltprotocol/types' import type { Struct } from '@polkadot/types/codec' import type { AccountId, Hash } from '@polkadot/types/interfaces/runtime' import type { u32 } from '@polkadot/types/primitive' import { DecoderUtils } from '@kiltprotocol/utils' -import { DelegationNode } from '..' +import DelegationNode from './DelegationNode' export type CodecWithId = { id: string codec: C } -export type RootDelegationRecord = Pick< - IDelegationRootNode, - 'cTypeHash' | 'account' | 'revoked' +export type DelegationHierarchyDetailsRecord = Pick< + IDelegationHierarchyDetails, + 'cTypeHash' > -export interface IChainDelegationRoot extends Struct { - readonly ctypeHash: Hash - readonly owner: AccountId - readonly revoked: boolean +export type CtypeHash = Hash + +export interface IChainDelegationHierarchyDetails extends Struct { + readonly ctypeHash: CtypeHash } -export function decodeRootDelegation( - encoded: Option -): RootDelegationRecord | null { - DecoderUtils.assertCodecIsType(encoded, ['Option']) - if (encoded.isSome) { - const delegationRoot = encoded.unwrap() - // TODO: check that root is none - return { - cTypeHash: delegationRoot.ctypeHash.toString(), - account: delegationRoot.owner.toString(), - revoked: delegationRoot.revoked.valueOf(), - } +export function decodeDelegationHierarchyDetails( + encoded: Option +): DelegationHierarchyDetailsRecord | null { + DecoderUtils.assertCodecIsType(encoded, [ + 'Option', + ]) + if (encoded.isNone) { + return null + } + const delegationHierarchyDetails = encoded.unwrap() + return { + cTypeHash: delegationHierarchyDetails.ctypeHash.toHex(), } - return null } /** @@ -81,35 +79,48 @@ function decodePermissions(bitset: number): Permission[] { export type DelegationNodeRecord = Pick< DelegationNode, - 'rootId' | 'parentId' | 'account' | 'permissions' | 'revoked' + | 'hierarchyId' + | 'parentId' + | 'childrenIds' + | 'account' + | 'permissions' + | 'revoked' > export type DelegationNodeId = Hash export interface IChainDelegationNode extends Struct { - readonly rootId: DelegationNodeId + readonly hierarchyRootId: DelegationNodeId readonly parent: Option - readonly owner: AccountId - readonly permissions: u32 + readonly children: DelegationNodeId[] + readonly details: IChainDelegationDetails +} + +export type DelegationOwner = AccountId + +export interface IChainDelegationDetails extends Struct { + readonly owner: DelegationOwner readonly revoked: boolean + readonly permissions: u32 } export function decodeDelegationNode( encoded: Option ): DelegationNodeRecord | null { DecoderUtils.assertCodecIsType(encoded, ['Option']) - if (encoded.isSome) { - const delegationNode = encoded.unwrap() + if (encoded.isNone) { + return null + } + const delegationNode = encoded.unwrap() - return { - rootId: delegationNode.rootId.toString(), - parentId: delegationNode.parent.isSome - ? delegationNode.parent.toString() - : undefined, - account: delegationNode.owner.toString(), - permissions: decodePermissions(delegationNode.permissions.toNumber()), - revoked: delegationNode.revoked.valueOf(), - } + return { + hierarchyId: delegationNode.hierarchyRootId.toHex(), + parentId: delegationNode.parent.toHex(), + childrenIds: delegationNode.children.map((id) => id.toHex()), + account: delegationNode.details.owner.toString(), + permissions: decodePermissions( + delegationNode.details.permissions.toNumber() + ), + revoked: delegationNode.details.revoked.valueOf(), } - return null } diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts new file mode 100644 index 000000000..f7f0c3e40 --- /dev/null +++ b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2018-2021 BOTLabs GmbH. + * + * This source code is licensed under the BSD 4-Clause "Original" license + * found in the LICENSE file in the root directory of this source tree. + */ + +import { + IDelegationHierarchyDetails, + IDelegationNode, + SubmittableExtrinsic, +} from '@kiltprotocol/sdk-js' +import { Option } from '@polkadot/types' +import { BlockchainApiConnection } from 'chain-helpers/src/blockchainApiConnection' +import { + decodeDelegationHierarchyDetails, + DelegationHierarchyDetailsRecord, + IChainDelegationHierarchyDetails, +} from './DelegationDecoder' +import DelegationHierarchyDetails from './DelegationHierarchyDetails' + +/** + * @packageDocumentation + * @module DelegationHierarchyDetails + */ + +/** + * @param delegationHierarchyDetails The details associated with the delegation hierarchy being created. + * @internal + */ +export async function store( + delegationHierarchyDetails: IDelegationHierarchyDetails +): Promise { + const blockchain = await BlockchainApiConnection.getConnectionOrConnect() + const tx: SubmittableExtrinsic = blockchain.api.tx.delegation.createHierarchy( + delegationHierarchyDetails.rootId, + delegationHierarchyDetails.cTypeHash + ) + return tx +} + +export async function query( + rootId: IDelegationNode['id'] +): Promise { + const blockchain = await BlockchainApiConnection.getConnectionOrConnect() + const decoded: DelegationHierarchyDetailsRecord | null = decodeDelegationHierarchyDetails( + await blockchain.api.query.delegation.delegationHierarchies< + Option + >(rootId) + ) + if (!decoded) { + return null + } + const details = new DelegationHierarchyDetails({ + rootId, + cTypeHash: decoded.cTypeHash, + }) + return details +} diff --git a/packages/core/src/delegation/DelegationRootNode.spec.ts b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts similarity index 88% rename from packages/core/src/delegation/DelegationRootNode.spec.ts rename to packages/core/src/delegation/DelegationHierarchyDetails.spec.ts index 0806e0626..e57d4a3c3 100644 --- a/packages/core/src/delegation/DelegationRootNode.spec.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts @@ -14,10 +14,10 @@ import { BlockchainUtils, BlockchainApiConnection, } from '@kiltprotocol/chain-helpers' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' -import { Identity } from '..' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { Identity } from '../identity' -import DelegationRootNode from './DelegationRootNode' +import DelegationHierarchyDetails from './DelegationHierarchyDetails' import Kilt from '../kilt/Kilt' jest.mock( @@ -37,20 +37,14 @@ describe('Delegation', () => { ROOT_IDENTIFIER = Crypto.hashStr('1') ROOT_SUCCESS = Crypto.hashStr('success') - require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.root.mockReturnValue( - mockChainQueryReturn('delegation', 'root', [ - ctypeHash, - identityAlice.address, - false, - ]) + require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.hierarchies.mockReturnValue( + mockChainQueryReturn('delegation', 'hierarchies', [ctypeHash]) ) require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.delegations.mockReturnValue( mockChainQueryReturn('delegation', 'delegations', [ - ctypeHash, + ROOT_IDENTIFIER, null, - identityAlice.address, - 1, - false, + [], ]) ) }) @@ -145,4 +139,4 @@ describe('Delegation', () => { ) expect(revokeStatus).toBeDefined() }) -}) +}) \ No newline at end of file diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.ts b/packages/core/src/delegation/DelegationHierarchyDetails.ts new file mode 100644 index 000000000..56a604d07 --- /dev/null +++ b/packages/core/src/delegation/DelegationHierarchyDetails.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2018-2021 BOTLabs GmbH. + * + * This source code is licensed under the BSD 4-Clause "Original" license + * found in the LICENSE file in the root directory of this source tree. + */ + +/** + * KILT enables top-down trust structures. + * On the lowest level, a delegation structure is always a **tree**. + * Each tree represents a delegation **hierarchy**, to each of which there are details associated. + * + * @packageDocumentation + * @module DelegationHierarchyDetails + * @preferred + */ + +import { ConfigService } from '@kiltprotocol/config' +import type { + ICType, + IDelegationHierarchyDetails, + IDelegationNode, + SubmittableExtrinsic, +} from '@kiltprotocol/types' +import { query, store } from './DelegationHierarchyDetails.chain' + +const log = ConfigService.LoggingFactory.getLogger('DelegationRootNode') + +export default class DelegationHierarchyDetails + implements IDelegationHierarchyDetails { + public rootId: IDelegationNode['id'] + public cTypeHash: ICType['hash'] + + /** + * Builds a new [DelegationHierarchy] instance. + * + * @param delegationHierarchyInput - The base object from which to create the delegation base node. + */ + public constructor(delegationHierarchyInput: IDelegationHierarchyDetails) { + this.rootId = delegationHierarchyInput.rootId + this.cTypeHash = delegationHierarchyInput.cTypeHash + } + + public static async query( + rootId: IDelegationNode['id'] + ): Promise { + log.info(`:: query('${rootId}')`) + const result = await query(rootId) + if (result) { + log.info(`result: ${JSON.stringify(result)}`) + } else { + log.info(`Delegation hierarchy not found`) + } + + return result + } + + public async store(): Promise { + log.debug(`:: store(${this.rootId})`) + return store(this) + } +} diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 3dd3e7970..d50b4746b 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -10,12 +10,12 @@ * @module DelegationNode */ -import type { Option } from '@polkadot/types' +import type { Option, Vec } from '@polkadot/types' import type { IDelegationNode, SubmittableExtrinsic } from '@kiltprotocol/types' import { ConfigService } from '@kiltprotocol/config' import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' -import DelegationBaseNode from './Delegation' -import { fetchChildren, getChildIds } from './Delegation.chain' +import { Hash } from '@polkadot/types/interfaces' +import { DecoderUtils } from '@kiltprotocol/utils' import { CodecWithId, decodeDelegationNode, @@ -27,8 +27,8 @@ import { permissionsAsBitset } from './DelegationNode.utils' const log = ConfigService.LoggingFactory.getLogger('DelegationBaseNode') /** - * @param delegation - * @param signature + * @param delegation The delegation to store on chain. + * @param signature The delegatee's signature to ensure the delegation is created with their consent. * @internal */ export async function store( @@ -36,13 +36,10 @@ export async function store( signature: string ): Promise { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const includeParentId: boolean = delegation.parentId - ? delegation.parentId !== delegation.rootId - : false const tx: SubmittableExtrinsic = blockchain.api.tx.delegation.addDelegation( delegation.id, - delegation.rootId, - includeParentId ? delegation.parentId : undefined, + delegation.hierarchyId, + delegation.parentId !== null ? delegation.parentId : undefined, delegation.account, permissionsAsBitset(delegation), signature @@ -51,7 +48,7 @@ export async function store( } /** - * @param delegationId + * @param delegationId The id of the delegation node to query. * @internal */ export async function query( @@ -63,19 +60,20 @@ export async function query( Option >(delegationId) ) - if (decoded) { - const root = new DelegationNode({ - id: delegationId, - rootId: decoded.rootId, - account: decoded.account, - permissions: decoded.permissions, - parentId: decoded.parentId, - revoked: decoded.revoked, - }) - - return root + if (!decoded) { + return null } - return null + const root = new DelegationNode({ + id: delegationId, + hierarchyId: decoded.hierarchyId, + parentId: decoded.parentId, + childrenIds: decoded.childrenIds, + account: decoded.account, + permissions: decoded.permissions, + revoked: decoded.revoked, + }) + + return root } /** @@ -102,34 +100,52 @@ export async function revoke( return tx } +async function fetchChildren( + childrenIds: string[] +): Promise>>> { + const blockchain = await BlockchainApiConnection.getConnectionOrConnect() + const val: Array + >> = await Promise.all( + childrenIds.map(async (childId: string) => { + const queryResult = await blockchain.api.query.delegation.delegations< + Option + >(childId) + return { + id: childId, + codec: queryResult, + } + }) + ) + return val +} + /** - * @param delegationNodeId + * @param delegationNode The delegation node to fetch children from. * @internal */ -// function lives here to avoid circular imports between DelegationBaseNode and DelegationNode export async function getChildren( - delegationNodeId: DelegationBaseNode['id'] + delegationNode: DelegationNode ): Promise { - log.info(` :: getChildren('${delegationNodeId}')`) - const childIds: string[] = await getChildIds(delegationNodeId) + log.info(` :: getChildren('${delegationNode.id}')`) const queryResults: Array - >> = await fetchChildren(childIds) + >> = await fetchChildren(delegationNode.childrenIds) const children: DelegationNode[] = queryResults .map((codec: CodecWithId>) => { const decoded = decodeDelegationNode(codec.codec) - if (decoded) { - const child = new DelegationNode({ - id: codec.id, - rootId: decoded.rootId, - account: decoded.account, - permissions: decoded.permissions, - parentId: decoded.parentId, - revoked: decoded.revoked, - }) - return child + if (!decoded) { + return null } - return null + const child = new DelegationNode({ + id: codec.id, + hierarchyId: decoded.hierarchyId, + parentId: decoded.parentId, + account: decoded.account, + permissions: decoded.permissions, + revoked: decoded.revoked, + }) + return child }) .filter((value): value is DelegationNode => { return value !== null @@ -137,3 +153,22 @@ export async function getChildren( log.info(`children: ${JSON.stringify(children)}`) return children } + +function decodeDelegatedAttestations(queryResult: Option>): string[] { + DecoderUtils.assertCodecIsType(queryResult, ['Option>']) + return queryResult.unwrapOrDefault().map((hash) => hash.toHex()) +} + +/** + * @param id The id of the delegation node for which to fetch all the attestations issued. + * @internal + */ +export async function getAttestationHashes( + id: IDelegationNode['id'] +): Promise { + const blockchain = await BlockchainApiConnection.getConnectionOrConnect() + const encodedHashes = await blockchain.api.query.attestation.delegatedAttestations< + Option> + >(id) + return decodeDelegatedAttestations(encodedHashes) +} diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index d9bb9b0ad..3851ec618 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -16,22 +16,21 @@ import { encodeAddress } from '@polkadot/keyring' import { Crypto, SDKErrors } from '@kiltprotocol/utils' import Identity from '../identity' import DelegationNode from './DelegationNode' -import DelegationRootNode from './DelegationRootNode' +import DelegationHierarchyDetails from './DelegationHierarchyDetails' import { permissionsAsBitset, errorCheck } from './DelegationNode.utils' -let childMap: Record = {} +let hierarchiesDetails: Record = {} let nodes: Record = {} -let rootNodes: Record = {} jest.mock('./DelegationNode.chain', () => ({ getChildren: jest.fn(async (id: string) => { - return childMap[id] || [] + return nodes[id].childrenIds }), query: jest.fn(async (id: string) => nodes[id] || null), })) -jest.mock('./DelegationRootNode.chain', () => ({ - query: jest.fn(async (id: string) => rootNodes[id] || null), +jest.mock('./DelegationHierarchyDetails.chain', () => ({ + query: jest.fn(async (id: string) => hierarchiesDetails[id] || null), })) let identityAlice: Identity @@ -39,7 +38,7 @@ let identityBob: Identity let id: string let successId: string let failureId: string -let rootId: string +let hierarchyId: string let parentId: string let hashList: string[] let addresses: string[] @@ -48,7 +47,7 @@ beforeAll(() => { identityAlice = Identity.buildFromURI('//Alice') identityBob = Identity.buildFromURI('//Bob') successId = Crypto.hashStr('success') - rootId = Crypto.hashStr('rootId') + hierarchyId = Crypto.hashStr('rootId') id = Crypto.hashStr('id') parentId = Crypto.hashStr('parentId') failureId = Crypto.hashStr('failure') @@ -66,10 +65,11 @@ describe('Delegation', () => { it('delegation generate hash', () => { const node = new DelegationNode({ id, - rootId, + hierarchyId, + parentId, account: identityBob.address, + childrenIds: [], permissions: [Permission.DELEGATE], - parentId, revoked: false, }) const hash: string = node.generateHash() @@ -81,8 +81,9 @@ describe('Delegation', () => { it('delegation permissionsAsBitset', () => { const node = new DelegationNode({ id, - rootId, + hierarchyId, account: identityBob.address, + childrenIds: [], permissions: [Permission.DELEGATE], parentId, revoked: false, @@ -97,8 +98,9 @@ describe('Delegation', () => { nodes = { [successId]: new DelegationNode({ id: successId, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.DELEGATE], parentId: undefined, revoked: false, @@ -113,8 +115,9 @@ describe('Delegation', () => { expect( await new DelegationNode({ id: successId, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.DELEGATE], parentId: undefined, revoked: false, @@ -124,8 +127,9 @@ describe('Delegation', () => { expect( await new DelegationNode({ id: failureId, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.DELEGATE], parentId: undefined, revoked: false, @@ -134,27 +138,37 @@ describe('Delegation', () => { }) it('get delegation root', async () => { - rootNodes = { - [rootId]: new DelegationRootNode({ - id: rootId, + hierarchiesDetails = { + [hierarchyId]: new DelegationHierarchyDetails({ + rootId: hierarchyId, cTypeHash: 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', + }), + } + + nodes = { + [hierarchyId]: new DelegationNode({ + id: hierarchyId, + hierarchyId, account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], revoked: false, }), } const node: DelegationNode = new DelegationNode({ id, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.DELEGATE], revoked: false, }) - const rootNode = await node.getRoot() + const hierarchyDetails = await node.getHierarchyDetails() - expect(rootNode).toBeDefined() - expect(rootNode.cTypeHash).toBe( + expect(hierarchyDetails).toBeDefined() + expect(hierarchyDetails.cTypeHash).toBe( 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' ) }) @@ -171,8 +185,9 @@ describe('count subtree', () => { beforeAll(() => { topNode = new DelegationNode({ id: a1, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [b1, b2], permissions: [Permission.ATTEST, Permission.DELEGATE], revoked: false, }) @@ -180,55 +195,57 @@ describe('count subtree', () => { nodes = { [b1]: new DelegationNode({ id: b1, - rootId, + hierarchyId, account: identityAlice.address, permissions: [Permission.ATTEST, Permission.DELEGATE], parentId: a1, + childrenIds: [c1, c2], revoked: false, }), [b2]: new DelegationNode({ id: b2, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.ATTEST, Permission.DELEGATE], parentId: a1, revoked: false, }), [c1]: new DelegationNode({ id: c1, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [d1], permissions: [Permission.ATTEST, Permission.DELEGATE], parentId: b1, revoked: false, }), [c2]: new DelegationNode({ id: c2, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.ATTEST, Permission.DELEGATE], parentId: b1, revoked: false, }), [d1]: new DelegationNode({ id: d1, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [Permission.ATTEST, Permission.DELEGATE], parentId: c1, revoked: false, }), } - childMap = { - [a1]: [nodes[b1], nodes[b2]], - [b1]: [nodes[c1], nodes[c2]], - [c1]: [nodes[d1]], - } }) it('mocks work', async () => { expect(topNode.id).toEqual(a1) - await expect(topNode.getChildren()).resolves.toBe(childMap[a1]) + await expect( + (await topNode.getChildren()).map((childNode) => childNode.id) + ).resolves.toBe(topNode.childrenIds) await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) }) @@ -244,16 +261,17 @@ describe('count subtree', () => { await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) }) - it('counts all subnodes in deeply nested childMap (100)', async () => { - childMap = hashList.slice(0, 101).reduce((previous, current, index) => { + it('counts all subnodes in deeply nested structure (100)', async () => { + nodes = hashList.slice(0, 101).reduce((previous, current, index) => { return { ...previous, [current]: [ new DelegationNode({ - id: hashList[index + 1], - rootId, + id: current, + hierarchyId, account: identityAlice.address, permissions: [Permission.DELEGATE], + childrenIds: [hashList[index + 1]], parentId: current, revoked: false, }), @@ -262,20 +280,21 @@ describe('count subtree', () => { }, {}) await expect( - childMap[hashList[0]][0].subtreeNodeCount() + nodes[hashList[0]][0].subtreeNodeCount() ).resolves.toStrictEqual(100) }) - it('counts all subnodes in deeply nested childMap (1000)', async () => { - childMap = hashList.slice(0, 1001).reduce((previous, current, index) => { + it('counts all subnodes in deeply nested structure (1000)', async () => { + nodes = hashList.slice(0, 1001).reduce((previous, current, index) => { return { ...previous, [current]: [ new DelegationNode({ - id: hashList[index + 1], - rootId, + id: current, + hierarchyId, account: identityAlice.address, permissions: [Permission.DELEGATE], + childrenIds: [hashList[index + 1]], parentId: current, revoked: false, }), @@ -283,20 +302,21 @@ describe('count subtree', () => { } }, {}) await expect( - childMap[hashList[0]][0].subtreeNodeCount() + nodes[hashList[0]][0].subtreeNodeCount() ).resolves.toStrictEqual(1000) }) - it('counts all subnodes in deeply nested childMap (10000)', async () => { - childMap = hashList.slice(0, 10001).reduce((previous, current, index) => { + it('counts all subnodes in deeply nested structure (10000)', async () => { + nodes = hashList.slice(0, 10001).reduce((previous, current, index) => { return { ...previous, [current]: [ new DelegationNode({ - id: hashList[index + 1], - rootId, + id: current, + hierarchyId, account: identityAlice.address, permissions: [Permission.DELEGATE], + childrenIds: [hashList[index + 1]], parentId: current, revoked: false, }), @@ -304,7 +324,7 @@ describe('count subtree', () => { } }, {}) await expect( - childMap[hashList[0]][0].subtreeNodeCount() + nodes[hashList[0]][0].subtreeNodeCount() ).resolves.toStrictEqual(10000) }) }) @@ -317,9 +337,10 @@ describe('count depth', () => { (nodeId, index) => new DelegationNode({ id: nodeId, - rootId, + hierarchyId, account: addresses[index], permissions: [Permission.DELEGATE], + childrenIds: [], parentId: hashList[index + 1], revoked: false, }) @@ -417,8 +438,9 @@ describe('count depth', () => { it('error check should throw errors on faulty delegation nodes', async () => { const malformedPremissionsDelegationNode = { id, - rootId, + hierarchyId, account: identityAlice.address, + childrenIds: [], permissions: [], parentId: undefined, revoked: false, @@ -426,7 +448,7 @@ describe('count depth', () => { const missingRootIdDelegationNode = { id, - rootId, + hierarchyId, account: identityAlice.address, permissions: [Permission.DELEGATE], parentId: undefined, @@ -434,11 +456,11 @@ describe('count depth', () => { } as IDelegationNode // @ts-expect-error - delete missingRootIdDelegationNode.rootId + delete missingRootIdDelegationNode.hierarchyId const malformedRootIdDelegationNode = { id, - rootId: rootId.slice(13) + rootId.slice(15), + hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), account: identityAlice.address, permissions: [Permission.DELEGATE], parentId: undefined, @@ -447,7 +469,7 @@ describe('count depth', () => { const malformedParentIdDelegationNode = { id, - rootId, + hierarchyId, account: identityAlice.address, permissions: [Permission.DELEGATE], parentId: 'malformed', diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 71ec75fd4..eb859d443 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -10,26 +10,126 @@ * * Starting from the root node, entities can delegate the right to issue attestations to Claimers for a certain CTYPE and also delegate the right to attest and to delegate further nodes. * + * A delegation object is stored on-chain, and can be revoked. A base node is created, a ID which may be used in a [[RequestForAttestation]]. + * + * A delegation can and may restrict permissions. + * + * Permissions: + * * Delegate. + * * Attest. + * * @packageDocumentation * @module DelegationNode * @preferred */ -import type { IDelegationNode, SubmittableExtrinsic } from '@kiltprotocol/types' +import type { + IDelegationNode, + IPublicIdentity, + SubmittableExtrinsic, +} from '@kiltprotocol/types' import { Crypto, SDKErrors } from '@kiltprotocol/utils' import { ConfigService } from '@kiltprotocol/config' -import DelegationBaseNode from './Delegation' -import { getChildren, query, revoke, store } from './DelegationNode.chain' +import { query as queryAttestation } from '../attestation/Attestation.chain' +import { + getChildren, + getAttestationHashes, + query, + revoke, + store, +} from './DelegationNode.chain' +import { query as queryDetails } from './DelegationHierarchyDetails.chain' import * as DelegationNodeUtils from './DelegationNode.utils' -import DelegationRootNode from './DelegationRootNode' -import { query as queryRoot } from './DelegationRootNode.chain' +import DelegationHierarchyDetails from './DelegationHierarchyDetails' +import Attestation from '../attestation/Attestation' +import Identity from '../identity/Identity' const log = ConfigService.LoggingFactory.getLogger('DelegationNode') -export default class DelegationNode extends DelegationBaseNode - implements IDelegationNode { +export default class DelegationNode implements IDelegationNode { + public id: IDelegationNode['id'] + public hierarchyId: IDelegationNode['hierarchyId'] + public parentId?: IDelegationNode['parentId'] + public childrenIds: Array = [] + public account: IPublicIdentity['address'] + public permissions: IDelegationNode['permissions'] + public revoked: boolean + + /** + * Creates a new [DelegationNode]. + * + * @param delegationNodeInput - The base object from which to create the delegation node. + */ + public constructor(delegationNodeInput: IDelegationNode) { + this.id = delegationNodeInput.id + this.hierarchyId = delegationNodeInput.hierarchyId + this.parentId = delegationNodeInput.parentId + this.childrenIds = delegationNodeInput.childrenIds + this.account = delegationNodeInput.account + this.permissions = delegationNodeInput.permissions + this.revoked = delegationNodeInput.revoked + DelegationNodeUtils.errorCheck(this) + } + + /** + * [ASYNC] Fetches the details of the hierarchy this delegation node belongs to. + * + * @throws [[ERROR_HIERARCHY_QUERY]] when the hierarchy details could not be queried. + * @returns Promise containing the [[DelegationHierarchyDetails]] of this delegation node. + */ + public async getHierarchyDetails(): Promise { + const hierarchyDetails = await queryDetails(this.hierarchyId) + if (!hierarchyDetails) { + throw SDKErrors.ERROR_HIERARCHY_QUERY(this.hierarchyId) + } + return hierarchyDetails + } + + /** + * [ASYNC] Fetches the parent node of this delegation node. + * + * @returns Promise containing the parent as [[DelegationNode]] or [null]. + */ + public async getParent(): Promise { + return this.parentId ? query(this.parentId) : Promise.resolve(null) + } + + /** + * [ASYNC] Fetches the children nodes of this delegation node. + * + * @returns Promise containing the children as an array of [[DelegationNode]], which is empty if there are no children. + */ + public async getChildren(): Promise { + return getChildren(this) + } + + /** + * [ASYNC] Fetches and resolves all attestations attested with this delegation node. + * + * @returns Promise containing all resolved attestations attested with this node. + */ + public async getAttestations(): Promise { + const attestationHashes = await this.getAttestationHashes() + const attestations = await Promise.all( + attestationHashes.map((claimHash: string) => { + return queryAttestation(claimHash) + }) + ) + + return attestations.filter((value): value is Attestation => !!value) + } + + /** + * [ASYNC] Fetches all hashes of attestations attested with this delegation node. + * + * @returns Promise containing all attestation hashes attested with this node. + */ + public async getAttestationHashes(): Promise { + return getAttestationHashes(this.id) + } + /** - * [STATIC] Queries the delegation node with [delegationId]. + * [STATIC] [ASYNC] Queries the delegation node with its [delegationId]. * * @param delegationId The unique identifier of the desired delegation. * @returns Promise containing the [[DelegationNode]] or [null]. @@ -43,23 +143,6 @@ export default class DelegationNode extends DelegationBaseNode return result } - public rootId: IDelegationNode['rootId'] - public parentId?: IDelegationNode['parentId'] - public permissions: IDelegationNode['permissions'] - - /** - * Creates a new [DelegationNode]. - * - * @param delegationNodeInput - The base object from which to create the delegation node. - */ - public constructor(delegationNodeInput: IDelegationNode) { - super(delegationNodeInput) - this.permissions = delegationNodeInput.permissions - this.rootId = delegationNodeInput.rootId - this.parentId = delegationNodeInput.parentId - DelegationNodeUtils.errorCheck(this) - } - /** * * Generates the delegation hash from the delegations' property values. @@ -82,8 +165,8 @@ export default class DelegationNode extends DelegationBaseNode * @returns The hash representation of this delegation **as a hex string**. */ public generateHash(): string { - const propsToHash: Array = [this.id, this.rootId] - if (this.parentId && this.parentId !== this.rootId) { + const propsToHash: Array = [this.id, this.hierarchyId] + if (this.parentId) { propsToHash.push(this.parentId) } const uint8Props: Uint8Array[] = propsToHash.map((value) => { @@ -97,34 +180,6 @@ export default class DelegationNode extends DelegationBaseNode return generated } - /** - * [ASYNC] Fetches the root of this delegation node. - * - * @throws [[ERROR_ROOT_NODE_QUERY]] when the rootId could not be queried. - * @returns Promise containing the [[DelegationRootNode]] of this delegation node. - */ - public async getRoot(): Promise { - const rootNode = await queryRoot(this.rootId) - if (!rootNode) { - throw SDKErrors.ERROR_ROOT_NODE_QUERY(this.rootId) - } - return rootNode - } - - /** - * [ASYNC] Fetches the parent node of this delegation node. - * - * @returns Promise containing the parent as [[DelegationBaseNode]] or [null]. - */ - - public async getParent(): Promise { - if (!this.parentId || this.parentId === this.rootId) { - // parent must be root - return this.getRoot() - } - return query(this.parentId) - } - /** * [ASYNC] Stores the delegation node on chain. * @@ -146,6 +201,52 @@ export default class DelegationNode extends DelegationBaseNode return node !== null && !node.revoked } + /** + * Checks on chain whether a identity with the given address is delegating to the current node. + * + * @param address The address of the identity. + * @returns An object containing a `node` owned by the identity if it is delegating, plus the number of `steps` traversed. `steps` is 0 if the address is owner of the current node. + */ + public async findAncestorOwnedBy( + address: Identity['address'] + ): Promise<{ steps: number; node: DelegationNode | null }> { + if (this.account === address) { + return { + steps: 0, + node: this, + } + } + const parent = await this.getParent() + if (parent) { + const result = await parent.findAncestorOwnedBy(address) + result.steps += 1 + return result + } + return { + steps: 0, + node: null, + } + } + + /** + * [ASYNC] Recursively counts all nodes that descend from the current node (excluding the current node). + * + * @returns Promise resolving to the node count. + */ + public async subtreeNodeCount(): Promise { + const children = await this.getChildren() + if (children.length === 0) { + return 0 + } + const childrensChildCounts = await Promise.all( + children.map((child) => child.subtreeNodeCount()) + ) + return ( + children.length + + childrensChildCounts.reduce((previous, current) => previous + current) + ) + } + /** * [ASYNC] Revokes the delegation node on chain. * @@ -159,16 +260,10 @@ export default class DelegationNode extends DelegationBaseNode `Identity with address ${address} is not among the delegators and may not revoke this node` ) } - const childCount = await this.subtreeNodeCount() - // must revoke all children and self - const revocationCount = childCount + 1 + const childrenCount = await this.subtreeNodeCount() log.debug( - `:: revoke(${this.id}) with maxRevocations=${revocationCount} and maxDepth = ${steps} through delegation node ${node?.id} and identity ${address}` + `:: revoke(${this.id}) with maxRevocations=${childrenCount} and maxDepth = ${steps} through delegation node ${node?.id} and identity ${address}` ) - return revoke(this.id, steps, revocationCount) - } - - public async getChildren(): Promise { - return getChildren(this.id) + return revoke(this.id, steps, childrenCount) } } diff --git a/packages/core/src/delegation/DelegationNode.utils.ts b/packages/core/src/delegation/DelegationNode.utils.ts index 18335ce24..f9fb908b7 100644 --- a/packages/core/src/delegation/DelegationNode.utils.ts +++ b/packages/core/src/delegation/DelegationNode.utils.ts @@ -72,7 +72,7 @@ export async function countNodeDepth( } export function errorCheck(delegationNodeInput: IDelegationNode): void { - const { permissions, rootId, parentId } = delegationNodeInput + const { permissions, hierarchyId: rootId, parentId } = delegationNodeInput if (permissions.length === 0 || permissions.length > 3) { throw SDKErrors.ERROR_UNAUTHORIZED( diff --git a/packages/core/src/delegation/DelegationRootNode.chain.ts b/packages/core/src/delegation/DelegationRootNode.chain.ts deleted file mode 100644 index 89ad16fdb..000000000 --- a/packages/core/src/delegation/DelegationRootNode.chain.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * @packageDocumentation - * @module DelegationRootNode - */ - -import type { Option } from '@polkadot/types' -import type { - IDelegationRootNode, - SubmittableExtrinsic, -} from '@kiltprotocol/types' -import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' -import { - decodeRootDelegation, - IChainDelegationRoot, - RootDelegationRecord, -} from './DelegationDecoder' -import DelegationRootNode from './DelegationRootNode' - -/** - * @param delegation - * @internal - */ -export async function store( - delegation: IDelegationRootNode -): Promise { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const tx: SubmittableExtrinsic = blockchain.api.tx.delegation.createRoot( - delegation.id, - delegation.cTypeHash - ) - return tx -} - -/** - * @param delegationId - * @internal - */ -export async function query( - delegationId: IDelegationRootNode['id'] -): Promise { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const decoded: RootDelegationRecord | null = decodeRootDelegation( - await blockchain.api.query.delegation.roots>( - delegationId - ) - ) - if (decoded) { - const root = new DelegationRootNode({ - id: delegationId, - cTypeHash: decoded.cTypeHash, - account: decoded.account, - revoked: decoded.revoked, - }) - return root - } - return null -} - -/** - * @internal - * - * Revokes a full delegation tree, also revoking all constituent nodes. - * - * @param delegation The [[DelegationRootNode]] node in the delegation tree at which to revoke. - * @param maxRevocations The maximum number of revocations that may be performed. Should be set to the number of nodes (including the root node) in the tree. Higher numbers result in a larger amount locked during the transaction, as each revocation adds to the fee that is charged. - * @returns Unsigned [[SubmittableExtrinsic]] ready to be signed and dispatched. - */ -export async function revoke( - delegation: IDelegationRootNode, - maxRevocations: number -): Promise { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const tx: SubmittableExtrinsic = blockchain.api.tx.delegation.revokeRoot( - delegation.id, - maxRevocations - ) - return tx -} diff --git a/packages/core/src/delegation/DelegationRootNode.ts b/packages/core/src/delegation/DelegationRootNode.ts deleted file mode 100644 index 76fd9b7ab..000000000 --- a/packages/core/src/delegation/DelegationRootNode.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * KILT enables top-down trust structures. - * On the lowest level, a delegation structure is always a **tree**. - * The root of this tree is DelegationRootNode. - * - * Apart from inheriting [[DelegationBaseNode]]'s structure, a DelegationRootNode has a [[cTypeHash]] property that refers to a specific [[CType]]. - * A DelegationRootNode is written on-chain, and can be queried by delegationId via the [[query]] method. - * - * @packageDocumentation - * @module DelegationRootNode - */ - -import type { - IDelegationRootNode, - SubmittableExtrinsic, -} from '@kiltprotocol/types' -import { ConfigService } from '@kiltprotocol/config' -import DelegationBaseNode from './Delegation' -import DelegationNode from './DelegationNode' -import { getChildren } from './DelegationNode.chain' -import { query, revoke, store } from './DelegationRootNode.chain' -import errorCheck from './DelegationRootNode.utils' - -const log = ConfigService.LoggingFactory.getLogger('DelegationRootNode') - -export default class DelegationRootNode extends DelegationBaseNode - implements IDelegationRootNode { - /** - * [STATIC] Queries the delegation root with ``delegationId``. - * - * @param delegationId Unique identifier of the delegation root. - * @returns Promise containing [[DelegationRootNode]] or [null]. - */ - public static async query( - delegationId: string - ): Promise { - log.info(`:: query('${delegationId}')`) - const result = await query(delegationId) - if (result) { - log.info(`result: ${JSON.stringify(result)}`) - } else { - log.info(`root node not found`) - } - - return result - } - - public cTypeHash: IDelegationRootNode['cTypeHash'] - - /** - * Creates a new [DelegationRootNode]. - * - * @param delegationRootNodeInput - The base object from which to create the delegation base node. - */ - public constructor(delegationRootNodeInput: IDelegationRootNode) { - super(delegationRootNodeInput) - this.cTypeHash = delegationRootNodeInput.cTypeHash - errorCheck(this) - } - - public getRoot(): Promise { - return Promise.resolve(this) - } - - /* eslint-disable class-methods-use-this */ - public getParent(): Promise { - return Promise.resolve(null) - } - /* eslint-enable class-methods-use-this */ - - /** - * Stores the delegation root node on chain. - * - * @returns Promise containing the unsigned SubmittableExtrinsic. - */ - public async store(): Promise { - log.debug(`:: store(${this.id})`) - return store(this) - } - - public async verify(): Promise { - const node = await query(this.id) - return node !== null && !node.revoked - } - - public async revoke(): Promise { - const childCount = await this.subtreeNodeCount() - log.debug(`:: revoke(${this.id})`) - return revoke(this, childCount + 1) - } - - public async getChildren(): Promise { - return getChildren(this.id) - } -} diff --git a/packages/core/src/delegation/DelegationRootNode.utils.ts b/packages/core/src/delegation/DelegationRootNode.utils.ts deleted file mode 100644 index 732f9c602..000000000 --- a/packages/core/src/delegation/DelegationRootNode.utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -import type { IDelegationRootNode } from '@kiltprotocol/types' -import { DataUtils, SDKErrors } from '@kiltprotocol/utils' - -export default function errorCheck( - delegationRootNodeInput: IDelegationRootNode -): void { - const { cTypeHash } = delegationRootNodeInput - - if (!cTypeHash) { - throw SDKErrors.ERROR_CTYPE_HASH_NOT_PROVIDED() - } else DataUtils.validateHash(cTypeHash, 'delegation root node ctype') -} diff --git a/packages/types/src/Delegation.ts b/packages/types/src/Delegation.ts index ceab81a31..8f63692fa 100644 --- a/packages/types/src/Delegation.ts +++ b/packages/types/src/Delegation.ts @@ -20,7 +20,7 @@ export enum Permission { export interface IDelegationNode { id: string - rootId: IDelegationNode['id'] + hierarchyId: IDelegationNode['id'] parentId?: IDelegationNode['id'] childrenIds: Array account: IPublicIdentity['address'] @@ -29,5 +29,6 @@ export interface IDelegationNode { } export interface IDelegationHierarchyDetails { + rootId: IDelegationNode['id'] cTypeHash: ICType['hash'] } diff --git a/packages/utils/src/SDKErrors.ts b/packages/utils/src/SDKErrors.ts index 443bb46f0..66f50182a 100644 --- a/packages/utils/src/SDKErrors.ts +++ b/packages/utils/src/SDKErrors.ts @@ -59,7 +59,7 @@ export enum ErrorCode { ERROR_CLAIM_HASHTREE_MISMATCH = 20014, ERROR_PE_MISMATCH = 20015, ERROR_DID_IDENTIFIER_MISMATCH = 20016, - ERROR_ROOT_NODE_QUERY = 20017, + ERROR_HIERARCHY_QUERY = 20017, ERROR_INVALID_DID_PREFIX = 20018, ERROR_MESSAGE_BODY_MALFORMED = 20019, ERROR_NODE_QUERY = 20020, @@ -366,11 +366,11 @@ export const ERROR_PE_MISMATCH: () => SDKError = () => { 'Verifier requested public presentation, but privacy enhancement was forced.' ) } -export const ERROR_ROOT_NODE_QUERY: (rootId: string) => SDKError = ( +export const ERROR_HIERARCHY_QUERY: (rootId: string) => SDKError = ( rootId: string ) => { return new SDKError( - ErrorCode.ERROR_ROOT_NODE_QUERY, + ErrorCode.ERROR_HIERARCHY_QUERY, `Could not find root node with id ${rootId}` ) } From 6889f559e0808bba58a958a2656ea72aa2cbf3ca Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 28 Jul 2021 15:00:11 +0200 Subject: [PATCH 03/21] fix: fixing compilation errors (wip) --- packages/actors_api/src/actors/Claimer.ts | 4 +- .../__mocks__/BlockchainApiConnection.ts | 44 ++++++------------- .../__mocks__/BlockchainQuery.ts | 3 +- .../__integrationtests__/Delegation.spec.ts | 15 +++---- packages/core/src/attestation/Attestation.ts | 29 ++++++------ .../DelegationHierarchyDetails.spec.ts | 2 +- .../delegation/DelegationHierarchyDetails.ts | 2 +- .../src/delegation/DelegationNode.chain.ts | 3 +- packages/core/src/delegation/index.ts | 10 +---- packages/core/src/index.ts | 6 +-- .../RequestForAttestation.ts | 8 ++-- packages/types/src/Attestation.ts | 4 +- packages/types/src/Message.ts | 16 +++---- packages/types/src/RequestForAttestation.ts | 4 +- packages/types/src/Terms.ts | 6 +-- 15 files changed, 64 insertions(+), 92 deletions(-) diff --git a/packages/actors_api/src/actors/Claimer.ts b/packages/actors_api/src/actors/Claimer.ts index 33f630406..e0093b1a1 100644 --- a/packages/actors_api/src/actors/Claimer.ts +++ b/packages/actors_api/src/actors/Claimer.ts @@ -20,7 +20,7 @@ import type { IClaim, IMessage, IRequestAttestationForClaim, - IDelegationBaseNode, + IDelegationNode, IPublicIdentity, IRequestForAttestation, } from '@kiltprotocol/types' @@ -109,7 +109,7 @@ export function requestAttestation( attesterPublicIdentity: IPublicIdentity, option: { legitimations?: AttestedClaim[] - delegationId?: IDelegationBaseNode['id'] + delegationId?: IDelegationNode['id'] } = {} ): { message: Message diff --git a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts index d0f4e882f..a3549e1de 100644 --- a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts +++ b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts @@ -31,10 +31,9 @@ * value setters `.mockReturnValue` or `.mockReturnValueOnce` on the method you want to modify: * ``` * const mocked_api = require('../blockchainApiConnection/BlockchainApiConnection').__mocked_api - * mocked_api.query.delegation.children.mockReturnValue( - * new Vec( - * 'Hash', - * ['0x123', '0x456', '0x789'] + * mocked_api.query.delegation.hierarchies.mockReturnValue( + * new Option( + * 'Hash' * ) * ) * ``` @@ -229,10 +228,10 @@ const __mocked_api: any = { }), }, delegation: { - createRoot: jest.fn((rootId, _ctypeHash) => { + createHierarchy: jest.fn((rootId, _ctypeHash) => { return __getMockSubmittableExtrinsic() }), - revokeRoot: jest.fn((rootId) => { + addDelegation: jest.fn((delegationId, parent_id, owner, permissions, signature) => { return __getMockSubmittableExtrinsic() }), revokeDelegation: jest.fn((delegationId) => { @@ -306,17 +305,13 @@ const __mocked_api: any = { delegation: { // default return value decodes to null, represents delegation not found roots: jest.fn(async (rootId: string) => - mockChainQueryReturn('delegation', 'root') + mockChainQueryReturn('delegation', 'hierarchies') ), /* example return value: new Option( TYPE_REGISTRY, - Tuple.with(['Hash', AccountId, Bool]), - [ - '0x1234', // ctype hash - '4r1WkS3t8rbCb11H8t3tJvGVCynwDXSUBiuGB6sLRHzCLCjs', // Account - false, // revoked flag - ] + Hash, + '0x1234', // ctype hash ) */ @@ -327,28 +322,15 @@ const __mocked_api: any = { /* example return value: new Option( TYPE_REGISTRY, - Tuple.with(['DelegationNodeId','Option',AccountId,U32,Bool]), + Tuple.with(['DelegationNodeId','Option','Vec',DelegationDetails]), [ - '0x1234', // root-id - null, // parent-id? - '4r1WkS3t8rbCb11H8t3tJvGVCynwDXSUBiuGB6sLRHzCLCjs', // Account - 0, // permissions - false, // revoked flag + '0x1234', // root-id + '0x1234', // parent-id? + '[0x2345,0x3456] // children ids + '{4r1WkS3t8rbCb11H8t3tJvGVCynwDXSUBiuGB6sLRHzCLCjs,false,0}', // {owner, revocation status, permissions} ] ) */ - - // default return value decodes to [], represents: no children found - children: jest.fn(async (id: string) => - mockChainQueryReturn('delegation', 'children') - ), - /* example return value: - new Vec( - TYPE_REGISTRY, - 'DelegationNodeId', - ['0x123', '0x456', '0x789'] - ) - */ }, did: { // default return value decodes to null, represents dID not found diff --git a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts index fb45d039a..a0664953b 100644 --- a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts +++ b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts @@ -36,7 +36,7 @@ const chainQueryReturnTuples: { delegation: { // Delegation hierarchies: root-id -> (ctype-hash)? hierarchies: TYPE_REGISTRY.getOrUnknown('DelegationHierarchies'), - // Delegations: delegation-id -> (hierarchy-id, parent-id?, childrenIds, account, permissions, revoked)? + // Delegations: delegation-id -> (hierarchy-id, parent-id?, childrenIds, details)? delegations: TYPE_REGISTRY.getOrUnknown('DelegationNodes'), }, attestation: { @@ -114,7 +114,6 @@ export function mockChainQueryReturn( return wrapInOption() } case 'delegation': { - if (innerQuery === 'children') return wrapInVec() return wrapInOption() } case 'did': { diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index 99fe7cbac..deecaa83e 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -13,6 +13,7 @@ import type { ICType } from '@kiltprotocol/types' import { Permission } from '@kiltprotocol/types' import { UUID } from '@kiltprotocol/utils' import { BlockchainUtils } from '@kiltprotocol/chain-helpers' +import { DelegationHierarchyDetails } from '@kiltprotocol/sdk-js' import { AttestedClaim, Identity } from '..' import Attestation from '../attestation/Attestation' import { config, disconnect } from '../kilt' @@ -20,11 +21,9 @@ import Claim from '../claim/Claim' import { fetchChildren, getAttestationHashes, - getChildIds, -} from '../delegation/Delegation.chain' +} from '../delegation/DelegationNode.chain' import { decodeDelegationNode } from '../delegation/DelegationDecoder' import DelegationNode from '../delegation/DelegationNode' -import DelegationRootNode from '../delegation/DelegationRootNode' import RequestForAttestation from '../requestforattestation/RequestForAttestation' import { CtypeOnChain, @@ -38,12 +37,10 @@ import { async function writeRoot( delegator: Identity, ctypeHash: ICType['hash'] -): Promise { - const root = new DelegationRootNode({ - id: UUID.generate(), +): Promise { + const root = new DelegationHierarchyDetails({ + rootId: UUID.generate(), cTypeHash: ctypeHash, - account: delegator.address, - revoked: false, }) await root.store().then((tx) => @@ -55,7 +52,7 @@ async function writeRoot( return root } async function addDelegation( - parentNode: DelegationRootNode | DelegationNode, + parentNode: DelegationNode | DelegationNode, delegator: Identity, delegee: Identity, permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] diff --git a/packages/core/src/attestation/Attestation.ts b/packages/core/src/attestation/Attestation.ts index d31723740..ee6a93201 100644 --- a/packages/core/src/attestation/Attestation.ts +++ b/packages/core/src/attestation/Attestation.ts @@ -25,9 +25,9 @@ import type { IRequestForAttestation, CompressedAttestation, } from '@kiltprotocol/types' +import { DelegationHierarchyDetails } from '@kiltprotocol/sdk-js' import { revoke, query, store } from './Attestation.chain' import AttestationUtils from './Attestation.utils' -import DelegationRootNode from '../delegation/DelegationRootNode' import DelegationNode from '../delegation/DelegationNode' export default class Attestation implements IAttestation { @@ -109,24 +109,25 @@ export default class Attestation implements IAttestation { * [STATIC] [ASYNC] Tries to query the delegationId and if successful query the rootId. * * @param delegationId - The Id of the Delegation stored in [[Attestation]]. - * @returns A promise of either null if querying was not successful or the affiliated [[DelegationRootNode]]. + * @returns A promise of either null if querying was not successful or the affiliated [[DelegationNode]]. */ - public static async getDelegationRoot( + public static async getDelegationDetails( delegationId: IAttestation['delegationId'] | null - ): Promise { - if (delegationId) { - const delegationNode: DelegationNode | null = await DelegationNode.query( - delegationId - ) - if (delegationNode) { - return delegationNode.getRoot() - } + ): Promise { + if (!delegationId) { + return null } - return null + const delegationNode: DelegationNode | null = await DelegationNode.query( + delegationId + ) + if (!delegationNode) { + return null + } + return delegationNode.getHierarchyDetails() } - public async getDelegationRoot(): Promise { - return Attestation.getDelegationRoot(this.delegationId) + public async getDelegationDetails(): Promise { + return Attestation.getDelegationDetails(this.delegationId) } /** diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts index e57d4a3c3..c3a97ac66 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts @@ -37,7 +37,7 @@ describe('Delegation', () => { ROOT_IDENTIFIER = Crypto.hashStr('1') ROOT_SUCCESS = Crypto.hashStr('success') - require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.hierarchies.mockReturnValue( + require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.hierarchies.mockReturnValue( mockChainQueryReturn('delegation', 'hierarchies', [ctypeHash]) ) require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.delegations.mockReturnValue( diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.ts b/packages/core/src/delegation/DelegationHierarchyDetails.ts index 56a604d07..f4fefe0cd 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.ts @@ -24,7 +24,7 @@ import type { } from '@kiltprotocol/types' import { query, store } from './DelegationHierarchyDetails.chain' -const log = ConfigService.LoggingFactory.getLogger('DelegationRootNode') +const log = ConfigService.LoggingFactory.getLogger('DelegationHierarchyDetails') export default class DelegationHierarchyDetails implements IDelegationHierarchyDetails { diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index d50b4746b..f89310b1e 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -100,7 +100,7 @@ export async function revoke( return tx } -async function fetchChildren( +export async function fetchChildren( childrenIds: string[] ): Promise>>> { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() @@ -141,6 +141,7 @@ export async function getChildren( id: codec.id, hierarchyId: decoded.hierarchyId, parentId: decoded.parentId, + childrenIds: decoded.childrenIds, account: decoded.account, permissions: decoded.permissions, revoked: decoded.revoked, diff --git a/packages/core/src/delegation/index.ts b/packages/core/src/delegation/index.ts index 26fbbe396..da669d011 100644 --- a/packages/core/src/delegation/index.ts +++ b/packages/core/src/delegation/index.ts @@ -5,14 +5,8 @@ * found in the LICENSE file in the root directory of this source tree. */ -import DelegationBaseNode from './Delegation' import DelegationNode from './DelegationNode' -import DelegationRootNode from './DelegationRootNode' +import DelegationHierarchyDetails from './DelegationHierarchyDetails' import * as DelegationNodeUtils from './DelegationNode.utils' -export { - DelegationBaseNode, - DelegationNode, - DelegationRootNode, - DelegationNodeUtils, -} +export { DelegationNode, DelegationHierarchyDetails, DelegationNodeUtils } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index aea3aca7b..25143beca 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,9 +11,8 @@ import { Balance, BalanceUtils } from './balance' import Claim, { ClaimUtils } from './claim' import { CType, CTypeMetadata, CTypeSchema, CTypeUtils } from './ctype' import { - DelegationBaseNode, DelegationNode, - DelegationRootNode, + DelegationHierarchyDetails, DelegationNodeUtils, } from './delegation' import Did, { @@ -50,10 +49,9 @@ export { AttestationUtils, AttestedClaim, AttestedClaimUtils, - DelegationBaseNode, + DelegationHierarchyDetails, DelegationNode, DelegationNodeUtils, - DelegationRootNode, Did, IDid, IDidDocument, diff --git a/packages/core/src/requestforattestation/RequestForAttestation.ts b/packages/core/src/requestforattestation/RequestForAttestation.ts index 5660d084b..b2d772df5 100644 --- a/packages/core/src/requestforattestation/RequestForAttestation.ts +++ b/packages/core/src/requestforattestation/RequestForAttestation.ts @@ -21,7 +21,7 @@ import type { IRequestForAttestation, CompressedRequestForAttestation, Hash, - IDelegationBaseNode, + IDelegationNode, IClaim, IAttestedClaim, } from '@kiltprotocol/types' @@ -46,7 +46,7 @@ function getHashRoot(leaves: Uint8Array[]): Uint8Array { export type Options = { legitimations?: AttestedClaim[] - delegationId?: IDelegationBaseNode['id'] + delegationId?: IDelegationNode['id'] } export default class RequestForAttestation implements IRequestForAttestation { @@ -138,7 +138,7 @@ export default class RequestForAttestation implements IRequestForAttestation { public claimHashes: string[] public claimNonceMap: Record public rootHash: Hash - public delegationId: IDelegationBaseNode['id'] | null + public delegationId: IDelegationNode['id'] | null /** * Builds a new [[RequestForAttestation]] instance. @@ -301,7 +301,7 @@ export default class RequestForAttestation implements IRequestForAttestation { private static getHashLeaves( claimHashes: Hash[], legitimations: IAttestedClaim[], - delegationId: IDelegationBaseNode['id'] | null + delegationId: IDelegationNode['id'] | null ): Uint8Array[] { const result: Uint8Array[] = [] claimHashes.forEach((item) => { diff --git a/packages/types/src/Attestation.ts b/packages/types/src/Attestation.ts index 6e44267de..1030d7549 100644 --- a/packages/types/src/Attestation.ts +++ b/packages/types/src/Attestation.ts @@ -10,14 +10,14 @@ * @module IAttestation */ import type { ICType } from './CType' -import type { IDelegationBaseNode } from './Delegation' +import type { IDelegationNode } from './Delegation' import type { IPublicIdentity } from './PublicIdentity' export interface IAttestation { claimHash: string cTypeHash: ICType['hash'] owner: IPublicIdentity['address'] - delegationId: IDelegationBaseNode['id'] | null + delegationId: IDelegationNode['id'] | null revoked: boolean } diff --git a/packages/types/src/Message.ts b/packages/types/src/Message.ts index e4c62a33d..da46a7147 100644 --- a/packages/types/src/Message.ts +++ b/packages/types/src/Message.ts @@ -20,7 +20,7 @@ import type { PartialClaim, } from './Claim' import type { ICType } from './CType' -import type { IDelegationBaseNode, IDelegationNode } from './Delegation' +import type { IDelegationNode } from './Delegation' import type { IPublicIdentity } from './PublicIdentity' import type { CompressedQuoteAgreed, IQuoteAgreement } from './Quote' import type { @@ -110,7 +110,7 @@ export interface IRejectTerms extends IMessageBodyBase { content: { claim: PartialClaim legitimations: IAttestedClaim[] - delegationId?: IDelegationBaseNode['id'] + delegationId?: IDelegationNode['id'] } type: MessageBodyType.REJECT_TERMS } @@ -238,8 +238,8 @@ export interface IRequestClaimsForCTypesContent { } export interface IDelegationData { - account: IDelegationBaseNode['account'] - id: IDelegationBaseNode['id'] + account: IDelegationNode['account'] + id: IDelegationNode['id'] parentId: IDelegationNode['id'] permissions: IDelegationNode['permissions'] isPCR: boolean @@ -261,7 +261,7 @@ export interface ISubmitDelegationApproval { } export interface IInformDelegationCreation { - delegationId: IDelegationBaseNode['id'] + delegationId: IDelegationNode['id'] isPCR: boolean } @@ -274,7 +274,7 @@ export type CompressedPartialClaim = [ export type CompressedRejectedTerms = [ CompressedPartialClaim, CompressedAttestedClaim[], - IDelegationBaseNode['id'] | undefined + IDelegationNode['id'] | undefined ] export type CompressedRequestClaimsForCTypesContent = [ @@ -290,8 +290,8 @@ export type CompressedRequestAttestationForClaimContent = [ ] export type CompressedDelegationData = [ - IDelegationBaseNode['account'], - IDelegationBaseNode['id'], + IDelegationNode['account'], + IDelegationNode['id'], IDelegationNode['id'], IDelegationNode['permissions'], boolean diff --git a/packages/types/src/RequestForAttestation.ts b/packages/types/src/RequestForAttestation.ts index 8afccc275..eea4e389a 100644 --- a/packages/types/src/RequestForAttestation.ts +++ b/packages/types/src/RequestForAttestation.ts @@ -12,7 +12,7 @@ import type { IAttestedClaim, CompressedAttestedClaim } from './AttestedClaim' import type { IClaim, CompressedClaim } from './Claim' -import type { IDelegationBaseNode } from './Delegation' +import type { IDelegationNode } from './Delegation' export type Hash = string @@ -26,7 +26,7 @@ export interface IRequestForAttestation { claimNonceMap: Record claimHashes: Hash[] claimerSignature: string - delegationId: IDelegationBaseNode['id'] | null + delegationId: IDelegationNode['id'] | null legitimations: IAttestedClaim[] rootHash: Hash } diff --git a/packages/types/src/Terms.ts b/packages/types/src/Terms.ts index af5badbaf..3c64e1b8b 100644 --- a/packages/types/src/Terms.ts +++ b/packages/types/src/Terms.ts @@ -12,7 +12,7 @@ import type { IAttestedClaim, CompressedAttestedClaim } from './AttestedClaim' import type { CompressedCType, ICType } from './CType' -import type { IDelegationBaseNode } from './Delegation' +import type { IDelegationNode } from './Delegation' import type { IQuoteAttesterSigned, CompressedQuoteAttesterSigned, @@ -23,7 +23,7 @@ import type { PartialClaim } from './Claim' export interface ITerms { claim: PartialClaim legitimations: IAttestedClaim[] - delegationId?: IDelegationBaseNode['id'] + delegationId?: IDelegationNode['id'] quote?: IQuoteAttesterSigned prerequisiteClaims?: ICType['hash'] cTypes?: ICType[] @@ -32,7 +32,7 @@ export interface ITerms { export type CompressedTerms = [ CompressedPartialClaim, CompressedAttestedClaim[], - IDelegationBaseNode['id'] | undefined, + IDelegationNode['id'] | undefined, CompressedQuoteAttesterSigned | undefined, ICType['hash'] | undefined, CompressedCType[] | undefined From 09cefca3b3fa6912eb8b44265e6ce7dca522bd1e Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 28 Jul 2021 15:37:14 +0200 Subject: [PATCH 04/21] fix: more refactoring going on --- .../__integrationtests__/Delegation.spec.ts | 71 ++++++++++++------- .../src/delegation/DelegationNode.chain.ts | 2 +- .../src/delegation/DelegationNode.spec.ts | 20 +++--- .../core/src/delegation/DelegationNode.ts | 20 +++++- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index deecaa83e..6011483fb 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -9,7 +9,7 @@ * @group integration/delegation */ -import type { ICType } from '@kiltprotocol/types' +import type { ICType, IDelegationNode } from '@kiltprotocol/types' import { Permission } from '@kiltprotocol/types' import { UUID } from '@kiltprotocol/utils' import { BlockchainUtils } from '@kiltprotocol/chain-helpers' @@ -34,39 +34,41 @@ import { WS_ADDRESS, } from './utils' -async function writeRoot( +async function writeHierarchy( delegator: Identity, ctypeHash: ICType['hash'] -): Promise { - const root = new DelegationHierarchyDetails({ +): Promise<{ details: DelegationHierarchyDetails; rootNode: DelegationNode }> { + const details = new DelegationHierarchyDetails({ rootId: UUID.generate(), cTypeHash: ctypeHash, }) - await root.store().then((tx) => + await details.store().then((tx) => BlockchainUtils.signAndSubmitTx(tx, delegator, { resolveOn: BlockchainUtils.IS_IN_BLOCK, reSign: true, }) ) - return root + + const rootNode = (await DelegationNode.query( + details.rootId + )) as DelegationNode + return { details, rootNode } } async function addDelegation( - parentNode: DelegationNode | DelegationNode, + hierarchyId: IDelegationNode['id'], + parentId: DelegationNode['id'], delegator: Identity, delegee: Identity, permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] ): Promise { - const rootId = - parentNode instanceof DelegationRootNode ? parentNode.id : parentNode.hierarchyId - const delegation = new DelegationNode({ - id: UUID.generate(), - hierarchyId: rootId, - account: delegee.address, - permissions, - parentId: parentNode.id, - revoked: false, - }) + const delegation = DelegationNode.new( + UUID.generate(), + hierarchyId, + parentId, + delegee.address, + permissions + ) await delegation .store(delegee.signStr(delegation.generateHash())) .then((tx) => @@ -99,8 +101,13 @@ beforeAll(async () => { }, 30_000) it('should be possible to delegate attestation rights', async () => { - const rootNode = await writeRoot(root, DriversLicense.hash) - const delegatedNode = await addDelegation(rootNode, root, attester) + const { rootNode } = await writeHierarchy(root, DriversLicense.hash) + const delegatedNode = await addDelegation( + rootNode.id, + rootNode.id, + root, + attester + ) await Promise.all([ expect(rootNode.verify()).resolves.toBeTruthy(), expect(delegatedNode.verify()).resolves.toBeTruthy(), @@ -108,12 +115,20 @@ it('should be possible to delegate attestation rights', async () => { }, 60_000) describe('and attestation rights have been delegated', () => { - let rootNode: DelegationRootNode + let hierarchyDetails: DelegationHierarchyDetails + let rootNode: DelegationNode let delegatedNode: DelegationNode beforeAll(async () => { - rootNode = await writeRoot(root, DriversLicense.hash) - delegatedNode = await addDelegation(rootNode, root, attester) + const result = await writeHierarchy(root, DriversLicense.hash) + hierarchyDetails = result.details + rootNode = result.rootNode + delegatedNode = await addDelegation( + hierarchyDetails.rootId, + hierarchyDetails.rootId, + root, + attester + ) await Promise.all([ expect(rootNode.verify()).resolves.toBeTruthy(), @@ -178,9 +193,13 @@ describe('revocation', () => { }) it('delegator can revoke delegation', async () => { - const delegationRoot = await writeRoot(delegator, DriversLicense.hash) + const { details, rootNode } = await writeHierarchy( + delegator, + DriversLicense.hash + ) const delegationA = await addDelegation( - delegationRoot, + details.rootId, + details.rootId, delegator, firstDelegee ) @@ -196,7 +215,7 @@ describe('revocation', () => { }, 40_000) it('delegee cannot revoke root but can revoke own delegation', async () => { - const delegationRoot = await writeRoot(delegator, DriversLicense.hash) + const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) const delegationA = await addDelegation( delegationRoot, delegator, @@ -224,7 +243,7 @@ describe('revocation', () => { }, 60_000) it('delegator can revoke root, revoking all delegations in tree', async () => { - const delegationRoot = await writeRoot(delegator, DriversLicense.hash) + const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) const delegationA = await addDelegation( delegationRoot, delegator, diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index f89310b1e..4d44518b4 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -24,7 +24,7 @@ import { import DelegationNode from './DelegationNode' import { permissionsAsBitset } from './DelegationNode.utils' -const log = ConfigService.LoggingFactory.getLogger('DelegationBaseNode') +const log = ConfigService.LoggingFactory.getLogger('DelegationNode') /** * @param delegation The delegation to store on chain. diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index 3851ec618..e7d6c2ae4 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -63,15 +63,13 @@ beforeAll(() => { describe('Delegation', () => { it('delegation generate hash', () => { - const node = new DelegationNode({ + const node = DelegationNode.new( id, hierarchyId, parentId, - account: identityBob.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - revoked: false, - }) + identityBob.address, + [Permission.DELEGATE] + ) const hash: string = node.generateHash() expect(hash).toBe( '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' @@ -79,15 +77,13 @@ describe('Delegation', () => { }) it('delegation permissionsAsBitset', () => { - const node = new DelegationNode({ + const node = DelegationNode.new( id, hierarchyId, - account: identityBob.address, - childrenIds: [], - permissions: [Permission.DELEGATE], parentId, - revoked: false, - }) + identityBob.address, + [Permission.DELEGATE] + ) const permissions: Uint8Array = permissionsAsBitset(node) const expected: Uint8Array = new Uint8Array(4) expected[0] = 2 diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index eb859d443..68ca7d0f8 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -60,7 +60,7 @@ export default class DelegationNode implements IDelegationNode { * * @param delegationNodeInput - The base object from which to create the delegation node. */ - public constructor(delegationNodeInput: IDelegationNode) { + constructor(delegationNodeInput: IDelegationNode) { this.id = delegationNodeInput.id this.hierarchyId = delegationNodeInput.hierarchyId this.parentId = delegationNodeInput.parentId @@ -71,6 +71,24 @@ export default class DelegationNode implements IDelegationNode { DelegationNodeUtils.errorCheck(this) } + public static new( + id: IDelegationNode['id'], + hierarchyId: IDelegationNode['hierarchyId'], + parentId: string, // Cannot be undefined here + account: IDelegationNode['account'], + permissions: IDelegationNode['permissions'] + ): DelegationNode { + return new DelegationNode({ + id, + hierarchyId, + parentId, + account, + permissions, + childrenIds: [], + revoked: false, + }) + } + /** * [ASYNC] Fetches the details of the hierarchy this delegation node belongs to. * From 5a56f0a115b4110a90c44d01c863cd7996c0dec4 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 28 Jul 2021 16:27:34 +0200 Subject: [PATCH 05/21] feat: (wip) refactoring Delegation stuff --- .../__integrationtests__/Delegation.spec.ts | 550 +++++----- packages/core/src/attestation/Attestation.ts | 2 +- .../DelegationHierarchyDetails.chain.ts | 17 +- .../DelegationHierarchyDetails.spec.ts | 284 ++--- .../delegation/DelegationHierarchyDetails.ts | 62 -- .../src/delegation/DelegationNode.spec.ts | 986 +++++++++--------- .../core/src/delegation/DelegationNode.ts | 30 +- 7 files changed, 937 insertions(+), 994 deletions(-) delete mode 100644 packages/core/src/delegation/DelegationHierarchyDetails.ts diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index 6011483fb..faef295f4 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -1,304 +1,304 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ +// /** +// * Copyright 2018-2021 BOTLabs GmbH. +// * +// * This source code is licensed under the BSD 4-Clause "Original" license +// * found in the LICENSE file in the root directory of this source tree. +// */ -/** - * @group integration/delegation - */ +// /** +// * @group integration/delegation +// */ -import type { ICType, IDelegationNode } from '@kiltprotocol/types' -import { Permission } from '@kiltprotocol/types' -import { UUID } from '@kiltprotocol/utils' -import { BlockchainUtils } from '@kiltprotocol/chain-helpers' -import { DelegationHierarchyDetails } from '@kiltprotocol/sdk-js' -import { AttestedClaim, Identity } from '..' -import Attestation from '../attestation/Attestation' -import { config, disconnect } from '../kilt' -import Claim from '../claim/Claim' -import { - fetchChildren, - getAttestationHashes, -} from '../delegation/DelegationNode.chain' -import { decodeDelegationNode } from '../delegation/DelegationDecoder' -import DelegationNode from '../delegation/DelegationNode' -import RequestForAttestation from '../requestforattestation/RequestForAttestation' -import { - CtypeOnChain, - DriversLicense, - wannabeAlice, - wannabeBob, - wannabeFaucet, - WS_ADDRESS, -} from './utils' +// import type { ICType, IDelegationNode } from '@kiltprotocol/types' +// import { Permission } from '@kiltprotocol/types' +// import { UUID } from '@kiltprotocol/utils' +// import { BlockchainUtils } from '@kiltprotocol/chain-helpers' +// import { DelegationHierarchyDetails } from '@kiltprotocol/sdk-js' +// import { AttestedClaim, Identity } from '..' +// import Attestation from '../attestation/Attestation' +// import { config, disconnect } from '../kilt' +// import Claim from '../claim/Claim' +// import { +// fetchChildren, +// getAttestationHashes, +// } from '../delegation/DelegationNode.chain' +// import { decodeDelegationNode } from '../delegation/DelegationDecoder' +// import DelegationNode from '../delegation/DelegationNode' +// import RequestForAttestation from '../requestforattestation/RequestForAttestation' +// import { +// CtypeOnChain, +// DriversLicense, +// wannabeAlice, +// wannabeBob, +// wannabeFaucet, +// WS_ADDRESS, +// } from './utils' -async function writeHierarchy( - delegator: Identity, - ctypeHash: ICType['hash'] -): Promise<{ details: DelegationHierarchyDetails; rootNode: DelegationNode }> { - const details = new DelegationHierarchyDetails({ - rootId: UUID.generate(), - cTypeHash: ctypeHash, - }) +// async function writeHierarchy( +// delegator: Identity, +// ctypeHash: ICType['hash'] +// ): Promise<{ details: DelegationHierarchyDetails; rootNode: DelegationNode }> { +// const details = new DelegationHierarchyDetails({ +// rootId: UUID.generate(), +// cTypeHash: ctypeHash, +// }) - await details.store().then((tx) => - BlockchainUtils.signAndSubmitTx(tx, delegator, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) +// await details.store().then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, delegator, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) - const rootNode = (await DelegationNode.query( - details.rootId - )) as DelegationNode - return { details, rootNode } -} -async function addDelegation( - hierarchyId: IDelegationNode['id'], - parentId: DelegationNode['id'], - delegator: Identity, - delegee: Identity, - permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] -): Promise { - const delegation = DelegationNode.new( - UUID.generate(), - hierarchyId, - parentId, - delegee.address, - permissions - ) - await delegation - .store(delegee.signStr(delegation.generateHash())) - .then((tx) => - BlockchainUtils.signAndSubmitTx(tx, delegator, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - return delegation -} +// const rootNode = (await DelegationNode.query( +// details.rootId +// )) as DelegationNode +// return { details, rootNode } +// } +// async function addDelegation( +// hierarchyId: IDelegationNode['id'], +// parentId: DelegationNode['id'], +// delegator: Identity, +// delegee: Identity, +// permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] +// ): Promise { +// const delegation = DelegationNode.new( +// UUID.generate(), +// hierarchyId, +// parentId, +// delegee.address, +// permissions +// ) +// await delegation +// .store(delegee.signStr(delegation.generateHash())) +// .then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, delegator, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// return delegation +// } -let root: Identity -let claimer: Identity -let attester: Identity +// let root: Identity +// let claimer: Identity +// let attester: Identity -beforeAll(async () => { - config({ address: WS_ADDRESS }) - root = wannabeFaucet - claimer = wannabeBob - attester = wannabeAlice +// beforeAll(async () => { +// config({ address: WS_ADDRESS }) +// root = wannabeFaucet +// claimer = wannabeBob +// attester = wannabeAlice - if (!(await CtypeOnChain(DriversLicense))) { - await DriversLicense.store().then((tx) => - BlockchainUtils.signAndSubmitTx(tx, attester, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - } -}, 30_000) +// if (!(await CtypeOnChain(DriversLicense))) { +// await DriversLicense.store().then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, attester, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// } +// }, 30_000) -it('should be possible to delegate attestation rights', async () => { - const { rootNode } = await writeHierarchy(root, DriversLicense.hash) - const delegatedNode = await addDelegation( - rootNode.id, - rootNode.id, - root, - attester - ) - await Promise.all([ - expect(rootNode.verify()).resolves.toBeTruthy(), - expect(delegatedNode.verify()).resolves.toBeTruthy(), - ]) -}, 60_000) +// it('should be possible to delegate attestation rights', async () => { +// const { rootNode } = await writeHierarchy(root, DriversLicense.hash) +// const delegatedNode = await addDelegation( +// rootNode.id, +// rootNode.id, +// root, +// attester +// ) +// await Promise.all([ +// expect(rootNode.verify()).resolves.toBeTruthy(), +// expect(delegatedNode.verify()).resolves.toBeTruthy(), +// ]) +// }, 60_000) -describe('and attestation rights have been delegated', () => { - let hierarchyDetails: DelegationHierarchyDetails - let rootNode: DelegationNode - let delegatedNode: DelegationNode +// describe('and attestation rights have been delegated', () => { +// let hierarchyDetails: DelegationHierarchyDetails +// let rootNode: DelegationNode +// let delegatedNode: DelegationNode - beforeAll(async () => { - const result = await writeHierarchy(root, DriversLicense.hash) - hierarchyDetails = result.details - rootNode = result.rootNode - delegatedNode = await addDelegation( - hierarchyDetails.rootId, - hierarchyDetails.rootId, - root, - attester - ) +// beforeAll(async () => { +// const result = await writeHierarchy(root, DriversLicense.hash) +// hierarchyDetails = result.details +// rootNode = result.rootNode +// delegatedNode = await addDelegation( +// hierarchyDetails.rootId, +// hierarchyDetails.rootId, +// root, +// attester +// ) - await Promise.all([ - expect(rootNode.verify()).resolves.toBeTruthy(), - expect(delegatedNode.verify()).resolves.toBeTruthy(), - ]) - }, 75_000) +// await Promise.all([ +// expect(rootNode.verify()).resolves.toBeTruthy(), +// expect(delegatedNode.verify()).resolves.toBeTruthy(), +// ]) +// }, 75_000) - it("should be possible to attest a claim in the root's name and revoke it by the root", async () => { - const content = { - name: 'Ralph', - age: 12, - } - const claim = Claim.fromCTypeAndClaimContents( - DriversLicense, - content, - claimer.address - ) - const request = RequestForAttestation.fromClaimAndIdentity(claim, claimer, { - delegationId: delegatedNode.id, - }) - expect(request.verifyData()).toBeTruthy() - expect(request.verifySignature()).toBeTruthy() +// it("should be possible to attest a claim in the root's name and revoke it by the root", async () => { +// const content = { +// name: 'Ralph', +// age: 12, +// } +// const claim = Claim.fromCTypeAndClaimContents( +// DriversLicense, +// content, +// claimer.address +// ) +// const request = RequestForAttestation.fromClaimAndIdentity(claim, claimer, { +// delegationId: delegatedNode.id, +// }) +// expect(request.verifyData()).toBeTruthy() +// expect(request.verifySignature()).toBeTruthy() - const attestation = Attestation.fromRequestAndPublicIdentity( - request, - attester.getPublicIdentity() - ) - await attestation.store().then((tx) => - BlockchainUtils.signAndSubmitTx(tx, attester, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) +// const attestation = Attestation.fromRequestAndPublicIdentity( +// request, +// attester.getPublicIdentity() +// ) +// await attestation.store().then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, attester, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) - const attClaim = AttestedClaim.fromRequestAndAttestation( - request, - attestation - ) - expect(attClaim.verifyData()).toBeTruthy() - await expect(attClaim.verify()).resolves.toBeTruthy() +// const attClaim = AttestedClaim.fromRequestAndAttestation( +// request, +// attestation +// ) +// expect(attClaim.verifyData()).toBeTruthy() +// await expect(attClaim.verify()).resolves.toBeTruthy() - // revoke attestation through root - await attClaim.attestation.revoke(1).then((tx) => - BlockchainUtils.signAndSubmitTx(tx, root, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - await expect(attClaim.verify()).resolves.toBeFalsy() - }, 75_000) -}) +// // revoke attestation through root +// await attClaim.attestation.revoke(1).then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, root, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// await expect(attClaim.verify()).resolves.toBeFalsy() +// }, 75_000) +// }) -describe('revocation', () => { - let delegator: Identity = root - let firstDelegee: Identity = attester - let secondDelegee: Identity = claimer +// describe('revocation', () => { +// let delegator: Identity = root +// let firstDelegee: Identity = attester +// let secondDelegee: Identity = claimer - beforeAll(() => { - delegator = root - firstDelegee = attester - secondDelegee = claimer - }) +// beforeAll(() => { +// delegator = root +// firstDelegee = attester +// secondDelegee = claimer +// }) - it('delegator can revoke delegation', async () => { - const { details, rootNode } = await writeHierarchy( - delegator, - DriversLicense.hash - ) - const delegationA = await addDelegation( - details.rootId, - details.rootId, - delegator, - firstDelegee - ) - await expect( - delegationA.revoke(delegator.address).then((tx) => - BlockchainUtils.signAndSubmitTx(tx, delegator, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - ).resolves.not.toThrow() - await expect(delegationA.verify()).resolves.toBe(false) - }, 40_000) +// it('delegator can revoke delegation', async () => { +// const { details, rootNode } = await writeHierarchy( +// delegator, +// DriversLicense.hash +// ) +// const delegationA = await addDelegation( +// details.rootId, +// details.rootId, +// delegator, +// firstDelegee +// ) +// await expect( +// delegationA.revoke(delegator.address).then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, delegator, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// ).resolves.not.toThrow() +// await expect(delegationA.verify()).resolves.toBe(false) +// }, 40_000) - it('delegee cannot revoke root but can revoke own delegation', async () => { - const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) - const delegationA = await addDelegation( - delegationRoot, - delegator, - firstDelegee - ) - await expect( - delegationRoot.revoke().then((tx) => - BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - ).rejects.toThrow() - await expect(delegationRoot.verify()).resolves.toBe(true) +// it('delegee cannot revoke root but can revoke own delegation', async () => { +// const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) +// const delegationA = await addDelegation( +// delegationRoot, +// delegator, +// firstDelegee +// ) +// await expect( +// delegationRoot.revoke().then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// ).rejects.toThrow() +// await expect(delegationRoot.verify()).resolves.toBe(true) - await expect( - delegationA.revoke(firstDelegee.address).then((tx) => - BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - ).resolves.not.toThrow() - await expect(delegationA.verify()).resolves.toBe(false) - }, 60_000) +// await expect( +// delegationA.revoke(firstDelegee.address).then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// ).resolves.not.toThrow() +// await expect(delegationA.verify()).resolves.toBe(false) +// }, 60_000) - it('delegator can revoke root, revoking all delegations in tree', async () => { - const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) - const delegationA = await addDelegation( - delegationRoot, - delegator, - firstDelegee - ) - const delegationB = await addDelegation( - delegationA, - firstDelegee, - secondDelegee - ) - await expect( - delegationRoot.revoke().then((tx) => - BlockchainUtils.signAndSubmitTx(tx, delegator, { - resolveOn: BlockchainUtils.IS_IN_BLOCK, - reSign: true, - }) - ) - ).resolves.not.toThrow() +// it('delegator can revoke root, revoking all delegations in tree', async () => { +// const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) +// const delegationA = await addDelegation( +// delegationRoot, +// delegator, +// firstDelegee +// ) +// const delegationB = await addDelegation( +// delegationA, +// firstDelegee, +// secondDelegee +// ) +// await expect( +// delegationRoot.revoke().then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, delegator, { +// resolveOn: BlockchainUtils.IS_IN_BLOCK, +// reSign: true, +// }) +// ) +// ).resolves.not.toThrow() - await Promise.all([ - expect(delegationRoot.verify()).resolves.toBe(false), - expect(delegationA.verify()).resolves.toBe(false), - expect(delegationB.verify()).resolves.toBe(false), - ]) - }, 60_000) -}) +// await Promise.all([ +// expect(delegationRoot.verify()).resolves.toBe(false), +// expect(delegationA.verify()).resolves.toBe(false), +// expect(delegationB.verify()).resolves.toBe(false), +// ]) +// }, 60_000) +// }) -describe('handling queries to data not on chain', () => { - it('getChildIds on empty', async () => { - return expect(getChildIds('0x012012012')).resolves.toEqual([]) - }) +// describe('handling queries to data not on chain', () => { +// it('getChildIds on empty', async () => { +// return expect(getChildIds('0x012012012')).resolves.toEqual([]) +// }) - it('DelegationNode query on empty', async () => { - return expect(DelegationNode.query('0x012012012')).resolves.toBeNull() - }) +// it('DelegationNode query on empty', async () => { +// return expect(DelegationNode.query('0x012012012')).resolves.toBeNull() +// }) - it('DelegationRootNode.query on empty', async () => { - return expect(DelegationRootNode.query('0x012012012')).resolves.toBeNull() - }) +// it('DelegationRootNode.query on empty', async () => { +// return expect(DelegationRootNode.query('0x012012012')).resolves.toBeNull() +// }) - it('getAttestationHashes on empty', async () => { - return expect(getAttestationHashes('0x012012012')).resolves.toEqual([]) - }) +// it('getAttestationHashes on empty', async () => { +// return expect(getAttestationHashes('0x012012012')).resolves.toEqual([]) +// }) - it('fetchChildren on empty', async () => { - return expect( - fetchChildren(['0x012012012']).then((res) => - res.map((el) => { - return { id: el.id, codec: decodeDelegationNode(el.codec) } - }) - ) - ).resolves.toEqual([{ id: '0x012012012', codec: null }]) - }) -}) +// it('fetchChildren on empty', async () => { +// return expect( +// fetchChildren(['0x012012012']).then((res) => +// res.map((el) => { +// return { id: el.id, codec: decodeDelegationNode(el.codec) } +// }) +// ) +// ).resolves.toEqual([{ id: '0x012012012', codec: null }]) +// }) +// }) -afterAll(() => { - disconnect() -}) +// afterAll(() => { +// disconnect() +// }) diff --git a/packages/core/src/attestation/Attestation.ts b/packages/core/src/attestation/Attestation.ts index ee6a93201..562adb017 100644 --- a/packages/core/src/attestation/Attestation.ts +++ b/packages/core/src/attestation/Attestation.ts @@ -25,7 +25,7 @@ import type { IRequestForAttestation, CompressedAttestation, } from '@kiltprotocol/types' -import { DelegationHierarchyDetails } from '@kiltprotocol/sdk-js' +import { DelegationHierarchyDetails } from '../delegation' import { revoke, query, store } from './Attestation.chain' import AttestationUtils from './Attestation.utils' import DelegationNode from '../delegation/DelegationNode' diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts index f7f0c3e40..6e26b99b0 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts @@ -5,29 +5,24 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { +import type { IDelegationHierarchyDetails, IDelegationNode, SubmittableExtrinsic, -} from '@kiltprotocol/sdk-js' +} from '@kiltprotocol/types' import { Option } from '@polkadot/types' -import { BlockchainApiConnection } from 'chain-helpers/src/blockchainApiConnection' +import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' import { decodeDelegationHierarchyDetails, DelegationHierarchyDetailsRecord, IChainDelegationHierarchyDetails, } from './DelegationDecoder' -import DelegationHierarchyDetails from './DelegationHierarchyDetails' /** * @packageDocumentation * @module DelegationHierarchyDetails */ -/** - * @param delegationHierarchyDetails The details associated with the delegation hierarchy being created. - * @internal - */ export async function store( delegationHierarchyDetails: IDelegationHierarchyDetails ): Promise { @@ -41,7 +36,7 @@ export async function store( export async function query( rootId: IDelegationNode['id'] -): Promise { +): Promise { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() const decoded: DelegationHierarchyDetailsRecord | null = decodeDelegationHierarchyDetails( await blockchain.api.query.delegation.delegationHierarchies< @@ -51,9 +46,9 @@ export async function query( if (!decoded) { return null } - const details = new DelegationHierarchyDetails({ + const details = { rootId, cTypeHash: decoded.cTypeHash, - }) + } return details } diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts index c3a97ac66..2a79894d9 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts @@ -1,142 +1,142 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * @group unit/delegation - */ - -import { Crypto } from '@kiltprotocol/utils' -import { - BlockchainUtils, - BlockchainApiConnection, -} from '@kiltprotocol/chain-helpers' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' -import { Identity } from '../identity' - -import DelegationHierarchyDetails from './DelegationHierarchyDetails' -import Kilt from '../kilt/Kilt' - -jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' -) - -describe('Delegation', () => { - let identityAlice: Identity - let ctypeHash: string - let ROOT_IDENTIFIER: string - let ROOT_SUCCESS: string - Kilt.config({ address: 'ws://testString' }) - - beforeAll(async () => { - identityAlice = Identity.buildFromURI('//Alice') - ctypeHash = `0x6b696c743a63747970653a307830303031000000000000000000000000000000` - ROOT_IDENTIFIER = Crypto.hashStr('1') - ROOT_SUCCESS = Crypto.hashStr('success') - - require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.hierarchies.mockReturnValue( - mockChainQueryReturn('delegation', 'hierarchies', [ctypeHash]) - ) - require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.delegations.mockReturnValue( - mockChainQueryReturn('delegation', 'delegations', [ - ROOT_IDENTIFIER, - null, - [], - ]) - ) - }) - - it('stores root delegation', async () => { - const rootDelegation = new DelegationRootNode({ - id: ROOT_IDENTIFIER, - cTypeHash: ctypeHash, - account: identityAlice.address, - revoked: false, - }) - await rootDelegation - .store() - .then((tx) => - BlockchainUtils.signAndSubmitTx(tx, identityAlice, { reSign: true }) - ) - - const rootNode = await DelegationRootNode.query(ROOT_IDENTIFIER) - if (rootNode) { - expect(rootNode.id).toBe(ROOT_IDENTIFIER) - } - }) - - it('query root delegation', async () => { - const queriedDelegation = await DelegationRootNode.query(ROOT_IDENTIFIER) - expect(queriedDelegation).not.toBe(undefined) - if (queriedDelegation) { - expect(queriedDelegation.account).toBe(identityAlice.address) - expect(queriedDelegation.cTypeHash).toBe(ctypeHash) - expect(queriedDelegation.id).toBe(ROOT_IDENTIFIER) - } - }) - - it('root delegation verify', async () => { - require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.root = jest.fn( - async (rootId) => { - if (rootId === ROOT_SUCCESS) { - const tuple = mockChainQueryReturn('delegation', 'root', [ - ctypeHash, - identityAlice.address, - false, - ]) - - return Promise.resolve(tuple) - } - const tuple = mockChainQueryReturn('delegation', 'root', [ - ctypeHash, - identityAlice.address, - true, - ]) - - return Promise.resolve(tuple) - } - ) - - expect( - await new DelegationRootNode({ - id: ROOT_IDENTIFIER, - cTypeHash: ctypeHash, - account: identityAlice.address, - revoked: false, - }).verify() - ).toBe(false) - - expect( - await new DelegationRootNode({ - id: ROOT_SUCCESS, - cTypeHash: ctypeHash, - account: identityAlice.address, - revoked: true, - }).verify() - ).toBe(true) - }) - - it('root delegation verify', async () => { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - - const aDelegationRootNode = new DelegationRootNode({ - id: ROOT_IDENTIFIER, - cTypeHash: ctypeHash, - account: identityAlice.address, - revoked: false, - }) - const revokeStatus = await aDelegationRootNode - .revoke() - .then((tx) => - BlockchainUtils.signAndSubmitTx(tx, identityAlice, { reSign: true }) - ) - expect(blockchain.api.tx.delegation.revokeRoot).toBeCalledWith( - ROOT_IDENTIFIER, - 1 - ) - expect(revokeStatus).toBeDefined() - }) -}) \ No newline at end of file +// /** +// * Copyright 2018-2021 BOTLabs GmbH. +// * +// * This source code is licensed under the BSD 4-Clause "Original" license +// * found in the LICENSE file in the root directory of this source tree. +// */ + +// /** +// * @group unit/delegation +// */ + +// import { Crypto } from '@kiltprotocol/utils' +// import { +// BlockchainUtils, +// BlockchainApiConnection, +// } from '@kiltprotocol/chain-helpers' +// import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +// import { Identity } from '../identity' + +// import DelegationHierarchyDetails from './DelegationHierarchyDetails' +// import Kilt from '../kilt/Kilt' +// import { DelegationNode } from '@kiltprotocol/sdk-js' + +// jest.mock( +// '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' +// ) + +// describe('Delegation', () => { +// let identityAlice: Identity +// let ctypeHash: string +// let ROOT_IDENTIFIER: string +// let ROOT_SUCCESS: string +// Kilt.config({ address: 'ws://testString' }) + +// beforeAll(async () => { +// identityAlice = Identity.buildFromURI('//Alice') +// ctypeHash = `0x6b696c743a63747970653a307830303031000000000000000000000000000000` +// ROOT_IDENTIFIER = Crypto.hashStr('1') +// ROOT_SUCCESS = Crypto.hashStr('success') + +// require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.hierarchies.mockReturnValue( +// mockChainQueryReturn('delegation', 'hierarchies', [ctypeHash]) +// ) +// require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.delegations.mockReturnValue( +// mockChainQueryReturn('delegation', 'delegations', [ +// ROOT_IDENTIFIER, +// null, +// [], +// ]) +// ) +// }) + +// it('stores root delegation', async () => { +// const hierarchyDetails = new DelegationHierarchyDetails({ +// rootId: ROOT_IDENTIFIER, +// cTypeHash: ctypeHash, +// }) +// await hierarchyDetails +// .store() +// .then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, identityAlice, { reSign: true }) +// ) + +// const rootNode = await DelegationNode.query(ROOT_IDENTIFIER) +// if (rootNode) { +// expect(rootNode.id).toBe(ROOT_IDENTIFIER) +// } +// }) + +// it('query root delegation', async () => { +// const queriedDelegation = await DelegationNode.query(ROOT_IDENTIFIER) +// expect(queriedDelegation).not.toBe(undefined) +// if (queriedDelegation) { +// const details = await queriedDelegation.getHierarchyDetails() +// expect(queriedDelegation.account).toBe(identityAlice.address) +// expect(details.cTypeHash).toBe(ctypeHash) +// expect(queriedDelegation.id).toBe(ROOT_IDENTIFIER) +// } +// }) + +// it('root delegation verify', async () => { +// require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.root = jest.fn( +// async (rootId) => { +// if (rootId === ROOT_SUCCESS) { +// const tuple = mockChainQueryReturn( +// 'delegation', +// 'hierarchies', +// ctypeHash +// ) + +// return Promise.resolve(tuple) +// } +// const tuple = mockChainQueryReturn('delegation', 'hierarchies', [ +// ctypeHash, +// identityAlice.address, +// true, +// ]) + +// return Promise.resolve(tuple) +// } +// ) + +// expect( +// await new DelegationRootNode({ +// id: ROOT_IDENTIFIER, +// cTypeHash: ctypeHash, +// account: identityAlice.address, +// revoked: false, +// }).verify() +// ).toBe(false) + +// expect( +// await new DelegationRootNode({ +// id: ROOT_SUCCESS, +// cTypeHash: ctypeHash, +// account: identityAlice.address, +// revoked: true, +// }).verify() +// ).toBe(true) +// }) + +// it('root delegation verify', async () => { +// const blockchain = await BlockchainApiConnection.getConnectionOrConnect() + +// const aDelegationRootNode = new DelegationRootNode({ +// id: ROOT_IDENTIFIER, +// cTypeHash: ctypeHash, +// account: identityAlice.address, +// revoked: false, +// }) +// const revokeStatus = await aDelegationRootNode +// .revoke() +// .then((tx) => +// BlockchainUtils.signAndSubmitTx(tx, identityAlice, { reSign: true }) +// ) +// expect(blockchain.api.tx.delegation.revokeRoot).toBeCalledWith( +// ROOT_IDENTIFIER, +// 1 +// ) +// expect(revokeStatus).toBeDefined() +// }) +// }) \ No newline at end of file diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.ts b/packages/core/src/delegation/DelegationHierarchyDetails.ts deleted file mode 100644 index f4fefe0cd..000000000 --- a/packages/core/src/delegation/DelegationHierarchyDetails.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * KILT enables top-down trust structures. - * On the lowest level, a delegation structure is always a **tree**. - * Each tree represents a delegation **hierarchy**, to each of which there are details associated. - * - * @packageDocumentation - * @module DelegationHierarchyDetails - * @preferred - */ - -import { ConfigService } from '@kiltprotocol/config' -import type { - ICType, - IDelegationHierarchyDetails, - IDelegationNode, - SubmittableExtrinsic, -} from '@kiltprotocol/types' -import { query, store } from './DelegationHierarchyDetails.chain' - -const log = ConfigService.LoggingFactory.getLogger('DelegationHierarchyDetails') - -export default class DelegationHierarchyDetails - implements IDelegationHierarchyDetails { - public rootId: IDelegationNode['id'] - public cTypeHash: ICType['hash'] - - /** - * Builds a new [DelegationHierarchy] instance. - * - * @param delegationHierarchyInput - The base object from which to create the delegation base node. - */ - public constructor(delegationHierarchyInput: IDelegationHierarchyDetails) { - this.rootId = delegationHierarchyInput.rootId - this.cTypeHash = delegationHierarchyInput.cTypeHash - } - - public static async query( - rootId: IDelegationNode['id'] - ): Promise { - log.info(`:: query('${rootId}')`) - const result = await query(rootId) - if (result) { - log.info(`result: ${JSON.stringify(result)}`) - } else { - log.info(`Delegation hierarchy not found`) - } - - return result - } - - public async store(): Promise { - log.debug(`:: store(${this.rootId})`) - return store(this) - } -} diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index e7d6c2ae4..2949de48a 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -1,493 +1,493 @@ -/** - * Copyright 2018-2021 BOTLabs GmbH. - * - * This source code is licensed under the BSD 4-Clause "Original" license - * found in the LICENSE file in the root directory of this source tree. - */ - -/** - * @group unit/delegation - */ - -/* eslint-disable @typescript-eslint/ban-ts-comment */ - -import { IDelegationNode, Permission } from '@kiltprotocol/types' -import { encodeAddress } from '@polkadot/keyring' -import { Crypto, SDKErrors } from '@kiltprotocol/utils' -import Identity from '../identity' -import DelegationNode from './DelegationNode' -import DelegationHierarchyDetails from './DelegationHierarchyDetails' -import { permissionsAsBitset, errorCheck } from './DelegationNode.utils' - -let hierarchiesDetails: Record = {} -let nodes: Record = {} - -jest.mock('./DelegationNode.chain', () => ({ - getChildren: jest.fn(async (id: string) => { - return nodes[id].childrenIds - }), - query: jest.fn(async (id: string) => nodes[id] || null), -})) - -jest.mock('./DelegationHierarchyDetails.chain', () => ({ - query: jest.fn(async (id: string) => hierarchiesDetails[id] || null), -})) - -let identityAlice: Identity -let identityBob: Identity -let id: string -let successId: string -let failureId: string -let hierarchyId: string -let parentId: string -let hashList: string[] -let addresses: string[] - -beforeAll(() => { - identityAlice = Identity.buildFromURI('//Alice') - identityBob = Identity.buildFromURI('//Bob') - successId = Crypto.hashStr('success') - hierarchyId = Crypto.hashStr('rootId') - id = Crypto.hashStr('id') - parentId = Crypto.hashStr('parentId') - failureId = Crypto.hashStr('failure') - hashList = Array(10002) - .fill('') - .map((_val, index) => Crypto.hashStr(`${index + 1}`)) - addresses = Array(10002) - .fill('') - .map((_val, index) => - encodeAddress(Crypto.hash(`${index}`, 256), 38) - ) -}) - -describe('Delegation', () => { - it('delegation generate hash', () => { - const node = DelegationNode.new( - id, - hierarchyId, - parentId, - identityBob.address, - [Permission.DELEGATE] - ) - const hash: string = node.generateHash() - expect(hash).toBe( - '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' - ) - }) - - it('delegation permissionsAsBitset', () => { - const node = DelegationNode.new( - id, - hierarchyId, - parentId, - identityBob.address, - [Permission.DELEGATE] - ) - const permissions: Uint8Array = permissionsAsBitset(node) - const expected: Uint8Array = new Uint8Array(4) - expected[0] = 2 - expect(permissions.toString()).toBe(expected.toString()) - }) - - it('delegation verify', async () => { - nodes = { - [successId]: new DelegationNode({ - id: successId, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - parentId: undefined, - revoked: false, - }), - [failureId]: { - ...nodes.success, - revoked: true, - id: failureId, - } as DelegationNode, - } - - expect( - await new DelegationNode({ - id: successId, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - parentId: undefined, - revoked: false, - }).verify() - ).toBe(true) - - expect( - await new DelegationNode({ - id: failureId, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - parentId: undefined, - revoked: false, - }).verify() - ).toBe(false) - }) - - it('get delegation root', async () => { - hierarchiesDetails = { - [hierarchyId]: new DelegationHierarchyDetails({ - rootId: hierarchyId, - cTypeHash: - 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', - }), - } - - nodes = { - [hierarchyId]: new DelegationNode({ - id: hierarchyId, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - revoked: false, - }), - } - - const node: DelegationNode = new DelegationNode({ - id, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - revoked: false, - }) - const hierarchyDetails = await node.getHierarchyDetails() - - expect(hierarchyDetails).toBeDefined() - expect(hierarchyDetails.cTypeHash).toBe( - 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' - ) - }) -}) - -describe('count subtree', () => { - let topNode: DelegationNode - const a1: string = Crypto.hashStr('a1') - const b1: string = Crypto.hashStr('b1') - const b2: string = Crypto.hashStr('b2') - const c1: string = Crypto.hashStr('c1') - const c2: string = Crypto.hashStr('c2') - const d1: string = Crypto.hashStr('d1') - beforeAll(() => { - topNode = new DelegationNode({ - id: a1, - hierarchyId, - account: identityAlice.address, - childrenIds: [b1, b2], - permissions: [Permission.ATTEST, Permission.DELEGATE], - revoked: false, - }) - - nodes = { - [b1]: new DelegationNode({ - id: b1, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: a1, - childrenIds: [c1, c2], - revoked: false, - }), - [b2]: new DelegationNode({ - id: b2, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: a1, - revoked: false, - }), - [c1]: new DelegationNode({ - id: c1, - hierarchyId, - account: identityAlice.address, - childrenIds: [d1], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: b1, - revoked: false, - }), - [c2]: new DelegationNode({ - id: c2, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: b1, - revoked: false, - }), - [d1]: new DelegationNode({ - id: d1, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: c1, - revoked: false, - }), - } - }) - - it('mocks work', async () => { - expect(topNode.id).toEqual(a1) - await expect( - (await topNode.getChildren()).map((childNode) => childNode.id) - ).resolves.toBe(topNode.childrenIds) - await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) - }) - - it('counts all subnodes', async () => { - await expect(topNode.subtreeNodeCount()).resolves.toStrictEqual(5) - }) - - it('counts smaller subtrees', async () => { - await expect(nodes[b2].subtreeNodeCount()).resolves.toStrictEqual(0) - await expect(nodes[b1].subtreeNodeCount()).resolves.toStrictEqual(3) - await expect(nodes[c1].subtreeNodeCount()).resolves.toStrictEqual(1) - await expect(nodes[c2].subtreeNodeCount()).resolves.toStrictEqual(0) - await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) - }) - - it('counts all subnodes in deeply nested structure (100)', async () => { - nodes = hashList.slice(0, 101).reduce((previous, current, index) => { - return { - ...previous, - [current]: [ - new DelegationNode({ - id: current, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - childrenIds: [hashList[index + 1]], - parentId: current, - revoked: false, - }), - ], - } - }, {}) - - await expect( - nodes[hashList[0]][0].subtreeNodeCount() - ).resolves.toStrictEqual(100) - }) - - it('counts all subnodes in deeply nested structure (1000)', async () => { - nodes = hashList.slice(0, 1001).reduce((previous, current, index) => { - return { - ...previous, - [current]: [ - new DelegationNode({ - id: current, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - childrenIds: [hashList[index + 1]], - parentId: current, - revoked: false, - }), - ], - } - }, {}) - await expect( - nodes[hashList[0]][0].subtreeNodeCount() - ).resolves.toStrictEqual(1000) - }) - - it('counts all subnodes in deeply nested structure (10000)', async () => { - nodes = hashList.slice(0, 10001).reduce((previous, current, index) => { - return { - ...previous, - [current]: [ - new DelegationNode({ - id: current, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - childrenIds: [hashList[index + 1]], - parentId: current, - revoked: false, - }), - ], - } - }, {}) - await expect( - nodes[hashList[0]][0].subtreeNodeCount() - ).resolves.toStrictEqual(10000) - }) -}) - -describe('count depth', () => { - beforeAll(() => { - nodes = hashList - .slice(0, 1000) - .map( - (nodeId, index) => - new DelegationNode({ - id: nodeId, - hierarchyId, - account: addresses[index], - permissions: [Permission.DELEGATE], - childrenIds: [], - parentId: hashList[index + 1], - revoked: false, - }) - ) - .reduce((result, node) => { - return { - ...result, - [node.id]: node, - } - }, {}) - - expect(Object.keys(nodes)).toHaveLength(1000) - }) - - it('counts steps from last child till select parent', async () => { - await Promise.all( - [0, 1, 5, 10, 75, 100, 300, 500, 999].map((i) => - expect( - nodes[hashList[0]].findAncestorOwnedBy(nodes[hashList[i]].account) - ).resolves.toMatchObject({ - steps: i, - node: nodes[hashList[i]], - }) - ) - ) - }) - - it('counts various distances within the hierarchy', async () => { - await Promise.all([ - expect( - nodes[hashList[1]].findAncestorOwnedBy(nodes[hashList[2]].account) - ).resolves.toMatchObject({ - steps: 1, - node: nodes[hashList[2]], - }), - expect( - nodes[hashList[250]].findAncestorOwnedBy(nodes[hashList[450]].account) - ).resolves.toMatchObject({ steps: 200, node: nodes[hashList[450]] }), - expect( - nodes[hashList[800]].findAncestorOwnedBy(nodes[hashList[850]].account) - ).resolves.toMatchObject({ - steps: 50, - node: nodes[hashList[850]], - }), - expect( - nodes[hashList[5]].findAncestorOwnedBy(nodes[hashList[955]].account) - ).resolves.toMatchObject({ - steps: 950, - node: nodes[hashList[955]], - }), - ]) - }) - - it('returns null if trying to count backwards', async () => { - await Promise.all([ - expect( - nodes[hashList[10]].findAncestorOwnedBy(nodes[hashList[5]].account) - ).resolves.toMatchObject({ - steps: 989, - node: null, - }), - expect( - nodes[hashList[99]].findAncestorOwnedBy(nodes[hashList[95]].account) - ).resolves.toMatchObject({ steps: 900, node: null }), - expect( - nodes[hashList[900]].findAncestorOwnedBy(nodes[hashList[500]].account) - ).resolves.toMatchObject({ steps: 99, node: null }), - ]) - }) - - it('returns null if looking for non-existent account', async () => { - const noOnesAddress = encodeAddress(Crypto.hash('-1', 256), 38) - await Promise.all([ - expect( - nodes[hashList[10]].findAncestorOwnedBy(noOnesAddress) - ).resolves.toMatchObject({ - steps: 989, - node: null, - }), - expect( - nodes[hashList[99]].findAncestorOwnedBy(noOnesAddress) - ).resolves.toMatchObject({ - steps: 900, - node: null, - }), - expect( - nodes[hashList[900]].findAncestorOwnedBy(noOnesAddress) - ).resolves.toMatchObject({ - steps: 99, - node: null, - }), - ]) - }) - - it('error check should throw errors on faulty delegation nodes', async () => { - const malformedPremissionsDelegationNode = { - id, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [], - parentId: undefined, - revoked: false, - } as IDelegationNode - - const missingRootIdDelegationNode = { - id, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - parentId: undefined, - revoked: false, - } as IDelegationNode - - // @ts-expect-error - delete missingRootIdDelegationNode.hierarchyId - - const malformedRootIdDelegationNode = { - id, - hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), - account: identityAlice.address, - permissions: [Permission.DELEGATE], - parentId: undefined, - revoked: false, - } as IDelegationNode - - const malformedParentIdDelegationNode = { - id, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - parentId: 'malformed', - revoked: false, - } as IDelegationNode - - expect(() => errorCheck(malformedPremissionsDelegationNode)).toThrowError( - SDKErrors.ERROR_UNAUTHORIZED( - 'Must have at least one permission and no more then two' - ) - ) - - expect(() => errorCheck(missingRootIdDelegationNode)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_MISSING() - ) - - expect(() => errorCheck(malformedRootIdDelegationNode)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_TYPE() - ) - - expect(() => errorCheck(malformedParentIdDelegationNode)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_TYPE() - ) - }) -}) +// /** +// * Copyright 2018-2021 BOTLabs GmbH. +// * +// * This source code is licensed under the BSD 4-Clause "Original" license +// * found in the LICENSE file in the root directory of this source tree. +// */ + +// /** +// * @group unit/delegation +// */ + +// /* eslint-disable @typescript-eslint/ban-ts-comment */ + +// import { IDelegationNode, Permission } from '@kiltprotocol/types' +// import { encodeAddress } from '@polkadot/keyring' +// import { Crypto, SDKErrors } from '@kiltprotocol/utils' +// import Identity from '../identity' +// import DelegationNode from './DelegationNode' +// import DelegationHierarchyDetails from './DelegationHierarchyDetails' +// import { permissionsAsBitset, errorCheck } from './DelegationNode.utils' + +// let hierarchiesDetails: Record = {} +// let nodes: Record = {} + +// jest.mock('./DelegationNode.chain', () => ({ +// getChildren: jest.fn(async (id: string) => { +// return nodes[id].childrenIds +// }), +// query: jest.fn(async (id: string) => nodes[id] || null), +// })) + +// jest.mock('./DelegationHierarchyDetails.chain', () => ({ +// query: jest.fn(async (id: string) => hierarchiesDetails[id] || null), +// })) + +// let identityAlice: Identity +// let identityBob: Identity +// let id: string +// let successId: string +// let failureId: string +// let hierarchyId: string +// let parentId: string +// let hashList: string[] +// let addresses: string[] + +// beforeAll(() => { +// identityAlice = Identity.buildFromURI('//Alice') +// identityBob = Identity.buildFromURI('//Bob') +// successId = Crypto.hashStr('success') +// hierarchyId = Crypto.hashStr('rootId') +// id = Crypto.hashStr('id') +// parentId = Crypto.hashStr('parentId') +// failureId = Crypto.hashStr('failure') +// hashList = Array(10002) +// .fill('') +// .map((_val, index) => Crypto.hashStr(`${index + 1}`)) +// addresses = Array(10002) +// .fill('') +// .map((_val, index) => +// encodeAddress(Crypto.hash(`${index}`, 256), 38) +// ) +// }) + +// describe('Delegation', () => { +// it('delegation generate hash', () => { +// const node = DelegationNode.new( +// id, +// hierarchyId, +// parentId, +// identityBob.address, +// [Permission.DELEGATE] +// ) +// const hash: string = node.generateHash() +// expect(hash).toBe( +// '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' +// ) +// }) + +// it('delegation permissionsAsBitset', () => { +// const node = DelegationNode.new( +// id, +// hierarchyId, +// parentId, +// identityBob.address, +// [Permission.DELEGATE] +// ) +// const permissions: Uint8Array = permissionsAsBitset(node) +// const expected: Uint8Array = new Uint8Array(4) +// expected[0] = 2 +// expect(permissions.toString()).toBe(expected.toString()) +// }) + +// it('delegation verify', async () => { +// nodes = { +// [successId]: new DelegationNode({ +// id: successId, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.DELEGATE], +// parentId: undefined, +// revoked: false, +// }), +// [failureId]: { +// ...nodes.success, +// revoked: true, +// id: failureId, +// } as DelegationNode, +// } + +// expect( +// await new DelegationNode({ +// id: successId, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.DELEGATE], +// parentId: undefined, +// revoked: false, +// }).verify() +// ).toBe(true) + +// expect( +// await new DelegationNode({ +// id: failureId, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.DELEGATE], +// parentId: undefined, +// revoked: false, +// }).verify() +// ).toBe(false) +// }) + +// it('get delegation root', async () => { +// hierarchiesDetails = { +// [hierarchyId]: new DelegationHierarchyDetails({ +// rootId: hierarchyId, +// cTypeHash: +// 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', +// }), +// } + +// nodes = { +// [hierarchyId]: new DelegationNode({ +// id: hierarchyId, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.DELEGATE], +// revoked: false, +// }), +// } + +// const node: DelegationNode = new DelegationNode({ +// id, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.DELEGATE], +// revoked: false, +// }) +// const hierarchyDetails = await node.getHierarchyDetails() + +// expect(hierarchyDetails).toBeDefined() +// expect(hierarchyDetails.cTypeHash).toBe( +// 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' +// ) +// }) +// }) + +// describe('count subtree', () => { +// let topNode: DelegationNode +// const a1: string = Crypto.hashStr('a1') +// const b1: string = Crypto.hashStr('b1') +// const b2: string = Crypto.hashStr('b2') +// const c1: string = Crypto.hashStr('c1') +// const c2: string = Crypto.hashStr('c2') +// const d1: string = Crypto.hashStr('d1') +// beforeAll(() => { +// topNode = new DelegationNode({ +// id: a1, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [b1, b2], +// permissions: [Permission.ATTEST, Permission.DELEGATE], +// revoked: false, +// }) + +// nodes = { +// [b1]: new DelegationNode({ +// id: b1, +// hierarchyId, +// account: identityAlice.address, +// permissions: [Permission.ATTEST, Permission.DELEGATE], +// parentId: a1, +// childrenIds: [c1, c2], +// revoked: false, +// }), +// [b2]: new DelegationNode({ +// id: b2, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.ATTEST, Permission.DELEGATE], +// parentId: a1, +// revoked: false, +// }), +// [c1]: new DelegationNode({ +// id: c1, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [d1], +// permissions: [Permission.ATTEST, Permission.DELEGATE], +// parentId: b1, +// revoked: false, +// }), +// [c2]: new DelegationNode({ +// id: c2, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.ATTEST, Permission.DELEGATE], +// parentId: b1, +// revoked: false, +// }), +// [d1]: new DelegationNode({ +// id: d1, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [Permission.ATTEST, Permission.DELEGATE], +// parentId: c1, +// revoked: false, +// }), +// } +// }) + +// it('mocks work', async () => { +// expect(topNode.id).toEqual(a1) +// await expect( +// (await topNode.getChildren()).map((childNode) => childNode.id) +// ).resolves.toBe(topNode.childrenIds) +// await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) +// }) + +// it('counts all subnodes', async () => { +// await expect(topNode.subtreeNodeCount()).resolves.toStrictEqual(5) +// }) + +// it('counts smaller subtrees', async () => { +// await expect(nodes[b2].subtreeNodeCount()).resolves.toStrictEqual(0) +// await expect(nodes[b1].subtreeNodeCount()).resolves.toStrictEqual(3) +// await expect(nodes[c1].subtreeNodeCount()).resolves.toStrictEqual(1) +// await expect(nodes[c2].subtreeNodeCount()).resolves.toStrictEqual(0) +// await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) +// }) + +// it('counts all subnodes in deeply nested structure (100)', async () => { +// nodes = hashList.slice(0, 101).reduce((previous, current, index) => { +// return { +// ...previous, +// [current]: [ +// new DelegationNode({ +// id: current, +// hierarchyId, +// account: identityAlice.address, +// permissions: [Permission.DELEGATE], +// childrenIds: [hashList[index + 1]], +// parentId: current, +// revoked: false, +// }), +// ], +// } +// }, {}) + +// await expect( +// nodes[hashList[0]][0].subtreeNodeCount() +// ).resolves.toStrictEqual(100) +// }) + +// it('counts all subnodes in deeply nested structure (1000)', async () => { +// nodes = hashList.slice(0, 1001).reduce((previous, current, index) => { +// return { +// ...previous, +// [current]: [ +// new DelegationNode({ +// id: current, +// hierarchyId, +// account: identityAlice.address, +// permissions: [Permission.DELEGATE], +// childrenIds: [hashList[index + 1]], +// parentId: current, +// revoked: false, +// }), +// ], +// } +// }, {}) +// await expect( +// nodes[hashList[0]][0].subtreeNodeCount() +// ).resolves.toStrictEqual(1000) +// }) + +// it('counts all subnodes in deeply nested structure (10000)', async () => { +// nodes = hashList.slice(0, 10001).reduce((previous, current, index) => { +// return { +// ...previous, +// [current]: [ +// new DelegationNode({ +// id: current, +// hierarchyId, +// account: identityAlice.address, +// permissions: [Permission.DELEGATE], +// childrenIds: [hashList[index + 1]], +// parentId: current, +// revoked: false, +// }), +// ], +// } +// }, {}) +// await expect( +// nodes[hashList[0]][0].subtreeNodeCount() +// ).resolves.toStrictEqual(10000) +// }) +// }) + +// describe('count depth', () => { +// beforeAll(() => { +// nodes = hashList +// .slice(0, 1000) +// .map( +// (nodeId, index) => +// new DelegationNode({ +// id: nodeId, +// hierarchyId, +// account: addresses[index], +// permissions: [Permission.DELEGATE], +// childrenIds: [], +// parentId: hashList[index + 1], +// revoked: false, +// }) +// ) +// .reduce((result, node) => { +// return { +// ...result, +// [node.id]: node, +// } +// }, {}) + +// expect(Object.keys(nodes)).toHaveLength(1000) +// }) + +// it('counts steps from last child till select parent', async () => { +// await Promise.all( +// [0, 1, 5, 10, 75, 100, 300, 500, 999].map((i) => +// expect( +// nodes[hashList[0]].findAncestorOwnedBy(nodes[hashList[i]].account) +// ).resolves.toMatchObject({ +// steps: i, +// node: nodes[hashList[i]], +// }) +// ) +// ) +// }) + +// it('counts various distances within the hierarchy', async () => { +// await Promise.all([ +// expect( +// nodes[hashList[1]].findAncestorOwnedBy(nodes[hashList[2]].account) +// ).resolves.toMatchObject({ +// steps: 1, +// node: nodes[hashList[2]], +// }), +// expect( +// nodes[hashList[250]].findAncestorOwnedBy(nodes[hashList[450]].account) +// ).resolves.toMatchObject({ steps: 200, node: nodes[hashList[450]] }), +// expect( +// nodes[hashList[800]].findAncestorOwnedBy(nodes[hashList[850]].account) +// ).resolves.toMatchObject({ +// steps: 50, +// node: nodes[hashList[850]], +// }), +// expect( +// nodes[hashList[5]].findAncestorOwnedBy(nodes[hashList[955]].account) +// ).resolves.toMatchObject({ +// steps: 950, +// node: nodes[hashList[955]], +// }), +// ]) +// }) + +// it('returns null if trying to count backwards', async () => { +// await Promise.all([ +// expect( +// nodes[hashList[10]].findAncestorOwnedBy(nodes[hashList[5]].account) +// ).resolves.toMatchObject({ +// steps: 989, +// node: null, +// }), +// expect( +// nodes[hashList[99]].findAncestorOwnedBy(nodes[hashList[95]].account) +// ).resolves.toMatchObject({ steps: 900, node: null }), +// expect( +// nodes[hashList[900]].findAncestorOwnedBy(nodes[hashList[500]].account) +// ).resolves.toMatchObject({ steps: 99, node: null }), +// ]) +// }) + +// it('returns null if looking for non-existent account', async () => { +// const noOnesAddress = encodeAddress(Crypto.hash('-1', 256), 38) +// await Promise.all([ +// expect( +// nodes[hashList[10]].findAncestorOwnedBy(noOnesAddress) +// ).resolves.toMatchObject({ +// steps: 989, +// node: null, +// }), +// expect( +// nodes[hashList[99]].findAncestorOwnedBy(noOnesAddress) +// ).resolves.toMatchObject({ +// steps: 900, +// node: null, +// }), +// expect( +// nodes[hashList[900]].findAncestorOwnedBy(noOnesAddress) +// ).resolves.toMatchObject({ +// steps: 99, +// node: null, +// }), +// ]) +// }) + +// it('error check should throw errors on faulty delegation nodes', async () => { +// const malformedPremissionsDelegationNode = { +// id, +// hierarchyId, +// account: identityAlice.address, +// childrenIds: [], +// permissions: [], +// parentId: undefined, +// revoked: false, +// } as IDelegationNode + +// const missingRootIdDelegationNode = { +// id, +// hierarchyId, +// account: identityAlice.address, +// permissions: [Permission.DELEGATE], +// parentId: undefined, +// revoked: false, +// } as IDelegationNode + +// // @ts-expect-error +// delete missingRootIdDelegationNode.hierarchyId + +// const malformedRootIdDelegationNode = { +// id, +// hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), +// account: identityAlice.address, +// permissions: [Permission.DELEGATE], +// parentId: undefined, +// revoked: false, +// } as IDelegationNode + +// const malformedParentIdDelegationNode = { +// id, +// hierarchyId, +// account: identityAlice.address, +// permissions: [Permission.DELEGATE], +// parentId: 'malformed', +// revoked: false, +// } as IDelegationNode + +// expect(() => errorCheck(malformedPremissionsDelegationNode)).toThrowError( +// SDKErrors.ERROR_UNAUTHORIZED( +// 'Must have at least one permission and no more then two' +// ) +// ) + +// expect(() => errorCheck(missingRootIdDelegationNode)).toThrowError( +// SDKErrors.ERROR_DELEGATION_ID_MISSING() +// ) + +// expect(() => errorCheck(malformedRootIdDelegationNode)).toThrowError( +// SDKErrors.ERROR_DELEGATION_ID_TYPE() +// ) + +// expect(() => errorCheck(malformedParentIdDelegationNode)).toThrowError( +// SDKErrors.ERROR_DELEGATION_ID_TYPE() +// ) +// }) +// }) diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 68ca7d0f8..f8a6d2ead 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -24,11 +24,12 @@ */ import type { + IDelegationHierarchyDetails, IDelegationNode, IPublicIdentity, SubmittableExtrinsic, } from '@kiltprotocol/types' -import { Crypto, SDKErrors } from '@kiltprotocol/utils' +import { Crypto, SDKErrors, UUID } from '@kiltprotocol/utils' import { ConfigService } from '@kiltprotocol/config' import { query as queryAttestation } from '../attestation/Attestation.chain' import { @@ -40,7 +41,6 @@ import { } from './DelegationNode.chain' import { query as queryDetails } from './DelegationHierarchyDetails.chain' import * as DelegationNodeUtils from './DelegationNode.utils' -import DelegationHierarchyDetails from './DelegationHierarchyDetails' import Attestation from '../attestation/Attestation' import Identity from '../identity/Identity' @@ -53,6 +53,7 @@ export default class DelegationNode implements IDelegationNode { public childrenIds: Array = [] public account: IPublicIdentity['address'] public permissions: IDelegationNode['permissions'] + private hierarchyDetails?: IDelegationHierarchyDetails public revoked: boolean /** @@ -60,7 +61,7 @@ export default class DelegationNode implements IDelegationNode { * * @param delegationNodeInput - The base object from which to create the delegation node. */ - constructor(delegationNodeInput: IDelegationNode) { + public constructor(delegationNodeInput: IDelegationNode) { this.id = delegationNodeInput.id this.hierarchyId = delegationNodeInput.hierarchyId this.parentId = delegationNodeInput.parentId @@ -71,15 +72,15 @@ export default class DelegationNode implements IDelegationNode { DelegationNodeUtils.errorCheck(this) } + // TODO: hierarchyId can be optional and if not specified it is set the same as the (autogenerated) id. public static new( - id: IDelegationNode['id'], hierarchyId: IDelegationNode['hierarchyId'], parentId: string, // Cannot be undefined here account: IDelegationNode['account'], permissions: IDelegationNode['permissions'] ): DelegationNode { return new DelegationNode({ - id, + id: UUID.generate(), hierarchyId, parentId, account, @@ -89,18 +90,27 @@ export default class DelegationNode implements IDelegationNode { }) } + // TODO: Add another method to create a new hierarchy based on the DelegationNode. The method should fail if the node id != hierarchy id. + + public get cTypeHash(): Promise { + return this.getHierarchyDetails().then(i => i.cTypeHash) + } + /** * [ASYNC] Fetches the details of the hierarchy this delegation node belongs to. * * @throws [[ERROR_HIERARCHY_QUERY]] when the hierarchy details could not be queried. * @returns Promise containing the [[DelegationHierarchyDetails]] of this delegation node. */ - public async getHierarchyDetails(): Promise { - const hierarchyDetails = await queryDetails(this.hierarchyId) - if (!hierarchyDetails) { - throw SDKErrors.ERROR_HIERARCHY_QUERY(this.hierarchyId) + public async getHierarchyDetails(): Promise { + if (!this.hierarchyDetails) { + const hierarchyDetails = await queryDetails(this.hierarchyId) + if (!hierarchyDetails) { + throw SDKErrors.ERROR_HIERARCHY_QUERY(this.hierarchyId) + } + this.hierarchyDetails = hierarchyDetails } - return hierarchyDetails + return this.hierarchyDetails } /** From 93866e882f6a657235dbb1381a7b8084a24b7d97 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 29 Jul 2021 09:37:11 +0200 Subject: [PATCH 06/21] feat: add methods to store and retrieve root nodes --- packages/core/src/attestation/Attestation.ts | 6 +- .../DelegationHierarchyDetails.chain.ts | 14 +- .../DelegationHierarchyDetails.spec.ts | 142 ------------------ .../src/delegation/DelegationNode.chain.ts | 57 +++---- .../core/src/delegation/DelegationNode.ts | 96 ++++++++---- packages/core/src/delegation/index.ts | 3 +- packages/core/src/index.ts | 7 +- packages/types/src/Delegation.ts | 1 - packages/utils/src/SDKErrors.ts | 40 +++++ 9 files changed, 134 insertions(+), 232 deletions(-) delete mode 100644 packages/core/src/delegation/DelegationHierarchyDetails.spec.ts diff --git a/packages/core/src/attestation/Attestation.ts b/packages/core/src/attestation/Attestation.ts index 562adb017..4da2b3ab3 100644 --- a/packages/core/src/attestation/Attestation.ts +++ b/packages/core/src/attestation/Attestation.ts @@ -22,10 +22,10 @@ import type { SubmittableExtrinsic } from '@polkadot/api/promise/types' import type { IPublicIdentity, IAttestation, + IDelegationHierarchyDetails, IRequestForAttestation, CompressedAttestation, } from '@kiltprotocol/types' -import { DelegationHierarchyDetails } from '../delegation' import { revoke, query, store } from './Attestation.chain' import AttestationUtils from './Attestation.utils' import DelegationNode from '../delegation/DelegationNode' @@ -113,7 +113,7 @@ export default class Attestation implements IAttestation { */ public static async getDelegationDetails( delegationId: IAttestation['delegationId'] | null - ): Promise { + ): Promise { if (!delegationId) { return null } @@ -126,7 +126,7 @@ export default class Attestation implements IAttestation { return delegationNode.getHierarchyDetails() } - public async getDelegationDetails(): Promise { + public async getDelegationDetails(): Promise { return Attestation.getDelegationDetails(this.delegationId) } diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts index 6e26b99b0..890750b32 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts @@ -8,7 +8,6 @@ import type { IDelegationHierarchyDetails, IDelegationNode, - SubmittableExtrinsic, } from '@kiltprotocol/types' import { Option } from '@polkadot/types' import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' @@ -23,18 +22,7 @@ import { * @module DelegationHierarchyDetails */ -export async function store( - delegationHierarchyDetails: IDelegationHierarchyDetails -): Promise { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const tx: SubmittableExtrinsic = blockchain.api.tx.delegation.createHierarchy( - delegationHierarchyDetails.rootId, - delegationHierarchyDetails.cTypeHash - ) - return tx -} - -export async function query( +export default async function query( rootId: IDelegationNode['id'] ): Promise { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts b/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts deleted file mode 100644 index 2a79894d9..000000000 --- a/packages/core/src/delegation/DelegationHierarchyDetails.spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -// /** -// * Copyright 2018-2021 BOTLabs GmbH. -// * -// * This source code is licensed under the BSD 4-Clause "Original" license -// * found in the LICENSE file in the root directory of this source tree. -// */ - -// /** -// * @group unit/delegation -// */ - -// import { Crypto } from '@kiltprotocol/utils' -// import { -// BlockchainUtils, -// BlockchainApiConnection, -// } from '@kiltprotocol/chain-helpers' -// import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' -// import { Identity } from '../identity' - -// import DelegationHierarchyDetails from './DelegationHierarchyDetails' -// import Kilt from '../kilt/Kilt' -// import { DelegationNode } from '@kiltprotocol/sdk-js' - -// jest.mock( -// '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' -// ) - -// describe('Delegation', () => { -// let identityAlice: Identity -// let ctypeHash: string -// let ROOT_IDENTIFIER: string -// let ROOT_SUCCESS: string -// Kilt.config({ address: 'ws://testString' }) - -// beforeAll(async () => { -// identityAlice = Identity.buildFromURI('//Alice') -// ctypeHash = `0x6b696c743a63747970653a307830303031000000000000000000000000000000` -// ROOT_IDENTIFIER = Crypto.hashStr('1') -// ROOT_SUCCESS = Crypto.hashStr('success') - -// require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.hierarchies.mockReturnValue( -// mockChainQueryReturn('delegation', 'hierarchies', [ctypeHash]) -// ) -// require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.delegations.mockReturnValue( -// mockChainQueryReturn('delegation', 'delegations', [ -// ROOT_IDENTIFIER, -// null, -// [], -// ]) -// ) -// }) - -// it('stores root delegation', async () => { -// const hierarchyDetails = new DelegationHierarchyDetails({ -// rootId: ROOT_IDENTIFIER, -// cTypeHash: ctypeHash, -// }) -// await hierarchyDetails -// .store() -// .then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, identityAlice, { reSign: true }) -// ) - -// const rootNode = await DelegationNode.query(ROOT_IDENTIFIER) -// if (rootNode) { -// expect(rootNode.id).toBe(ROOT_IDENTIFIER) -// } -// }) - -// it('query root delegation', async () => { -// const queriedDelegation = await DelegationNode.query(ROOT_IDENTIFIER) -// expect(queriedDelegation).not.toBe(undefined) -// if (queriedDelegation) { -// const details = await queriedDelegation.getHierarchyDetails() -// expect(queriedDelegation.account).toBe(identityAlice.address) -// expect(details.cTypeHash).toBe(ctypeHash) -// expect(queriedDelegation.id).toBe(ROOT_IDENTIFIER) -// } -// }) - -// it('root delegation verify', async () => { -// require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.delegation.root = jest.fn( -// async (rootId) => { -// if (rootId === ROOT_SUCCESS) { -// const tuple = mockChainQueryReturn( -// 'delegation', -// 'hierarchies', -// ctypeHash -// ) - -// return Promise.resolve(tuple) -// } -// const tuple = mockChainQueryReturn('delegation', 'hierarchies', [ -// ctypeHash, -// identityAlice.address, -// true, -// ]) - -// return Promise.resolve(tuple) -// } -// ) - -// expect( -// await new DelegationRootNode({ -// id: ROOT_IDENTIFIER, -// cTypeHash: ctypeHash, -// account: identityAlice.address, -// revoked: false, -// }).verify() -// ).toBe(false) - -// expect( -// await new DelegationRootNode({ -// id: ROOT_SUCCESS, -// cTypeHash: ctypeHash, -// account: identityAlice.address, -// revoked: true, -// }).verify() -// ).toBe(true) -// }) - -// it('root delegation verify', async () => { -// const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - -// const aDelegationRootNode = new DelegationRootNode({ -// id: ROOT_IDENTIFIER, -// cTypeHash: ctypeHash, -// account: identityAlice.address, -// revoked: false, -// }) -// const revokeStatus = await aDelegationRootNode -// .revoke() -// .then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, identityAlice, { reSign: true }) -// ) -// expect(blockchain.api.tx.delegation.revokeRoot).toBeCalledWith( -// ROOT_IDENTIFIER, -// 1 -// ) -// expect(revokeStatus).toBeDefined() -// }) -// }) \ No newline at end of file diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 4d44518b4..800db432f 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -15,7 +15,7 @@ import type { IDelegationNode, SubmittableExtrinsic } from '@kiltprotocol/types' import { ConfigService } from '@kiltprotocol/config' import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' import { Hash } from '@polkadot/types/interfaces' -import { DecoderUtils } from '@kiltprotocol/utils' +import { DecoderUtils, SDKErrors } from '@kiltprotocol/utils' import { CodecWithId, decodeDelegationNode, @@ -26,31 +26,40 @@ import { permissionsAsBitset } from './DelegationNode.utils' const log = ConfigService.LoggingFactory.getLogger('DelegationNode') -/** - * @param delegation The delegation to store on chain. - * @param signature The delegatee's signature to ensure the delegation is created with their consent. - * @internal - */ -export async function store( - delegation: IDelegationNode, +export async function storeAsRoot( + delegation: DelegationNode +): Promise { + const blockchain = await BlockchainApiConnection.getConnectionOrConnect() + + if (!delegation.isRoot()) { + throw SDKErrors.ERROR_INVALID_ROOT_NODE + } + return blockchain.api.tx.delegation.createHierarchy( + delegation.id, + await delegation.cTypeHash + ) +} + +export async function storeAsDelegation( + delegation: DelegationNode, signature: string ): Promise { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const tx: SubmittableExtrinsic = blockchain.api.tx.delegation.addDelegation( + + if (delegation.isRoot()) { + throw SDKErrors.ERROR_INVALID_DELEGATION_NODE + } + + return blockchain.api.tx.delegation.addDelegation( delegation.id, delegation.hierarchyId, - delegation.parentId !== null ? delegation.parentId : undefined, + delegation.parentId, delegation.account, permissionsAsBitset(delegation), signature ) - return tx } -/** - * @param delegationId The id of the delegation node to query. - * @internal - */ export async function query( delegationId: IDelegationNode['id'] ): Promise { @@ -76,16 +85,6 @@ export async function query( return root } -/** - * @internal - * - * Revokes part of a delegation tree at specified node, also revoking all nodes below. - * - * @param delegationId The id of the node in the delegation tree at which to revoke. - * @param maxDepth How many nodes may be traversed upwards in the hierarchy when searching for a node owned by `identity`. Each traversal will add to the transaction fee. Therefore a higher number will increase the fees locked until the transaction is complete. A number lower than the actual required traversals will result in a failed extrinsic (node will not be revoked). - * @param maxRevocations How many delegation nodes may be revoked during the process. Each revocation adds to the transaction fee. A higher number will require more fees to be locked while an insufficiently high number will lead to premature abortion of the revocation process, leaving some nodes unrevoked. Revocations will first be performed on child nodes, therefore the current node is only revoked when this is accurate. - * @returns An unsigned SubmittableExtrinsic ready to be signed and dispatched. - */ export async function revoke( delegationId: IDelegationNode['id'], maxDepth: number, @@ -120,10 +119,6 @@ export async function fetchChildren( return val } -/** - * @param delegationNode The delegation node to fetch children from. - * @internal - */ export async function getChildren( delegationNode: DelegationNode ): Promise { @@ -160,10 +155,6 @@ function decodeDelegatedAttestations(queryResult: Option>): string[] { return queryResult.unwrapOrDefault().map((hash) => hash.toHex()) } -/** - * @param id The id of the delegation node for which to fetch all the attestations issued. - * @internal - */ export async function getAttestationHashes( id: IDelegationNode['id'] ): Promise { diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index f8a6d2ead..beced6dc6 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -37,9 +37,10 @@ import { getAttestationHashes, query, revoke, - store, + storeAsDelegation, + storeAsRoot, } from './DelegationNode.chain' -import { query as queryDetails } from './DelegationHierarchyDetails.chain' +import queryDetails from './DelegationHierarchyDetails.chain' import * as DelegationNodeUtils from './DelegationNode.utils' import Attestation from '../attestation/Attestation' import Identity from '../identity/Identity' @@ -47,14 +48,14 @@ import Identity from '../identity/Identity' const log = ConfigService.LoggingFactory.getLogger('DelegationNode') export default class DelegationNode implements IDelegationNode { - public id: IDelegationNode['id'] - public hierarchyId: IDelegationNode['hierarchyId'] - public parentId?: IDelegationNode['parentId'] - public childrenIds: Array = [] - public account: IPublicIdentity['address'] - public permissions: IDelegationNode['permissions'] + public readonly id: IDelegationNode['id'] + public readonly hierarchyId: IDelegationNode['hierarchyId'] + public readonly parentId?: IDelegationNode['parentId'] + public readonly childrenIds: Array = [] + public readonly account: IPublicIdentity['address'] + public readonly permissions: IDelegationNode['permissions'] private hierarchyDetails?: IDelegationHierarchyDetails - public revoked: boolean + public readonly revoked: boolean /** * Creates a new [DelegationNode]. @@ -72,8 +73,7 @@ export default class DelegationNode implements IDelegationNode { DelegationNodeUtils.errorCheck(this) } - // TODO: hierarchyId can be optional and if not specified it is set the same as the (autogenerated) id. - public static new( + public static newNode( hierarchyId: IDelegationNode['hierarchyId'], parentId: string, // Cannot be undefined here account: IDelegationNode['account'], @@ -90,10 +90,29 @@ export default class DelegationNode implements IDelegationNode { }) } - // TODO: Add another method to create a new hierarchy based on the DelegationNode. The method should fail if the node id != hierarchy id. + public static newRoot( + account: IDelegationNode['account'], + permissions: IDelegationNode['permissions'], + hierarchyDetails: IDelegationHierarchyDetails + ): DelegationNode { + const nodeId = UUID.generate() + const newNode = new DelegationNode({ + id: nodeId, + hierarchyId: nodeId, + account, + permissions, + childrenIds: [], + revoked: false, + }) + newNode.hierarchyDetails = hierarchyDetails + + return newNode + } + + // Utility method to retrieve the CType hash associated with a delegation node. public get cTypeHash(): Promise { - return this.getHierarchyDetails().then(i => i.cTypeHash) + return this.getHierarchyDetails().then((details) => details.cTypeHash) } /** @@ -110,7 +129,8 @@ export default class DelegationNode implements IDelegationNode { } this.hierarchyDetails = hierarchyDetails } - return this.hierarchyDetails + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.hierarchyDetails! } /** @@ -156,21 +176,6 @@ export default class DelegationNode implements IDelegationNode { return getAttestationHashes(this.id) } - /** - * [STATIC] [ASYNC] Queries the delegation node with its [delegationId]. - * - * @param delegationId The unique identifier of the desired delegation. - * @returns Promise containing the [[DelegationNode]] or [null]. - */ - public static async query( - delegationId: string - ): Promise { - log.info(`:: query('${delegationId}')`) - const result = await query(delegationId) - log.info(`result: ${JSON.stringify(result)}`) - return result - } - /** * * Generates the delegation hash from the delegations' property values. @@ -214,9 +219,21 @@ export default class DelegationNode implements IDelegationNode { * @param signature Signature of the delegate to ensure it is done under the delegate's permission. * @returns Promise containing a unsigned SubmittableExtrinsic. */ - public async store(signature: string): Promise { - log.info(`:: store(${this.id})`) - return store(this, signature) + public async store(signature?: string): Promise { + if (this.isRoot()) { + return storeAsRoot(this) + // eslint-disable-next-line no-else-return + } else { + if (!signature) { + throw SDKErrors.ERROR_DELEGATION_SIGNATURE_MISSING + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return storeAsDelegation(this, signature!) + } + } + + isRoot(): boolean { + return this.id === this.hierarchyId && !this.parentId } /** @@ -294,4 +311,19 @@ export default class DelegationNode implements IDelegationNode { ) return revoke(this.id, steps, childrenCount) } + + /** + * [STATIC] [ASYNC] Queries the delegation node with its [delegationId]. + * + * @param delegationId The unique identifier of the desired delegation. + * @returns Promise containing the [[DelegationNode]] or [null]. + */ + public static async query( + delegationId: string + ): Promise { + log.info(`:: query('${delegationId}')`) + const result = await query(delegationId) + log.info(`result: ${JSON.stringify(result)}`) + return result + } } diff --git a/packages/core/src/delegation/index.ts b/packages/core/src/delegation/index.ts index da669d011..c308d9bd0 100644 --- a/packages/core/src/delegation/index.ts +++ b/packages/core/src/delegation/index.ts @@ -6,7 +6,6 @@ */ import DelegationNode from './DelegationNode' -import DelegationHierarchyDetails from './DelegationHierarchyDetails' import * as DelegationNodeUtils from './DelegationNode.utils' -export { DelegationNode, DelegationHierarchyDetails, DelegationNodeUtils } +export { DelegationNode, DelegationNodeUtils } \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 25143beca..967d6ccd9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,11 +10,7 @@ import AttestedClaim, { AttestedClaimUtils } from './attestedclaim' import { Balance, BalanceUtils } from './balance' import Claim, { ClaimUtils } from './claim' import { CType, CTypeMetadata, CTypeSchema, CTypeUtils } from './ctype' -import { - DelegationNode, - DelegationHierarchyDetails, - DelegationNodeUtils, -} from './delegation' +import { DelegationNode, DelegationNodeUtils } from './delegation' import Did, { IDid, IDidDocument, @@ -49,7 +45,6 @@ export { AttestationUtils, AttestedClaim, AttestedClaimUtils, - DelegationHierarchyDetails, DelegationNode, DelegationNodeUtils, Did, diff --git a/packages/types/src/Delegation.ts b/packages/types/src/Delegation.ts index 8f63692fa..6d4a67d5e 100644 --- a/packages/types/src/Delegation.ts +++ b/packages/types/src/Delegation.ts @@ -29,6 +29,5 @@ export interface IDelegationNode { } export interface IDelegationHierarchyDetails { - rootId: IDelegationNode['id'] cTypeHash: ICType['hash'] } diff --git a/packages/utils/src/SDKErrors.ts b/packages/utils/src/SDKErrors.ts index 66f50182a..082ee9f7a 100644 --- a/packages/utils/src/SDKErrors.ts +++ b/packages/utils/src/SDKErrors.ts @@ -43,6 +43,11 @@ export enum ErrorCode { ERROR_IDENTITY_NOT_PE_ENABLED = 10016, ERROR_WS_ADDRESS_NOT_SET = 10017, ERROR_DELEGATION_ID_MISSING = 10018, + ERROR_HIERARCHY_DETAILS_MISSING = 10019, + ERROR_DELEGATION_SIGNATURE_MISSING = 10020, + ERROR_DELEGATION_PARENT_MISSING = 10021, + ERROR_INVALID_ROOT_NODE = 10022, + ERROR_INVALID_DELEGATION_NODE = 10023, // Data type is wrong or malformed ERROR_ADDRESS_TYPE = 20001, @@ -279,6 +284,41 @@ export const ERROR_DELEGATION_ID_MISSING: () => SDKError = () => { ) } +export const ERROR_HIERARCHY_DETAILS_MISSING: () => SDKError = () => { + return new SDKError( + ErrorCode.ERROR_HIERARCHY_DETAILS_MISSING, + 'Delegation hierarchy details missing' + ) +} + +export const ERROR_DELEGATION_SIGNATURE_MISSING: () => SDKError = () => { + return new SDKError( + ErrorCode.ERROR_DELEGATION_SIGNATURE_MISSING, + "Delegatee's signature missing" + ) +} + +export const ERROR_DELEGATION_PARENT_MISSING: () => SDKError = () => { + return new SDKError( + ErrorCode.ERROR_DELEGATION_PARENT_MISSING, + 'Delegation parentId missing' + ) +} + +export const ERROR_INVALID_ROOT_NODE: () => SDKError = () => { + return new SDKError( + ErrorCode.ERROR_INVALID_ROOT_NODE, + 'The given node is not a valid root node' + ) +} + +export const ERROR_INVALID_DELEGATION_NODE: () => SDKError = () => { + return new SDKError( + ErrorCode.ERROR_INVALID_DELEGATION_NODE, + 'The given node is not a valid delegation node' + ) +} + export const ERROR_CLAIM_CONTENTS_MALFORMED: () => SDKError = () => { return new SDKError( ErrorCode.ERROR_CLAIM_CONTENTS_MALFORMED, From f2527040b6295c5cd94e2db6919d094a2545715a Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 29 Jul 2021 13:03:28 +0200 Subject: [PATCH 07/21] test: unit tests passing --- .../DelegationHierarchyDetails.chain.ts | 5 +- .../src/delegation/DelegationNode.chain.ts | 58 +- .../src/delegation/DelegationNode.spec.ts | 999 +++++++++--------- .../core/src/delegation/DelegationNode.ts | 2 +- 4 files changed, 520 insertions(+), 544 deletions(-) diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts index 890750b32..2cb7d1835 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts @@ -22,7 +22,8 @@ import { * @module DelegationHierarchyDetails */ -export default async function query( +// eslint-disable-next-line import/prefer-default-export +export async function query( rootId: IDelegationNode['id'] ): Promise { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() @@ -39,4 +40,4 @@ export default async function query( cTypeHash: decoded.cTypeHash, } return details -} +} \ No newline at end of file diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 800db432f..8808bdf0b 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -16,11 +16,7 @@ import { ConfigService } from '@kiltprotocol/config' import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' import { Hash } from '@polkadot/types/interfaces' import { DecoderUtils, SDKErrors } from '@kiltprotocol/utils' -import { - CodecWithId, - decodeDelegationNode, - IChainDelegationNode, -} from './DelegationDecoder' +import { decodeDelegationNode, IChainDelegationNode } from './DelegationDecoder' import DelegationNode from './DelegationNode' import { permissionsAsBitset } from './DelegationNode.utils' @@ -99,55 +95,21 @@ export async function revoke( return tx } -export async function fetchChildren( - childrenIds: string[] -): Promise>>> { - const blockchain = await BlockchainApiConnection.getConnectionOrConnect() - const val: Array - >> = await Promise.all( - childrenIds.map(async (childId: string) => { - const queryResult = await blockchain.api.query.delegation.delegations< - Option - >(childId) - return { - id: childId, - codec: queryResult, - } - }) - ) - return val -} - export async function getChildren( delegationNode: DelegationNode ): Promise { log.info(` :: getChildren('${delegationNode.id}')`) - const queryResults: Array - >> = await fetchChildren(delegationNode.childrenIds) - const children: DelegationNode[] = queryResults - .map((codec: CodecWithId>) => { - const decoded = decodeDelegationNode(codec.codec) - if (!decoded) { - return null + const childrenNodes = await Promise.all( + delegationNode.childrenIds.map(async (childId) => { + const childNode = await query(childId) + if (!childNode) { + throw SDKErrors.ERROR_DELEGATION_ID_MISSING } - const child = new DelegationNode({ - id: codec.id, - hierarchyId: decoded.hierarchyId, - parentId: decoded.parentId, - childrenIds: decoded.childrenIds, - account: decoded.account, - permissions: decoded.permissions, - revoked: decoded.revoked, - }) - return child + return childNode }) - .filter((value): value is DelegationNode => { - return value !== null - }) - log.info(`children: ${JSON.stringify(children)}`) - return children + ) + log.info(`children: ${JSON.stringify(childrenNodes)}`) + return childrenNodes } function decodeDelegatedAttestations(queryResult: Option>): string[] { diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index 2949de48a..1042612b8 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -1,493 +1,506 @@ -// /** -// * Copyright 2018-2021 BOTLabs GmbH. -// * -// * This source code is licensed under the BSD 4-Clause "Original" license -// * found in the LICENSE file in the root directory of this source tree. -// */ - -// /** -// * @group unit/delegation -// */ - -// /* eslint-disable @typescript-eslint/ban-ts-comment */ - -// import { IDelegationNode, Permission } from '@kiltprotocol/types' -// import { encodeAddress } from '@polkadot/keyring' -// import { Crypto, SDKErrors } from '@kiltprotocol/utils' -// import Identity from '../identity' -// import DelegationNode from './DelegationNode' -// import DelegationHierarchyDetails from './DelegationHierarchyDetails' -// import { permissionsAsBitset, errorCheck } from './DelegationNode.utils' - -// let hierarchiesDetails: Record = {} -// let nodes: Record = {} - -// jest.mock('./DelegationNode.chain', () => ({ -// getChildren: jest.fn(async (id: string) => { -// return nodes[id].childrenIds -// }), -// query: jest.fn(async (id: string) => nodes[id] || null), -// })) - -// jest.mock('./DelegationHierarchyDetails.chain', () => ({ -// query: jest.fn(async (id: string) => hierarchiesDetails[id] || null), -// })) - -// let identityAlice: Identity -// let identityBob: Identity -// let id: string -// let successId: string -// let failureId: string -// let hierarchyId: string -// let parentId: string -// let hashList: string[] -// let addresses: string[] - -// beforeAll(() => { -// identityAlice = Identity.buildFromURI('//Alice') -// identityBob = Identity.buildFromURI('//Bob') -// successId = Crypto.hashStr('success') -// hierarchyId = Crypto.hashStr('rootId') -// id = Crypto.hashStr('id') -// parentId = Crypto.hashStr('parentId') -// failureId = Crypto.hashStr('failure') -// hashList = Array(10002) -// .fill('') -// .map((_val, index) => Crypto.hashStr(`${index + 1}`)) -// addresses = Array(10002) -// .fill('') -// .map((_val, index) => -// encodeAddress(Crypto.hash(`${index}`, 256), 38) -// ) -// }) - -// describe('Delegation', () => { -// it('delegation generate hash', () => { -// const node = DelegationNode.new( -// id, -// hierarchyId, -// parentId, -// identityBob.address, -// [Permission.DELEGATE] -// ) -// const hash: string = node.generateHash() -// expect(hash).toBe( -// '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' -// ) -// }) - -// it('delegation permissionsAsBitset', () => { -// const node = DelegationNode.new( -// id, -// hierarchyId, -// parentId, -// identityBob.address, -// [Permission.DELEGATE] -// ) -// const permissions: Uint8Array = permissionsAsBitset(node) -// const expected: Uint8Array = new Uint8Array(4) -// expected[0] = 2 -// expect(permissions.toString()).toBe(expected.toString()) -// }) - -// it('delegation verify', async () => { -// nodes = { -// [successId]: new DelegationNode({ -// id: successId, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.DELEGATE], -// parentId: undefined, -// revoked: false, -// }), -// [failureId]: { -// ...nodes.success, -// revoked: true, -// id: failureId, -// } as DelegationNode, -// } - -// expect( -// await new DelegationNode({ -// id: successId, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.DELEGATE], -// parentId: undefined, -// revoked: false, -// }).verify() -// ).toBe(true) - -// expect( -// await new DelegationNode({ -// id: failureId, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.DELEGATE], -// parentId: undefined, -// revoked: false, -// }).verify() -// ).toBe(false) -// }) - -// it('get delegation root', async () => { -// hierarchiesDetails = { -// [hierarchyId]: new DelegationHierarchyDetails({ -// rootId: hierarchyId, -// cTypeHash: -// 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', -// }), -// } - -// nodes = { -// [hierarchyId]: new DelegationNode({ -// id: hierarchyId, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.DELEGATE], -// revoked: false, -// }), -// } - -// const node: DelegationNode = new DelegationNode({ -// id, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.DELEGATE], -// revoked: false, -// }) -// const hierarchyDetails = await node.getHierarchyDetails() - -// expect(hierarchyDetails).toBeDefined() -// expect(hierarchyDetails.cTypeHash).toBe( -// 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' -// ) -// }) -// }) - -// describe('count subtree', () => { -// let topNode: DelegationNode -// const a1: string = Crypto.hashStr('a1') -// const b1: string = Crypto.hashStr('b1') -// const b2: string = Crypto.hashStr('b2') -// const c1: string = Crypto.hashStr('c1') -// const c2: string = Crypto.hashStr('c2') -// const d1: string = Crypto.hashStr('d1') -// beforeAll(() => { -// topNode = new DelegationNode({ -// id: a1, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [b1, b2], -// permissions: [Permission.ATTEST, Permission.DELEGATE], -// revoked: false, -// }) - -// nodes = { -// [b1]: new DelegationNode({ -// id: b1, -// hierarchyId, -// account: identityAlice.address, -// permissions: [Permission.ATTEST, Permission.DELEGATE], -// parentId: a1, -// childrenIds: [c1, c2], -// revoked: false, -// }), -// [b2]: new DelegationNode({ -// id: b2, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.ATTEST, Permission.DELEGATE], -// parentId: a1, -// revoked: false, -// }), -// [c1]: new DelegationNode({ -// id: c1, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [d1], -// permissions: [Permission.ATTEST, Permission.DELEGATE], -// parentId: b1, -// revoked: false, -// }), -// [c2]: new DelegationNode({ -// id: c2, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.ATTEST, Permission.DELEGATE], -// parentId: b1, -// revoked: false, -// }), -// [d1]: new DelegationNode({ -// id: d1, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [Permission.ATTEST, Permission.DELEGATE], -// parentId: c1, -// revoked: false, -// }), -// } -// }) - -// it('mocks work', async () => { -// expect(topNode.id).toEqual(a1) -// await expect( -// (await topNode.getChildren()).map((childNode) => childNode.id) -// ).resolves.toBe(topNode.childrenIds) -// await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) -// }) - -// it('counts all subnodes', async () => { -// await expect(topNode.subtreeNodeCount()).resolves.toStrictEqual(5) -// }) - -// it('counts smaller subtrees', async () => { -// await expect(nodes[b2].subtreeNodeCount()).resolves.toStrictEqual(0) -// await expect(nodes[b1].subtreeNodeCount()).resolves.toStrictEqual(3) -// await expect(nodes[c1].subtreeNodeCount()).resolves.toStrictEqual(1) -// await expect(nodes[c2].subtreeNodeCount()).resolves.toStrictEqual(0) -// await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) -// }) - -// it('counts all subnodes in deeply nested structure (100)', async () => { -// nodes = hashList.slice(0, 101).reduce((previous, current, index) => { -// return { -// ...previous, -// [current]: [ -// new DelegationNode({ -// id: current, -// hierarchyId, -// account: identityAlice.address, -// permissions: [Permission.DELEGATE], -// childrenIds: [hashList[index + 1]], -// parentId: current, -// revoked: false, -// }), -// ], -// } -// }, {}) - -// await expect( -// nodes[hashList[0]][0].subtreeNodeCount() -// ).resolves.toStrictEqual(100) -// }) - -// it('counts all subnodes in deeply nested structure (1000)', async () => { -// nodes = hashList.slice(0, 1001).reduce((previous, current, index) => { -// return { -// ...previous, -// [current]: [ -// new DelegationNode({ -// id: current, -// hierarchyId, -// account: identityAlice.address, -// permissions: [Permission.DELEGATE], -// childrenIds: [hashList[index + 1]], -// parentId: current, -// revoked: false, -// }), -// ], -// } -// }, {}) -// await expect( -// nodes[hashList[0]][0].subtreeNodeCount() -// ).resolves.toStrictEqual(1000) -// }) - -// it('counts all subnodes in deeply nested structure (10000)', async () => { -// nodes = hashList.slice(0, 10001).reduce((previous, current, index) => { -// return { -// ...previous, -// [current]: [ -// new DelegationNode({ -// id: current, -// hierarchyId, -// account: identityAlice.address, -// permissions: [Permission.DELEGATE], -// childrenIds: [hashList[index + 1]], -// parentId: current, -// revoked: false, -// }), -// ], -// } -// }, {}) -// await expect( -// nodes[hashList[0]][0].subtreeNodeCount() -// ).resolves.toStrictEqual(10000) -// }) -// }) - -// describe('count depth', () => { -// beforeAll(() => { -// nodes = hashList -// .slice(0, 1000) -// .map( -// (nodeId, index) => -// new DelegationNode({ -// id: nodeId, -// hierarchyId, -// account: addresses[index], -// permissions: [Permission.DELEGATE], -// childrenIds: [], -// parentId: hashList[index + 1], -// revoked: false, -// }) -// ) -// .reduce((result, node) => { -// return { -// ...result, -// [node.id]: node, -// } -// }, {}) - -// expect(Object.keys(nodes)).toHaveLength(1000) -// }) - -// it('counts steps from last child till select parent', async () => { -// await Promise.all( -// [0, 1, 5, 10, 75, 100, 300, 500, 999].map((i) => -// expect( -// nodes[hashList[0]].findAncestorOwnedBy(nodes[hashList[i]].account) -// ).resolves.toMatchObject({ -// steps: i, -// node: nodes[hashList[i]], -// }) -// ) -// ) -// }) - -// it('counts various distances within the hierarchy', async () => { -// await Promise.all([ -// expect( -// nodes[hashList[1]].findAncestorOwnedBy(nodes[hashList[2]].account) -// ).resolves.toMatchObject({ -// steps: 1, -// node: nodes[hashList[2]], -// }), -// expect( -// nodes[hashList[250]].findAncestorOwnedBy(nodes[hashList[450]].account) -// ).resolves.toMatchObject({ steps: 200, node: nodes[hashList[450]] }), -// expect( -// nodes[hashList[800]].findAncestorOwnedBy(nodes[hashList[850]].account) -// ).resolves.toMatchObject({ -// steps: 50, -// node: nodes[hashList[850]], -// }), -// expect( -// nodes[hashList[5]].findAncestorOwnedBy(nodes[hashList[955]].account) -// ).resolves.toMatchObject({ -// steps: 950, -// node: nodes[hashList[955]], -// }), -// ]) -// }) - -// it('returns null if trying to count backwards', async () => { -// await Promise.all([ -// expect( -// nodes[hashList[10]].findAncestorOwnedBy(nodes[hashList[5]].account) -// ).resolves.toMatchObject({ -// steps: 989, -// node: null, -// }), -// expect( -// nodes[hashList[99]].findAncestorOwnedBy(nodes[hashList[95]].account) -// ).resolves.toMatchObject({ steps: 900, node: null }), -// expect( -// nodes[hashList[900]].findAncestorOwnedBy(nodes[hashList[500]].account) -// ).resolves.toMatchObject({ steps: 99, node: null }), -// ]) -// }) - -// it('returns null if looking for non-existent account', async () => { -// const noOnesAddress = encodeAddress(Crypto.hash('-1', 256), 38) -// await Promise.all([ -// expect( -// nodes[hashList[10]].findAncestorOwnedBy(noOnesAddress) -// ).resolves.toMatchObject({ -// steps: 989, -// node: null, -// }), -// expect( -// nodes[hashList[99]].findAncestorOwnedBy(noOnesAddress) -// ).resolves.toMatchObject({ -// steps: 900, -// node: null, -// }), -// expect( -// nodes[hashList[900]].findAncestorOwnedBy(noOnesAddress) -// ).resolves.toMatchObject({ -// steps: 99, -// node: null, -// }), -// ]) -// }) - -// it('error check should throw errors on faulty delegation nodes', async () => { -// const malformedPremissionsDelegationNode = { -// id, -// hierarchyId, -// account: identityAlice.address, -// childrenIds: [], -// permissions: [], -// parentId: undefined, -// revoked: false, -// } as IDelegationNode - -// const missingRootIdDelegationNode = { -// id, -// hierarchyId, -// account: identityAlice.address, -// permissions: [Permission.DELEGATE], -// parentId: undefined, -// revoked: false, -// } as IDelegationNode - -// // @ts-expect-error -// delete missingRootIdDelegationNode.hierarchyId - -// const malformedRootIdDelegationNode = { -// id, -// hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), -// account: identityAlice.address, -// permissions: [Permission.DELEGATE], -// parentId: undefined, -// revoked: false, -// } as IDelegationNode - -// const malformedParentIdDelegationNode = { -// id, -// hierarchyId, -// account: identityAlice.address, -// permissions: [Permission.DELEGATE], -// parentId: 'malformed', -// revoked: false, -// } as IDelegationNode - -// expect(() => errorCheck(malformedPremissionsDelegationNode)).toThrowError( -// SDKErrors.ERROR_UNAUTHORIZED( -// 'Must have at least one permission and no more then two' -// ) -// ) - -// expect(() => errorCheck(missingRootIdDelegationNode)).toThrowError( -// SDKErrors.ERROR_DELEGATION_ID_MISSING() -// ) - -// expect(() => errorCheck(malformedRootIdDelegationNode)).toThrowError( -// SDKErrors.ERROR_DELEGATION_ID_TYPE() -// ) - -// expect(() => errorCheck(malformedParentIdDelegationNode)).toThrowError( -// SDKErrors.ERROR_DELEGATION_ID_TYPE() -// ) -// }) -// }) +/** + * Copyright 2018-2021 BOTLabs GmbH. + * + * This source code is licensed under the BSD 4-Clause "Original" license + * found in the LICENSE file in the root directory of this source tree. + */ + +/** + * @group unit/delegation + */ + +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import { + IDelegationNode, + IDelegationHierarchyDetails, + Permission, +} from '@kiltprotocol/types' +import { encodeAddress } from '@polkadot/keyring' +import { Crypto, SDKErrors } from '@kiltprotocol/utils' +import Identity from '../identity' +import DelegationNode from './DelegationNode' +import { permissionsAsBitset, errorCheck } from './DelegationNode.utils' + +let hierarchiesDetails: Record = {} +let nodes: Record = {} + +jest.mock('./DelegationNode.chain', () => { + return { + getChildren: jest.fn(async (node: DelegationNode) => + node.childrenIds.map((id) => nodes[id] || null) + ), + query: jest.fn(async (id: string) => nodes[id] || null), + } +}) + +jest.mock('./DelegationHierarchyDetails.chain', () => ({ + query: jest.fn(async (id: string) => hierarchiesDetails[id] || null), +})) + +let identityAlice: Identity +let identityBob: Identity +let id: string +let successId: string +let failureId: string +let hierarchyId: string +let parentId: string +let hashList: string[] +let addresses: string[] + +beforeAll(() => { + identityAlice = Identity.buildFromURI('//Alice') + identityBob = Identity.buildFromURI('//Bob') + successId = Crypto.hashStr('success') + hierarchyId = Crypto.hashStr('rootId') + id = Crypto.hashStr('id') + parentId = Crypto.hashStr('parentId') + failureId = Crypto.hashStr('failure') + hashList = Array(10002) + .fill('') + .map((_val, index) => Crypto.hashStr(`${index + 1}`)) + addresses = Array(10002) + .fill('') + .map((_val, index) => + encodeAddress(Crypto.hash(`${index}`, 256), 38) + ) +}) + +describe('Delegation', () => { + it('delegation generate hash', () => { + const node = new DelegationNode({ + id, + hierarchyId, + parentId, + account: identityBob.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + revoked: false, + }) + const hash: string = node.generateHash() + expect(hash).toBe( + '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' + ) + }) + + it('delegation permissionsAsBitset', () => { + const node = new DelegationNode({ + id, + hierarchyId, + parentId, + account: identityBob.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + revoked: false, + }) + const permissions: Uint8Array = permissionsAsBitset(node) + const expected: Uint8Array = new Uint8Array(4) + expected[0] = 2 + expect(permissions.toString()).toBe(expected.toString()) + }) + + it('delegation verify', async () => { + nodes = { + [successId]: new DelegationNode({ + id: successId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + }), + [failureId]: { + ...nodes.success, + revoked: true, + id: failureId, + } as DelegationNode, + } + + expect( + await new DelegationNode({ + id: successId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + }).verify() + ).toBe(true) + + expect( + await new DelegationNode({ + id: failureId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + }).verify() + ).toBe(false) + }) + + it('get delegation root', async () => { + hierarchiesDetails = { + [hierarchyId]: { + cTypeHash: + 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', + }, + } + + nodes = { + [hierarchyId]: new DelegationNode({ + id: hierarchyId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + revoked: false, + }), + } + + const node: DelegationNode = new DelegationNode({ + id, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + revoked: false, + }) + const hierarchyDetails = await node.getHierarchyDetails() + + expect(hierarchyDetails).toBeDefined() + expect(hierarchyDetails.cTypeHash).toBe( + 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' + ) + }) +}) + +describe('count subtree', () => { + let topNode: DelegationNode + const a1: string = Crypto.hashStr('a1') + const b1: string = Crypto.hashStr('b1') + const b2: string = Crypto.hashStr('b2') + const c1: string = Crypto.hashStr('c1') + const c2: string = Crypto.hashStr('c2') + const d1: string = Crypto.hashStr('d1') + beforeAll(() => { + topNode = new DelegationNode({ + id: a1, + hierarchyId, + account: identityAlice.address, + childrenIds: [b1, b2], + permissions: [Permission.ATTEST, Permission.DELEGATE], + revoked: false, + }) + + nodes = { + [a1]: topNode, + [b1]: new DelegationNode({ + id: b1, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: a1, + childrenIds: [c1, c2], + revoked: false, + }), + [b2]: new DelegationNode({ + id: b2, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: a1, + revoked: false, + }), + [c1]: new DelegationNode({ + id: c1, + hierarchyId, + account: identityAlice.address, + childrenIds: [d1], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: b1, + revoked: false, + }), + [c2]: new DelegationNode({ + id: c2, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: b1, + revoked: false, + }), + [d1]: new DelegationNode({ + id: d1, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: c1, + revoked: false, + }), + } + }) + + it('mocks work', async () => { + expect(topNode.id).toEqual(a1) + await expect( + topNode.getChildren().then((children) => { + return children.map((childNode) => childNode.id) + }) + ).resolves.toStrictEqual(topNode.childrenIds) + await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) + }) + + it('counts all subnodes', async () => { + await expect(topNode.subtreeNodeCount()).resolves.toStrictEqual(5) + }) + + it('counts smaller subtrees', async () => { + await expect(nodes[b2].subtreeNodeCount()).resolves.toStrictEqual(0) + await expect(nodes[b1].subtreeNodeCount()).resolves.toStrictEqual(3) + await expect(nodes[c1].subtreeNodeCount()).resolves.toStrictEqual(1) + await expect(nodes[c2].subtreeNodeCount()).resolves.toStrictEqual(0) + await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) + }) + + it('counts all subnodes in deeply nested structure (100)', async () => { + const lastIndex = 100 + nodes = hashList + .slice(0, lastIndex + 1) + .reduce((previous, current, index) => { + return { + ...previous, + [current]: new DelegationNode({ + id: current, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + childrenIds: index < lastIndex ? [hashList[index + 1]] : [], + parentId: hashList[index - 1], + revoked: false, + }), + } + }, {}) + await expect(nodes[hashList[0]].subtreeNodeCount()).resolves.toStrictEqual( + 100 + ) + }) + + it('counts all subnodes in deeply nested structure (1000)', async () => { + const lastIndex = 1000 + nodes = hashList + .slice(0, lastIndex + 1) + .reduce((previous, current, index) => { + return { + ...previous, + [current]: new DelegationNode({ + id: current, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + childrenIds: index < lastIndex ? [hashList[index + 1]] : [], + parentId: hashList[index - 1], + revoked: false, + }), + } + }, {}) + await expect(nodes[hashList[0]].subtreeNodeCount()).resolves.toStrictEqual( + 1000 + ) + }) + + it('counts all subnodes in deeply nested structure (10000)', async () => { + const lastIndex = 10000 + nodes = hashList + .slice(0, lastIndex + 1) + .reduce((previous, current, index) => { + return { + ...previous, + [current]: new DelegationNode({ + id: current, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + childrenIds: index < lastIndex ? [hashList[index + 1]] : [], + parentId: hashList[index - 1], + revoked: false, + }), + } + }, {}) + await expect(nodes[hashList[0]].subtreeNodeCount()).resolves.toStrictEqual( + 10000 + ) + }) +}) + +describe('count depth', () => { + beforeAll(() => { + nodes = hashList + .slice(0, 1000) + .map( + (nodeId, index) => + new DelegationNode({ + id: nodeId, + hierarchyId, + account: addresses[index], + permissions: [Permission.DELEGATE], + childrenIds: [], + parentId: hashList[index + 1], + revoked: false, + }) + ) + .reduce((result, node) => { + return { + ...result, + [node.id]: node, + } + }, {}) + + expect(Object.keys(nodes)).toHaveLength(1000) + }) + + it('counts steps from last child till select parent', async () => { + await Promise.all( + [0, 1, 5, 10, 75, 100, 300, 500, 999].map((i) => + expect( + nodes[hashList[0]].findAncestorOwnedBy(nodes[hashList[i]].account) + ).resolves.toMatchObject({ + steps: i, + node: nodes[hashList[i]], + }) + ) + ) + }) + + it('counts various distances within the hierarchy', async () => { + await Promise.all([ + expect( + nodes[hashList[1]].findAncestorOwnedBy(nodes[hashList[2]].account) + ).resolves.toMatchObject({ + steps: 1, + node: nodes[hashList[2]], + }), + expect( + nodes[hashList[250]].findAncestorOwnedBy(nodes[hashList[450]].account) + ).resolves.toMatchObject({ steps: 200, node: nodes[hashList[450]] }), + expect( + nodes[hashList[800]].findAncestorOwnedBy(nodes[hashList[850]].account) + ).resolves.toMatchObject({ + steps: 50, + node: nodes[hashList[850]], + }), + expect( + nodes[hashList[5]].findAncestorOwnedBy(nodes[hashList[955]].account) + ).resolves.toMatchObject({ + steps: 950, + node: nodes[hashList[955]], + }), + ]) + }) + + it('returns null if trying to count backwards', async () => { + await Promise.all([ + expect( + nodes[hashList[10]].findAncestorOwnedBy(nodes[hashList[5]].account) + ).resolves.toMatchObject({ + steps: 989, + node: null, + }), + expect( + nodes[hashList[99]].findAncestorOwnedBy(nodes[hashList[95]].account) + ).resolves.toMatchObject({ steps: 900, node: null }), + expect( + nodes[hashList[900]].findAncestorOwnedBy(nodes[hashList[500]].account) + ).resolves.toMatchObject({ steps: 99, node: null }), + ]) + }) + + it('returns null if looking for non-existent account', async () => { + const noOnesAddress = encodeAddress(Crypto.hash('-1', 256), 38) + await Promise.all([ + expect( + nodes[hashList[10]].findAncestorOwnedBy(noOnesAddress) + ).resolves.toMatchObject({ + steps: 989, + node: null, + }), + expect( + nodes[hashList[99]].findAncestorOwnedBy(noOnesAddress) + ).resolves.toMatchObject({ + steps: 900, + node: null, + }), + expect( + nodes[hashList[900]].findAncestorOwnedBy(noOnesAddress) + ).resolves.toMatchObject({ + steps: 99, + node: null, + }), + ]) + }) + + it('error check should throw errors on faulty delegation nodes', async () => { + const malformedPremissionsDelegationNode = { + id, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [], + parentId: undefined, + revoked: false, + } as IDelegationNode + + const missingRootIdDelegationNode = { + id, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + } as IDelegationNode + + // @ts-expect-error + delete missingRootIdDelegationNode.hierarchyId + + const malformedRootIdDelegationNode = { + id, + hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), + account: identityAlice.address, + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + } as IDelegationNode + + const malformedParentIdDelegationNode = { + id, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + parentId: 'malformed', + revoked: false, + } as IDelegationNode + + expect(() => errorCheck(malformedPremissionsDelegationNode)).toThrowError( + SDKErrors.ERROR_UNAUTHORIZED( + 'Must have at least one permission and no more then two' + ) + ) + + expect(() => errorCheck(missingRootIdDelegationNode)).toThrowError( + SDKErrors.ERROR_DELEGATION_ID_MISSING() + ) + + expect(() => errorCheck(malformedRootIdDelegationNode)).toThrowError( + SDKErrors.ERROR_DELEGATION_ID_TYPE() + ) + + expect(() => errorCheck(malformedParentIdDelegationNode)).toThrowError( + SDKErrors.ERROR_DELEGATION_ID_TYPE() + ) + }) +}) diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index beced6dc6..7c20e4cfc 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -40,7 +40,7 @@ import { storeAsDelegation, storeAsRoot, } from './DelegationNode.chain' -import queryDetails from './DelegationHierarchyDetails.chain' +import { query as queryDetails } from './DelegationHierarchyDetails.chain' import * as DelegationNodeUtils from './DelegationNode.utils' import Attestation from '../attestation/Attestation' import Identity from '../identity/Identity' From 3b92218b8d4cd283efa19457414bb4a45ad5a237 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 29 Jul 2021 15:31:45 +0200 Subject: [PATCH 08/21] test: more unit tests --- .../actors_api/src/actors/Attester.spec.ts | 6 +- .../actors_api/src/actors/Claimer.spec.ts | 6 +- .../actors_api/src/actors/Verifier.spec.ts | 6 +- packages/chain-helpers/package.json | 2 +- .../blockchainApiConnection/TypeRegistry.ts | 2 +- .../__mocks__/BlockchainApiConnection.ts | 2 +- .../__mocks__/BlockchainQuery.ts | 6 +- .../core/src/attestation/Attestation.spec.ts | 6 +- packages/core/src/balance/Balance.spec.ts | 4 +- packages/core/src/ctype/CType.spec.ts | 4 +- .../src/delegation/DelegationNode.spec.ts | 900 ++++++++++-------- packages/core/src/did/Did.spec.ts | 6 +- .../core/src/identity/PublicIdentity.spec.ts | 6 +- yarn.lock | 10 +- 14 files changed, 535 insertions(+), 431 deletions(-) diff --git a/packages/actors_api/src/actors/Attester.spec.ts b/packages/actors_api/src/actors/Attester.spec.ts index 0dfdca9c2..88bc21f24 100644 --- a/packages/actors_api/src/actors/Attester.spec.ts +++ b/packages/actors_api/src/actors/Attester.spec.ts @@ -11,18 +11,18 @@ import { CType, Identity, SDKErrors } from '@kiltprotocol/core' import type { ICType, IClaim, IMessage } from '@kiltprotocol/types' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' import Message from '@kiltprotocol/messaging' import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer } from '..' import { issueAttestation } from './Attester' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('Attester', () => { - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api let attester: Identity let claimer: Identity diff --git a/packages/actors_api/src/actors/Claimer.spec.ts b/packages/actors_api/src/actors/Claimer.spec.ts index 2d4890bb1..93a177c38 100644 --- a/packages/actors_api/src/actors/Claimer.spec.ts +++ b/packages/actors_api/src/actors/Claimer.spec.ts @@ -17,18 +17,18 @@ import type { ISubmitClaimsForCTypes, MessageBodyType, } from '@kiltprotocol/types' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' import Message from '@kiltprotocol/messaging' import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer, Verifier } from '..' import type { ClaimerAttestationSession } from './Claimer' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('Claimer', () => { - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api let attester: Identity let claimer: Identity diff --git a/packages/actors_api/src/actors/Verifier.spec.ts b/packages/actors_api/src/actors/Verifier.spec.ts index c65fa9223..cab8b1830 100644 --- a/packages/actors_api/src/actors/Verifier.spec.ts +++ b/packages/actors_api/src/actors/Verifier.spec.ts @@ -11,13 +11,13 @@ import { AttestedClaim, CType, Identity } from '@kiltprotocol/core' import type { IClaim, ICType } from '@kiltprotocol/types' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' import Message from '@kiltprotocol/messaging' import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer, Verifier } from '..' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('Verifier', () => { @@ -27,7 +27,7 @@ describe('Verifier', () => { let cType: CType let claim: IClaim let credential: AttestedClaim - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api beforeAll(async () => { diff --git a/packages/chain-helpers/package.json b/packages/chain-helpers/package.json index bbaaaf939..932412b7b 100644 --- a/packages/chain-helpers/package.json +++ b/packages/chain-helpers/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@kiltprotocol/config": "workspace:*", - "@kiltprotocol/type-definitions": "0.1.6", + "@kiltprotocol/type-definitions": "0.1.10", "@kiltprotocol/types": "workspace:*", "@kiltprotocol/utils": "workspace:*", "@polkadot/api": "^4.13.1", diff --git a/packages/chain-helpers/src/blockchainApiConnection/TypeRegistry.ts b/packages/chain-helpers/src/blockchainApiConnection/TypeRegistry.ts index 81621fe2f..27b8a4371 100644 --- a/packages/chain-helpers/src/blockchainApiConnection/TypeRegistry.ts +++ b/packages/chain-helpers/src/blockchainApiConnection/TypeRegistry.ts @@ -5,7 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { types10 as KILT_TYPES } from '@kiltprotocol/type-definitions' +import { types17 as KILT_TYPES } from '@kiltprotocol/type-definitions' import { TypeRegistry } from '@polkadot/types' const TYPE_REGISTRY = new TypeRegistry() diff --git a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts index a3549e1de..b25b7d65c 100644 --- a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts +++ b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainApiConnection.ts @@ -304,7 +304,7 @@ const __mocked_api: any = { }, delegation: { // default return value decodes to null, represents delegation not found - roots: jest.fn(async (rootId: string) => + hierarchies: jest.fn(async (rootId: string) => mockChainQueryReturn('delegation', 'hierarchies') ), /* example return value: diff --git a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts index a0664953b..91656c90c 100644 --- a/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts +++ b/packages/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery.ts @@ -35,13 +35,13 @@ const chainQueryReturnTuples: { }, delegation: { // Delegation hierarchies: root-id -> (ctype-hash)? - hierarchies: TYPE_REGISTRY.getOrUnknown('DelegationHierarchies'), + hierarchies: TYPE_REGISTRY.getOrUnknown('DelegationHierarchyDetails'), // Delegations: delegation-id -> (hierarchy-id, parent-id?, childrenIds, details)? - delegations: TYPE_REGISTRY.getOrUnknown('DelegationNodes'), + delegations: TYPE_REGISTRY.getOrUnknown('DelegationNode'), }, attestation: { // Attestations: claim-hash -> (ctype-hash, attester-account, delegation-id?, revoked)? - attestations: TYPE_REGISTRY.getOrUnknown('Attestation'), + attestations: TYPE_REGISTRY.getOrUnknown('AttestationDetails'), // DelegatedAttestations: delegation-id -> [claim-hash] delegatedAttestations: TYPE_REGISTRY.getOrUnknown('Hash'), }, diff --git a/packages/core/src/attestation/Attestation.spec.ts b/packages/core/src/attestation/Attestation.spec.ts index 2d1ce1c99..fc75cb445 100644 --- a/packages/core/src/attestation/Attestation.spec.ts +++ b/packages/core/src/attestation/Attestation.spec.ts @@ -17,7 +17,7 @@ import type { ICType, } from '@kiltprotocol/types' import { SDKErrors } from '@kiltprotocol/utils' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' import Claim from '../claim/Claim' import CType from '../ctype/CType' import Identity from '../identity/Identity' @@ -27,7 +27,7 @@ import AttestationUtils from './Attestation.utils' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('Attestation', () => { @@ -39,7 +39,7 @@ describe('Attestation', () => { let testcontents: any let testClaim: Claim let requestForAttestation: RequestForAttestation - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api beforeAll(async () => { diff --git a/packages/core/src/balance/Balance.spec.ts b/packages/core/src/balance/Balance.spec.ts index ca81d262b..abdec945e 100644 --- a/packages/core/src/balance/Balance.spec.ts +++ b/packages/core/src/balance/Balance.spec.ts @@ -29,7 +29,7 @@ import BalanceUtils from './Balance.utils' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) const BALANCE = 42 @@ -39,7 +39,7 @@ describe('Balance', () => { Kilt.config({ address: 'ws://testSting' }) let alice: Identity let bob: Identity - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api const accountInfo = (balance: number): AccountInfo => { diff --git a/packages/core/src/ctype/CType.spec.ts b/packages/core/src/ctype/CType.spec.ts index 333f31684..89756e5cf 100644 --- a/packages/core/src/ctype/CType.spec.ts +++ b/packages/core/src/ctype/CType.spec.ts @@ -31,11 +31,11 @@ import CTypeUtils, { getIdForSchema } from './CType.utils' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('CType', () => { - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api const registry = new TypeRegistry() let ctypeModel: ICType['schema'] diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index 1042612b8..4b65f1dfe 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -31,6 +31,24 @@ jest.mock('./DelegationNode.chain', () => { node.childrenIds.map((id) => nodes[id] || null) ), query: jest.fn(async (id: string) => nodes[id] || null), + storeAsRoot: jest.fn(async (node: DelegationNode) => { + nodes[node.id] = node + hierarchiesDetails[node.id] = { cTypeHash: await node.cTypeHash } + }), + revoke: jest.fn( + async ( + nodeId: IDelegationNode['id'], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + maxDepth: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + maxRevocations: number + ) => { + nodes[nodeId] = new DelegationNode({ + ...nodes[nodeId], + revoked: true, + }) + } + ), } }) @@ -38,469 +56,555 @@ jest.mock('./DelegationHierarchyDetails.chain', () => ({ query: jest.fn(async (id: string) => hierarchiesDetails[id] || null), })) -let identityAlice: Identity -let identityBob: Identity -let id: string -let successId: string -let failureId: string -let hierarchyId: string -let parentId: string -let hashList: string[] -let addresses: string[] - -beforeAll(() => { - identityAlice = Identity.buildFromURI('//Alice') - identityBob = Identity.buildFromURI('//Bob') - successId = Crypto.hashStr('success') - hierarchyId = Crypto.hashStr('rootId') - id = Crypto.hashStr('id') - parentId = Crypto.hashStr('parentId') - failureId = Crypto.hashStr('failure') - hashList = Array(10002) - .fill('') - .map((_val, index) => Crypto.hashStr(`${index + 1}`)) - addresses = Array(10002) - .fill('') - .map((_val, index) => - encodeAddress(Crypto.hash(`${index}`, 256), 38) - ) -}) - -describe('Delegation', () => { - it('delegation generate hash', () => { - const node = new DelegationNode({ - id, - hierarchyId, - parentId, - account: identityBob.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - revoked: false, - }) - const hash: string = node.generateHash() - expect(hash).toBe( - '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' - ) - }) +describe('DelegationNode', () => { + let identityAlice: Identity + let identityBob: Identity + let id: string + let successId: string + let failureId: string + let hierarchyId: string + let parentId: string + let hashList: string[] + let addresses: string[] - it('delegation permissionsAsBitset', () => { - const node = new DelegationNode({ - id, - hierarchyId, - parentId, - account: identityBob.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - revoked: false, - }) - const permissions: Uint8Array = permissionsAsBitset(node) - const expected: Uint8Array = new Uint8Array(4) - expected[0] = 2 - expect(permissions.toString()).toBe(expected.toString()) + beforeAll(() => { + identityAlice = Identity.buildFromURI('//Alice') + identityBob = Identity.buildFromURI('//Bob') + successId = Crypto.hashStr('success') + hierarchyId = Crypto.hashStr('rootId') + id = Crypto.hashStr('id') + parentId = Crypto.hashStr('parentId') + failureId = Crypto.hashStr('failure') + hashList = Array(10002) + .fill('') + .map((_val, index) => Crypto.hashStr(`${index + 1}`)) + addresses = Array(10002) + .fill('') + .map((_val, index) => + encodeAddress(Crypto.hash(`${index}`, 256), 38) + ) }) - it('delegation verify', async () => { - nodes = { - [successId]: new DelegationNode({ - id: successId, + describe('Delegation', () => { + it('delegation generate hash', () => { + const node = new DelegationNode({ + id, hierarchyId, - account: identityAlice.address, + parentId, + account: identityBob.address, childrenIds: [], permissions: [Permission.DELEGATE], - parentId: undefined, revoked: false, - }), - [failureId]: { - ...nodes.success, - revoked: true, - id: failureId, - } as DelegationNode, - } + }) + const hash: string = node.generateHash() + expect(hash).toBe( + '0x3f3dc0df7527013f2373f18f55cf87847df3249526e9b1d5aa75df8eeb5b7d6e' + ) + }) - expect( - await new DelegationNode({ - id: successId, + it('delegation permissionsAsBitset', () => { + const node = new DelegationNode({ + id, hierarchyId, - account: identityAlice.address, + parentId, + account: identityBob.address, childrenIds: [], permissions: [Permission.DELEGATE], - parentId: undefined, revoked: false, - }).verify() - ).toBe(true) + }) + const permissions: Uint8Array = permissionsAsBitset(node) + const expected: Uint8Array = new Uint8Array(4) + expected[0] = 2 + expect(permissions.toString()).toBe(expected.toString()) + }) + + it('delegation verify', async () => { + nodes = { + [successId]: new DelegationNode({ + id: successId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + }), + [failureId]: { + ...nodes.success, + revoked: true, + id: failureId, + } as DelegationNode, + } + + expect( + await new DelegationNode({ + id: successId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + }).verify() + ).toBe(true) - expect( - await new DelegationNode({ - id: failureId, + expect( + await new DelegationNode({ + id: failureId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + parentId: undefined, + revoked: false, + }).verify() + ).toBe(false) + }) + + it('get delegation root', async () => { + hierarchiesDetails = { + [hierarchyId]: { + cTypeHash: + 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', + }, + } + + nodes = { + [hierarchyId]: new DelegationNode({ + id: hierarchyId, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.DELEGATE], + revoked: false, + }), + } + + const node: DelegationNode = new DelegationNode({ + id, hierarchyId, account: identityAlice.address, childrenIds: [], permissions: [Permission.DELEGATE], - parentId: undefined, revoked: false, - }).verify() - ).toBe(false) - }) + }) + const hierarchyDetails = await node.getHierarchyDetails() - it('get delegation root', async () => { - hierarchiesDetails = { - [hierarchyId]: { - cTypeHash: - 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', - }, - } + expect(hierarchyDetails).toBeDefined() + expect(hierarchyDetails.cTypeHash).toBe( + 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' + ) + }) + }) - nodes = { - [hierarchyId]: new DelegationNode({ - id: hierarchyId, + describe('count subtree', () => { + let topNode: DelegationNode + const a1: string = Crypto.hashStr('a1') + const b1: string = Crypto.hashStr('b1') + const b2: string = Crypto.hashStr('b2') + const c1: string = Crypto.hashStr('c1') + const c2: string = Crypto.hashStr('c2') + const d1: string = Crypto.hashStr('d1') + beforeAll(() => { + topNode = new DelegationNode({ + id: a1, hierarchyId, account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], + childrenIds: [b1, b2], + permissions: [Permission.ATTEST, Permission.DELEGATE], revoked: false, - }), - } + }) - const node: DelegationNode = new DelegationNode({ - id, - hierarchyId, - account: identityAlice.address, - childrenIds: [], - permissions: [Permission.DELEGATE], - revoked: false, + nodes = { + [a1]: topNode, + [b1]: new DelegationNode({ + id: b1, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: a1, + childrenIds: [c1, c2], + revoked: false, + }), + [b2]: new DelegationNode({ + id: b2, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: a1, + revoked: false, + }), + [c1]: new DelegationNode({ + id: c1, + hierarchyId, + account: identityAlice.address, + childrenIds: [d1], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: b1, + revoked: false, + }), + [c2]: new DelegationNode({ + id: c2, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: b1, + revoked: false, + }), + [d1]: new DelegationNode({ + id: d1, + hierarchyId, + account: identityAlice.address, + childrenIds: [], + permissions: [Permission.ATTEST, Permission.DELEGATE], + parentId: c1, + revoked: false, + }), + } + }) + + it('mocks work', async () => { + expect(topNode.id).toEqual(a1) + await expect( + topNode.getChildren().then((children) => { + return children.map((childNode) => childNode.id) + }) + ).resolves.toStrictEqual(topNode.childrenIds) + await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) + }) + + it('counts all subnodes', async () => { + await expect(topNode.subtreeNodeCount()).resolves.toStrictEqual(5) + }) + + it('counts smaller subtrees', async () => { + await expect(nodes[b2].subtreeNodeCount()).resolves.toStrictEqual(0) + await expect(nodes[b1].subtreeNodeCount()).resolves.toStrictEqual(3) + await expect(nodes[c1].subtreeNodeCount()).resolves.toStrictEqual(1) + await expect(nodes[c2].subtreeNodeCount()).resolves.toStrictEqual(0) + await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) }) - const hierarchyDetails = await node.getHierarchyDetails() - expect(hierarchyDetails).toBeDefined() - expect(hierarchyDetails.cTypeHash).toBe( - 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84' - ) + it('counts all subnodes in deeply nested structure (100)', async () => { + const lastIndex = 100 + nodes = hashList + .slice(0, lastIndex + 1) + .reduce((previous, current, index) => { + return { + ...previous, + [current]: new DelegationNode({ + id: current, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + childrenIds: index < lastIndex ? [hashList[index + 1]] : [], + parentId: hashList[index - 1], + revoked: false, + }), + } + }, {}) + await expect( + nodes[hashList[0]].subtreeNodeCount() + ).resolves.toStrictEqual(100) + }) + + it('counts all subnodes in deeply nested structure (1000)', async () => { + const lastIndex = 1000 + nodes = hashList + .slice(0, lastIndex + 1) + .reduce((previous, current, index) => { + return { + ...previous, + [current]: new DelegationNode({ + id: current, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + childrenIds: index < lastIndex ? [hashList[index + 1]] : [], + parentId: hashList[index - 1], + revoked: false, + }), + } + }, {}) + await expect( + nodes[hashList[0]].subtreeNodeCount() + ).resolves.toStrictEqual(1000) + }) + + it('counts all subnodes in deeply nested structure (10000)', async () => { + const lastIndex = 10000 + nodes = hashList + .slice(0, lastIndex + 1) + .reduce((previous, current, index) => { + return { + ...previous, + [current]: new DelegationNode({ + id: current, + hierarchyId, + account: identityAlice.address, + permissions: [Permission.DELEGATE], + childrenIds: index < lastIndex ? [hashList[index + 1]] : [], + parentId: hashList[index - 1], + revoked: false, + }), + } + }, {}) + await expect( + nodes[hashList[0]].subtreeNodeCount() + ).resolves.toStrictEqual(10000) + }) }) -}) -describe('count subtree', () => { - let topNode: DelegationNode - const a1: string = Crypto.hashStr('a1') - const b1: string = Crypto.hashStr('b1') - const b2: string = Crypto.hashStr('b2') - const c1: string = Crypto.hashStr('c1') - const c2: string = Crypto.hashStr('c2') - const d1: string = Crypto.hashStr('d1') - beforeAll(() => { - topNode = new DelegationNode({ - id: a1, - hierarchyId, - account: identityAlice.address, - childrenIds: [b1, b2], - permissions: [Permission.ATTEST, Permission.DELEGATE], - revoked: false, + describe('count depth', () => { + beforeAll(() => { + nodes = hashList + .slice(0, 1000) + .map( + (nodeId, index) => + new DelegationNode({ + id: nodeId, + hierarchyId, + account: addresses[index], + permissions: [Permission.DELEGATE], + childrenIds: [], + parentId: hashList[index + 1], + revoked: false, + }) + ) + .reduce((result, node) => { + return { + ...result, + [node.id]: node, + } + }, {}) + + expect(Object.keys(nodes)).toHaveLength(1000) }) - nodes = { - [a1]: topNode, - [b1]: new DelegationNode({ - id: b1, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: a1, - childrenIds: [c1, c2], - revoked: false, - }), - [b2]: new DelegationNode({ - id: b2, + it('counts steps from last child till select parent', async () => { + await Promise.all( + [0, 1, 5, 10, 75, 100, 300, 500, 999].map((i) => + expect( + nodes[hashList[0]].findAncestorOwnedBy(nodes[hashList[i]].account) + ).resolves.toMatchObject({ + steps: i, + node: nodes[hashList[i]], + }) + ) + ) + }) + + it('counts various distances within the hierarchy', async () => { + await Promise.all([ + expect( + nodes[hashList[1]].findAncestorOwnedBy(nodes[hashList[2]].account) + ).resolves.toMatchObject({ + steps: 1, + node: nodes[hashList[2]], + }), + expect( + nodes[hashList[250]].findAncestorOwnedBy(nodes[hashList[450]].account) + ).resolves.toMatchObject({ steps: 200, node: nodes[hashList[450]] }), + expect( + nodes[hashList[800]].findAncestorOwnedBy(nodes[hashList[850]].account) + ).resolves.toMatchObject({ + steps: 50, + node: nodes[hashList[850]], + }), + expect( + nodes[hashList[5]].findAncestorOwnedBy(nodes[hashList[955]].account) + ).resolves.toMatchObject({ + steps: 950, + node: nodes[hashList[955]], + }), + ]) + }) + + it('returns null if trying to count backwards', async () => { + await Promise.all([ + expect( + nodes[hashList[10]].findAncestorOwnedBy(nodes[hashList[5]].account) + ).resolves.toMatchObject({ + steps: 989, + node: null, + }), + expect( + nodes[hashList[99]].findAncestorOwnedBy(nodes[hashList[95]].account) + ).resolves.toMatchObject({ steps: 900, node: null }), + expect( + nodes[hashList[900]].findAncestorOwnedBy(nodes[hashList[500]].account) + ).resolves.toMatchObject({ steps: 99, node: null }), + ]) + }) + + it('returns null if looking for non-existent account', async () => { + const noOnesAddress = encodeAddress(Crypto.hash('-1', 256), 38) + await Promise.all([ + expect( + nodes[hashList[10]].findAncestorOwnedBy(noOnesAddress) + ).resolves.toMatchObject({ + steps: 989, + node: null, + }), + expect( + nodes[hashList[99]].findAncestorOwnedBy(noOnesAddress) + ).resolves.toMatchObject({ + steps: 900, + node: null, + }), + expect( + nodes[hashList[900]].findAncestorOwnedBy(noOnesAddress) + ).resolves.toMatchObject({ + steps: 99, + node: null, + }), + ]) + }) + + it('error check should throw errors on faulty delegation nodes', async () => { + const malformedPremissionsDelegationNode = { + id, hierarchyId, account: identityAlice.address, childrenIds: [], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: a1, + permissions: [], + parentId: undefined, revoked: false, - }), - [c1]: new DelegationNode({ - id: c1, + } as IDelegationNode + + const missingRootIdDelegationNode = { + id, hierarchyId, account: identityAlice.address, - childrenIds: [d1], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: b1, + permissions: [Permission.DELEGATE], + parentId: undefined, revoked: false, - }), - [c2]: new DelegationNode({ - id: c2, - hierarchyId, + } as IDelegationNode + + // @ts-expect-error + delete missingRootIdDelegationNode.hierarchyId + + const malformedRootIdDelegationNode = { + id, + hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), account: identityAlice.address, - childrenIds: [], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: b1, + permissions: [Permission.DELEGATE], + parentId: undefined, revoked: false, - }), - [d1]: new DelegationNode({ - id: d1, + } as IDelegationNode + + const malformedParentIdDelegationNode = { + id, hierarchyId, account: identityAlice.address, - childrenIds: [], - permissions: [Permission.ATTEST, Permission.DELEGATE], - parentId: c1, + permissions: [Permission.DELEGATE], + parentId: 'malformed', revoked: false, - }), - } - }) - - it('mocks work', async () => { - expect(topNode.id).toEqual(a1) - await expect( - topNode.getChildren().then((children) => { - return children.map((childNode) => childNode.id) - }) - ).resolves.toStrictEqual(topNode.childrenIds) - await expect(nodes[d1].getChildren()).resolves.toStrictEqual([]) - }) - - it('counts all subnodes', async () => { - await expect(topNode.subtreeNodeCount()).resolves.toStrictEqual(5) - }) - - it('counts smaller subtrees', async () => { - await expect(nodes[b2].subtreeNodeCount()).resolves.toStrictEqual(0) - await expect(nodes[b1].subtreeNodeCount()).resolves.toStrictEqual(3) - await expect(nodes[c1].subtreeNodeCount()).resolves.toStrictEqual(1) - await expect(nodes[c2].subtreeNodeCount()).resolves.toStrictEqual(0) - await expect(nodes[d1].subtreeNodeCount()).resolves.toStrictEqual(0) - }) + } as IDelegationNode - it('counts all subnodes in deeply nested structure (100)', async () => { - const lastIndex = 100 - nodes = hashList - .slice(0, lastIndex + 1) - .reduce((previous, current, index) => { - return { - ...previous, - [current]: new DelegationNode({ - id: current, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - childrenIds: index < lastIndex ? [hashList[index + 1]] : [], - parentId: hashList[index - 1], - revoked: false, - }), - } - }, {}) - await expect(nodes[hashList[0]].subtreeNodeCount()).resolves.toStrictEqual( - 100 - ) - }) - - it('counts all subnodes in deeply nested structure (1000)', async () => { - const lastIndex = 1000 - nodes = hashList - .slice(0, lastIndex + 1) - .reduce((previous, current, index) => { - return { - ...previous, - [current]: new DelegationNode({ - id: current, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - childrenIds: index < lastIndex ? [hashList[index + 1]] : [], - parentId: hashList[index - 1], - revoked: false, - }), - } - }, {}) - await expect(nodes[hashList[0]].subtreeNodeCount()).resolves.toStrictEqual( - 1000 - ) - }) - - it('counts all subnodes in deeply nested structure (10000)', async () => { - const lastIndex = 10000 - nodes = hashList - .slice(0, lastIndex + 1) - .reduce((previous, current, index) => { - return { - ...previous, - [current]: new DelegationNode({ - id: current, - hierarchyId, - account: identityAlice.address, - permissions: [Permission.DELEGATE], - childrenIds: index < lastIndex ? [hashList[index + 1]] : [], - parentId: hashList[index - 1], - revoked: false, - }), - } - }, {}) - await expect(nodes[hashList[0]].subtreeNodeCount()).resolves.toStrictEqual( - 10000 - ) - }) -}) + expect(() => errorCheck(malformedPremissionsDelegationNode)).toThrowError( + SDKErrors.ERROR_UNAUTHORIZED( + 'Must have at least one permission and no more then two' + ) + ) -describe('count depth', () => { - beforeAll(() => { - nodes = hashList - .slice(0, 1000) - .map( - (nodeId, index) => - new DelegationNode({ - id: nodeId, - hierarchyId, - account: addresses[index], - permissions: [Permission.DELEGATE], - childrenIds: [], - parentId: hashList[index + 1], - revoked: false, - }) + expect(() => errorCheck(missingRootIdDelegationNode)).toThrowError( + SDKErrors.ERROR_DELEGATION_ID_MISSING() ) - .reduce((result, node) => { - return { - ...result, - [node.id]: node, - } - }, {}) - - expect(Object.keys(nodes)).toHaveLength(1000) - }) - it('counts steps from last child till select parent', async () => { - await Promise.all( - [0, 1, 5, 10, 75, 100, 300, 500, 999].map((i) => - expect( - nodes[hashList[0]].findAncestorOwnedBy(nodes[hashList[i]].account) - ).resolves.toMatchObject({ - steps: i, - node: nodes[hashList[i]], - }) + expect(() => errorCheck(malformedRootIdDelegationNode)).toThrowError( + SDKErrors.ERROR_DELEGATION_ID_TYPE() ) - ) - }) - it('counts various distances within the hierarchy', async () => { - await Promise.all([ - expect( - nodes[hashList[1]].findAncestorOwnedBy(nodes[hashList[2]].account) - ).resolves.toMatchObject({ - steps: 1, - node: nodes[hashList[2]], - }), - expect( - nodes[hashList[250]].findAncestorOwnedBy(nodes[hashList[450]].account) - ).resolves.toMatchObject({ steps: 200, node: nodes[hashList[450]] }), - expect( - nodes[hashList[800]].findAncestorOwnedBy(nodes[hashList[850]].account) - ).resolves.toMatchObject({ - steps: 50, - node: nodes[hashList[850]], - }), - expect( - nodes[hashList[5]].findAncestorOwnedBy(nodes[hashList[955]].account) - ).resolves.toMatchObject({ - steps: 950, - node: nodes[hashList[955]], - }), - ]) + expect(() => errorCheck(malformedParentIdDelegationNode)).toThrowError( + SDKErrors.ERROR_DELEGATION_ID_TYPE() + ) + }) }) +}) - it('returns null if trying to count backwards', async () => { - await Promise.all([ - expect( - nodes[hashList[10]].findAncestorOwnedBy(nodes[hashList[5]].account) - ).resolves.toMatchObject({ - steps: 989, - node: null, - }), - expect( - nodes[hashList[99]].findAncestorOwnedBy(nodes[hashList[95]].account) - ).resolves.toMatchObject({ steps: 900, node: null }), - expect( - nodes[hashList[900]].findAncestorOwnedBy(nodes[hashList[500]].account) - ).resolves.toMatchObject({ steps: 99, node: null }), - ]) - }) +describe('DelegationHierarchy', () => { + let identityAlice: Identity + let ctypeHash: string + let ROOT_IDENTIFIER: string + let ROOT_SUCCESS: string - it('returns null if looking for non-existent account', async () => { - const noOnesAddress = encodeAddress(Crypto.hash('-1', 256), 38) - await Promise.all([ - expect( - nodes[hashList[10]].findAncestorOwnedBy(noOnesAddress) - ).resolves.toMatchObject({ - steps: 989, - node: null, - }), - expect( - nodes[hashList[99]].findAncestorOwnedBy(noOnesAddress) - ).resolves.toMatchObject({ - steps: 900, - node: null, - }), - expect( - nodes[hashList[900]].findAncestorOwnedBy(noOnesAddress) - ).resolves.toMatchObject({ - steps: 99, - node: null, - }), - ]) - }) + beforeAll(async () => { + identityAlice = Identity.buildFromURI('//Alice') + ctypeHash = `0x6b696c743a63747970653a307830303031000000000000000000000000000000` + ROOT_IDENTIFIER = Crypto.hashStr('1') + ROOT_SUCCESS = Crypto.hashStr('success') - it('error check should throw errors on faulty delegation nodes', async () => { - const malformedPremissionsDelegationNode = { - id, - hierarchyId, + const revokedRootDelegationNode = new DelegationNode({ account: identityAlice.address, childrenIds: [], - permissions: [], - parentId: undefined, - revoked: false, - } as IDelegationNode - - const missingRootIdDelegationNode = { - id, - hierarchyId, + hierarchyId: ROOT_IDENTIFIER, + id: ROOT_IDENTIFIER, + permissions: [Permission.DELEGATE], + revoked: true, + }) + const notRevokedRootDelegationNode = new DelegationNode({ account: identityAlice.address, + childrenIds: [], + hierarchyId: ROOT_SUCCESS, + id: ROOT_SUCCESS, permissions: [Permission.DELEGATE], - parentId: undefined, revoked: false, - } as IDelegationNode + }) - // @ts-expect-error - delete missingRootIdDelegationNode.hierarchyId + nodes = { + [ROOT_IDENTIFIER]: revokedRootDelegationNode, + [ROOT_SUCCESS]: notRevokedRootDelegationNode, + } + hierarchiesDetails = { + [ROOT_IDENTIFIER]: { cTypeHash: ctypeHash }, + [ROOT_SUCCESS]: { cTypeHash: ctypeHash }, + } + }) - const malformedRootIdDelegationNode = { - id, - hierarchyId: hierarchyId.slice(13) + hierarchyId.slice(15), + it('stores root delegation', async () => { + const rootDelegation = new DelegationNode({ account: identityAlice.address, + childrenIds: [], + hierarchyId: ROOT_IDENTIFIER, + id: ROOT_IDENTIFIER, permissions: [Permission.DELEGATE], - parentId: undefined, revoked: false, - } as IDelegationNode + }) + await rootDelegation.store() + + const rootNode = await DelegationNode.query(ROOT_IDENTIFIER) + if (rootNode) { + expect(rootNode.id).toBe(ROOT_IDENTIFIER) + } + }) + + it('query root delegation', async () => { + const queriedDelegation = await DelegationNode.query(ROOT_IDENTIFIER) + expect(queriedDelegation).not.toBe(undefined) + if (queriedDelegation) { + expect(queriedDelegation.account).toBe(identityAlice.address) + expect(queriedDelegation.cTypeHash).resolves.toBe(ctypeHash) + expect(queriedDelegation.id).toBe(ROOT_IDENTIFIER) + } + }) - const malformedParentIdDelegationNode = { - id, - hierarchyId, + it('root delegation verify', async () => { + const aDelegationRootNode = new DelegationNode({ account: identityAlice.address, + childrenIds: [], + hierarchyId: ROOT_IDENTIFIER, + id: ROOT_IDENTIFIER, permissions: [Permission.DELEGATE], - parentId: 'malformed', revoked: false, - } as IDelegationNode - - expect(() => errorCheck(malformedPremissionsDelegationNode)).toThrowError( - SDKErrors.ERROR_UNAUTHORIZED( - 'Must have at least one permission and no more then two' - ) - ) - - expect(() => errorCheck(missingRootIdDelegationNode)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_MISSING() - ) - - expect(() => errorCheck(malformedRootIdDelegationNode)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_TYPE() - ) - - expect(() => errorCheck(malformedParentIdDelegationNode)).toThrowError( - SDKErrors.ERROR_DELEGATION_ID_TYPE() - ) + }) + await aDelegationRootNode.revoke(identityAlice.address) + const fetchedNodeRevocationStatus = DelegationNode.query( + ROOT_IDENTIFIER + ).then((node) => node?.revoked) + expect(fetchedNodeRevocationStatus).resolves.not.toBeNull() + expect(fetchedNodeRevocationStatus).resolves.toEqual(true) }) }) diff --git a/packages/core/src/did/Did.spec.ts b/packages/core/src/did/Did.spec.ts index 62e29b29b..af6f37a99 100644 --- a/packages/core/src/did/Did.spec.ts +++ b/packages/core/src/did/Did.spec.ts @@ -11,7 +11,7 @@ import { U8aFixed } from '@polkadot/types' import { SDKErrors } from '@kiltprotocol/utils' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' import { BlockchainUtils, TypeRegistry as TYPE_REGISTRY, @@ -25,14 +25,14 @@ import { import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('DID', () => { const key1 = new U8aFixed(TYPE_REGISTRY, 'box-me', 256) const key2 = new U8aFixed(TYPE_REGISTRY, 'sign-me', 256) Kilt.config({ address: 'ws://testString' }) - const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') .__mocked_api beforeAll(() => { diff --git a/packages/core/src/identity/PublicIdentity.spec.ts b/packages/core/src/identity/PublicIdentity.spec.ts index 8217f6d76..fce716b42 100644 --- a/packages/core/src/identity/PublicIdentity.spec.ts +++ b/packages/core/src/identity/PublicIdentity.spec.ts @@ -12,12 +12,12 @@ import { U8aFixed } from '@polkadot/types' import type { IPublicIdentity } from '@kiltprotocol/types' import { TypeRegistry as TYPE_REGISTRY } from '@kiltprotocol/chain-helpers' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' import PublicIdentity, { IURLResolver } from './PublicIdentity' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' ) describe('PublicIdentity', () => { @@ -26,7 +26,7 @@ describe('PublicIdentity', () => { Kilt.config({ address: 'ws://testString' }) // https://polkadot.js.org/api/examples/promise/ // testing to create correct demo accounts - require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.did.dIDs = jest.fn( + require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.did.dIDs = jest.fn( async (id) => { switch (id) { case '1': diff --git a/yarn.lock b/yarn.lock index 61d62bd32..ad4ccfccb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -815,7 +815,7 @@ __metadata: resolution: "@kiltprotocol/chain-helpers@workspace:packages/chain-helpers" dependencies: "@kiltprotocol/config": "workspace:*" - "@kiltprotocol/type-definitions": 0.1.6 + "@kiltprotocol/type-definitions": 0.1.10 "@kiltprotocol/types": "workspace:*" "@kiltprotocol/utils": "workspace:*" "@polkadot/api": ^4.13.1 @@ -913,10 +913,10 @@ __metadata: languageName: unknown linkType: soft -"@kiltprotocol/type-definitions@npm:0.1.6": - version: 0.1.6 - resolution: "@kiltprotocol/type-definitions@npm:0.1.6" - checksum: 685d3a63959d9482116293eb38993e84825615a0a994eb75315fbe28ba4a6fe3d0d4b5db3947942c6aeaa2d2c44a75b0f64fbb961b8df8707d4dd2b2c7939090 +"@kiltprotocol/type-definitions@npm:0.1.10": + version: 0.1.10 + resolution: "@kiltprotocol/type-definitions@npm:0.1.10" + checksum: 73a0a2720f2d7a7905043971699fa0a0daeb8e32ccbe2386ff385a2bdec773c1fd36cf33a422cfaf5069f78b0013e046d4edcd55099d6f86c715d2d585fafa7e languageName: node linkType: hard From 639f902875ee059c48df1398bc96916b0372f692 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 29 Jul 2021 16:10:08 +0200 Subject: [PATCH 09/21] test: first integration test passing --- .eslintignore | 2 +- .../__integrationtests__/Delegation.spec.ts | 206 +++++++++--------- .../core/src/__integrationtests__/utils.ts | 2 +- .../core/src/delegation/DelegationDecoder.ts | 6 +- .../src/delegation/DelegationNode.chain.ts | 3 +- 5 files changed, 104 insertions(+), 115 deletions(-) diff --git a/.eslintignore b/.eslintignore index e31e345a4..61e59a3ca 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,4 @@ dist lib jest.* coverage -*.d.ts +*.d.ts \ No newline at end of file diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index faef295f4..2d8e4e088 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -1,118 +1,108 @@ -// /** -// * Copyright 2018-2021 BOTLabs GmbH. -// * -// * This source code is licensed under the BSD 4-Clause "Original" license -// * found in the LICENSE file in the root directory of this source tree. -// */ +/** + * Copyright 2018-2021 BOTLabs GmbH. + * + * This source code is licensed under the BSD 4-Clause "Original" license + * found in the LICENSE file in the root directory of this source tree. + */ -// /** -// * @group integration/delegation -// */ +/** + * @group integration/delegation + */ -// import type { ICType, IDelegationNode } from '@kiltprotocol/types' -// import { Permission } from '@kiltprotocol/types' -// import { UUID } from '@kiltprotocol/utils' -// import { BlockchainUtils } from '@kiltprotocol/chain-helpers' -// import { DelegationHierarchyDetails } from '@kiltprotocol/sdk-js' -// import { AttestedClaim, Identity } from '..' -// import Attestation from '../attestation/Attestation' -// import { config, disconnect } from '../kilt' -// import Claim from '../claim/Claim' -// import { -// fetchChildren, -// getAttestationHashes, -// } from '../delegation/DelegationNode.chain' -// import { decodeDelegationNode } from '../delegation/DelegationDecoder' -// import DelegationNode from '../delegation/DelegationNode' -// import RequestForAttestation from '../requestforattestation/RequestForAttestation' -// import { -// CtypeOnChain, -// DriversLicense, -// wannabeAlice, -// wannabeBob, -// wannabeFaucet, -// WS_ADDRESS, -// } from './utils' +import type { ICType, IDelegationNode } from '@kiltprotocol/types' +import { Permission } from '@kiltprotocol/types' +import { BlockchainUtils } from '@kiltprotocol/chain-helpers' +import { Identity } from '..' +import { config, disconnect } from '../kilt' +import DelegationNode from '../delegation/DelegationNode' +import { + CtypeOnChain, + DriversLicense, + wannabeAlice, + wannabeFaucet, + WS_ADDRESS, +} from './utils' -// async function writeHierarchy( -// delegator: Identity, -// ctypeHash: ICType['hash'] -// ): Promise<{ details: DelegationHierarchyDetails; rootNode: DelegationNode }> { -// const details = new DelegationHierarchyDetails({ -// rootId: UUID.generate(), -// cTypeHash: ctypeHash, -// }) +async function writeHierarchy( + delegator: Identity, + ctypeHash: ICType['hash'] +): Promise { + const rootNode = DelegationNode.newRoot( + delegator.address, + [Permission.DELEGATE], + { + cTypeHash: ctypeHash, + } + ) -// await details.store().then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, delegator, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) + await rootNode.store().then((tx) => + BlockchainUtils.signAndSubmitTx(tx, delegator, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) -// const rootNode = (await DelegationNode.query( -// details.rootId -// )) as DelegationNode -// return { details, rootNode } -// } -// async function addDelegation( -// hierarchyId: IDelegationNode['id'], -// parentId: DelegationNode['id'], -// delegator: Identity, -// delegee: Identity, -// permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] -// ): Promise { -// const delegation = DelegationNode.new( -// UUID.generate(), -// hierarchyId, -// parentId, -// delegee.address, -// permissions -// ) -// await delegation -// .store(delegee.signStr(delegation.generateHash())) -// .then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, delegator, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// return delegation -// } + return rootNode +} + +async function addDelegation( + hierarchyId: IDelegationNode['id'], + parentId: DelegationNode['id'], + delegator: Identity, + delegee: Identity, + permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] +): Promise { + const delegationNode = DelegationNode.newNode( + hierarchyId, + parentId, + delegee.address, + permissions + ) -// let root: Identity + await delegationNode + .store(delegee.signStr(delegationNode.generateHash())) + .then((tx) => + BlockchainUtils.signAndSubmitTx(tx, delegator, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + return delegationNode +} + +let root: Identity // let claimer: Identity -// let attester: Identity +let attester: Identity -// beforeAll(async () => { -// config({ address: WS_ADDRESS }) -// root = wannabeFaucet -// claimer = wannabeBob -// attester = wannabeAlice +beforeAll(async () => { + config({ address: WS_ADDRESS }) + root = wannabeFaucet + // claimer = wannabeBob + attester = wannabeAlice -// if (!(await CtypeOnChain(DriversLicense))) { -// await DriversLicense.store().then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, attester, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// } -// }, 30_000) + if (!(await CtypeOnChain(DriversLicense))) { + await DriversLicense.store().then((tx) => + BlockchainUtils.signAndSubmitTx(tx, attester, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + } +}, 30_000) -// it('should be possible to delegate attestation rights', async () => { -// const { rootNode } = await writeHierarchy(root, DriversLicense.hash) -// const delegatedNode = await addDelegation( -// rootNode.id, -// rootNode.id, -// root, -// attester -// ) -// await Promise.all([ -// expect(rootNode.verify()).resolves.toBeTruthy(), -// expect(delegatedNode.verify()).resolves.toBeTruthy(), -// ]) -// }, 60_000) +it('should be possible to delegate attestation rights', async () => { + const rootNode = await writeHierarchy(root, DriversLicense.hash) + const delegatedNode = await addDelegation( + rootNode.id, + rootNode.id, + root, + attester + ) + await Promise.all([ + expect(rootNode.verify()).resolves.toBeTruthy(), + expect(delegatedNode.verify()).resolves.toBeTruthy(), + ]) +}, 60_000) // describe('and attestation rights have been delegated', () => { // let hierarchyDetails: DelegationHierarchyDetails @@ -299,6 +289,6 @@ // }) // }) -// afterAll(() => { -// disconnect() -// }) +afterAll(() => { + disconnect() +}) diff --git a/packages/core/src/__integrationtests__/utils.ts b/packages/core/src/__integrationtests__/utils.ts index 128f1f2d1..6851c6632 100644 --- a/packages/core/src/__integrationtests__/utils.ts +++ b/packages/core/src/__integrationtests__/utils.ts @@ -8,7 +8,7 @@ import Identity from '../identity/Identity' export const MIN_TRANSACTION = new BN(100_000_000) export const ENDOWMENT = MIN_TRANSACTION.mul(new BN(100)) -export const WS_ADDRESS = 'ws://127.0.0.1:9944' +export const WS_ADDRESS = 'ws://127.0.0.1:50000' // Dev Faucet account seed phrase export const FaucetSeed = 'receive clutch item involve chaos clutch furnace arrest claw isolate okay together' diff --git a/packages/core/src/delegation/DelegationDecoder.ts b/packages/core/src/delegation/DelegationDecoder.ts index af321c956..ce8059bc6 100644 --- a/packages/core/src/delegation/DelegationDecoder.ts +++ b/packages/core/src/delegation/DelegationDecoder.ts @@ -20,7 +20,7 @@ */ import { IDelegationHierarchyDetails, Permission } from '@kiltprotocol/types' import type { Option } from '@polkadot/types' -import type { Struct } from '@polkadot/types/codec' +import type { BTreeSet, Struct } from '@polkadot/types/codec' import type { AccountId, Hash } from '@polkadot/types/interfaces/runtime' import type { u32 } from '@polkadot/types/primitive' import { DecoderUtils } from '@kiltprotocol/utils' @@ -92,7 +92,7 @@ export type DelegationNodeId = Hash export interface IChainDelegationNode extends Struct { readonly hierarchyRootId: DelegationNodeId readonly parent: Option - readonly children: DelegationNodeId[] + readonly children: BTreeSet readonly details: IChainDelegationDetails } @@ -116,7 +116,7 @@ export function decodeDelegationNode( return { hierarchyId: delegationNode.hierarchyRootId.toHex(), parentId: delegationNode.parent.toHex(), - childrenIds: delegationNode.children.map((id) => id.toHex()), + childrenIds: [...delegationNode.children.keys()].map((id) => id.toHex()), account: delegationNode.details.owner.toString(), permissions: decodePermissions( delegationNode.details.permissions.toNumber() diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 8808bdf0b..5c6fdc477 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -48,7 +48,6 @@ export async function storeAsDelegation( return blockchain.api.tx.delegation.addDelegation( delegation.id, - delegation.hierarchyId, delegation.parentId, delegation.account, permissionsAsBitset(delegation), @@ -61,7 +60,7 @@ export async function query( ): Promise { const blockchain = await BlockchainApiConnection.getConnectionOrConnect() const decoded = decodeDelegationNode( - await blockchain.api.query.delegation.delegations< + await blockchain.api.query.delegation.delegationNodes< Option >(delegationId) ) From 2a06350b8b08d2c3d1365bba4d5328248a47328f Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 30 Jul 2021 09:07:17 +0200 Subject: [PATCH 10/21] feat: refactor complete and ready for review --- .../actors_api/src/actors/Attester.spec.ts | 4 +- .../actors_api/src/actors/Claimer.spec.ts | 4 +- .../actors_api/src/actors/Verifier.spec.ts | 4 +- .../__integrationtests__/Delegation.spec.ts | 330 +++++++++--------- .../core/src/attestation/Attestation.spec.ts | 6 +- packages/core/src/balance/Balance.spec.ts | 4 +- packages/core/src/ctype/CType.spec.ts | 4 +- .../core/src/delegation/DelegationDecoder.ts | 4 +- .../DelegationHierarchyDetails.chain.ts | 6 +- .../src/delegation/DelegationNode.chain.ts | 39 +++ .../core/src/delegation/DelegationNode.ts | 30 +- .../src/delegation/DelegationNode.utils.ts | 18 + packages/core/src/delegation/index.ts | 2 +- packages/core/src/did/Did.spec.ts | 4 +- .../core/src/identity/PublicIdentity.spec.ts | 4 +- 15 files changed, 269 insertions(+), 194 deletions(-) diff --git a/packages/actors_api/src/actors/Attester.spec.ts b/packages/actors_api/src/actors/Attester.spec.ts index 88bc21f24..e076bbc6d 100644 --- a/packages/actors_api/src/actors/Attester.spec.ts +++ b/packages/actors_api/src/actors/Attester.spec.ts @@ -18,11 +18,11 @@ import { Attester, Claimer } from '..' import { issueAttestation } from './Attester' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('Attester', () => { - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api let attester: Identity let claimer: Identity diff --git a/packages/actors_api/src/actors/Claimer.spec.ts b/packages/actors_api/src/actors/Claimer.spec.ts index 93a177c38..7b73f7493 100644 --- a/packages/actors_api/src/actors/Claimer.spec.ts +++ b/packages/actors_api/src/actors/Claimer.spec.ts @@ -24,11 +24,11 @@ import { Attester, Claimer, Verifier } from '..' import type { ClaimerAttestationSession } from './Claimer' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('Claimer', () => { - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api let attester: Identity let claimer: Identity diff --git a/packages/actors_api/src/actors/Verifier.spec.ts b/packages/actors_api/src/actors/Verifier.spec.ts index cab8b1830..70e001a4a 100644 --- a/packages/actors_api/src/actors/Verifier.spec.ts +++ b/packages/actors_api/src/actors/Verifier.spec.ts @@ -17,7 +17,7 @@ import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer, Verifier } from '..' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('Verifier', () => { @@ -27,7 +27,7 @@ describe('Verifier', () => { let cType: CType let claim: IClaim let credential: AttestedClaim - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api beforeAll(async () => { diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index 2d8e4e088..e99c895eb 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -12,16 +12,22 @@ import type { ICType, IDelegationNode } from '@kiltprotocol/types' import { Permission } from '@kiltprotocol/types' import { BlockchainUtils } from '@kiltprotocol/chain-helpers' -import { Identity } from '..' +import Attestation from '../attestation/Attestation' +import Claim from '../claim/Claim' +import RequestForAttestation from '../requestforattestation/RequestForAttestation' +import { AttestedClaim, Identity } from '..' import { config, disconnect } from '../kilt' import DelegationNode from '../delegation/DelegationNode' import { CtypeOnChain, DriversLicense, wannabeAlice, + wannabeBob, wannabeFaucet, WS_ADDRESS, } from './utils' +import { DelegationNodeUtils } from '../delegation' +import { getAttestationHashes } from '../delegation/DelegationNode.chain' async function writeHierarchy( delegator: Identity, @@ -71,13 +77,13 @@ async function addDelegation( } let root: Identity -// let claimer: Identity +let claimer: Identity let attester: Identity beforeAll(async () => { config({ address: WS_ADDRESS }) root = wannabeFaucet - // claimer = wannabeBob + claimer = wannabeBob attester = wannabeAlice if (!(await CtypeOnChain(DriversLicense))) { @@ -104,190 +110,170 @@ it('should be possible to delegate attestation rights', async () => { ]) }, 60_000) -// describe('and attestation rights have been delegated', () => { -// let hierarchyDetails: DelegationHierarchyDetails -// let rootNode: DelegationNode -// let delegatedNode: DelegationNode +describe('and attestation rights have been delegated', () => { + let rootNode: DelegationNode + let delegatedNode: DelegationNode -// beforeAll(async () => { -// const result = await writeHierarchy(root, DriversLicense.hash) -// hierarchyDetails = result.details -// rootNode = result.rootNode -// delegatedNode = await addDelegation( -// hierarchyDetails.rootId, -// hierarchyDetails.rootId, -// root, -// attester -// ) - -// await Promise.all([ -// expect(rootNode.verify()).resolves.toBeTruthy(), -// expect(delegatedNode.verify()).resolves.toBeTruthy(), -// ]) -// }, 75_000) - -// it("should be possible to attest a claim in the root's name and revoke it by the root", async () => { -// const content = { -// name: 'Ralph', -// age: 12, -// } -// const claim = Claim.fromCTypeAndClaimContents( -// DriversLicense, -// content, -// claimer.address -// ) -// const request = RequestForAttestation.fromClaimAndIdentity(claim, claimer, { -// delegationId: delegatedNode.id, -// }) -// expect(request.verifyData()).toBeTruthy() -// expect(request.verifySignature()).toBeTruthy() - -// const attestation = Attestation.fromRequestAndPublicIdentity( -// request, -// attester.getPublicIdentity() -// ) -// await attestation.store().then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, attester, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) + beforeAll(async () => { + rootNode = await writeHierarchy(root, DriversLicense.hash) + delegatedNode = await addDelegation( + rootNode.id, + rootNode.id, + root, + attester + ) -// const attClaim = AttestedClaim.fromRequestAndAttestation( -// request, -// attestation -// ) -// expect(attClaim.verifyData()).toBeTruthy() -// await expect(attClaim.verify()).resolves.toBeTruthy() + await Promise.all([ + expect(rootNode.verify()).resolves.toBeTruthy(), + expect(delegatedNode.verify()).resolves.toBeTruthy(), + ]) + }, 75_000) -// // revoke attestation through root -// await attClaim.attestation.revoke(1).then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, root, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// await expect(attClaim.verify()).resolves.toBeFalsy() -// }, 75_000) -// }) + it("should be possible to attest a claim in the root's name and revoke it by the root", async () => { + const content = { + name: 'Ralph', + age: 12, + } + const claim = Claim.fromCTypeAndClaimContents( + DriversLicense, + content, + claimer.address + ) + const request = RequestForAttestation.fromClaimAndIdentity(claim, claimer, { + delegationId: delegatedNode.id, + }) + expect(request.verifyData()).toBeTruthy() + expect(request.verifySignature()).toBeTruthy() -// describe('revocation', () => { -// let delegator: Identity = root -// let firstDelegee: Identity = attester -// let secondDelegee: Identity = claimer + const attestation = Attestation.fromRequestAndPublicIdentity( + request, + attester.getPublicIdentity() + ) + await attestation.store().then((tx) => + BlockchainUtils.signAndSubmitTx(tx, attester, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) -// beforeAll(() => { -// delegator = root -// firstDelegee = attester -// secondDelegee = claimer -// }) + const attClaim = AttestedClaim.fromRequestAndAttestation( + request, + attestation + ) + expect(attClaim.verifyData()).toBeTruthy() + await expect(attClaim.verify()).resolves.toBeTruthy() -// it('delegator can revoke delegation', async () => { -// const { details, rootNode } = await writeHierarchy( -// delegator, -// DriversLicense.hash -// ) -// const delegationA = await addDelegation( -// details.rootId, -// details.rootId, -// delegator, -// firstDelegee -// ) -// await expect( -// delegationA.revoke(delegator.address).then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, delegator, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// ).resolves.not.toThrow() -// await expect(delegationA.verify()).resolves.toBe(false) -// }, 40_000) + // revoke attestation through root + await attClaim.attestation.revoke(1).then((tx) => + BlockchainUtils.signAndSubmitTx(tx, root, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + await expect(attClaim.verify()).resolves.toBeFalsy() + }, 75_000) +}) -// it('delegee cannot revoke root but can revoke own delegation', async () => { -// const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) -// const delegationA = await addDelegation( -// delegationRoot, -// delegator, -// firstDelegee -// ) -// await expect( -// delegationRoot.revoke().then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// ).rejects.toThrow() -// await expect(delegationRoot.verify()).resolves.toBe(true) +describe('revocation', () => { + let delegator: Identity = root + let firstDelegee: Identity = attester + let secondDelegee: Identity = claimer -// await expect( -// delegationA.revoke(firstDelegee.address).then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// ).resolves.not.toThrow() -// await expect(delegationA.verify()).resolves.toBe(false) -// }, 60_000) + beforeAll(() => { + delegator = root + firstDelegee = attester + secondDelegee = claimer + }) -// it('delegator can revoke root, revoking all delegations in tree', async () => { -// const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) -// const delegationA = await addDelegation( -// delegationRoot, -// delegator, -// firstDelegee -// ) -// const delegationB = await addDelegation( -// delegationA, -// firstDelegee, -// secondDelegee -// ) -// await expect( -// delegationRoot.revoke().then((tx) => -// BlockchainUtils.signAndSubmitTx(tx, delegator, { -// resolveOn: BlockchainUtils.IS_IN_BLOCK, -// reSign: true, -// }) -// ) -// ).resolves.not.toThrow() + it('delegator can revoke delegation', async () => { + const rootNode = await writeHierarchy(delegator, DriversLicense.hash) + const delegationA = await addDelegation( + rootNode.id, + rootNode.id, + delegator, + firstDelegee + ) + await expect( + delegationA.revoke(delegator.address).then((tx) => + BlockchainUtils.signAndSubmitTx(tx, delegator, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + ).resolves.not.toThrow() + await expect(delegationA.verify()).resolves.toBe(false) + }, 40_000) -// await Promise.all([ -// expect(delegationRoot.verify()).resolves.toBe(false), -// expect(delegationA.verify()).resolves.toBe(false), -// expect(delegationB.verify()).resolves.toBe(false), -// ]) -// }, 60_000) -// }) + it('delegee cannot revoke root but can revoke own delegation', async () => { + const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) + const delegationA = await addDelegation( + delegationRoot.id, + delegationRoot.id, + delegator, + firstDelegee + ) + await expect( + delegationRoot.revoke(firstDelegee.address).then((tx) => + BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + ).rejects.toThrow() + await expect(delegationRoot.verify()).resolves.toBe(true) -// describe('handling queries to data not on chain', () => { -// it('getChildIds on empty', async () => { -// return expect(getChildIds('0x012012012')).resolves.toEqual([]) -// }) + await expect( + delegationA.revoke(firstDelegee.address).then((tx) => + BlockchainUtils.signAndSubmitTx(tx, firstDelegee, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + ).resolves.not.toThrow() + await expect(delegationA.verify()).resolves.toBe(false) + }, 60_000) -// it('DelegationNode query on empty', async () => { -// return expect(DelegationNode.query('0x012012012')).resolves.toBeNull() -// }) + it('delegator can revoke root, revoking all delegations in tree', async () => { + let delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) + const delegationA = await addDelegation( + delegationRoot.id, + delegationRoot.id, + delegator, + firstDelegee + ) + const delegationB = await addDelegation( + delegationRoot.id, + delegationA.id, + firstDelegee, + secondDelegee + ) + delegationRoot = await DelegationNodeUtils.getSyncedState(delegationRoot) + await expect( + delegationRoot.revoke(delegator.address).then((tx) => + BlockchainUtils.signAndSubmitTx(tx, delegator, { + resolveOn: BlockchainUtils.IS_IN_BLOCK, + reSign: true, + }) + ) + ).resolves.not.toThrow() -// it('DelegationRootNode.query on empty', async () => { -// return expect(DelegationRootNode.query('0x012012012')).resolves.toBeNull() -// }) + await Promise.all([ + expect(delegationRoot.verify()).resolves.toBe(false), + expect(delegationA.verify()).resolves.toBe(false), + expect(delegationB.verify()).resolves.toBe(false), + ]) + }, 60_000) +}) -// it('getAttestationHashes on empty', async () => { -// return expect(getAttestationHashes('0x012012012')).resolves.toEqual([]) -// }) +describe('handling queries to data not on chain', () => { + it('DelegationNode query on empty', async () => { + return expect(DelegationNode.query('0x012012012')).resolves.toBeNull() + }) -// it('fetchChildren on empty', async () => { -// return expect( -// fetchChildren(['0x012012012']).then((res) => -// res.map((el) => { -// return { id: el.id, codec: decodeDelegationNode(el.codec) } -// }) -// ) -// ).resolves.toEqual([{ id: '0x012012012', codec: null }]) -// }) -// }) + it('getAttestationHashes on empty', async () => { + return expect(getAttestationHashes('0x012012012')).resolves.toEqual([]) + }) +}) afterAll(() => { disconnect() diff --git a/packages/core/src/attestation/Attestation.spec.ts b/packages/core/src/attestation/Attestation.spec.ts index fc75cb445..2d1ce1c99 100644 --- a/packages/core/src/attestation/Attestation.spec.ts +++ b/packages/core/src/attestation/Attestation.spec.ts @@ -17,7 +17,7 @@ import type { ICType, } from '@kiltprotocol/types' import { SDKErrors } from '@kiltprotocol/utils' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' import Claim from '../claim/Claim' import CType from '../ctype/CType' import Identity from '../identity/Identity' @@ -27,7 +27,7 @@ import AttestationUtils from './Attestation.utils' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('Attestation', () => { @@ -39,7 +39,7 @@ describe('Attestation', () => { let testcontents: any let testClaim: Claim let requestForAttestation: RequestForAttestation - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api beforeAll(async () => { diff --git a/packages/core/src/balance/Balance.spec.ts b/packages/core/src/balance/Balance.spec.ts index abdec945e..ca81d262b 100644 --- a/packages/core/src/balance/Balance.spec.ts +++ b/packages/core/src/balance/Balance.spec.ts @@ -29,7 +29,7 @@ import BalanceUtils from './Balance.utils' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) const BALANCE = 42 @@ -39,7 +39,7 @@ describe('Balance', () => { Kilt.config({ address: 'ws://testSting' }) let alice: Identity let bob: Identity - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api const accountInfo = (balance: number): AccountInfo => { diff --git a/packages/core/src/ctype/CType.spec.ts b/packages/core/src/ctype/CType.spec.ts index 89756e5cf..333f31684 100644 --- a/packages/core/src/ctype/CType.spec.ts +++ b/packages/core/src/ctype/CType.spec.ts @@ -31,11 +31,11 @@ import CTypeUtils, { getIdForSchema } from './CType.utils' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('CType', () => { - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api const registry = new TypeRegistry() let ctypeModel: ICType['schema'] diff --git a/packages/core/src/delegation/DelegationDecoder.ts b/packages/core/src/delegation/DelegationDecoder.ts index ce8059bc6..ceadeee53 100644 --- a/packages/core/src/delegation/DelegationDecoder.ts +++ b/packages/core/src/delegation/DelegationDecoder.ts @@ -115,7 +115,9 @@ export function decodeDelegationNode( return { hierarchyId: delegationNode.hierarchyRootId.toHex(), - parentId: delegationNode.parent.toHex(), + parentId: delegationNode.parent.isNone + ? delegationNode.parent.toHex() + : undefined, childrenIds: [...delegationNode.children.keys()].map((id) => id.toHex()), account: delegationNode.details.owner.toString(), permissions: decodePermissions( diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts index 2cb7d1835..a9c8ec6e5 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts @@ -22,6 +22,10 @@ import { * @module DelegationHierarchyDetails */ +/** + * @param rootId + * @internal + */ // eslint-disable-next-line import/prefer-default-export export async function query( rootId: IDelegationNode['id'] @@ -40,4 +44,4 @@ export async function query( cTypeHash: decoded.cTypeHash, } return details -} \ No newline at end of file +} diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 5c6fdc477..5e2ba02af 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -22,6 +22,10 @@ import { permissionsAsBitset } from './DelegationNode.utils' const log = ConfigService.LoggingFactory.getLogger('DelegationNode') +/** + * @param delegation + * @internal + */ export async function storeAsRoot( delegation: DelegationNode ): Promise { @@ -36,6 +40,11 @@ export async function storeAsRoot( ) } +/** + * @param delegation + * @param signature + * @internal + */ export async function storeAsDelegation( delegation: DelegationNode, signature: string @@ -55,6 +64,11 @@ export async function storeAsDelegation( ) } +/** + * @param delegation + * @param delegationId + * @internal + */ export async function query( delegationId: IDelegationNode['id'] ): Promise { @@ -80,6 +94,16 @@ export async function query( return root } +/** + * @internal + * + * Revokes part of a delegation tree at specified node, also revoking all nodes below. + * + * @param delegationId The id of the node in the delegation tree at which to revoke. + * @param maxDepth How many nodes may be traversed upwards in the hierarchy when searching for a node owned by `identity`. Each traversal will add to the transaction fee. Therefore a higher number will increase the fees locked until the transaction is complete. A number lower than the actual required traversals will result in a failed extrinsic (node will not be revoked). + * @param maxRevocations How many delegation nodes may be revoked during the process. Each revocation adds to the transaction fee. A higher number will require more fees to be locked while an insufficiently high number will lead to premature abortion of the revocation process, leaving some nodes unrevoked. Revocations will first be performed on child nodes, therefore the current node is only revoked when this is accurate. + * @returns An unsigned SubmittableExtrinsic ready to be signed and dispatched. + */ export async function revoke( delegationId: IDelegationNode['id'], maxDepth: number, @@ -94,6 +118,11 @@ export async function revoke( return tx } +/** + * @param delegationNodeId + * @param delegationNode + * @internal + */ export async function getChildren( delegationNode: DelegationNode ): Promise { @@ -111,11 +140,21 @@ export async function getChildren( return childrenNodes } +/** + * @param delegationNodeId + * @param queryResult + * @internal + */ function decodeDelegatedAttestations(queryResult: Option>): string[] { DecoderUtils.assertCodecIsType(queryResult, ['Option>']) return queryResult.unwrapOrDefault().map((hash) => hash.toHex()) } +/** + * @param delegationNodeId + * @param id + * @internal + */ export async function getAttestationHashes( id: IDelegationNode['id'] ): Promise { diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 7c20e4cfc..333ab6d61 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -73,6 +73,16 @@ export default class DelegationNode implements IDelegationNode { DelegationNodeUtils.errorCheck(this) } + /** + * Builds a new [DelegationNode] representing a regular delegation node ready to be submitted to the chain for creation. + * + * @param hierarchyId - The delegation hierarchy under which to store the node. + * @param parentId - The parent node under which to store the node. + * @param account - The address of this delegation. + * @param permissions - The set of permissions to associated with this delegation node. + * + * @returns A new [DelegationNode] with a randomly-generated id. + */ public static newNode( hierarchyId: IDelegationNode['hierarchyId'], parentId: string, // Cannot be undefined here @@ -90,6 +100,15 @@ export default class DelegationNode implements IDelegationNode { }) } + /** + * Builds a new [DelegationNode] representing a root delegation node ready to be submitted to the chain for creation. + * + * @param account - The address of this delegation (and of the whole hierarchy under it). + * @param permissions - The set of permissions to associated with this delegation node. + * @param hierarchyDetails - The details associated with the delegation hierarchy (e.g. The CType hash of allowed attestations). + * + * @returns A new [DelegationNode] with a randomly-generated id. + */ public static newRoot( account: IDelegationNode['account'], permissions: IDelegationNode['permissions'], @@ -110,7 +129,11 @@ export default class DelegationNode implements IDelegationNode { return newNode } - // Utility method to retrieve the CType hash associated with a delegation node. + /** + * Lazily fetches the details of the hierarchy the node is part of and return its CType. + * + * @returns The CType hash associated with the delegation hierarchy. + */ public get cTypeHash(): Promise { return this.getHierarchyDetails().then((details) => details.cTypeHash) } @@ -145,6 +168,8 @@ export default class DelegationNode implements IDelegationNode { /** * [ASYNC] Fetches the children nodes of this delegation node. * + * If new nodes have been created with this node as parent, **it is advised to first sync up this node with the latest blockchain state by calling [DelegationNodeUtils.getSyncedState]**. + * * @returns Promise containing the children as an array of [[DelegationNode]], which is empty if there are no children. */ public async getChildren(): Promise { @@ -247,9 +272,10 @@ export default class DelegationNode implements IDelegationNode { } /** - * Checks on chain whether a identity with the given address is delegating to the current node. + * [ASYNC] Checks on chain whether a identity with the given address is delegating to the current node. * * @param address The address of the identity. + * * @returns An object containing a `node` owned by the identity if it is delegating, plus the number of `steps` traversed. `steps` is 0 if the address is owner of the current node. */ public async findAncestorOwnedBy( diff --git a/packages/core/src/delegation/DelegationNode.utils.ts b/packages/core/src/delegation/DelegationNode.utils.ts index f9fb908b7..d5712d0ea 100644 --- a/packages/core/src/delegation/DelegationNode.utils.ts +++ b/packages/core/src/delegation/DelegationNode.utils.ts @@ -13,6 +13,7 @@ import type { IAttestation, IDelegationNode } from '@kiltprotocol/types' import { SDKErrors } from '@kiltprotocol/utils' import { isHex } from '@polkadot/util' +import { query } from './DelegationNode.chain' import Identity from '../identity' import DelegationNode from './DelegationNode' @@ -71,6 +72,23 @@ export async function countNodeDepth( return delegationTreeTraversalSteps } +/** + * [ASYNC] Syncronise a delegation node with the latest state as stored on the blockchain. + * + * @param delegationNode The input delegation node for which the state needs to be syncronised. + * + * @returns A new instance of the same [DelegationNode] containing the up-to-date state fetched from the chain. + */ +export async function getSyncedState( + delegationNode: DelegationNode +): Promise { + const fetchedNode = await query(delegationNode.id) + if (!fetchedNode) { + throw SDKErrors.ERROR_DELEGATION_ID_MISSING + } + return fetchedNode +} + export function errorCheck(delegationNodeInput: IDelegationNode): void { const { permissions, hierarchyId: rootId, parentId } = delegationNodeInput diff --git a/packages/core/src/delegation/index.ts b/packages/core/src/delegation/index.ts index c308d9bd0..69f256508 100644 --- a/packages/core/src/delegation/index.ts +++ b/packages/core/src/delegation/index.ts @@ -8,4 +8,4 @@ import DelegationNode from './DelegationNode' import * as DelegationNodeUtils from './DelegationNode.utils' -export { DelegationNode, DelegationNodeUtils } \ No newline at end of file +export { DelegationNode, DelegationNodeUtils } diff --git a/packages/core/src/did/Did.spec.ts b/packages/core/src/did/Did.spec.ts index af6f37a99..243b20635 100644 --- a/packages/core/src/did/Did.spec.ts +++ b/packages/core/src/did/Did.spec.ts @@ -25,14 +25,14 @@ import { import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('DID', () => { const key1 = new U8aFixed(TYPE_REGISTRY, 'box-me', 256) const key2 = new U8aFixed(TYPE_REGISTRY, 'sign-me', 256) Kilt.config({ address: 'ws://testString' }) - const blockchainApi = require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection') + const blockchainApi = require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection') .__mocked_api beforeAll(() => { diff --git a/packages/core/src/identity/PublicIdentity.spec.ts b/packages/core/src/identity/PublicIdentity.spec.ts index fce716b42..8ebdd2251 100644 --- a/packages/core/src/identity/PublicIdentity.spec.ts +++ b/packages/core/src/identity/PublicIdentity.spec.ts @@ -17,7 +17,7 @@ import PublicIdentity, { IURLResolver } from './PublicIdentity' import Kilt from '../kilt/Kilt' jest.mock( - '@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection' + '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection' ) describe('PublicIdentity', () => { @@ -26,7 +26,7 @@ describe('PublicIdentity', () => { Kilt.config({ address: 'ws://testString' }) // https://polkadot.js.org/api/examples/promise/ // testing to create correct demo accounts - require('@kiltprotocol/chain-helpers/src/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.did.dIDs = jest.fn( + require('@kiltprotocol/chain-helpers/lib/blockchainApiConnection/BlockchainApiConnection').__mocked_api.query.did.dIDs = jest.fn( async (id) => { switch (id) { case '1': From c08550679088faf088b0d1420256b0b16073d932 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 30 Jul 2021 09:22:12 +0200 Subject: [PATCH 11/21] fix: replace src with lib imports for tests --- packages/actors_api/src/actors/Attester.spec.ts | 2 +- packages/actors_api/src/actors/Claimer.spec.ts | 2 +- packages/actors_api/src/actors/Verifier.spec.ts | 2 +- packages/core/src/did/Did.spec.ts | 2 +- packages/core/src/identity/PublicIdentity.spec.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/actors_api/src/actors/Attester.spec.ts b/packages/actors_api/src/actors/Attester.spec.ts index e076bbc6d..0dfdca9c2 100644 --- a/packages/actors_api/src/actors/Attester.spec.ts +++ b/packages/actors_api/src/actors/Attester.spec.ts @@ -11,7 +11,7 @@ import { CType, Identity, SDKErrors } from '@kiltprotocol/core' import type { ICType, IClaim, IMessage } from '@kiltprotocol/types' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' import Message from '@kiltprotocol/messaging' import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer } from '..' diff --git a/packages/actors_api/src/actors/Claimer.spec.ts b/packages/actors_api/src/actors/Claimer.spec.ts index 7b73f7493..2d4890bb1 100644 --- a/packages/actors_api/src/actors/Claimer.spec.ts +++ b/packages/actors_api/src/actors/Claimer.spec.ts @@ -17,7 +17,7 @@ import type { ISubmitClaimsForCTypes, MessageBodyType, } from '@kiltprotocol/types' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' import Message from '@kiltprotocol/messaging' import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer, Verifier } from '..' diff --git a/packages/actors_api/src/actors/Verifier.spec.ts b/packages/actors_api/src/actors/Verifier.spec.ts index 70e001a4a..c65fa9223 100644 --- a/packages/actors_api/src/actors/Verifier.spec.ts +++ b/packages/actors_api/src/actors/Verifier.spec.ts @@ -11,7 +11,7 @@ import { AttestedClaim, CType, Identity } from '@kiltprotocol/core' import type { IClaim, ICType } from '@kiltprotocol/types' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' import Message from '@kiltprotocol/messaging' import { Crypto } from '@kiltprotocol/utils' import { Attester, Claimer, Verifier } from '..' diff --git a/packages/core/src/did/Did.spec.ts b/packages/core/src/did/Did.spec.ts index 243b20635..62e29b29b 100644 --- a/packages/core/src/did/Did.spec.ts +++ b/packages/core/src/did/Did.spec.ts @@ -11,7 +11,7 @@ import { U8aFixed } from '@polkadot/types' import { SDKErrors } from '@kiltprotocol/utils' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' import { BlockchainUtils, TypeRegistry as TYPE_REGISTRY, diff --git a/packages/core/src/identity/PublicIdentity.spec.ts b/packages/core/src/identity/PublicIdentity.spec.ts index 8ebdd2251..8217f6d76 100644 --- a/packages/core/src/identity/PublicIdentity.spec.ts +++ b/packages/core/src/identity/PublicIdentity.spec.ts @@ -12,7 +12,7 @@ import { U8aFixed } from '@polkadot/types' import type { IPublicIdentity } from '@kiltprotocol/types' import { TypeRegistry as TYPE_REGISTRY } from '@kiltprotocol/chain-helpers' -import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/src/blockchainApiConnection/__mocks__/BlockchainQuery' +import { mockChainQueryReturn } from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery' import PublicIdentity, { IURLResolver } from './PublicIdentity' import Kilt from '../kilt/Kilt' From dbfb335a2d4cd5d52bbdbb1774040908a43f13f4 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 30 Jul 2021 09:31:19 +0200 Subject: [PATCH 12/21] fix: revert old port for integration tests --- packages/core/src/__integrationtests__/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/__integrationtests__/utils.ts b/packages/core/src/__integrationtests__/utils.ts index 6851c6632..128f1f2d1 100644 --- a/packages/core/src/__integrationtests__/utils.ts +++ b/packages/core/src/__integrationtests__/utils.ts @@ -8,7 +8,7 @@ import Identity from '../identity/Identity' export const MIN_TRANSACTION = new BN(100_000_000) export const ENDOWMENT = MIN_TRANSACTION.mul(new BN(100)) -export const WS_ADDRESS = 'ws://127.0.0.1:50000' +export const WS_ADDRESS = 'ws://127.0.0.1:9944' // Dev Faucet account seed phrase export const FaucetSeed = 'receive clutch item involve chaos clutch furnace arrest claw isolate okay together' From 0d9825aa3f49635c8bda2e6a9b6f3e8d8b6ee178 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 30 Jul 2021 09:35:09 +0200 Subject: [PATCH 13/21] fix: replace isNone with isNone for parent ID to HEX conversion --- packages/core/src/delegation/DelegationDecoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/delegation/DelegationDecoder.ts b/packages/core/src/delegation/DelegationDecoder.ts index ceadeee53..bb6513138 100644 --- a/packages/core/src/delegation/DelegationDecoder.ts +++ b/packages/core/src/delegation/DelegationDecoder.ts @@ -115,7 +115,7 @@ export function decodeDelegationNode( return { hierarchyId: delegationNode.hierarchyRootId.toHex(), - parentId: delegationNode.parent.isNone + parentId: delegationNode.parent.isSome ? delegationNode.parent.toHex() : undefined, childrenIds: [...delegationNode.children.keys()].map((id) => id.toHex()), From 8a20ab281b2ba7d57cd26af3a0736060d6fa3309 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Mon, 2 Aug 2021 08:44:45 +0200 Subject: [PATCH 14/21] fix: hints from PR --- .../__integrationtests__/Delegation.spec.ts | 4 +--- .../core/src/delegation/DelegationDecoder.ts | 21 ++++------------ .../core/src/delegation/DelegationNode.ts | 24 ++++++++++++++----- .../src/delegation/DelegationNode.utils.ts | 18 -------------- 4 files changed, 24 insertions(+), 43 deletions(-) diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index e99c895eb..9024280e1 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -26,7 +26,6 @@ import { wannabeFaucet, WS_ADDRESS, } from './utils' -import { DelegationNodeUtils } from '../delegation' import { getAttestationHashes } from '../delegation/DelegationNode.chain' async function writeHierarchy( @@ -234,7 +233,7 @@ describe('revocation', () => { }, 60_000) it('delegator can revoke root, revoking all delegations in tree', async () => { - let delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) + const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) const delegationA = await addDelegation( delegationRoot.id, delegationRoot.id, @@ -247,7 +246,6 @@ describe('revocation', () => { firstDelegee, secondDelegee ) - delegationRoot = await DelegationNodeUtils.getSyncedState(delegationRoot) await expect( delegationRoot.revoke(delegator.address).then((tx) => BlockchainUtils.signAndSubmitTx(tx, delegator, { diff --git a/packages/core/src/delegation/DelegationDecoder.ts b/packages/core/src/delegation/DelegationDecoder.ts index bb6513138..ba7bb4bfb 100644 --- a/packages/core/src/delegation/DelegationDecoder.ts +++ b/packages/core/src/delegation/DelegationDecoder.ts @@ -18,23 +18,20 @@ /** * Dummy comment needed for correct doc display, do not remove. */ +import type { IDelegationNode } from '@kiltprotocol/types' import { IDelegationHierarchyDetails, Permission } from '@kiltprotocol/types' import type { Option } from '@polkadot/types' import type { BTreeSet, Struct } from '@polkadot/types/codec' import type { AccountId, Hash } from '@polkadot/types/interfaces/runtime' -import type { u32 } from '@polkadot/types/primitive' +import type { Bool, u32 } from '@polkadot/types/primitive' import { DecoderUtils } from '@kiltprotocol/utils' -import DelegationNode from './DelegationNode' export type CodecWithId = { id: string codec: C } -export type DelegationHierarchyDetailsRecord = Pick< - IDelegationHierarchyDetails, - 'cTypeHash' -> +export type DelegationHierarchyDetailsRecord = IDelegationHierarchyDetails export type CtypeHash = Hash @@ -77,15 +74,7 @@ function decodePermissions(bitset: number): Permission[] { return permissions } -export type DelegationNodeRecord = Pick< - DelegationNode, - | 'hierarchyId' - | 'parentId' - | 'childrenIds' - | 'account' - | 'permissions' - | 'revoked' -> +export type DelegationNodeRecord = Omit export type DelegationNodeId = Hash @@ -100,7 +89,7 @@ export type DelegationOwner = AccountId export interface IChainDelegationDetails extends Struct { readonly owner: DelegationOwner - readonly revoked: boolean + readonly revoked: Bool readonly permissions: u32 } diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 333ab6d61..00e885209 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -168,8 +168,6 @@ export default class DelegationNode implements IDelegationNode { /** * [ASYNC] Fetches the children nodes of this delegation node. * - * If new nodes have been created with this node as parent, **it is advised to first sync up this node with the latest blockchain state by calling [DelegationNodeUtils.getSyncedState]**. - * * @returns Promise containing the children as an array of [[DelegationNode]], which is empty if there are no children. */ public async getChildren(): Promise { @@ -238,11 +236,24 @@ export default class DelegationNode implements IDelegationNode { return generated } + /** + * [ASYNC] Syncronise the delegation node state with the latest state as stored on the blockchain. + * + * @returns An updated instance of the same [DelegationNode] containing the up-to-date state fetched from the chain. + */ + public async refreshState(): Promise { + const newNodeState = query(this.id) + if (!newNodeState) { + throw SDKErrors.ERROR_DELEGATION_ID_MISSING + } + Object.assign(this, newNodeState) + } + /** * [ASYNC] Stores the delegation node on chain. * * @param signature Signature of the delegate to ensure it is done under the delegate's permission. - * @returns Promise containing a unsigned SubmittableExtrinsic. + * @returns Promise containing an unsigned SubmittableExtrinsic. */ public async store(signature?: string): Promise { if (this.isRoot()) { @@ -252,8 +263,7 @@ export default class DelegationNode implements IDelegationNode { if (!signature) { throw SDKErrors.ERROR_DELEGATION_SIGNATURE_MISSING } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return storeAsDelegation(this, signature!) + return storeAsDelegation(this, signature) } } @@ -300,11 +310,13 @@ export default class DelegationNode implements IDelegationNode { } /** - * [ASYNC] Recursively counts all nodes that descend from the current node (excluding the current node). + * [ASYNC] Recursively counts all nodes that descend from the current node (excluding the current node), after refreshing the node status with the one stored on chain. * * @returns Promise resolving to the node count. */ public async subtreeNodeCount(): Promise { + // Fetch the latest state from chain first + await this.refreshState() const children = await this.getChildren() if (children.length === 0) { return 0 diff --git a/packages/core/src/delegation/DelegationNode.utils.ts b/packages/core/src/delegation/DelegationNode.utils.ts index d5712d0ea..f9fb908b7 100644 --- a/packages/core/src/delegation/DelegationNode.utils.ts +++ b/packages/core/src/delegation/DelegationNode.utils.ts @@ -13,7 +13,6 @@ import type { IAttestation, IDelegationNode } from '@kiltprotocol/types' import { SDKErrors } from '@kiltprotocol/utils' import { isHex } from '@polkadot/util' -import { query } from './DelegationNode.chain' import Identity from '../identity' import DelegationNode from './DelegationNode' @@ -72,23 +71,6 @@ export async function countNodeDepth( return delegationTreeTraversalSteps } -/** - * [ASYNC] Syncronise a delegation node with the latest state as stored on the blockchain. - * - * @param delegationNode The input delegation node for which the state needs to be syncronised. - * - * @returns A new instance of the same [DelegationNode] containing the up-to-date state fetched from the chain. - */ -export async function getSyncedState( - delegationNode: DelegationNode -): Promise { - const fetchedNode = await query(delegationNode.id) - if (!fetchedNode) { - throw SDKErrors.ERROR_DELEGATION_ID_MISSING - } - return fetchedNode -} - export function errorCheck(delegationNodeInput: IDelegationNode): void { const { permissions, hierarchyId: rootId, parentId } = delegationNodeInput From 0578f3e71b1c9b0a01c2166f2558bb688b703888 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Mon, 2 Aug 2021 13:04:11 +0200 Subject: [PATCH 15/21] fix: apply remaining PR comments --- .../core/src/delegation/DelegationDecoder.ts | 5 ++- .../DelegationHierarchyDetails.chain.ts | 9 ++--- .../src/delegation/DelegationNode.chain.ts | 15 ++------ .../core/src/delegation/DelegationNode.ts | 38 ++++++++++--------- packages/types/src/Delegation.ts | 1 + 5 files changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/core/src/delegation/DelegationDecoder.ts b/packages/core/src/delegation/DelegationDecoder.ts index ba7bb4bfb..3ddc25ffe 100644 --- a/packages/core/src/delegation/DelegationDecoder.ts +++ b/packages/core/src/delegation/DelegationDecoder.ts @@ -31,7 +31,10 @@ export type CodecWithId = { codec: C } -export type DelegationHierarchyDetailsRecord = IDelegationHierarchyDetails +export type DelegationHierarchyDetailsRecord = Pick< + IDelegationHierarchyDetails, + 'cTypeHash' +> export type CtypeHash = Hash diff --git a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts index a9c8ec6e5..fa88d6aeb 100644 --- a/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts +++ b/packages/core/src/delegation/DelegationHierarchyDetails.chain.ts @@ -9,7 +9,7 @@ import type { IDelegationHierarchyDetails, IDelegationNode, } from '@kiltprotocol/types' -import { Option } from '@polkadot/types' +import type { Option } from '@polkadot/types' import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' import { decodeDelegationHierarchyDetails, @@ -39,9 +39,8 @@ export async function query( if (!decoded) { return null } - const details = { - rootId, - cTypeHash: decoded.cTypeHash, + return { + ...decoded, + id: rootId, } - return details } diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 5e2ba02af..253cd57cb 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -14,7 +14,7 @@ import type { Option, Vec } from '@polkadot/types' import type { IDelegationNode, SubmittableExtrinsic } from '@kiltprotocol/types' import { ConfigService } from '@kiltprotocol/config' import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers' -import { Hash } from '@polkadot/types/interfaces' +import type { Hash } from '@polkadot/types/interfaces' import { DecoderUtils, SDKErrors } from '@kiltprotocol/utils' import { decodeDelegationNode, IChainDelegationNode } from './DelegationDecoder' import DelegationNode from './DelegationNode' @@ -35,7 +35,7 @@ export async function storeAsRoot( throw SDKErrors.ERROR_INVALID_ROOT_NODE } return blockchain.api.tx.delegation.createHierarchy( - delegation.id, + delegation.hierarchyId, await delegation.cTypeHash ) } @@ -81,17 +81,10 @@ export async function query( if (!decoded) { return null } - const root = new DelegationNode({ + return new DelegationNode({ + ...decoded, id: delegationId, - hierarchyId: decoded.hierarchyId, - parentId: decoded.parentId, - childrenIds: decoded.childrenIds, - account: decoded.account, - permissions: decoded.permissions, - revoked: decoded.revoked, }) - - return root } /** diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 00e885209..698ba4934 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -10,7 +10,7 @@ * * Starting from the root node, entities can delegate the right to issue attestations to Claimers for a certain CTYPE and also delegate the right to attest and to delegate further nodes. * - * A delegation object is stored on-chain, and can be revoked. A base node is created, a ID which may be used in a [[RequestForAttestation]]. + * A delegation object is stored on-chain, and can be revoked. * * A delegation can and may restrict permissions. * @@ -76,19 +76,22 @@ export default class DelegationNode implements IDelegationNode { /** * Builds a new [DelegationNode] representing a regular delegation node ready to be submitted to the chain for creation. * + * @param hierarchyId.hierarchyId * @param hierarchyId - The delegation hierarchy under which to store the node. * @param parentId - The parent node under which to store the node. - * @param account - The address of this delegation. - * @param permissions - The set of permissions to associated with this delegation node. - * - * @returns A new [DelegationNode] with a randomly-generated id. + * @param account - The owner (i.e., delegate) of this delegation. + * @param permissions - The set of permissions associated with this delegation node. + * @param hierarchyId.parentId + * @param hierarchyId.account + * @param hierarchyId.permissions + * @returns A new [DelegationNode] with a randomly generated id. */ - public static newNode( - hierarchyId: IDelegationNode['hierarchyId'], - parentId: string, // Cannot be undefined here - account: IDelegationNode['account'], - permissions: IDelegationNode['permissions'] - ): DelegationNode { + public static newNode({ + hierarchyId, + parentId, // Cannot be undefined here + account, + permissions, + }: IDelegationNode): DelegationNode { return new DelegationNode({ id: UUID.generate(), hierarchyId, @@ -104,14 +107,13 @@ export default class DelegationNode implements IDelegationNode { * Builds a new [DelegationNode] representing a root delegation node ready to be submitted to the chain for creation. * * @param account - The address of this delegation (and of the whole hierarchy under it). - * @param permissions - The set of permissions to associated with this delegation node. + * @param permissions - The set of permissions associated with this delegation node. * @param hierarchyDetails - The details associated with the delegation hierarchy (e.g. The CType hash of allowed attestations). * - * @returns A new [DelegationNode] with a randomly-generated id. + * @returns A new [DelegationNode] with a randomly generated id. */ public static newRoot( - account: IDelegationNode['account'], - permissions: IDelegationNode['permissions'], + { account, permissions }: IDelegationNode, hierarchyDetails: IDelegationHierarchyDetails ): DelegationNode { const nodeId = UUID.generate() @@ -151,9 +153,9 @@ export default class DelegationNode implements IDelegationNode { throw SDKErrors.ERROR_HIERARCHY_QUERY(this.hierarchyId) } this.hierarchyDetails = hierarchyDetails + return hierarchyDetails } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.hierarchyDetails! + return this.hierarchyDetails } /** @@ -162,7 +164,7 @@ export default class DelegationNode implements IDelegationNode { * @returns Promise containing the parent as [[DelegationNode]] or [null]. */ public async getParent(): Promise { - return this.parentId ? query(this.parentId) : Promise.resolve(null) + return this.parentId ? query(this.parentId) : null } /** diff --git a/packages/types/src/Delegation.ts b/packages/types/src/Delegation.ts index 6d4a67d5e..f58f31b20 100644 --- a/packages/types/src/Delegation.ts +++ b/packages/types/src/Delegation.ts @@ -29,5 +29,6 @@ export interface IDelegationNode { } export interface IDelegationHierarchyDetails { + id: IDelegationNode['id'] cTypeHash: ICType['hash'] } From e8b1f490f96ca3b4c907390041bd89d1aab60468 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Mon, 2 Aug 2021 16:38:26 +0200 Subject: [PATCH 16/21] feat: refactor delegation node creation input --- .../__integrationtests__/Delegation.spec.ts | 12 ++--- .../core/src/delegation/DelegationNode.ts | 53 +++++++++++++------ 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index 9024280e1..79a99055b 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -32,13 +32,11 @@ async function writeHierarchy( delegator: Identity, ctypeHash: ICType['hash'] ): Promise { - const rootNode = DelegationNode.newRoot( - delegator.address, - [Permission.DELEGATE], - { - cTypeHash: ctypeHash, - } - ) + const rootNode = DelegationNode.newRoot({ + account: delegator.address, + permissions: [Permission.DELEGATE], + cTypeHash: ctypeHash, + }) await rootNode.store().then((tx) => BlockchainUtils.signAndSubmitTx(tx, delegator, { diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 698ba4934..fb2ab3423 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -31,6 +31,7 @@ import type { } from '@kiltprotocol/types' import { Crypto, SDKErrors, UUID } from '@kiltprotocol/utils' import { ConfigService } from '@kiltprotocol/config' +import type { DelegationHierarchyDetailsRecord } from './DelegationDecoder' import { query as queryAttestation } from '../attestation/Attestation.chain' import { getChildren, @@ -47,6 +48,13 @@ import Identity from '../identity/Identity' const log = ConfigService.LoggingFactory.getLogger('DelegationNode') +type NewDelegationNodeInput = Required< + Pick +> + +type NewDelegationRootInput = Pick & + DelegationHierarchyDetailsRecord + export default class DelegationNode implements IDelegationNode { public readonly id: IDelegationNode['id'] public readonly hierarchyId: IDelegationNode['hierarchyId'] @@ -57,19 +65,27 @@ export default class DelegationNode implements IDelegationNode { private hierarchyDetails?: IDelegationHierarchyDetails public readonly revoked: boolean + // eslint-disable-next-line jsdoc/require-param /** - * Creates a new [DelegationNode]. + * Creates a new [DelegationNode] from an [IDelegationNode]. * - * @param delegationNodeInput - The base object from which to create the delegation node. */ - public constructor(delegationNodeInput: IDelegationNode) { - this.id = delegationNodeInput.id - this.hierarchyId = delegationNodeInput.hierarchyId - this.parentId = delegationNodeInput.parentId - this.childrenIds = delegationNodeInput.childrenIds - this.account = delegationNodeInput.account - this.permissions = delegationNodeInput.permissions - this.revoked = delegationNodeInput.revoked + public constructor({ + id, + hierarchyId, + parentId, + childrenIds, + account, + permissions, + revoked, + }: IDelegationNode) { + this.id = id + this.hierarchyId = hierarchyId + this.parentId = parentId + this.childrenIds = childrenIds + this.account = account + this.permissions = permissions + this.revoked = revoked DelegationNodeUtils.errorCheck(this) } @@ -91,7 +107,7 @@ export default class DelegationNode implements IDelegationNode { parentId, // Cannot be undefined here account, permissions, - }: IDelegationNode): DelegationNode { + }: NewDelegationNodeInput): DelegationNode { return new DelegationNode({ id: UUID.generate(), hierarchyId, @@ -112,10 +128,11 @@ export default class DelegationNode implements IDelegationNode { * * @returns A new [DelegationNode] with a randomly generated id. */ - public static newRoot( - { account, permissions }: IDelegationNode, - hierarchyDetails: IDelegationHierarchyDetails - ): DelegationNode { + public static newRoot({ + account, + permissions, + cTypeHash, + }: NewDelegationRootInput): DelegationNode { const nodeId = UUID.generate() const newNode = new DelegationNode({ @@ -126,7 +143,10 @@ export default class DelegationNode implements IDelegationNode { childrenIds: [], revoked: false, }) - newNode.hierarchyDetails = hierarchyDetails + newNode.hierarchyDetails = { + id: nodeId, + cTypeHash, + } return newNode } @@ -319,6 +339,7 @@ export default class DelegationNode implements IDelegationNode { public async subtreeNodeCount(): Promise { // Fetch the latest state from chain first await this.refreshState() + const children = await this.getChildren() if (children.length === 0) { return 0 From 2c4680969a98538712a77c1c4194009cc1e2677e Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 3 Aug 2021 08:31:30 +0200 Subject: [PATCH 17/21] feat: update delegation errors --- .../src/errorhandling/ExtrinsicError.ts | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/packages/chain-helpers/src/errorhandling/ExtrinsicError.ts b/packages/chain-helpers/src/errorhandling/ExtrinsicError.ts index 8658e8991..ab17215e6 100644 --- a/packages/chain-helpers/src/errorhandling/ExtrinsicError.ts +++ b/packages/chain-helpers/src/errorhandling/ExtrinsicError.ts @@ -80,27 +80,53 @@ export const ExtrinsicErrors = { code: 13002, message: 'delegation not found', }, - ERROR_ROOT_ALREADY_EXISTS: { code: 13003, message: 'root already exist' }, - ERROR_ROOT_NOT_FOUND: { code: 13004, message: 'root not found' }, + ERROR_DELEGATE_NOT_FOUND: { + code: 13003, + message: 'delegate not found', + }, + ERROR_HIERARCHY_ALREADY_EXISTS: { + code: 13004, + message: 'hierarchy already exist', + }, + ERROR_HIERARCHY_NOT_FOUND: { code: 13005, message: 'hierarchy not found' }, ERROR_MAX_DELEGATION_SEARCH_DEPTH_REACHED: { - code: 13005, + code: 13006, message: 'maximum delegation search depth reached', }, - ERROR_NOT_OWNER_OF_PARENT: { code: 13006, message: 'not owner of parent' }, - ERROR_NOT_OWNER_OF_ROOT: { code: 13007, message: 'not owner of root' }, - ERROR_PARENT_NOT_FOUND: { code: 13008, message: 'parent not found' }, + ERROR_NOT_OWNER_OF_PARENT: { code: 13007, message: 'not owner of parent' }, + ERROR_NOT_OWNER_OF_HIERARCHY: { + code: 13008, + message: 'not owner of hierarchy', + }, + ERROR_PARENT_NOT_FOUND: { code: 13009, message: 'parent not found' }, + ERROR_PARENT_REVOKED: { + code: 13010, + message: 'parent delegation revoked', + }, ERROR_NOT_PERMITTED_TO_REVOKE: { - code: 13009, + code: 13011, message: 'not permitted to revoke', }, ERROR_NOT_AUTHORIZED_TO_DELEGATE: { - code: 13010, + code: 13012, message: 'not authorized to delegate', }, ERROR_EXCEEDED_REVOCATION_BOUNDS: { - code: 13011, + code: 13013, message: 'exceeded revocation bounds', }, + ERROR_EXCEEDED_MAX_REVOCATIONS_ALLOWED: { + code: 13014, + message: 'exceeded max revocations allowed', + }, + ERROR_EXCEEDED_MAX_PARENT_CHECKS_ALLOWED: { + code: 13015, + message: 'exceeded max parent checks allowed', + }, + INTERNAL_ERROR: { + code: 13016, + message: 'an internal delegation module error occured', + }, UNKNOWN_ERROR: { code: 13100, message: 'an unknown delegation module error occured', @@ -163,15 +189,20 @@ export const PalletToExtrinsicErrors: IPalletToExtrinsicErrors = { 0: ExtrinsicErrors.Delegation.ERROR_DELEGATION_ALREADY_EXISTS, 1: ExtrinsicErrors.Delegation.ERROR_BAD_DELEGATION_SIGNATURE, 2: ExtrinsicErrors.Delegation.ERROR_DELEGATION_NOT_FOUND, - 3: ExtrinsicErrors.Delegation.ERROR_ROOT_ALREADY_EXISTS, - 4: ExtrinsicErrors.Delegation.ERROR_ROOT_NOT_FOUND, - 5: ExtrinsicErrors.Delegation.ERROR_MAX_DELEGATION_SEARCH_DEPTH_REACHED, - 6: ExtrinsicErrors.Delegation.ERROR_NOT_OWNER_OF_PARENT, - 7: ExtrinsicErrors.Delegation.ERROR_NOT_OWNER_OF_ROOT, - 8: ExtrinsicErrors.Delegation.ERROR_PARENT_NOT_FOUND, - 9: ExtrinsicErrors.Delegation.ERROR_NOT_PERMITTED_TO_REVOKE, - 10: ExtrinsicErrors.Delegation.ERROR_NOT_AUTHORIZED_TO_DELEGATE, - 11: ExtrinsicErrors.Delegation.ERROR_EXCEEDED_REVOCATION_BOUNDS, + 3: ExtrinsicErrors.Delegation.ERROR_DELEGATE_NOT_FOUND, + 4: ExtrinsicErrors.Delegation.ERROR_HIERARCHY_ALREADY_EXISTS, + 5: ExtrinsicErrors.Delegation.ERROR_HIERARCHY_NOT_FOUND, + 6: ExtrinsicErrors.Delegation.ERROR_MAX_DELEGATION_SEARCH_DEPTH_REACHED, + 7: ExtrinsicErrors.Delegation.ERROR_NOT_OWNER_OF_PARENT, + 8: ExtrinsicErrors.Delegation.ERROR_NOT_OWNER_OF_HIERARCHY, + 9: ExtrinsicErrors.Delegation.ERROR_PARENT_NOT_FOUND, + 10: ExtrinsicErrors.Delegation.ERROR_PARENT_REVOKED, + 11: ExtrinsicErrors.Delegation.ERROR_NOT_PERMITTED_TO_REVOKE, + 12: ExtrinsicErrors.Delegation.ERROR_NOT_AUTHORIZED_TO_DELEGATE, + 13: ExtrinsicErrors.Delegation.ERROR_EXCEEDED_REVOCATION_BOUNDS, + 14: ExtrinsicErrors.Delegation.ERROR_EXCEEDED_MAX_REVOCATIONS_ALLOWED, + 15: ExtrinsicErrors.Delegation.ERROR_EXCEEDED_MAX_PARENT_CHECKS_ALLOWED, + 16: ExtrinsicErrors.Delegation.INTERNAL_ERROR, [-1]: ExtrinsicErrors.Delegation.UNKNOWN_ERROR, }, [PalletIndex.DID]: { From b1d15b6a5b2d916bab8ba21b810d9fdd3a547966 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 3 Aug 2021 08:32:30 +0200 Subject: [PATCH 18/21] fix: fix bug in refreshState() --- packages/core/src/delegation/DelegationNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index fb2ab3423..a80fcba17 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -264,7 +264,7 @@ export default class DelegationNode implements IDelegationNode { * @returns An updated instance of the same [DelegationNode] containing the up-to-date state fetched from the chain. */ public async refreshState(): Promise { - const newNodeState = query(this.id) + const newNodeState = await query(this.id) if (!newNodeState) { throw SDKErrors.ERROR_DELEGATION_ID_MISSING } From a6a5866c89a4a462a37d685024b01fc3f92f3b25 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 3 Aug 2021 08:32:46 +0200 Subject: [PATCH 19/21] test: unit and integration tests now passing --- .../core/src/__integrationtests__/Delegation.spec.ts | 8 ++++---- packages/core/src/delegation/DelegationNode.spec.ts | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index 79a99055b..d2124fcae 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -55,12 +55,12 @@ async function addDelegation( delegee: Identity, permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE] ): Promise { - const delegationNode = DelegationNode.newNode( + const delegationNode = DelegationNode.newNode({ hierarchyId, parentId, - delegee.address, - permissions - ) + account: delegee.address, + permissions, + }) await delegationNode .store(delegee.signStr(delegationNode.generateHash())) diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index 4b65f1dfe..ca4fcec63 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -33,7 +33,10 @@ jest.mock('./DelegationNode.chain', () => { query: jest.fn(async (id: string) => nodes[id] || null), storeAsRoot: jest.fn(async (node: DelegationNode) => { nodes[node.id] = node - hierarchiesDetails[node.id] = { cTypeHash: await node.cTypeHash } + hierarchiesDetails[node.id] = { + id: node.id, + cTypeHash: await node.cTypeHash, + } }), revoke: jest.fn( async ( @@ -164,6 +167,7 @@ describe('DelegationNode', () => { it('get delegation root', async () => { hierarchiesDetails = { [hierarchyId]: { + id: hierarchyId, cTypeHash: 'kilt:ctype:0xba15bf4960766b0a6ad7613aa3338edce95df6b22ed29dd72f6e72d740829b84', }, @@ -558,9 +562,10 @@ describe('DelegationHierarchy', () => { [ROOT_IDENTIFIER]: revokedRootDelegationNode, [ROOT_SUCCESS]: notRevokedRootDelegationNode, } + hierarchiesDetails = { - [ROOT_IDENTIFIER]: { cTypeHash: ctypeHash }, - [ROOT_SUCCESS]: { cTypeHash: ctypeHash }, + [ROOT_IDENTIFIER]: { id: ROOT_IDENTIFIER, cTypeHash: ctypeHash }, + [ROOT_SUCCESS]: { id: ROOT_SUCCESS, cTypeHash: ctypeHash }, } }) From 1e440c843dacf153a1be3483994fb510a3544478 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 3 Aug 2021 12:04:27 +0200 Subject: [PATCH 20/21] fix: rename refreshState() to getLatestState() --- .../core/src/__integrationtests__/Delegation.spec.ts | 3 ++- packages/core/src/delegation/DelegationNode.ts | 11 ++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/core/src/__integrationtests__/Delegation.spec.ts b/packages/core/src/__integrationtests__/Delegation.spec.ts index d2124fcae..5a7702df5 100644 --- a/packages/core/src/__integrationtests__/Delegation.spec.ts +++ b/packages/core/src/__integrationtests__/Delegation.spec.ts @@ -231,7 +231,7 @@ describe('revocation', () => { }, 60_000) it('delegator can revoke root, revoking all delegations in tree', async () => { - const delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) + let delegationRoot = await writeHierarchy(delegator, DriversLicense.hash) const delegationA = await addDelegation( delegationRoot.id, delegationRoot.id, @@ -244,6 +244,7 @@ describe('revocation', () => { firstDelegee, secondDelegee ) + delegationRoot = await delegationRoot.getLatestState() await expect( delegationRoot.revoke(delegator.address).then((tx) => BlockchainUtils.signAndSubmitTx(tx, delegator, { diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index a80fcba17..3282efc23 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -156,7 +156,7 @@ export default class DelegationNode implements IDelegationNode { * * @returns The CType hash associated with the delegation hierarchy. */ - public get cTypeHash(): Promise { + public async getCTypeHash(): Promise { return this.getHierarchyDetails().then((details) => details.cTypeHash) } @@ -263,12 +263,12 @@ export default class DelegationNode implements IDelegationNode { * * @returns An updated instance of the same [DelegationNode] containing the up-to-date state fetched from the chain. */ - public async refreshState(): Promise { + public async getLatestState(): Promise { const newNodeState = await query(this.id) if (!newNodeState) { throw SDKErrors.ERROR_DELEGATION_ID_MISSING } - Object.assign(this, newNodeState) + return newNodeState } /** @@ -332,14 +332,11 @@ export default class DelegationNode implements IDelegationNode { } /** - * [ASYNC] Recursively counts all nodes that descend from the current node (excluding the current node), after refreshing the node status with the one stored on chain. + * [ASYNC] Recursively counts all nodes that descend from the current node (excluding the current node). It is important to first refresh the state of the node from the chain. * * @returns Promise resolving to the node count. */ public async subtreeNodeCount(): Promise { - // Fetch the latest state from chain first - await this.refreshState() - const children = await this.getChildren() if (children.length === 0) { return 0 From 23a9e1f25443d939c04bd7ed5a017dbb82d4ff64 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 3 Aug 2021 12:27:23 +0200 Subject: [PATCH 21/21] fix: test cases --- packages/core/src/delegation/DelegationNode.chain.ts | 2 +- packages/core/src/delegation/DelegationNode.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/delegation/DelegationNode.chain.ts b/packages/core/src/delegation/DelegationNode.chain.ts index 253cd57cb..4c9310f0a 100644 --- a/packages/core/src/delegation/DelegationNode.chain.ts +++ b/packages/core/src/delegation/DelegationNode.chain.ts @@ -36,7 +36,7 @@ export async function storeAsRoot( } return blockchain.api.tx.delegation.createHierarchy( delegation.hierarchyId, - await delegation.cTypeHash + await delegation.getCTypeHash() ) } diff --git a/packages/core/src/delegation/DelegationNode.spec.ts b/packages/core/src/delegation/DelegationNode.spec.ts index ca4fcec63..0901c9b75 100644 --- a/packages/core/src/delegation/DelegationNode.spec.ts +++ b/packages/core/src/delegation/DelegationNode.spec.ts @@ -35,7 +35,7 @@ jest.mock('./DelegationNode.chain', () => { nodes[node.id] = node hierarchiesDetails[node.id] = { id: node.id, - cTypeHash: await node.cTypeHash, + cTypeHash: await node.getCTypeHash(), } }), revoke: jest.fn( @@ -591,7 +591,7 @@ describe('DelegationHierarchy', () => { expect(queriedDelegation).not.toBe(undefined) if (queriedDelegation) { expect(queriedDelegation.account).toBe(identityAlice.address) - expect(queriedDelegation.cTypeHash).resolves.toBe(ctypeHash) + expect(queriedDelegation.getCTypeHash()).resolves.toBe(ctypeHash) expect(queriedDelegation.id).toBe(ROOT_IDENTIFIER) } })