Skip to content

Commit cba2906

Browse files
committed
feat(core): add timeout and logger to the tokenTransferInspector and transactionSummaryInspector
BREAKING CHANGE: add timeout prop to inspectors for them to return a timeout error
1 parent deb27de commit cba2906

File tree

6 files changed

+118
-114
lines changed

6 files changed

+118
-114
lines changed

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@cardano-ogmios/schema": "5.6.0",
6060
"@cardano-sdk/crypto": "workspace:~",
6161
"@cardano-sdk/util": "workspace:~",
62+
"@cardano-sdk/util-dev": "workspace:~",
6263
"@foxglove/crc": "^0.0.3",
6364
"@scure/base": "^1.1.1",
6465
"fraction.js": "4.0.1",

packages/core/src/util/tokenTransferInspector.ts

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as Cardano from '../Cardano';
33
import { AssetInfo } from '../Asset';
44
import { AssetProvider } from '../Provider';
5-
import { Inspector, resolveInputs } from './txInspector';
5+
import { Inspector, ResolutionResult, resolveInputs } from './txInspector';
66
import { Logger } from 'ts-log';
77
import { Milliseconds } from './time';
88
import { coalesceValueQuantities } from './coalesceValueQuantities';
@@ -17,8 +17,8 @@ export type TokenTransferValue = {
1717
};
1818

1919
export type TokenTransferInspection = {
20-
fromAddress: Map<Cardano.PaymentAddress | null, TokenTransferValue>;
21-
toAddress: Map<Cardano.PaymentAddress | null, TokenTransferValue>;
20+
fromAddress: Map<Cardano.PaymentAddress, TokenTransferValue>;
21+
toAddress: Map<Cardano.PaymentAddress, TokenTransferValue>;
2222
};
2323

2424
/** Arguments for the token transfer inspector. */
@@ -39,7 +39,7 @@ export interface TokenTransferInspectorArgs {
3939
logger: Logger;
4040
}
4141

42-
export type TokenTransferInspector = (args: TokenTransferInspectorArgs) => Promise<TokenTransferInspection>;
42+
export type TokenTransferInspector = (args: TokenTransferInspectorArgs) => Inspector<TokenTransferInspection>;
4343

