Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(common-lib): derive dynamic field instead of call to fullnode #246

8 changes: 4 additions & 4 deletions portal/common/lib/bcs_data_parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

import { bcs, BcsType } from "@mysten/bcs";
import { fromHEX, toHEX, toB64 } from "@mysten/sui/utils";
import { fromHex, toHex, toBase64 } from "@mysten/sui/utils";
import { base64UrlSafeEncode } from "./url_safe_base64";
import { Range } from "./types";

const Address = bcs.bytes(32).transform({
input: (id: string) => fromHEX(id),
output: (id) => toHEX(id),
input: (id: string) => fromHex(id),
output: (id) => toHex(id),
});

// Blob IDs & hashes are represented on chain as u256, but serialized in URLs as URL-safe Base64.
Expand All @@ -21,7 +21,7 @@ const BLOB_ID = bcs.u256().transform({
// otherwise, it will mess up with the checksum results.
const DATA_HASH = bcs.u256().transform({
input: (id: string) => id,
output: (id) => toB64(bcs.u256().serialize(id).toBytes()),
output: (id) => toBase64(bcs.u256().serialize(id).toBytes()),
});

export const ResourcePathStruct = bcs.struct("ResourcePath", {
Expand Down
8 changes: 4 additions & 4 deletions portal/common/lib/objectId_operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, test } from 'vitest';
import { subdomainToObjectId, HEXtoBase36, Base36ToHEX } from './objectId_operations';
import { subdomainToObjectId, HEXtoBase36, Base36toHex } from './objectId_operations';

// Test cases for subdomainToObjectId
const subdomainToObjectIdTestCases: [string, string | null][] = [
Expand All @@ -21,22 +21,22 @@ describe('subdomainToObjectId', () => {
});
});

// Test cases for HEXtoBase36 and Base36ToHEX
// Test cases for HEXtoBase36 and Base36toHex
const HEXtoBase36TestCases: [string, string][] = [
["0x5ac988828a0c9842d91e6d5bdd9552ec9fcdddf11c56bf82dff6d5566685a31e",
"29gjzk8yjl1v7zm2etee1siyzaqfj9jaru5ufs6yyh1yqsgun2"], // Valid HEX to Base36
["0x01", "1"], // Minimal HEX to Base36
];

describe('HEXtoBase36 and Base36ToHEX', () => {
describe('HEXtoBase36 and Base36toHex', () => {
HEXtoBase36TestCases.forEach(([hexInput, base36Expected]) => {
test(`Converting HEX ${hexInput} to Base36 should return ${base36Expected}`, () => {
const result = HEXtoBase36(hexInput);
expect(result).toBe(base36Expected);
});

test(`Converting Base36 ${base36Expected} back to HEX should return ${hexInput}`, () => {
const result = Base36ToHEX(base36Expected);
const result = Base36toHex(base36Expected);
expect(result).toBe(hexInput);
});
});
Expand Down
15 changes: 10 additions & 5 deletions portal/common/lib/objectId_operations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { fromB64, fromHEX, isValidSuiObjectId, isValidSuiAddress, toHEX } from "@mysten/sui/utils";
import {
fromHex,
isValidSuiObjectId,
isValidSuiAddress,
toHex
} from "@mysten/sui/utils";
const baseX = require('base-x');

const BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz";
Expand All @@ -16,7 +21,7 @@ const b36 = baseX(BASE36);
*/
export function subdomainToObjectId(subdomain: string): string | null {
try{
const objectId = Base36ToHEX(subdomain.toLowerCase());
const objectId = Base36toHex(subdomain.toLowerCase());
console.log(
"obtained object id: ",
objectId,
Expand All @@ -31,9 +36,9 @@ export function subdomainToObjectId(subdomain: string): string | null {
}

export function HEXtoBase36(objectId: string): string {
return b36.encode(fromHEX(objectId.slice(2))).toLowerCase();
return b36.encode(fromHex(objectId.slice(2))).toLowerCase();
}

export function Base36ToHEX(objectId: string): string {
return "0x" + toHEX(b36.decode(objectId.toLowerCase()));
export function Base36toHex(objectId: string): string {
return "0x" + toHex(b36.decode(objectId.toLowerCase()));
}
72 changes: 34 additions & 38 deletions portal/common/lib/page_fetching.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { describe, bench, expect, vi, beforeAll, beforeEach, afterAll } from 'vi
import { fetchPage } from './page_fetching';
import { SuiClient, SuiObjectData } from '@mysten/sui/client';
import { sha256 } from './crypto';
import { toB64 } from '@mysten/bcs';
import { toBase64 } from '@mysten/bcs';
import { checkRedirect } from './redirects';
import { Resource } from './types';

Expand All @@ -16,11 +16,9 @@ let expectedHash: string;

const fetchMock = vi.fn();

const getDynamicFieldObject = vi.fn();
const getObject = vi.fn();

const mockClient = {
getDynamicFieldObject,
getObject,
} as unknown as SuiClient;

Expand All @@ -34,7 +32,7 @@ describe('Page fetching with mocked network calls', () => {

const decompressed = new Uint8Array(contentBuffer);
const hashArray = await sha256(decompressed);
expectedHash = toB64(hashArray);
expectedHash = toBase64(hashArray);

fetchMock.mockResolvedValue({
ok: true,
Expand Down Expand Up @@ -70,9 +68,7 @@ describe('Page fetching with mocked network calls', () => {

beforeEach(() => {
// Clear mocks.
getDynamicFieldObject.mockClear();
getObject.mockClear();

});

afterAll(() => {
Expand All @@ -81,19 +77,11 @@ describe('Page fetching with mocked network calls', () => {
vi.restoreAllMocks();
});

const landingPageObjectId = '0xLandingPage';
const flatlanderObjectId = '0xFlatlanderObject';
const landingPageObjectId = '0x1';
const flatlanderObjectId = '0x2';

// 1. Benchmark for normal page fetching.
bench('fetchPage: should successfully fetch the mocked landing page site', async () => {

getDynamicFieldObject.mockResolvedValueOnce({
data: {
objectId: '0xObjectId',
digest: 'mocked-digest',
},
});

bench.skip('fetchPage: should successfully fetch the mocked landing page site', async () => {
getObject.mockResolvedValueOnce({
data: {
bcs: {
Expand All @@ -108,27 +96,35 @@ describe('Page fetching with mocked network calls', () => {
});

// 2. Benchmark for page fetching with redirect.
bench('fetchPage: should successfully fetch a mocked page site using redirect', async () => {

getDynamicFieldObject.mockResolvedValueOnce(null);

(checkRedirect as any).mockResolvedValueOnce('0xRedirectId');

getDynamicFieldObject.mockResolvedValueOnce({
data: {
objectId: '0xFinalObjectId',
digest: 'mocked-digest',
},
});

getObject.mockResolvedValueOnce({
data: {
bcs: {
dataType: 'moveObject',
bcsBytes: 'mockBcsBytes',
},
} as SuiObjectData,
});
bench.skip('fetchPage: should successfully fetch a mocked page site using redirect',
async () => {
(checkRedirect as any)
.mockResolvedValueOnce('0x3')
.mockResolvedValueOnce(undefined);

getObject
.mockResolvedValueOnce({
data: {
bcs: {
dataType: 'moveObject',
bcsBytes: 'mockBcsBytes',
},
} as SuiObjectData,
}).mockResolvedValueOnce({
data: {
bcs: {
dataType: 'moveObject',
bcsBytes: 'mockBcsBytes',
},
} as SuiObjectData,
}).mockResolvedValueOnce({
data: {
bcs: {
dataType: 'moveObject',
bcsBytes: 'mockBcsBytes',
},
} as SuiObjectData,
});

const response = await fetchPage(mockClient, flatlanderObjectId, '/index.html');
expect(checkRedirect).toHaveBeenCalledWith(mockClient, flatlanderObjectId);
Expand Down
4 changes: 2 additions & 2 deletions portal/common/lib/page_fetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
generateHashErrorResponse,
} from "./http/http_error_responses";
import { aggregatorEndpoint } from "./aggregator";
import { toB64 } from "@mysten/bcs";
import { toBase64 } from "@mysten/bcs";
import { sha256 } from "./crypto";
import { getRoutes, matchPathToRoute } from "./routing";
import { HttpStatusCodes } from "./http/http_status_codes";
Expand Down Expand Up @@ -124,7 +124,7 @@ export async function fetchPage(
const body = await contents.arrayBuffer();
// Verify the integrity of the aggregator response by hashing
// the response contents.
const h10b = toB64(await sha256(body));
const h10b = toBase64(await sha256(body));
if (result.blob_hash != h10b) {
console.warn(
"[!] checksum mismatch [!] for:",
Expand Down
85 changes: 34 additions & 51 deletions portal/common/lib/resource.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,87 +4,70 @@
import { bench, describe, expect, vi, beforeEach } from 'vitest';
import { fetchResource } from './resource';
import { SuiClient, SuiObjectData } from '@mysten/sui/client';
import { Resource, isVersionedResource } from './types';
import { checkRedirect } from './redirects';
import { fromBase64 } from '@mysten/bcs';

// Mock SuiClient methods.
const getDynamicFieldObject = vi.fn();
const getObject = vi.fn();
const mockClient = {
getDynamicFieldObject,
getObject,
} as unknown as SuiClient;
// Mock `checkRedirect`.

// Mock checkRedirect
vi.mock('./redirects', () => ({
checkRedirect: vi.fn(),
}));
// Mock `bcs_data_parsing` to simulate parsing of the BCS data.
vi.mock('./bcs_data_parsing', () => ({
ResourcePathStruct: vi.fn(),
ResourceStruct: vi.fn(),
DynamicFieldStruct: vi.fn(() => ({
parse: vi.fn().mockReturnValue({
value: {
blob_id: '0xresourceBlobId',
path: '/index.html',
blob_hash: 'mockedBlobHash',
headers: new Map([
['Content-Type', 'text/html'],
['Content-Encoding', 'utf8'],
]),
} as Resource,
}),
})),
}));

// Mock fromBase64
vi.mock('@mysten/bcs', async () => {
const actual = await vi.importActual<typeof import('@mysten/bcs')>('@mysten/bcs');
return {
...actual,
fromBase64: vi.fn(),
};
});

vi.mock('./bcs_data_parsing', async (importOriginal) => {
const actual = await importOriginal() as typeof import('./bcs_data_parsing');
return {
...actual,
DynamicFieldStruct: vi.fn(() => ({
parse: vi.fn(() => ({ value: { blob_id: '0xresourceBlobId' } })),
})),
};
});

describe('Resource fetching with mocked network calls', () => {
const landingPageObjectId = '0xLandingPage';
const flatlanderObjectId = '0xFlatlanderObject';
const landingPageObjectId = '0x1';
const flatlanderObjectId = '0x2';

beforeEach(() => {
getDynamicFieldObject.mockClear();
getObject.mockClear();
(checkRedirect as any).mockClear();
});

// 1. Benchmark for a page like the landing page (without redirects).
bench('fetchResource: fetch the landing page site (no redirects)', async () => {
const resourcePath = '/index.html';
getDynamicFieldObject.mockResolvedValueOnce({
data: {
objectId: '0xObjectId',
digest: 'mocked-digest',
},
});

// Mock object response
getObject.mockResolvedValueOnce({
data: {
bcs: {
dataType: 'moveObject',
bcsBytes: 'mockBcsBytes',
},
} as SuiObjectData,
},
});

const resp = await fetchResource(mockClient, landingPageObjectId, resourcePath, new Set());
expect(isVersionedResource(resp)).toBeTruthy();
(fromBase64 as any).mockReturnValueOnce('decodedBcsBytes');
const resp = await fetchResource(mockClient, landingPageObjectId, '/index.html', new Set());
expect(resp).toBeDefined();
});

// 2. Benchmark for a page with redirects (such as accessing a Flatlander).
bench('fetchResource: fetch the flatlander site (with redirects)', async () => {
const resourcePath = '/index.html';

getDynamicFieldObject.mockResolvedValueOnce(null);

(checkRedirect as any).mockResolvedValueOnce('0xRedirectId');

// Found Display object.
getDynamicFieldObject.mockResolvedValueOnce({
data: {
objectId: '0xFinalObjectId',
digest: 'mocked-digest',
},
});
(checkRedirect as any)
.mockResolvedValueOnce('0x3')
.mockResolvedValueOnce(undefined);

// Redirecting to the flatlander display object.
getObject.mockResolvedValueOnce({
Expand All @@ -97,7 +80,7 @@ describe('Resource fetching with mocked network calls', () => {
});

const resp = await fetchResource(mockClient, flatlanderObjectId, resourcePath, new Set());
expect(isVersionedResource(resp)).toBeTruthy();
expect(checkRedirect).toHaveBeenCalledWith(mockClient, flatlanderObjectId);
expect(checkRedirect).toHaveBeenCalledTimes(2);
expect(resp).toBeDefined();
});
});
Loading
Loading