Skip to content

Commit

Permalink
Merge pull request #25 from PolymeshAssociation/get-leg-proofs
Browse files Browse the repository at this point in the history
feat: 🎸 add `getProofDetails` to `ConfidentialTransaction`
  • Loading branch information
prashantasdeveloper authored May 1, 2024
2 parents 1dbbd1f + 316bd91 commit a526420
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 80 deletions.
101 changes: 91 additions & 10 deletions src/api/entities/ConfidentialTransaction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import BigNumber from 'bignumber.js';
import { convertSubQueryAssetIdToUuid } from '~/api/entities/ConfidentialAccount/helpers';
import {
affirmConfidentialTransactions,
ConfidentialAccount,
Context,
Entity,
executeConfidentialTransaction,
Expand All @@ -40,7 +41,10 @@ import {
ConfidentialProcedureMethod,
ConfidentialTransactionDetails,
ConfidentialTransactionStatus,
PendingProof,
SenderAssetProof,
SenderProofs,
TransactionProofDetails,
} from '~/types';
import { Ensured } from '~/types/utils';
import {
Expand Down Expand Up @@ -376,33 +380,110 @@ export class ConfidentialTransaction extends Entity<UniqueIdentifiers, string> {
}

/**
* Get all submitted sender proofs for this transaction
* Get information for the each of the legs proof status
*
* The results are divided between `proved` and `pending`, depending if the sender has already submitted a proof or not. Proved results contain a proof for each asset involved in the leg which can be verified by the receiver or specified auditors.
*
* @note uses the middlewareV2
*/
public async getSenderProofs(): Promise<SenderProofs[]> {
public async getProofDetails(): Promise<TransactionProofDetails> {
const { context } = this;
const {
data: {
confidentialTransactionAffirmations: { nodes },
confidentialTransaction: { affirmations, legs },
},
} = await context.queryMiddleware<Ensured<Query, 'confidentialTransactionAffirmations'>>(
getConfidentialTransactionProofsQuery(this.id)
} = await context.queryMiddleware<Ensured<Query, 'confidentialTransaction'>>(
getConfidentialTransactionProofsQuery({ id: this.id.toString() })
);

return nodes.map(({ proofs: sqProofs, legId: sqLegId }) => {
const legIdToParties = legs.nodes.reduce<
Record<
string,
{
sender: ConfidentialAccount;
receiver: ConfidentialAccount;
assetAuditors: Record<string, ConfidentialAccount[]>;
}
>
>((result, { senderId, receiverId, id: sqLegId, assetAuditors: sqAssetAuditors }) => {
const legId = sqLegId.split('/')[1];
const sender = new ConfidentialAccount({ publicKey: senderId }, context);
const receiver = new ConfidentialAccount({ publicKey: receiverId }, context);

const typed = sqAssetAuditors as { assetId: string; auditors: string[] }[];

const assetAuditors = typed.reduce((record, { assetId, auditors }) => {
record[assetId] = auditors.map(
auditorId => new ConfidentialAccount({ publicKey: auditorId }, context)
);

return record;
}, {} as Record<string, ConfidentialAccount[]>);

result[legId] = { sender, receiver, assetAuditors };

return result;
}, {});

const proved = affirmations.nodes.map(({ proofs: sqProofs, legId: sqLegId }) => {
const legId = new BigNumber(sqLegId);
const { sender, receiver, assetAuditors } = legIdToParties[sqLegId];

const proofs = sqProofs.map(({ assetId, proof }: { assetId: string; proof: string }) => ({
assetId: convertSubQueryAssetIdToUuid(assetId),
proof,
}));
const proofs: SenderAssetProof[] = sqProofs.map(
({ assetId, proof }: { assetId: string; proof: string }) => {
const auditors = assetAuditors[assetId];

return {
assetId: convertSubQueryAssetIdToUuid(assetId),
proof,
auditors,
};
}
);

return {
proofs,
legId,
sender,
receiver,
};
});

const pending: PendingProof[] = [];
for (let i = 0; i < legs.nodes.length; i++) {
const legId = i.toString();
const provenLeg = proved.find(leg => leg.legId.toString() === legId);

if (provenLeg) {
continue;
}

const { sender, receiver, assetAuditors } = legIdToParties[legId];
const neededProofs = Object.entries(assetAuditors).map(([assetId, auditors]) => ({
assetId: convertSubQueryAssetIdToUuid(assetId),
auditors,
}));

pending.push({
proofs: neededProofs,
sender,
receiver,
legId: new BigNumber(legId),
});
}

return { proved, pending };
}

/**
* Get all submitted sender proofs for this transaction
*
* @note uses the middlewareV2
*/
public async getSenderProofs(): Promise<SenderProofs[]> {
const { proved } = await this.getProofDetails();

return proved;
}

/**
Expand Down
27 changes: 27 additions & 0 deletions src/api/entities/ConfidentialTransaction/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,36 @@ export type AffirmConfidentialTransactionParams = { legId: BigNumber } & (
export interface SenderAssetProof {
proof: string;
assetId: string;
auditors: ConfidentialAccount[];
}

export type SenderProofs = {
legId: BigNumber;
sender: ConfidentialAccount;
receiver: ConfidentialAccount;
proofs: SenderAssetProof[];
};

export interface PendingAssetProof {
assetId: string;
auditors: ConfidentialAccount[];
}

export type PendingProof = {
legId: BigNumber;
sender: ConfidentialAccount;
receiver: ConfidentialAccount;
proofs: PendingAssetProof[];
};

export interface TransactionProofDetails {
/**
* The legs referenced in `proved` will contain a proof for each asset. The receiver is able to decrypt all amounts with their private key. Auditors are able to decrypt the proof for the associated asset.
*/
proved: SenderProofs[];

/**
* The legs in `pending` have not yet received a proof from the sender. For these the sender has yet to commit amounts on chain, so there is no proof to decrypt
*/
pending: PendingProof[];
}
156 changes: 130 additions & 26 deletions src/api/entities/__tests__/ConfidentialTransaction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,42 +641,146 @@ describe('ConfidentialTransaction class', () => {
});
});

describe('method: getSenderProofs', () => {
describe('method: getLegProofDetails', () => {
it('should return the query results', async () => {
const senderProofsResult = {
confidentialTransactionAffirmations: {
nodes: [
{
legId: 1,
proofs: [
{
assetId: '0x08abb6e3550f385721cfd4a35bd5c6fa',
proof: '0xsomeProof',
},
],
},
],
const legProofResult = {
confidentialTransaction: {
affirmations: {
nodes: [
{
legId: 0,
proofs: [
{
assetId: '0x08abb6e3550f385721cfd4a35bd5c6fa',
proof: '0xsomeProof',
},
],
},
],
},
legs: {
nodes: [
{
id: '1/0',
senderId: 'someSender',
receiverId: 'someReceiver',
assetAuditors: [
{ assetId: '0x08abb6e3550f385721cfd4a35bd5c6fa', auditors: ['someAuditor'] },
],
},
],
},
},
};

dsMockUtils.createApolloQueryMock(
getConfidentialTransactionProofsQuery(transaction.id),
senderProofsResult
getConfidentialTransactionProofsQuery({ id: transaction.id.toString() }),
legProofResult
);

const result = await transaction.getSenderProofs();

expect(result).toEqual([
{
legId: new BigNumber('1'),
proofs: [
{
assetId: '08abb6e3-550f-3857-21cf-d4a35bd5c6fa',
proof: '0xsomeProof',
},
],
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
legId: new BigNumber('0'),
receiver: expect.objectContaining({ publicKey: 'someReceiver' }),
sender: expect.objectContaining({ publicKey: 'someSender' }),
proofs: expect.arrayContaining([
{
assetId: '08abb6e3-550f-3857-21cf-d4a35bd5c6fa',
proof: '0xsomeProof',
auditors: expect.arrayContaining([
expect.objectContaining({ publicKey: 'someAuditor' }),
]),
},
]),
}),
])
);
});
});

describe('method: getLegProofDetails', () => {
it('should return the query results', async () => {
const legProofResult = {
confidentialTransaction: {
affirmations: {
nodes: [
{
legId: 0,
proofs: [
{
assetId: '0x08abb6e3550f385721cfd4a35bd5c6fa',
proof: '0xsomeProof',
},
],
},
],
},
legs: {
nodes: [
{
id: '1/0',
senderId: 'someSender',
receiverId: 'someReceiver',
assetAuditors: [
{ assetId: '0x08abb6e3550f385721cfd4a35bd5c6fa', auditors: ['someAuditor'] },
],
},
{
id: '1/1',
senderId: 'someSender',
receiverId: 'someReceiver',
assetAuditors: [
{ assetId: '0x08abb6e3550f385721cfd4a35bd5c6fa', auditors: ['someAuditor'] },
],
},
],
},
},
]);
};

dsMockUtils.createApolloQueryMock(
getConfidentialTransactionProofsQuery({ id: transaction.id.toString() }),
legProofResult
);

const result = await transaction.getProofDetails();

expect(result).toEqual({
proved: expect.arrayContaining([
expect.objectContaining({
legId: new BigNumber('0'),
receiver: expect.objectContaining({ publicKey: 'someReceiver' }),
sender: expect.objectContaining({ publicKey: 'someSender' }),
proofs: expect.arrayContaining([
{
assetId: '08abb6e3-550f-3857-21cf-d4a35bd5c6fa',
proof: '0xsomeProof',
auditors: expect.arrayContaining([
expect.objectContaining({ publicKey: 'someAuditor' }),
]),
},
]),
}),
]),
pending: expect.arrayContaining([
expect.objectContaining({
legId: new BigNumber('1'),
receiver: expect.objectContaining({ publicKey: 'someReceiver' }),
sender: expect.objectContaining({ publicKey: 'someSender' }),
proofs: expect.arrayContaining([
{
assetId: '08abb6e3-550f-3857-21cf-d4a35bd5c6fa',
auditors: expect.arrayContaining([
expect.objectContaining({ publicKey: 'someAuditor' }),
]),
},
]),
}),
]),
});
});
});

Expand Down
Loading

0 comments on commit a526420

Please sign in to comment.