Skip to content

Commit ee0d5b2

Browse files
authored
Merge pull request #220 from proto-kit/feature/contract-compiling
Enable Proving: Contracts Compiling
2 parents a1af0d4 + 4d63b5a commit ee0d5b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+977
-392
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
AreProofsEnabled,
3+
CompileArtifact,
4+
MOCK_VERIFICATION_KEY,
5+
} from "../zkProgrammable/ZkProgrammable";
6+
import { isSubtypeOfName } from "../utils";
7+
import { TypedClass } from "../types";
8+
import { log } from "../log";
9+
10+
export type ArtifactRecord = Record<string, CompileArtifact>;
11+
12+
export type CompileTarget = {
13+
name: string;
14+
compile: () => Promise<CompileArtifact>;
15+
};
16+
17+
export class AtomicCompileHelper {
18+
public constructor(private readonly areProofsEnabled: AreProofsEnabled) {}
19+
20+
private compilationPromises: {
21+
[key: string]: Promise<CompileArtifact>;
22+
} = {};
23+
24+
public async compileContract(
25+
contract: CompileTarget,
26+
overrideProofsEnabled?: boolean
27+
): Promise<CompileArtifact> {
28+
let newPromise = false;
29+
const { name } = contract;
30+
if (this.compilationPromises[name] === undefined) {
31+
const proofsEnabled =
32+
overrideProofsEnabled ?? this.areProofsEnabled.areProofsEnabled;
33+
34+
// We only care about proofs enabled here if it's a contract, because
35+
// in all other cases, ZkProgrammable already handles this switch, and we
36+
// want to preserve the artifact layout (which might be more than one
37+
// entry for ZkProgrammables)
38+
if (
39+
proofsEnabled ||
40+
!isSubtypeOfName(
41+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
42+
contract as unknown as TypedClass<any>,
43+
"SmartContract"
44+
)
45+
) {
46+
log.time(`Compiling ${name}`);
47+
this.compilationPromises[name] = contract.compile();
48+
newPromise = true;
49+
} else {
50+
log.trace(`Compiling ${name} - mock`);
51+
this.compilationPromises[name] = Promise.resolve({
52+
verificationKey: MOCK_VERIFICATION_KEY,
53+
});
54+
}
55+
}
56+
const result = await this.compilationPromises[name];
57+
if (newPromise) {
58+
log.timeEnd.info(`Compiling ${name}`);
59+
}
60+
return result;
61+
}
62+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { CompileRegistry } from "./CompileRegistry";
2+
import type { ArtifactRecord } from "./AtomicCompileHelper";
3+
4+
export interface CompilableModule {
5+
compile(registry: CompileRegistry): Promise<ArtifactRecord | void>;
6+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { inject, injectable, singleton } from "tsyringe";
2+
3+
import { AreProofsEnabled } from "../zkProgrammable/ZkProgrammable";
4+
5+
import {
6+
ArtifactRecord,
7+
AtomicCompileHelper,
8+
CompileTarget,
9+
} from "./AtomicCompileHelper";
10+
11+
/**
12+
* The CompileRegistry compiles "compilable modules"
13+
* (i.e. zkprograms, contracts or contractmodules)
14+
* while making sure they don't get compiled twice in the same process in parallel.
15+
*/
16+
@injectable()
17+
@singleton()
18+
export class CompileRegistry {
19+
public constructor(
20+
@inject("AreProofsEnabled")
21+
private readonly areProofsEnabled: AreProofsEnabled
22+
) {
23+
this.compiler = new AtomicCompileHelper(this.areProofsEnabled);
24+
}
25+
26+
private compiler: AtomicCompileHelper;
27+
28+
private artifacts: ArtifactRecord = {};
29+
30+
// TODO Add possibility to force recompilation for non-sideloaded dependencies
31+
32+
public async compile(target: CompileTarget) {
33+
if (this.artifacts[target.name] === undefined) {
34+
const artifact = await this.compiler.compileContract(target);
35+
this.artifacts[target.name] = artifact;
36+
return artifact;
37+
}
38+
return this.artifacts[target.name];
39+
}
40+
41+
public getArtifact(name: string) {
42+
if (this.artifacts[name] === undefined) {
43+
throw new Error(
44+
`Artifact for ${name} not available, did you compile it via the CompileRegistry?`
45+
);
46+
}
47+
48+
return this.artifacts[name];
49+
}
50+
51+
public addArtifactsRaw(artifacts: ArtifactRecord) {
52+
this.artifacts = {
53+
...this.artifacts,
54+
...artifacts,
55+
};
56+
}
57+
58+
public getAllArtifacts() {
59+
return this.artifacts;
60+
}
61+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { injectable, Lifecycle, scoped } from "tsyringe";
2+
3+
import { CompileRegistry } from "../CompileRegistry";
4+
5+
@injectable()
6+
@scoped(Lifecycle.ContainerScoped)
7+
export class ChildVerificationKeyService {
8+
private compileRegistry?: CompileRegistry;
9+
10+
public setCompileRegistry(registry: CompileRegistry) {
11+
this.compileRegistry = registry;
12+
}
13+
14+
public getVerificationKey(name: string) {
15+
if (this.compileRegistry === undefined) {
16+
throw new Error("CompileRegistry hasn't been set yet");
17+
}
18+
const artifact = this.compileRegistry.getArtifact(name);
19+
if (artifact === undefined) {
20+
throw new Error(
21+
`Verification Key for child program ${name} not found in registry`
22+
);
23+
}
24+
return artifact.verificationKey;
25+
}
26+
}

packages/common/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ export * from "./trees/RollupMerkleTree";
1818
export * from "./events/EventEmitterProxy";
1919
export * from "./events/ReplayingSingleUseEventEmitter";
2020
export * from "./trees/MockAsyncMerkleStore";
21+
export * from "./compiling/AtomicCompileHelper";
22+
export * from "./compiling/CompileRegistry";
23+
export * from "./compiling/CompilableModule";
24+
export * from "./compiling/services/ChildVerificationKeyService";

packages/common/src/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// allows to reference interfaces as 'classes' rather than instances
2-
import { Bool, Field, PublicKey } from "o1js";
2+
import { Bool, DynamicProof, Field, Proof, ProofBase, PublicKey } from "o1js";
33

44
export type TypedClass<Class> = new (...args: any[]) => Class;
55

@@ -47,3 +47,12 @@ export const EMPTY_PUBLICKEY = PublicKey.fromObject({
4747
export type OverwriteObjectType<Base, New> = {
4848
[Key in keyof Base]: Key extends keyof New ? New[Key] : Base[Key];
4949
} & New;
50+
51+
export type InferProofBase<
52+
ProofType extends Proof<any, any> | DynamicProof<any, any>,
53+
> =
54+
ProofType extends Proof<infer PI, infer PO>
55+
? ProofBase<PI, PO>
56+
: ProofType extends DynamicProof<infer PI, infer PO>
57+
? ProofBase<PI, PO>
58+
: undefined;

packages/common/src/utils.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
Proof,
77
} from "o1js";
88

9+
import { TypedClass } from "./types";
10+
911
export function requireTrue(
1012
condition: boolean,
1113
errorOrFunction: Error | (() => Error)
@@ -56,7 +58,7 @@ export function reduceSequential<T, U>(
5658
array: T[]
5759
) => Promise<U>,
5860
initialValue: U
59-
) {
61+
): Promise<U> {
6062
return array.reduce<Promise<U>>(
6163
async (previousPromise, current, index, arr) => {
6264
const previous = await previousPromise;
@@ -164,3 +166,25 @@ type NonMethodKeys<Type> = {
164166
[Key in keyof Type]: Type[Key] extends Function ? never : Key;
165167
}[keyof Type];
166168
export type NonMethods<Type> = Pick<Type, NonMethodKeys<Type>>;
169+
170+
/**
171+
* Returns a boolean indicating whether a given class is a subclass of another class,
172+
* indicated by the name parameter.
173+
*/
174+
// TODO Change to class reference based comparisons
175+
export function isSubtypeOfName(
176+
clas: TypedClass<unknown>,
177+
name: string
178+
): boolean {
179+
if (clas.name === name) {
180+
return true;
181+
}
182+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
183+
return isSubtypeOfName(Object.getPrototypeOf(clas), name);
184+
}
185+
186+
// TODO Eventually, replace this by a schema validation library
187+
export function safeParseJson<T>(json: string) {
188+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
189+
return JSON.parse(json) as T;
190+
}

packages/common/src/zkProgrammable/ZkProgrammable.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Memoize } from "typescript-memoize";
33

44
import { log } from "../log";
55
import { dummyVerificationKey } from "../dummyVerificationKey";
6+
import { reduceSequential } from "../utils";
7+
import type { CompileRegistry } from "../compiling/CompileRegistry";
68

79
import { MOCK_PROOF } from "./provableMethod";
810

@@ -32,6 +34,7 @@ export interface Compile {
3234
}
3335

3436
export interface PlainZkProgram<PublicInput = undefined, PublicOutput = void> {
37+
name: string;
3538
compile: Compile;
3639
verify: Verify<PublicInput, PublicOutput>;
3740
Proof: ReturnType<
@@ -72,8 +75,6 @@ export function verifyToMockable<PublicInput, PublicOutput>(
7275
return verified;
7376
}
7477

75-
console.log("VerifyMocked");
76-
7778
return proof.proof === MOCK_PROOF;
7879
};
7980
}
@@ -125,6 +126,21 @@ export abstract class ZkProgrammable<
125126
};
126127
});
127128
}
129+
130+
public async compile(registry: CompileRegistry) {
131+
return await reduceSequential(
132+
this.zkProgram,
133+
async (acc, program) => {
134+
const result = await registry.compile(program);
135+
return {
136+
...acc,
137+
[program.name]: result,
138+
};
139+
},
140+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
141+
{} as Record<string, CompileArtifact>
142+
);
143+
}
128144
}
129145

