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

Commit

Permalink
Merge pull request #105 from ar-io/PE-5550-sort-key-read-interaction
Browse files Browse the repository at this point in the history
feat(PE-5550): support sortKey and blockHeight for read interactions
  • Loading branch information
dtfiedler authored Mar 4, 2024
2 parents bb76069 + 8177cfe commit fde46b8
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
GATEWAY_HOST=localhost
GATEWAY_HOST=arlocal
GATEWAY_PORT=1984
GATEWAY_PROTOCOL=http
LOG_LEVEL=debug
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
"@aws-sdk/client-s3": "^3.490.0",
"@koa/cors": "^4.0.0",
"@koa/router": "^12.0.0",
"arweave": "^1.13.7",
"arweave": "^1.14.4",
"koa": "^2.14.2",
"koa-bodyparser": "^4.4.0",
"koa-router": "^12.0.0",
"koa2-swagger-ui": "^5.10.0",
"lodash": "^4.17.21",
"prom-client": "^14.2.0",
"warp-contracts": "^1.4.32",
"warp-contracts": "^1.4.37",
"warp-contracts-lmdb": "^1.1.10",
"warp-contracts-sqlite": "^1.0.2",
"winston": "^3.8.2",
Expand Down
50 changes: 40 additions & 10 deletions src/api/warp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import {
DEFAULT_EVALUATION_OPTIONS,
DEFAULT_PAGES_PER_BATCH,
DEFAULT_STATE_EVALUATION_TIMEOUT_MS,
allowedContractTypes,
} from '../constants';
import { ContractType, EvaluatedContractState } from '../types';
Expand Down Expand Up @@ -110,13 +111,16 @@ class ContractReadInteractionCacheKey {
public readonly input: any,

Check warning on line 111 in src/api/warp.ts

View workflow job for this annotation

GitHub Actions / build (lint:check)

Unexpected any. Specify a different type
public readonly warp: Warp,
public readonly evaluationOptions: Partial<EvaluationOptions>,
public readonly sortKey?: string,
public readonly logger?: winston.Logger,
) {}

toString(): string {
return `${this.contractTxId}-${this.functionName}-${createQueryParamHash(
this.input,
)}-${createQueryParamHash(this.evaluationOptions)}`;
return `${this.contractTxId}-${this.functionName}-${
this.sortKey || 'latest'
}-${createQueryParamHash(this.input)}-${createQueryParamHash(
this.evaluationOptions,
)}`;
}

