From 316bd91ddb15e8f0b5fa690103190dbe745a4c57 Mon Sep 17 00:00:00 2001 From: Eric Richardson Date: Tue, 30 Apr 2024 17:15:17 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20`getProofDetails`?= =?UTF-8?q?=20to=20`ConfidentialTransaction`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit allow fetching all proof information related to a confidential transaction. The new method is similar to `getSenderProofs`, except it also includes details about legs where the sender has not comitted proofs for the assets --- .../entities/ConfidentialTransaction/index.ts | 101 ++++++++++-- .../entities/ConfidentialTransaction/types.ts | 27 +++ .../ConfidentialTransaction/index.ts | 156 +++++++++++++++--- src/middleware/queries/confidential.ts | 72 ++++---- 4 files changed, 276 insertions(+), 80 deletions(-) diff --git a/src/api/entities/ConfidentialTransaction/index.ts b/src/api/entities/ConfidentialTransaction/index.ts index b1d8d8e6e5..4a308edbda 100644 --- a/src/api/entities/ConfidentialTransaction/index.ts +++ b/src/api/entities/ConfidentialTransaction/index.ts @@ -20,6 +20,7 @@ import BigNumber from 'bignumber.js'; import { convertSubQueryAssetIdToUuid } from '~/api/entities/ConfidentialAccount/helpers'; import { affirmConfidentialTransactions, + ConfidentialAccount, Context, Entity, executeConfidentialTransaction, @@ -40,7 +41,10 @@ import { ConfidentialProcedureMethod, ConfidentialTransactionDetails, ConfidentialTransactionStatus, + PendingProof, + SenderAssetProof, SenderProofs, + TransactionProofDetails, } from '~/types'; import { Ensured } from '~/types/utils'; import { @@ -376,33 +380,110 @@ export class ConfidentialTransaction extends Entity { } /** - * 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 { + public async getProofDetails(): Promise { const { context } = this; const { data: { - confidentialTransactionAffirmations: { nodes }, + confidentialTransaction: { affirmations, legs }, }, - } = await context.queryMiddleware>( - getConfidentialTransactionProofsQuery(this.id) + } = await context.queryMiddleware>( + 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; + } + > + >((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); + + 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 { + const { proved } = await this.getProofDetails(); + + return proved; } /** diff --git a/src/api/entities/ConfidentialTransaction/types.ts b/src/api/entities/ConfidentialTransaction/types.ts index 2f5b7f63ba..ece49c87a6 100644 --- a/src/api/entities/ConfidentialTransaction/types.ts +++ b/src/api/entities/ConfidentialTransaction/types.ts @@ -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[]; +} diff --git a/src/api/entities/__tests__/ConfidentialTransaction/index.ts b/src/api/entities/__tests__/ConfidentialTransaction/index.ts index 004bcdad79..3229d8becc 100644 --- a/src/api/entities/__tests__/ConfidentialTransaction/index.ts +++ b/src/api/entities/__tests__/ConfidentialTransaction/index.ts @@ -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' }), + ]), + }, + ]), + }), + ]), + }); }); }); diff --git a/src/middleware/queries/confidential.ts b/src/middleware/queries/confidential.ts index 4eebba5686..7d09293ed1 100644 --- a/src/middleware/queries/confidential.ts +++ b/src/middleware/queries/confidential.ts @@ -271,51 +271,26 @@ export function getConfidentialAssetHistoryByConfidentialAccountQuery( }; } -export type ConfidentialTransactionProofsArgs = { - transactionId: string; -}; - -/** - * @hidden - */ -function createGetConfidentialTransactionProofArgs(transactionId: BigNumber): { - args: string; - filter: string; - variables: { transactionId: string }; -} { - const args = ['$transactionId: String!']; - const filter = ['transactionId: { equalTo: $transactionId }, party: { equalTo: Sender }']; - - return { - args: `(${args.join()})`, - filter: `filter: { ${filter.join()} }`, - variables: { transactionId: transactionId.toString() }, - }; -} - /** * @hidden * - * Get sender proofs for a transaction + * Get confidential transaction details by ID */ -export function getConfidentialTransactionProofsQuery( - transactionId: BigNumber -): QueryOptions { - const { args, filter, variables } = createGetConfidentialTransactionProofArgs(transactionId); - +export function confidentialTransactionQuery( + variables: QueryArgs +): QueryOptions> { const query = gql` - query ConfidentialTransactionProofs - ${args} - { - confidentialTransactionAffirmations( - ${filter} - ) { - nodes { - proofs - legId + query ConfidentialTransaction($id: String!) { + confidentialTransaction(id: $id) { + eventIdx + createdBlock { + blockId + datetime + hash + } + } } - } - }`; + `; return { query, @@ -328,17 +303,26 @@ export function getConfidentialTransactionProofsQuery( * * Get confidential transaction details by ID */ -export function confidentialTransactionQuery( +export function getConfidentialTransactionProofsQuery( variables: QueryArgs ): QueryOptions> { const query = gql` query ConfidentialTransaction($id: String!) { confidentialTransaction(id: $id) { eventIdx - createdBlock { - blockId - datetime - hash + affirmations(filter: { party: { equalTo: Sender } }) { + nodes { + proofs + legId + } + } + legs { + nodes { + id + senderId + receiverId + assetAuditors + } } } }