130146
export interface WithZkProgrammable<

packages/module/src/runtime/Runtime.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument */
22
import { ZkProgram } from "o1js";
3-
import { DependencyContainer, injectable } from "tsyringe";
3+
import { container, DependencyContainer, injectable } from "tsyringe";
44
import {
55
StringKeyOf,
66
ModuleContainer,
@@ -11,11 +11,16 @@ import {
1111
PlainZkProgram,
1212
AreProofsEnabled,
1313
ChildContainerProvider,
14+
CompilableModule,
15+
CompileRegistry,
1416
} from "@proto-kit/common";
1517
import {
1618
MethodPublicOutput,
1719
StateServiceProvider,
1820
SimpleAsyncStateService,
21+
RuntimeMethodExecutionContext,
22+
RuntimeTransaction,
23+
NetworkState,
1924
} from "@proto-kit/protocol";
2025

2126
import {
@@ -227,9 +232,10 @@ export class RuntimeZkProgrammable<
227232
return buckets;
228233
};
229234

230-
return splitRuntimeMethods().map((bucket) => {
235+
return splitRuntimeMethods().map((bucket, index) => {
236+
const name = `RuntimeProgram-${index}`;
231237
const program = ZkProgram({
232-
name: "RuntimeProgram",
238+
name,
233239
publicOutput: MethodPublicOutput,
234240
methods: bucket,
235241
});
@@ -245,6 +251,7 @@ export class RuntimeZkProgrammable<
245251
);
246252

247253
return {
254+
name,
248255
compile: program.compile.bind(program),
249256
verify: program.verify.bind(program),
250257
analyzeMethods: program.analyzeMethods.bind(program),
@@ -262,7 +269,7 @@ export class RuntimeZkProgrammable<
262269
@injectable()
263270
export class Runtime<Modules extends RuntimeModulesRecord>
264271
extends ModuleContainer<Modules>
265-
implements RuntimeEnvironment
272+
implements RuntimeEnvironment, CompilableModule
266273
{
267274
public static from<Modules extends RuntimeModulesRecord>(
268275
definition: RuntimeDefinition<Modules>
@@ -375,5 +382,14 @@ export class Runtime<Modules extends RuntimeModulesRecord>
375382
public get runtimeModuleNames() {
376383
return Object.keys(this.definition.modules);
377384
}
385+
386+
public async compile(registry: CompileRegistry) {
387+
const context = container.resolve(RuntimeMethodExecutionContext);
388+
context.setup({
389+
transaction: RuntimeTransaction.dummyTransaction(),
390+
networkState: NetworkState.empty(),
391+
});
392+
return await this.zkProgrammable.compile(registry);
393+
}
378394
}
379395
/* eslint-enable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument */

packages/persistance/test-integration/SequencerRestart.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe("sequencer restart", () => {
3232
},
3333
});
3434

35-
await appChain.start(container.createChildContainer());
35+
await appChain.start(false, container.createChildContainer());
3636
};
3737

3838
const teardown = async () => {

0 commit comments

Comments
 (0)