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

Commit

Permalink
fix(grapqhl): update graphql to use sorter from warp
Browse files Browse the repository at this point in the history
This allows our interactions endpoint to sort interactions using sorts logic before we compare with their evaluation results.
  • Loading branch information
dtfiedler committed Nov 30, 2023
1 parent b504466 commit 305a9e6
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 52 deletions.
74 changes: 45 additions & 29 deletions src/api/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
*/
import Arweave from 'arweave';
import { ArNSInteraction } from '../types.js';
import { LexicographicalInteractionsSorter, TagsParser } from 'warp-contracts';
import {
GQLResultInterface,
LexicographicalInteractionsSorter,
TagsParser,
} from 'warp-contracts';
import logger from '../logger';

export const MAX_REQUEST_SIZE = 100;
Expand Down Expand Up @@ -102,8 +106,9 @@ export async function getDeployedContractsByWallet(
export async function getWalletInteractionsForContract(
arweave: Arweave,
params: {
address: string | undefined;
address?: string;
contractTxId: string;
sortKey: string | undefined;
blockHeight: number | undefined;
},
): Promise<{
Expand All @@ -114,7 +119,12 @@ export async function getWalletInteractionsForContract(
}> {
const parser = new TagsParser();
const interactionSorter = new LexicographicalInteractionsSorter(arweave);
const { address, contractTxId, blockHeight } = params;
const {
address,
contractTxId,
blockHeight: blockHeightFilter,
sortKey: sortKeyFilter,
} = params;
let hasNextPage = false;
let cursor: string | undefined;
const interactions = new Map<
Expand All @@ -123,23 +133,21 @@ export async function getWalletInteractionsForContract(
>();
do {
const ownerFilter = address ? `owners: ["${address}"]` : '';
const blockHeightFilter =
blockHeight !== null && blockHeight !== undefined
? `block: { min: 0 max: ${blockHeight} }`
: '';

const queryObject = {
query: `
{
transactions (
${ownerFilter}
${blockHeightFilter}
tags:[
{
name:"Contract",
values:["${contractTxId}"]
}
],
block: {
min: 0,
max: ${blockHeightFilter ?? null}
},
sort: HEIGHT_DESC,
first: ${MAX_REQUEST_SIZE},
bundledIn: null,
Expand Down Expand Up @@ -170,27 +178,37 @@ export async function getWalletInteractionsForContract(
}`,
};

const { status, ...response } = await arweave.api.post(
const { status, data } = await arweave.api.post<GQLResultInterface>(
'/graphql',
queryObject,
);

if (status !== 200) {
throw Error(
`Failed to fetch contracts for wallet. Status code: ${status}`,
);
}

if (!response.data.data?.transactions?.edges?.length) {
if (!data.data.transactions?.edges?.length) {
continue;
}
for (const e of response.data.data.transactions.edges) {

console.log(JSON.stringify(data.data.transactions.edges, null, 2));

// remove interactions without block data
const validInteractions = data.data.transactions.edges.filter(
(i) => i.node.block && i.node.block.height && i.node.block.id,
);
// sort them using warps sort logic and adds sort keys
const sortedInteractions = await interactionSorter.sort(validInteractions);
for (const i of sortedInteractions) {
// basic validation for smartweave tags
const inputTag = parser.getInputTag(e.node, contractTxId);
const contractTag = parser.getContractTag(e.node);
const inputTag = parser.getInputTag(i.node, contractTxId);
const contractTag = parser.getContractTag(i.node);
if (!inputTag || !contractTag) {
logger.debug('Invalid tags for interaction via GQL, ignoring...', {
contractTxId,
interactionId: e.node.id,
interactionId: i.node.id,
inputTag,
contractTag,
});
Expand All @@ -199,24 +217,21 @@ export async function getWalletInteractionsForContract(
const parsedInput = inputTag?.value
? JSON.parse(inputTag.value)
: undefined;
const sortKey = await interactionSorter.createSortKey(
e.node.block.id,
e.node.id,
e.node.block.height,
);
interactions.set(e.node.id, {
height: e.node.block.height,
timestamp: e.node.block.timestamp,
sortKey,
interactions.set(i.node.id, {
height: i.node.block.height,
timestamp: i.node.block.timestamp,
input: parsedInput,
owner: e.node.owner.address,
owner: i.node.owner.address,
sortKey: i.node.sortKey,
});
// if we have a sort key filter, we can stop here
if (i.node.sortKey === sortKeyFilter) {
break;
}
}
cursor =
response.data.data.transactions.edges[MAX_REQUEST_SIZE - 1]?.cursor ??
undefined;
hasNextPage =
response.data.data.transactions.pageInfo?.hasNextPage ?? false;
data.data.transactions.edges[MAX_REQUEST_SIZE - 1]?.cursor ?? undefined;
hasNextPage = data.data.transactions.pageInfo?.hasNextPage ?? false;
} while (hasNextPage);
return {
interactions,
Expand All @@ -229,6 +244,7 @@ export async function getContractsTransferredToOrControlledByWallet(
): Promise<{ ids: string[] }> {
const { address } = params;
let hasNextPage = false;
[];
let cursor: string | undefined;
const ids = new Set<string>();
do {
Expand Down
19 changes: 16 additions & 3 deletions src/api/warp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ class ContractStateCacheKey {
) {}

toString(): string {
return `${this.contractTxId}-${createQueryParamHash(
this.evaluationOptions,
)}`;
return `${this.contractTxId}-${createSortKeyBlockHeightHash({
sortKey: this.sortKey,
blockHeight: this.blockHeight,
})}-${createQueryParamHash(this.evaluationOptions)}`;
}

// Facilitate ReadThroughPromiseCache key derivation
Expand Down Expand Up @@ -154,6 +155,18 @@ function createQueryParamHash(evalOptions: Partial<EvaluationOptions>): string {
return hash.digest('hex');
}

function createSortKeyBlockHeightHash({
sortKey,
blockHeight,
}: {
sortKey: string | undefined;
blockHeight: number | undefined;
}) {
const hash = createHash('sha256');
hash.update(`${sortKey}-${blockHeight}`);
return hash.digest('hex');
}

async function readThroughToContractState(
cacheKey: ContractStateCacheKey,
): Promise<EvaluatedContractState> {
Expand Down
9 changes: 2 additions & 7 deletions src/routes/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../types';
import { getContractReadInteraction, getContractState } from '../api/warp';
import { getWalletInteractionsForContract } from '../api/graphql';
import { BadRequestError, NotFoundError } from '../errors';
import { NotFoundError } from '../errors';
import { mismatchedInteractionCount } from '../metrics';

export async function contractHandler(ctx: KoaContext) {
Expand Down Expand Up @@ -54,12 +54,6 @@ export async function contractInteractionsHandler(ctx: KoaContext) {
const { arweave, logger, warp, sortKey, blockHeight } = ctx.state;
const { contractTxId, address } = ctx.params;

if (sortKey) {
throw new BadRequestError(
'Sort key is not supported for contract interactions',
);
}

logger.debug('Fetching all contract interactions', {
contractTxId,
});
Expand All @@ -75,6 +69,7 @@ export async function contractInteractionsHandler(ctx: KoaContext) {
getWalletInteractionsForContract(arweave, {
address,
contractTxId,
sortKey,
blockHeight,
}),
]);
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type ArNSInteraction = {
input: PstInput | undefined;
height: number;
owner: string;
sortKey: string;
sortKey?: string;
timestamp: number;
errorMessage?: string;
};
Expand Down
28 changes: 16 additions & 12 deletions tests/integration/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,13 @@ describe('Integration tests', () => {
expect(status).to.equal(400);
});

it.only('should return contract state evaluated up to a given block height', async () => {
// block height before interactions
const blockHeight = 1;
it('should return contract state evaluated up to a given block height', async () => {
const { height: previousBlockHeight } =
await arweave.blocks.getCurrent();
// mine a block height to ensure the contract is evaluated at previous one
await arweave.api.get('mine');
const { status, data } = await axios.get(
`/v1/contract/${id}?blockHeight=${blockHeight}`,
`/v1/contract/${id}?blockHeight=${previousBlockHeight}`,
);
const { contractTxId, state, evaluationOptions, sortKey } = data;
expect(status).to.equal(200);
Expand All @@ -195,7 +197,7 @@ describe('Integration tests', () => {
expect(state).not.to.be.undefined;
expect(sortKey).not.be.undefined;
expect(sortKey.split(',')[0]).to.equal(
`${blockHeight}`.padStart(12, '0'),
`${previousBlockHeight}`.padStart(12, '0'),
);
});
});
Expand Down Expand Up @@ -268,14 +270,16 @@ describe('Integration tests', () => {
expect(interactions).to.deep.equal([]);
});

it('should throw a bad request error when trying to use sortKey for interactions endpoint', async () => {
// block height before the interactions were created
const sortKey = 'test-sort-key';

const { status } = await axios.get(
`/v1/contract/${id}/interactions?sortKey=${sortKey}`,
it('should only return interactions up to a provided sort key height', async () => {
const knownSortKey = contractInteractions[0].sortKey;
const { status, data } = await axios.get(
`/v1/contract/${id}/interactions?sortKey=${knownSortKey}`,
);
expect(status).to.equal(400);
expect(status).to.equal(200);
expect(data).to.not.be.undefined;
const { contractTxId, interactions } = data;
expect(contractTxId).to.equal(id);
expect(interactions).to.deep.equal([contractInteractions[0]]);
});
});

Expand Down

0 comments on commit 305a9e6

Please sign in to comment.