Skip to content

Commit 4e049e6

Browse files
committed
port revoking mechanisms from #6
1 parent f735098 commit 4e049e6

File tree

4 files changed

+171
-14
lines changed

4 files changed

+171
-14
lines changed

src/library/plugin/fp/pluginLoader.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import env from 'env-var';
2020
import { existsSync } from 'fs';
2121
import fs from 'fs/promises';
2222
import { fromFailablePromise, liftZodParseResult } from '@utils/fp/TaskEither';
23+
import {
24+
IProofCache,
25+
IProofCacheProvider,
26+
toTsCheckCachedProof
27+
} from './proofCache';
2328

2429
export const configurationSchema = z.object({
2530
pluginDir: z.string().optional(),
@@ -102,7 +107,8 @@ const validatePluginCfg = (
102107

103108
const initializePlugin = (
104109
pluginModulePath: string,
105-
pluginCfg: unknown
110+
pluginCfg: unknown,
111+
proofCache: IProofCache
106112
): TaskEither<string, UntypedPluginInstance> =>
107113
pipe(
108114
TE.Do,
@@ -117,11 +123,14 @@ const initializePlugin = (
117123
typedPluginCfg
118124
}): TaskEither<string, UntypedPluginInstance> =>
119125
pluginFactory.__interface_tag === fpInterfaceTag
120-
? pluginFactory.initialize(typedPluginCfg)
126+
? pluginFactory.initialize(typedPluginCfg, proofCache.checkEachProof)
121127
: pluginFactory.__interface_tag === tsInterfaceTag
122128
? pipe(
123129
fromFailablePromise(() =>
124-
pluginFactory.initialize(typedPluginCfg)
130+
pluginFactory.initialize(
131+
typedPluginCfg,
132+
toTsCheckCachedProof(proofCache.checkEachProof)
133+
)
125134
),
126135
TE.map(
127136
(obj): UntypedPluginInstance => ({
@@ -140,7 +149,8 @@ const initializePlugin = (
140149
);
141150

142151
export const initializePlugins = (
143-
cfg: Configuration
152+
cfg: Configuration,
153+
proofCacheProvider: IProofCacheProvider
144154
): TaskEither<string, ActivePlugins> => {
145155
const resolvePluginModulePath =
146156
(name: string, optionalPath?: string) => () => {
@@ -162,9 +172,14 @@ export const initializePlugins = (
162172
}
163173
): TaskEither<string, UntypedPluginInstance> =>
164174
pipe(
165-
TE.fromIO(resolvePluginModulePath(pluginName, pluginCfg.path)),
166-
TE.chain((modulePath: string) =>
167-
initializePlugin(modulePath, pluginCfg.config ?? {})
175+
TE.Do,
176+
TE.bind('modulePath', () =>
177+
TE.fromIO(resolvePluginModulePath(pluginName, pluginCfg.path))
178+
),
179+
TE.tap(() => proofCacheProvider.initCacheFor(pluginName)),
180+
TE.bind('proofCache', () => proofCacheProvider.getCacheOf(pluginName)),
181+
TE.chain(({ modulePath, proofCache }) =>
182+
initializePlugin(modulePath, pluginCfg.config ?? {}, proofCache)
168183
)
169184
);
170185

src/library/plugin/fp/pluginType.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { RequestHandler } from 'express';
22
import { TaskEither } from 'fp-ts/lib/TaskEither';
33
import { JsonProof } from 'o1js';
44
import z from 'zod';
5+
import { CachedProof } from './proofCache';
56

67
// Interfaces used on the server side.
78

@@ -13,15 +14,22 @@ export const fpInterfaceTag: FpInterfaceType = 'fp';
1314
export type TsInterfaceType = 'ts';
1415
export const tsInterfaceTag: TsInterfaceType = 'ts';
1516

16-
export type RetType<
17+
export type ChooseType<
1718
InterfaceType extends InterfaceKind,
18-
T
19+
FpType,
20+
TsType
1921
> = InterfaceType extends FpInterfaceType
20-
? TaskEither<string, T>
22+
? FpType
2123
: InterfaceType extends TsInterfaceType
22-
? Promise<T>
24+
? TsType
2325
: never;
2426

27+
export type RetType<InterfaceType extends InterfaceKind, T> = ChooseType<
28+
InterfaceType,
29+
TaskEither<string, T>,
30+
Promise<T>
31+
>;
32+
2533
export interface WithInterfaceTag<IType extends InterfaceKind> {
2634
readonly __interface_tag: IType;
2735
}
@@ -76,7 +84,12 @@ export interface IMinAuthPluginFactory<
7684
> extends WithInterfaceTag<InterfaceType> {
7785
// Initialize the plugin given the configuration. The underlying zk program is
7886
// typically compiled here.
79-
initialize(cfg: Configuration): RetType<InterfaceType, PluginType>;
87+
initialize(
88+
cfg: Configuration,
89+
checkCacheProofs: (
90+
check: (p: CachedProof) => RetType<InterfaceType, boolean>
91+
) => RetType<InterfaceType, void>
92+
): RetType<InterfaceType, PluginType>;
8093

8194
readonly configurationSchema: z.ZodType<Configuration>;
8295
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { TaskEither } from 'fp-ts/TaskEither';
2+
import * as TE from 'fp-ts/TaskEither';
3+
import { JsonProof } from 'o1js';
4+
import { Option } from 'fp-ts/Option';
5+
import * as IOE from 'fp-ts/IOEither';
6+
import { pipe } from 'fp-ts/lib/function';
7+
import { createHash } from 'crypto';
8+
import * as O from 'fp-ts/Option';
9+
import * as R from 'fp-ts/Record';
10+
import { IORef, newIORef } from 'fp-ts/lib/IORef';
11+
import * as IO from 'fp-ts/IO';
12+
import { fromFailablePromise } from '@utils/fp/TaskEither';
13+
import * as E from 'fp-ts/Either';
14+
15+
export interface CachedProof {
16+
publicInputArgs: unknown;
17+
proof: JsonProof;
18+
}
19+
20+
export type ProofKey = string;
21+
22+
export type FpCheckCachedProofs = (
23+
check: (p: CachedProof) => TaskEither<string, boolean>
24+
) => TaskEither<string, void>;
25+
26+
export type TsCheckCachedProofs = (
27+
check: (p: CachedProof) => Promise<boolean>
28+
) => Promise<void>;
29+
30+
export interface IProofCache {
31+
storeProof(p: CachedProof): TaskEither<string, ProofKey>;
32+
getProof(k: ProofKey): TaskEither<string, Option<CachedProof>>;
33+
invalidateProof(k: ProofKey): TaskEither<string, void>;
34+
35+
checkEachProof: FpCheckCachedProofs;
36+
}
37+
38+
export const toTsCheckCachedProof: (
39+
_: FpCheckCachedProofs
40+
) => TsCheckCachedProofs =
41+
(f: FpCheckCachedProofs) =>
42+
(check: (p: CachedProof) => Promise<boolean>): Promise<void> =>
43+
f((p: CachedProof) => fromFailablePromise(() => check(p)))().then(
44+
E.match(
45+
(err) => Promise.reject(err),
46+
() => Promise.resolve()
47+
)
48+
);
49+
50+
export interface IProofCacheProvider {
51+
getCacheOf(plugin: string): TaskEither<string, IProofCache>;
52+
53+
initCacheFor(plugin: string): TaskEither<string, void>;
54+
}
55+
56+
class InMemoryProofCache implements IProofCache {
57+
private cache: IORef<Record<ProofKey, CachedProof>>;
58+
59+
constructor(cache: IORef<Record<ProofKey, CachedProof>>) {
60+
this.cache = cache;
61+
}
62+
63+
storeProof(proof: CachedProof): TaskEither<string, ProofKey> {
64+
return TE.fromIOEither(
65+
pipe(
66+
IOE.tryCatch(
67+
() =>
68+
createHash('sha256').update(JSON.stringify(proof)).digest('hex'),
69+
(reason) => `unable to hash the proof: ${reason}`
70+
),
71+
IOE.tapIO((hash) => () => this.cache.modify(R.upsertAt(hash, proof)))
72+
)
73+
);
74+
}
75+
76+
getProof(k: ProofKey): TaskEither<string, Option<CachedProof>> {
77+
return TE.fromIO(pipe(this.cache.read, IO.map(R.lookup(k))));
78+
}
79+
80+
invalidateProof(k: ProofKey): TaskEither<string, void> {
81+
return TE.fromIO(this.cache.modify(R.deleteAt(k)));
82+
}
83+
84+
checkEachProof(
85+
f: (p: CachedProof) => TaskEither<string, boolean>
86+
): TaskEither<string, void> {
87+
return pipe(
88+
TE.fromIO(this.cache.read),
89+
R.traverse(TE.ApplicativePar)((proof: CachedProof) =>
90+
pipe(
91+
f(proof),
92+
TE.map((keep) => (keep ? O.some(proof) : O.none))
93+
)
94+
),
95+
TE.map(R.compact),
96+
TE.chain((r) => TE.fromIO(this.cache.write(r)))
97+
);
98+
}
99+
}
100+
101+
export class InMemoryProofCacheProvider implements IProofCacheProvider {
102+
private store: Record<string, Record<ProofKey, CachedProof>> = {};
103+
104+
getCacheOf(plugin: string): TaskEither<string, IProofCache> {
105+
return pipe(
106+
TE.fromOption(() => `cache for plugin ${plugin} not initialized`)(
107+
R.lookup(plugin)(this.store)
108+
),
109+
TE.chain((r) => TE.fromIO(newIORef(r))),
110+
TE.chain((r) => TE.right(new InMemoryProofCache(r)))
111+
);
112+
}
113+
114+
initCacheFor(plugin: string): TaskEither<string, void> {
115+
return pipe(R.has(plugin, this.store), (hasCache) =>
116+
TE.fromIO(() => {
117+
if (!hasCache) this.store = R.upsertAt(plugin, {})(this.store);
118+
})
119+
);
120+
}
121+
}

src/library/plugin/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as expressCore from 'express-serve-static-core';
44
import * as fpUtils from './fp/utils';
55
import { JsonProof } from 'o1js';
66
import { launchTE } from '@utils/fp/TaskEither';
7+
import { IProofCacheProvider } from './fp/proofCache';
78

89
export * from './fp/pluginType';
910
export {
@@ -14,12 +15,19 @@ export {
1415
UntypedPluginModule,
1516
ActivePlugins
1617
} from './fp/pluginLoader';
18+
export {
19+
IProofCacheProvider,
20+
InMemoryProofCacheProvider
21+
} from './fp/proofCache';
1722

1823
export const readConfiguration = (cfgPath?: string): Promise<Configuration> =>
1924
launchTE(fpPluginLoader.readConfiguration(cfgPath));
2025

21-
export const initializePlugins = (cfg: Configuration): Promise<ActivePlugins> =>
22-
launchTE(fpPluginLoader.initializePlugins(cfg));
26+
export const initializePlugins = (
27+
cfg: Configuration,
28+
proofCacheProvider: IProofCacheProvider
29+
): Promise<ActivePlugins> =>
30+
launchTE(fpPluginLoader.initializePlugins(cfg, proofCacheProvider));
2331

2432
export const installCustomRoutes = (
2533
activePlugins: ActivePlugins,

0 commit comments

Comments
 (0)