Skip to content

Commit 8e61820

Browse files
committed
add import and metadata caching
1 parent f429bd7 commit 8e61820

File tree

6 files changed

+112
-10
lines changed

6 files changed

+112
-10
lines changed

packages/cli/src/cmds/runTests.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export async function executeTests(env: Environment, testRunArgs?: testRunArgs &
153153
.setTimeout(env.timeout || globalConfig.defaultTestTimeout)
154154
.setInclude(env.include || ["**/*{test,spec,test_,test-}*{ts,mts,cts}"])
155155
.addThreadConfig(env.multiThreads)
156+
.setCacheImports(env.cacheImports)
156157
.addVitestPassthroughArgs(env.vitestArgs)
157158
.build();
158159

@@ -311,6 +312,21 @@ class VitestOptionsBuilder {
311312
return this;
312313
}
313314

315+
setCacheImports(enabled?: boolean): this {
316+
if (enabled) {
317+
this.options.deps = {
318+
optimizer: {
319+
ssr: {
320+
enabled: true,
321+
include: ["viem", "ethers"],
322+
},
323+
web: { enabled: false },
324+
},
325+
};
326+
}
327+
return this;
328+
}
329+
314330
build(): UserConfig {
315331
return this.options;
316332
}

packages/cli/src/internal/commandParsers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ import { Effect } from "effect";
1313
import { standardRepos } from "../lib/repoDefinitions";
1414
import { shardManager } from "../lib/shardManager";
1515
import invariant from "tiny-invariant";
16-
import {
17-
StartupCacheService,
18-
StartupCacheServiceLive,
19-
} from "./effect/StartupCacheService.js";
16+
import { StartupCacheService, StartupCacheServiceLive } from "./effect/StartupCacheService.js";
2017

2118
const logger = createLogger({ name: "commandParsers" });
2219

@@ -225,6 +222,9 @@ export class LaunchCommandParser {
225222
logger.debug(`Using raw chain spec for ~10x faster startup: ${result.rawChainSpecPath}`);
226223
}
227224

225+
// Set cache directory env var for metadata caching in provider factories
226+
process.env.MOONWALL_CACHE_DIR = precompiledDir;
227+
228228
logger.debug(
229229
result.fromCache
230230
? `Using cached precompiled WASM: ${result.precompiledPath}`

packages/cli/src/internal/providerFactories.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,78 @@ import { privateKeyToAccount } from "viem/accounts";
2121
import { Web3 } from "web3";
2222
import { WebSocketProvider } from "web3-providers-ws";
2323
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
24+
import * as fs from "node:fs";
25+
import * as path from "node:path";
2426
const logger = createLogger({ name: "providers" });
2527
const debug = logger.debug.bind(logger);
2628

29+
/**
30+
* Load cached metadata if available, returns { genesisHash: metadataHex } or undefined
31+
*/
32+
const loadCachedMetadata = (): Record<string, `0x${string}`> | undefined => {
33+
const cacheDir = process.env.MOONWALL_CACHE_DIR;
34+
if (!cacheDir) return undefined;
35+
36+
const metadataPath = path.join(cacheDir, "metadata-cache.json");
37+
try {
38+
const data = fs.readFileSync(metadataPath, "utf-8");
39+
const cached = JSON.parse(data) as Record<string, `0x${string}`>;
40+
debug(`Loaded cached metadata for genesis: ${Object.keys(cached).join(", ")}`);
41+
return cached;
42+
} catch {
43+
return undefined;
44+
}
45+
};
46+
47+
/**
48+
* Save metadata to cache for future connections
49+
*/
50+
const saveCachedMetadata = (genesisHash: string, metadataHex: string): void => {
51+
const cacheDir = process.env.MOONWALL_CACHE_DIR;
52+
if (!cacheDir) return;
53+
54+
const metadataPath = path.join(cacheDir, "metadata-cache.json");
55+
const lockPath = `${metadataPath}.lock`;
56+
57+
try {
58+
// Simple lock to prevent concurrent writes
59+
try {
60+
fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL);
61+
} catch {
62+
// Another process is writing, skip
63+
return;
64+
}
65+
66+
const data = JSON.stringify({ [genesisHash]: metadataHex });
67+
fs.writeFileSync(metadataPath, data, "utf-8");
68+
debug(`Saved metadata cache for genesis: ${genesisHash}`);
69+
} catch (e) {
70+
debug(`Failed to save metadata cache: ${e}`);
71+
} finally {
72+
try {
73+
fs.unlinkSync(lockPath);
74+
} catch {
75+
/* ignore */
76+
}
77+
}
78+
};
79+
2780
export class ProviderFactory {
2881
private url: string;
2982
private privateKey: string;
3083

3184
constructor(private providerConfig: ProviderConfig) {
32-
this.url = providerConfig.endpoints.includes("ENV_VAR")
33-
? process.env.WSS_URL || "error_missing_WSS_URL_env_var"
34-
: providerConfig.endpoints[0];
35-
debug(
36-
`Constructor - providerConfig.endpoints[0]: ${providerConfig.endpoints[0]}, this.url: ${this.url}`
37-
);
85+
const endpoint = providerConfig.endpoints[0];
86+
// Support "AUTO" endpoint that uses dynamic MOONWALL_RPC_PORT
87+
if (endpoint === "AUTO" || endpoint.includes("ENV_VAR")) {
88+
this.url =
89+
endpoint === "AUTO"
90+
? vitestAutoUrl()
91+
: process.env.WSS_URL || "error_missing_WSS_URL_env_var";
92+
} else {
93+
this.url = endpoint;
94+
}
95+
debug(`Constructor - providerConfig.endpoints[0]: ${endpoint}, this.url: ${this.url}`);
3896
this.privateKey = process.env.MOON_PRIV_KEY || ALITH_PRIVATE_KEY;
3997
}
4098

@@ -63,6 +121,9 @@ export class ProviderFactory {
63121
name: this.providerConfig.name,
64122
type: this.providerConfig.type,
65123
connect: async () => {
124+
const cachedMetadata = loadCachedMetadata();
125+
const startTime = Date.now();
126+
66127
const options: ApiOptions = {
67128
provider: new WsProvider(this.url),
68129
initWasm: false,
@@ -72,10 +133,22 @@ export class ProviderFactory {
72133
typesBundle: this.providerConfig.additionalTypes
73134
? this.providerConfig.additionalTypes
74135
: undefined,
136+
metadata: cachedMetadata,
75137
};
76138

77139
const api = await ApiPromise.create(options);
78140
await api.isReady;
141+
142+
// Cache metadata for future connections if not already cached
143+
if (!cachedMetadata) {
144+
const genesisHash = api.genesisHash.toHex();
145+
const metadataHex = api.runtimeMetadata.toHex();
146+
saveCachedMetadata(genesisHash, metadataHex);
147+
debug(`PolkadotJs connected in ${Date.now() - startTime}ms (metadata fetched & cached)`);
148+
} else {
149+
debug(`PolkadotJs connected in ${Date.now() - startTime}ms (using cached metadata)`);
150+
}
151+
79152
return api;
80153
},
81154
ws: () => new WsProvider(this.url),

packages/types/config_schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,11 @@
619619
"items": {
620620
"description": "The environment configuration for testing.",
621621
"properties": {
622+
"cacheImports": {
623+
"default": false,
624+
"description": "Enable Vitest's dependency optimizer to pre-bundle imports (viem, ethers).\nThis can reduce test collection time by ~10% by caching bundled dependencies.",
625+
"type": "boolean"
626+
},
622627
"connections": {
623628
"description": "An optional array of ProviderConfig objects.",
624629
"items": {

packages/types/src/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ export type Environment = {
105105
*/
106106
multiThreads?: boolean | number | object;
107107

108+
/**
109+
* Enable Vitest's dependency optimizer to pre-bundle imports (viem, ethers).
110+
* This can reduce test collection time by ~10% by caching bundled dependencies.
111+
* @default false
112+
*/
113+
cacheImports?: boolean;
114+
108115
/**
109116
* Path to directory containing smart contracts for testing against.
110117
*/

test/moonwall.config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@
482482
"testFileDir": ["suites/test_separation"],
483483
"reporters": ["basic", "html"],
484484
"multiThreads": true,
485+
"cacheImports": true,
485486
"foundation": {
486487
"type": "dev",
487488
"launchSpec": [

0 commit comments

Comments
 (0)