Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
feat(interactions): add a readthroughpromisecache for contract intera…
Browse files Browse the repository at this point in the history
…ctions PE-5202
  • Loading branch information
Ariel Melendez committed Dec 10, 2023
1 parent 63c49e3 commit 53f66da
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 38 deletions.
90 changes: 78 additions & 12 deletions src/api/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Arweave from 'arweave';
import { ArNSInteraction } from '../types.js';
import {
ArNSInteraction,
GenericContractInteraction,
TransactionID,
} from '../types.js';
import {
GQLResultInterface,
LexicographicalInteractionsSorter,
TagsParser,
} from 'warp-contracts';
import logger from '../logger';
import { ReadThroughPromiseCache } from '@ardrive/ardrive-promise-cache';
import winston from 'winston';

export const MAX_REQUEST_SIZE = 100;

Expand Down Expand Up @@ -103,22 +109,82 @@ export async function getDeployedContractsByWallet(
};
}

export async function getWalletInteractionsForContract(
arweave: Arweave,
params: {
address?: string | undefined;
contractTxId: string;
blockHeight: number | undefined;
class ContractInteractionsCacheKey {
constructor(
public readonly arweave: Arweave,
public readonly contractTxId: string,
public readonly blockHeight: number | undefined,
public readonly address?: string | undefined,
public readonly logger?: winston.Logger | undefined,
) {}

toString(): string {
return `${this.contractTxId}-${
this.blockHeight ? `this.blockHeight` : 'null'
}${this.address ? `-${this.address}` : ''}`;
}

// Facilitate ReadThroughPromiseCache key derivation
toJSON() {
return { cacheKey: this.toString() };
}
}

// Cache contract interactions for 60 seconds since block time is around 2 minutes
const contractInteractionsCache: ReadThroughPromiseCache<
ContractInteractionsCacheKey,
{ interactions: Map<TransactionID, GenericContractInteraction> }
> = new ReadThroughPromiseCache({
cacheParams: {
cacheCapacity: 10_000,
cacheTTL: 1000 * 60, // 60 seconds
},
readThroughFunction: readThroughToWalletInteractionsForContract,
});

export async function getWalletInteractionsForContract({
arweave,
contractTxId,
address,
blockHeight,
logger,
}: {
arweave: Arweave;
contractTxId: string;
address?: string | undefined;
blockHeight?: number | undefined;
logger?: winston.Logger | undefined;
}) {
return contractInteractionsCache.get(
new ContractInteractionsCacheKey(
arweave,
contractTxId,
blockHeight,
address,
logger,
),
);
}

export async function readThroughToWalletInteractionsForContract(
cacheKey: ContractInteractionsCacheKey,
): Promise<{
interactions: Map<
string,
Omit<ArNSInteraction, 'valid' | 'errorMessage' | 'id'>
>;
interactions: Map<TransactionID, GenericContractInteraction>;
}> {
const {
arweave,
contractTxId,
address,
blockHeight: blockHeightFilter,
} = cacheKey;
logger?.debug('Reading through to wallet interactions for contract...', {
contractTxId,
address,
blockHeightFilter,
cacheKey: cacheKey.toString(),
});
const parser = new TagsParser();
const interactionSorter = new LexicographicalInteractionsSorter(arweave);
const { address, contractTxId, blockHeight: blockHeightFilter } = params;
let hasNextPage = false;
let cursor: string | undefined;
const interactions = new Map<
Expand Down
59 changes: 33 additions & 26 deletions src/routes/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
*/

import {
ArNSInteraction,
ContractRecordResponse,
ContractReservedResponse,
GenericContractInteraction,
KoaContext,
} from '../types';
import { getContractReadInteraction, getContractState } from '../api/warp';
Expand Down Expand Up @@ -72,7 +72,9 @@ export async function contractInteractionsHandler(ctx: KoaContext) {
contractTxId,
sortKey: requestedSortKey,
blockHeight: requestedBlockHeight,
address,
});

const [
{ validity, errorMessages, evaluationOptions, sortKey: evaluatedSortKey },
{ interactions },
Expand All @@ -84,38 +86,42 @@ export async function contractInteractionsHandler(ctx: KoaContext) {
sortKey: requestedSortKey,
blockHeight: requestedBlockHeight,
}),
getWalletInteractionsForContract(arweave, {
getWalletInteractionsForContract({
arweave,
address,
contractTxId,
blockHeight: requestedBlockHeight,
logger,
}),
]);

logger.debug('Mapping interactions', {
contractTxId,
sortKey: requestedSortKey,
blockHeight: requestedBlockHeight,
address,
});

let mappedInteractions = Array.from(interactions)
.map(
([id, interaction]: [
string,
Omit<ArNSInteraction, 'valid' | 'errorMessage' | 'id'>,
]) => {
// found in graphql but not by warp
if (!Object.keys(validity).includes(id)) {
logger.debug(
'Interaction found via GraphQL but not evaluated by warp',
{
contractTxId,
interaction: id,
},
);
mismatchedInteractionCount.inc();
}
return {
...interaction,
valid: validity[id] ?? false,
error: errorMessages[id],
id,
};
},
)
.map(([id, interaction]: [string, GenericContractInteraction]) => {
// found in graphql but not by warp
if (!Object.keys(validity).includes(id)) {
logger.debug(
'Interaction found via GraphQL but not evaluated by warp',
{
contractTxId,
interaction: id,
},
);
mismatchedInteractionCount.inc();
}
return {
...interaction,
valid: validity[id] ?? false,
error: errorMessages[id],
id,
};
})
// sort them in order
.sort((a, b) => {
// prioritize sort key if it exists
Expand All @@ -133,6 +139,7 @@ export async function contractInteractionsHandler(ctx: KoaContext) {
);
mappedInteractions = mappedInteractions.slice(0, sortKeyIndex + 1);
}

ctx.body = {
contractTxId,
// return them in descending order
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,10 @@ export type EvaluatedReadInteraction = {
result: any;

Check warning on line 96 in src/types.ts

View workflow job for this annotation

GitHub Actions / build (lint:check)

Unexpected any. Specify a different type
evaluationOptions?: Partial<EvaluationOptions>;
};

export type GenericContractInteraction = Omit<
ArNSInteraction,
'valid' | 'errorMessage' | 'id'
>;

export type TransactionID = string;

0 comments on commit 53f66da

Please sign in to comment.