// Facilitate ReadThroughPromiseCache key derivation
Expand Down Expand Up @@ -368,14 +372,23 @@ export async function readThroughToContractReadInteraction(
cacheKey: ContractReadInteractionCacheKey,
): Promise<{
result: unknown;
sortKey: string | undefined;
evaluationOptions: Partial<EvaluationOptions>;
input: unknown;
}> {
const { contractTxId, evaluationOptions, warp, logger, functionName, input } =
cacheKey;
const {
contractTxId,
evaluationOptions,
sortKey,
warp,
logger,
functionName,
input,
} = cacheKey;
logger?.debug('Reading through to contract read interaction...', {
contractTxId,
cacheKey: cacheKey.toString(),
sortKey,
});
const cacheId = cacheKey.toString();

Expand All @@ -386,11 +399,13 @@ export async function readThroughToContractReadInteraction(
logger?.debug('Deduplicating in flight requests for read interaction...', {
contractTxId,
cacheKey: cacheKey.toString(),
sortKey,
});
const { result } = await inFlightRequest;
return {
result,
input,
sortKey,
evaluationOptions,
};
}
Expand All @@ -406,18 +421,25 @@ export async function readThroughToContractReadInteraction(
.setEvaluationOptions(evaluationOptions);

// set cached value for multiple requests during initial promise
// TODO: add abort signal when view state supports it
const readInteractionPromise = contract.viewState({
function: functionName,
...input,
});
const readInteractionPromise = contract.viewState(
{
function: functionName,
...input,
},
undefined, // tags
undefined, // transfer
undefined, // caller
AbortSignal.timeout(DEFAULT_STATE_EVALUATION_TIMEOUT_MS),
sortKey,
);
readRequestMap.set(cacheId, readInteractionPromise);

readInteractionPromise
.catch((error: unknown) => {
logger?.debug('Failed to evaluate read interaction on contract!', {
contractTxId,
cacheKey: cacheKey.toString(),
sortKey,
error: error instanceof Error ? error.message : 'Unknown error',
});
})
Expand All @@ -436,6 +458,7 @@ export async function readThroughToContractReadInteraction(
if (!readInteractionResult) {
logger?.error('Read interaction did not return a result!', {
contractTxId,
sortKey,
cacheKey: cacheKey.toString(),
input,
});
Expand All @@ -451,6 +474,7 @@ export async function readThroughToContractReadInteraction(
contractTxId,
cacheKey: cacheKey.toString(),
input,
sortKey,
error,
errorMessage,
});
Expand All @@ -459,12 +483,15 @@ export async function readThroughToContractReadInteraction(

logger?.debug('Successfully evaluated read interaction on contract.', {
contractTxId,
sortKey,

cacheKey: cacheKey.toString(),
});

return {
result,
input,
sortKey,
evaluationOptions,
};
}
Expand All @@ -475,12 +502,14 @@ export async function getContractReadInteraction({
logger,
functionName,
input,
sortKey,
}: {
contractTxId: string;
warp: Warp;
logger: winston.Logger;
functionName: string;
input: ParsedUrlQuery;
sortKey?: string | undefined;
}): Promise<{
result: any;

Check warning on line 514 in src/api/warp.ts

View workflow job for this annotation

GitHub Actions / build (lint:check)

Unexpected any. Specify a different type
evaluationOptions: Partial<EvaluationOptions>;
Expand All @@ -498,6 +527,7 @@ export async function getContractReadInteraction({
input,
warp,
evaluationOptions,
sortKey,
logger,
),
);
Expand Down
28 changes: 27 additions & 1 deletion src/routes/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,12 @@ export async function contractReservedHandler(ctx: KoaContext) {

const queryParamsCastedToNumbers = ['qty', 'years', 'height'];
export async function contractReadInteractionHandler(ctx: KoaContext) {
const { warp, logger: _logger } = ctx.state;
const {
warp,
logger: _logger,
sortKey: requestedSortKey,
blockHeight: requestedBlockHeight,
} = ctx.state;
const { contractTxId, functionName } = ctx.params;
const { query: input } = ctx.request;

Expand All @@ -550,6 +555,21 @@ export async function contractReadInteractionHandler(ctx: KoaContext) {
functionName,
});

let evaluateWithSortKey = requestedSortKey;
if (!requestedSortKey && requestedBlockHeight) {
const { sortKey } = await getContractState({
contractTxId,
warp,
logger,
blockHeight: requestedBlockHeight,
});
logger.info('Using sort key from block height', {
blockHeight: requestedBlockHeight,
sortKey,
});
evaluateWithSortKey = sortKey;
}

const parsedInput = Object.entries(input).reduce(
(parsedParams: { [x: string]: any }, [key, value]) => {

Check warning on line 574 in src/routes/contract.ts

View workflow job for this annotation

GitHub Actions / build (lint:check)

Unexpected any. Specify a different type
// parse known integer values for parameters we care about
Expand All @@ -561,6 +581,10 @@ export async function contractReadInteractionHandler(ctx: KoaContext) {
parsedParams[key] = +value;
return parsedParams;
}
// exclude sortKey and blockHeight from input as they are used to evaluate the contract state
if (key === 'sortKey' || key === 'blockHeight') {
return parsedParams;
}
parsedParams[key] = value;
return parsedParams;
},
Expand All @@ -571,13 +595,15 @@ export async function contractReadInteractionHandler(ctx: KoaContext) {
contractTxId,
warp,
logger,
sortKey: evaluateWithSortKey,
functionName,
input: parsedInput,
});

ctx.body = {
contractTxId,
result,
sortKey: evaluateWithSortKey,
evaluationOptions,
};
}
44 changes: 44 additions & 0 deletions tests/integration/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,50 @@ describe('Integration tests', () => {
});
});

describe('/:contractTxId/read/:readInteraction', () => {
it('should return the read interaction for the provided contract and read interaction id', async () => {
const { status, data } = await axios.get(
`/v1/contract/${id}/read/priceForInteraction`,
);
expect(status).to.equal(200);
expect(data).to.not.be.undefined;
const { contractTxId, result } = data;
expect(contractTxId).to.equal(id);
expect(result).not.to.be.undefined;
});

it('should return a 400 for an invalid read interaction id', async () => {
const { status } = await axios.get(
`/v1/contract/${id}/read/non-existent-read-api`,
);
expect(status).to.equal(400);
});

it('should properly evaluate state for a read interaction at a provided sortKey', async () => {
const { status, data } = await axios.get(
`/v1/contract/${id}/read/priceForInteraction?sortKey=${contractInteractions[0].sortKey}`,
);
expect(status).to.equal(200);
expect(data).to.not.be.undefined;
const { contractTxId, sortKey, result } = data;
expect(contractTxId).to.equal(id);
expect(result).not.to.be.undefined;
expect(sortKey).to.equal(contractInteractions[0].sortKey);
});

it('should properly evaluate state for a read interaction at a provided block height', async () => {
const { status, data } = await axios.get(
`/v1/contract/${id}/read/priceForInteraction?blockHeight=${contractInteractions[0].height}`,
);
expect(status).to.equal(200);
expect(data).to.not.be.undefined;
const { contractTxId, sortKey, result } = data;
expect(contractTxId).to.equal(id);
expect(result).not.to.be.undefined;
expect(sortKey).to.equal(contractInteractions[0].sortKey);
});
});

describe('/:contractTxId/state/:nestedPath', () => {
for (const nestedPath of [
'owner',
Expand Down
28 changes: 19 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3040,10 +3040,20 @@ arweave-stream-tx@^1.1.0:
dependencies:
exponential-backoff "^3.1.0"

[email protected], arweave@^1.10.13, arweave@^1.10.5, arweave@^1.11.4, arweave@^1.13.7:
version "1.13.7"
resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.13.7.tgz#cda8534c833baec372a7052c61f53b4e39a886d7"
integrity sha512-Hv+x2bSI6UyBHpuVbUDMMpMje1ETfpJWj52kKfz44O0IqDRi/LukOkkDUptup1p6OT6KP1/DdpnUnsNHoskFeA==
[email protected], arweave@^1.14.4:
version "1.14.4"
resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.14.4.tgz#5ba22136aa0e7fd9495258a3931fb770c9d6bf21"
integrity sha512-tmqU9fug8XAmFETYwgUhLaD3WKav5DaM4p1vgJpEj/Px2ORPPMikwnSySlFymmL2qgRh2ZBcZsg11+RXPPGLsA==
dependencies:
arconnect "^0.4.2"
asn1.js "^5.4.1"
base64-js "^1.5.1"
bignumber.js "^9.0.2"

arweave@^1.10.13, arweave@^1.10.5, arweave@^1.11.4, arweave@^1.13.7:
version "1.14.0"
resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.14.0.tgz#a4424455a4137b7708cdc627b5bda1881d6881b5"
integrity sha512-P2g9FjbJZQfk0Q3a5R2aCyPP3jen3ZN6Oxh6p6BlwEGHn5M5O0KvZSaiNV4X/PENgnZA4+afOf9MR3ySGcR3JQ==
dependencies:
arconnect "^0.4.2"
asn1.js "^5.4.1"
Expand Down Expand Up @@ -8223,13 +8233,13 @@ warp-contracts-sqlite@^1.0.2:
better-sqlite3 "^8.3.0"
safe-stable-stringify "^2.4.3"

warp-contracts@^1.4.32:
version "1.4.32"
resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.32.tgz#3d5c1ce973dde9d481618b1e060fac2de2870049"
integrity sha512-33OGr5xmarXFAfdmnJvAOrFLIT/Z6mp5BJEXw3dfaffM7mOGDsegWMDxNRoqWC2LFQFvvqnmuxq/FhEC6QRNgg==
warp-contracts@^1.4.37:
version "1.4.37"
resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.37.tgz#d22d34750bb50638188ca40d53015207688d1f58"
integrity sha512-jDQQF4FqRCy6OAnsb87d9LMzVp18eRTFyDQp9QirLD2C7IwBAH2DVrfpVcyPeZuxw4bNz3CSVemdMS+oFJIldQ==
dependencies:
archiver "^5.3.0"
arweave "1.13.7"
arweave "1.14.4"
async-mutex "^0.4.0"
bignumber.js "9.1.1"
events "3.3.0"
Expand Down

0 comments on commit fde46b8

Please sign in to comment.