4444
const coalesceByAddress = <T extends { address: Cardano.PaymentAddress; value: Cardano.Value }>(
4545
elements: T[]
@@ -157,6 +157,12 @@ const toTokenTransferValue = async (
157157
return tokenTransferValue;
158158
};
159159

160+
/**
161+
* Resolve inputs with a timeout so that if the inputs fail to resolve in n Miliseconds
162+
* we throw an error so the UI consumer can handle situation accordingly.
163+
* In this case we create a race condition in which we race to send the resolved inputs before the
164+
* timeout, and timeout only if the inputs aren't there.
165+
*/
160166
export const resolveWithTimeout = <T>(promise: Promise<T>, timeout: Milliseconds): Promise<T> =>
161167
Promise.race([
162168
promise,
@@ -169,40 +175,35 @@ export const resolveWithTimeout = <T>(promise: Promise<T>, timeout: Milliseconds
169175
export const tokenTransferInspector: TokenTransferInspector =
170176
({ inputResolver, fromAddressAssetProvider, toAddressAssetProvider, timeout, logger }) =>
171177
async (tx) => {
172-
const emptyInspection: TokenTransferInspection = {
173-
fromAddress: new Map(),
174-
toAddress: new Map()
175-
};
178+
let resolvedInputs: ResolutionResult['resolvedInputs'];
176179

177180
try {
178-
const { resolvedInputs } = await resolveWithTimeout(resolveInputs(tx.body.inputs, inputResolver), timeout);
179-
180-
const coalescedInputsByAddress = coalesceByAddress(resolvedInputs);
181-
const coalescedOutputsByAddress = coalesceByAddress(tx.body.outputs);
182-
183-
const addresses = uniq([...coalescedInputsByAddress.keys(), ...coalescedOutputsByAddress.keys()]);
184-
185-
const fromAddress = initializeAddressMap(addresses);
186-
const toAddress = initializeAddressMap(addresses);
187-
188-
computeNetDifferences(coalescedInputsByAddress, coalescedOutputsByAddress, fromAddress, toAddress);
189-
190-
removeZeroBalanceEntries(fromAddress);
191-
removeZeroBalanceEntries(toAddress);
192-
193-
return {
194-
fromAddress: await toTokenTransferValue(fromAddressAssetProvider, fromAddress),
195-
toAddress: await toTokenTransferValue(toAddressAssetProvider, toAddress)
196-
};
181+
const inputResolution = await resolveWithTimeout(resolveInputs(tx.body.inputs, inputResolver), timeout);
182+
resolvedInputs = inputResolution.resolvedInputs;
197183
} catch (error) {
198-
// also, resolve this null
199-
// todo: when resolving AssetInfo fails due to provider error or timeout, resolve it without any metadata and set quantity/supply to -1. Document this behavior in doc comments.
200184
if (error === 'Timeout: failed to resolve inputs in time') {
201185
// Handle timeout error specifically
202186
logger.log('Error: Inputs resolution timed out');
203-
return { error, inspection: null };
204187
}
205-
// Bubble up any other error
206-
return { error, inspection: emptyInspection };
188+
189+
resolvedInputs = [];
207190
}
191+
192+
const coalescedInputsByAddress = coalesceByAddress(resolvedInputs);
193+
const coalescedOutputsByAddress = coalesceByAddress(tx.body.outputs);
194+
195+
const addresses = uniq([...coalescedInputsByAddress.keys(), ...coalescedOutputsByAddress.keys()]);
196+
197+
const fromAddress = initializeAddressMap(addresses);
198+
const toAddress = initializeAddressMap(addresses);
199+
200+
computeNetDifferences(coalescedInputsByAddress, coalescedOutputsByAddress, fromAddress, toAddress);
201+
202+
removeZeroBalanceEntries(fromAddress);
203+
removeZeroBalanceEntries(toAddress);
204+
205+
return {
206+
fromAddress: await toTokenTransferValue(fromAddressAssetProvider, fromAddress),
207+
toAddress: await toTokenTransferValue(toAddressAssetProvider, toAddress)
208+
};
208209
};

packages/core/src/util/transactionSummaryInspector.ts

Lines changed: 42 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -165,85 +165,54 @@ export const transactionSummaryInspector: TransactionSummaryInspector =
165165
logger
166166
} = args;
167167

168+
let resolvedInputs: ResolutionResult;
169+
168170
try {
169171
// eslint-disable-next-line sonarjs/prefer-immediate-return
170-
const resolvedInputs = await resolveWithTimeout(resolveInputs(tx.body.inputs, inputResolver), timeout);
171-
const fee = tx.body.fee;
172-
173-
const implicit = computeImplicitCoin(
174-
protocolParameters,
175-
{ certificates: tx.body.certificates, withdrawals: tx.body.withdrawals },
176-
rewardAccounts || [],
177-
dRepKeyHash
178-
);
179-
180-
const collateral = await getCollateral(tx, inputResolver, addresses);
181-
182-
const totalOutputValue = await totalAddressOutputsValueInspector(addresses)(tx);
183-
const totalInputValue = await totalAddressInputsValueInspector(addresses, inputResolver)(tx);
184-
const implicitCoin = (implicit.withdrawals || 0n) + (implicit.reclaimDeposit || 0n) - (implicit.deposit || 0n);
185-
const implicitAssets = await getImplicitAssets(tx);
186-
187-
const diff = {
188-
assets: subtractTokenMaps([totalOutputValue.assets, totalInputValue.assets]),
189-
coins: totalOutputValue.coins - totalInputValue.coins
190-
};
191-
192-
return {
193-
assets: await toAssetInfoWithAmount(assetProvider, diff.assets),
194-
coins: diff.coins,
195-
collateral,
196-
deposit: implicit.deposit || 0n,
197-
fee,
198-
returnedDeposit: implicit.reclaimDeposit || 0n,
199-
unresolved: {
200-
inputs: resolvedInputs.unresolvedInputs,
201-
value: await getUnaccountedFunds(tx, resolvedInputs, implicitCoin, fee, implicitAssets)
202-
}
203-
};
172+
resolvedInputs = await resolveWithTimeout(resolveInputs(tx.body.inputs, inputResolver), timeout);
204173
} catch (error) {
205-
// also, resolve this null
206-
// todo: when resolving AssetInfo fails due to provider error or timeout, resolve it without any metadata and set quantity/supply to -1. Document this behavior in doc comments.
207174
if (error === 'Timeout: failed to resolve inputs in time') {
208175
// Handle timeout error specifically
209-
logger.log('Error: Inputs resolution timed out');
210-
} else {
211-
// Bubble up any other error
212-
throw error;
176+
logger?.log('Error: Inputs resolution timed out');
213177
}
178+
179+
resolvedInputs = {
180+
resolvedInputs: [],
181+
unresolvedInputs: tx.body.inputs
182+
};
214183
}
215184

216-
// const fee = tx.body.fee;
217-
218-
// const implicit = computeImplicitCoin(
219-
// protocolParameters,
220-
// { certificates: tx.body.certificates, withdrawals: tx.body.withdrawals },
221-
// rewardAccounts || [],
222-
// dRepKeyHash
223-
// );
224-
225-
// const collateral = await getCollateral(tx, inputResolver, addresses);
226-
227-
// const totalOutputValue = await totalAddressOutputsValueInspector(addresses)(tx);
228-
// const totalInputValue = await totalAddressInputsValueInspector(addresses, inputResolver)(tx);
229-
// const implicitCoin = (implicit.withdrawals || 0n) + (implicit.reclaimDeposit || 0n) - (implicit.deposit || 0n);
230-
// const implicitAssets = await getImplicitAssets(tx);
231-
232-
// const diff = {
233-
// assets: subtractTokenMaps([totalOutputValue.assets, totalInputValue.assets]),
234-
// coins: totalOutputValue.coins - totalInputValue.coins
235-
// };
236-
237-
// return {
238-
// assets: await toAssetInfoWithAmount(assetProvider, diff.assets),
239-
// coins: diff.coins,
240-
// collateral,
241-
// deposit: implicit.deposit || 0n,
242-
// fee,
243-
// returnedDeposit: implicit.reclaimDeposit || 0n,
244-
// unresolved: {
245-
// inputs: resolvedInputs.unresolvedInputs,
246-
// value: await getUnaccountedFunds(tx, resolvedInputs, implicitCoin, fee, implicitAssets)
247-
// }
248-
// };
185+
const fee = tx.body.fee;
186+
187+
const implicit = computeImplicitCoin(
188+
protocolParameters,
189+
{ certificates: tx.body.certificates, withdrawals: tx.body.withdrawals },
190+
rewardAccounts || [],
191+
dRepKeyHash
192+
);
193+
194+
const collateral = await getCollateral(tx, inputResolver, addresses);
195+
196+
const totalOutputValue = await totalAddressOutputsValueInspector(addresses)(tx);
197+
const totalInputValue = await totalAddressInputsValueInspector(addresses, inputResolver)(tx);
198+
const implicitCoin = (implicit.withdrawals || 0n) + (implicit.reclaimDeposit || 0n) - (implicit.deposit || 0n);
199+
const implicitAssets = await getImplicitAssets(tx);
200+
201+
const diff = {
202+
assets: subtractTokenMaps([totalOutputValue.assets, totalInputValue.assets]),
203+
coins: totalOutputValue.coins - totalInputValue.coins
204+
};
205+
206+
return {
207+
assets: await toAssetInfoWithAmount(assetProvider, diff.assets),
208+
coins: diff.coins,
209+
collateral,
210+
deposit: implicit.deposit || 0n,
211+
fee,
212+
returnedDeposit: implicit.reclaimDeposit || 0n,
213+
unresolved: {
214+
inputs: resolvedInputs.unresolvedInputs,
215+
value: await getUnaccountedFunds(tx, resolvedInputs, implicitCoin, fee, implicitAssets)
216+
}
217+
};
249218
};

packages/core/test/util/tokenTransferInspector.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import {
66
AssetInfoWithAmount,
77
AssetProvider,
88
HealthCheckResponse,
9+
Milliseconds,
910
TokenTransferValue,
1011
createTxInspector,
1112
tokenTransferInspector
1213
} from '../../src';
1314
import { Ed25519KeyHashHex, Ed25519PublicKeyHex, Ed25519SignatureHex } from '@cardano-sdk/crypto';
1415
import { jsonToMetadatum } from '../../src/util/metadatum';
16+
import { logger } from '@cardano-sdk/util-dev';
1517

1618
const buildTokenTransferValue = (coins: bigint, assets: Array<[Asset.AssetInfo, bigint]>): TokenTransferValue => ({
1719
assets: new Map<Cardano.AssetId, AssetInfoWithAmount>(
@@ -221,6 +223,8 @@ describe('txInspector', () => {
221223

222224
const assetProvider = createMockAssetProvider(assetInfos);
223225

226+
const timeout = 6000 as Milliseconds;
227+
224228
describe('Token Transfer Inspector', () => {
225229
it('does not include addresses which net difference is 0 (assets and coins)', async () => {
226230
// Arrange
@@ -302,6 +306,8 @@ describe('txInspector', () => {
302306
tokenTransfer: tokenTransferInspector({
303307
fromAddressAssetProvider: assetProvider,
304308
inputResolver: createMockInputResolver(histTx),
309+
logger,
310+
timeout,
305311
toAddressAssetProvider: assetProvider
306312
})
307313
});
@@ -365,6 +371,8 @@ describe('txInspector', () => {
365371
tokenTransfer: tokenTransferInspector({
366372
fromAddressAssetProvider: assetProvider,
367373
inputResolver: createMockInputResolver(histTx),
374+
logger,
375+
timeout,
368376
toAddressAssetProvider: assetProvider
369377
})
370378
});
@@ -423,6 +431,8 @@ describe('txInspector', () => {
423431
tokenTransfer: tokenTransferInspector({
424432
fromAddressAssetProvider: assetProvider,
425433
inputResolver: createMockInputResolver(histTx),
434+
logger,
435+
timeout,
426436
toAddressAssetProvider: assetProvider
427437
})
428438
});
@@ -500,6 +510,8 @@ describe('txInspector', () => {
500510
tokenTransfer: tokenTransferInspector({
501511
fromAddressAssetProvider: assetProvider,
502512
inputResolver: createMockInputResolver(histTx),
513+
logger,
514+
timeout,
503515
toAddressAssetProvider: assetProvider
504516
})
505517
});
@@ -568,6 +580,8 @@ describe('txInspector', () => {
568580
tokenTransfer: tokenTransferInspector({
569581
fromAddressAssetProvider: assetProvider,
570582
inputResolver: createMockInputResolver(histTx),
583+
logger,
584+
timeout,
571585
toAddressAssetProvider: assetProvider
572586
})
573587
});
@@ -720,6 +734,8 @@ describe('txInspector', () => {
720734
tokenTransfer: tokenTransferInspector({
721735
fromAddressAssetProvider: assetProvider,
722736
inputResolver: createMockInputResolver(histTx),
737+
logger,
738+
timeout,
723739
toAddressAssetProvider: assetProvider
724740
})
725741
});
@@ -856,6 +872,8 @@ describe('txInspector', () => {
856872
tokenTransfer: tokenTransferInspector({
857873
fromAddressAssetProvider: assetProvider,
858874
inputResolver: createMockInputResolver(histTx),
875+
logger,
876+
timeout,
859877
toAddressAssetProvider: assetProvider
860878
})
861879
});
@@ -999,6 +1017,8 @@ describe('txInspector', () => {
9991017
tokenTransfer: tokenTransferInspector({
10001018
fromAddressAssetProvider: createMockAssetProvider(oldAssetInfos),
10011019
inputResolver: createMockInputResolver(histTx),
1020+
logger,
1021+
timeout,
10021022
toAddressAssetProvider: assetProvider
10031023
})
10041024
});

0 commit comments

Comments
 (0)