From 8b50c29e141cb8342cdca8b99e84c77832591170 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 31 Oct 2024 15:30:12 +0100 Subject: [PATCH 1/7] Moved CompileRegistry to be consumed by contract modules --- .../src/zkProgrammable/ZkProgrammable.ts | 1 + packages/module/src/runtime/Runtime.ts | 6 +- packages/protocol/src/index.ts | 1 + .../protocol/src/prover/block/BlockProver.ts | 1 + .../statetransition/StateTransitionProver.ts | 1 + .../protocol/src/settlement/ContractModule.ts | 6 +- .../contracts/BridgeContractProtocolModule.ts | 5 +- .../DispatchContractProtocolModule.ts | 8 +- .../SettlementContractProtocolModule.ts | 9 ++- .../protocol/src/utils/CompileRegistry.ts | 79 +++++++++++++++++++ packages/sequencer/src/index.ts | 1 - .../production/helpers/CompileRegistry.ts | 62 --------------- .../production/tasks/BlockProvingTask.ts | 4 +- .../protocol/production/tasks/NewBlockTask.ts | 7 +- .../production/tasks/StateTransitionTask.ts | 4 +- .../settlement/tasks/SettlementProvingTask.ts | 10 +-- 16 files changed, 115 insertions(+), 90 deletions(-) create mode 100644 packages/protocol/src/utils/CompileRegistry.ts delete mode 100644 packages/sequencer/src/protocol/production/helpers/CompileRegistry.ts diff --git a/packages/common/src/zkProgrammable/ZkProgrammable.ts b/packages/common/src/zkProgrammable/ZkProgrammable.ts index 0b5f36fc8..afe65b5ee 100644 --- a/packages/common/src/zkProgrammable/ZkProgrammable.ts +++ b/packages/common/src/zkProgrammable/ZkProgrammable.ts @@ -32,6 +32,7 @@ export interface Compile { } export interface PlainZkProgram { + name: string; compile: Compile; verify: Verify; Proof: ReturnType< diff --git a/packages/module/src/runtime/Runtime.ts b/packages/module/src/runtime/Runtime.ts index 32d9050c6..cf7573e03 100644 --- a/packages/module/src/runtime/Runtime.ts +++ b/packages/module/src/runtime/Runtime.ts @@ -227,9 +227,10 @@ export class RuntimeZkProgrammable< return buckets; }; - return splitRuntimeMethods().map((bucket) => { + return splitRuntimeMethods().map((bucket, index) => { + const name = `RuntimeProgram-${index}`; const program = ZkProgram({ - name: "RuntimeProgram", + name, publicOutput: MethodPublicOutput, methods: bucket, }); @@ -245,6 +246,7 @@ export class RuntimeZkProgrammable< ); return { + name, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 93ed646f4..01ef39f85 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -17,6 +17,7 @@ export * from "./utils/MinaPrefixedProvableHashList"; export * from "./utils/ProvableReductionHashList"; export * from "./utils/StateTransitionReductionList"; export * from "./utils/utils"; +export * from "./utils/CompileRegistry"; export * from "./prover/block/BlockProver"; export * from "./prover/block/BlockProvable"; export * from "./prover/block/accummulators/RuntimeVerificationKeyTree"; diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 3432a5771..f76e637ac 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -872,6 +872,7 @@ export class BlockProverProgrammable extends ZkProgrammable< return [ { + name: program.name, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 2fcf143be..04bcc0874 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -134,6 +134,7 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< return [ { + name: program.name, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), diff --git a/packages/protocol/src/settlement/ContractModule.ts b/packages/protocol/src/settlement/ContractModule.ts index a1116b25c..c4372130f 100644 --- a/packages/protocol/src/settlement/ContractModule.ts +++ b/packages/protocol/src/settlement/ContractModule.ts @@ -6,6 +6,8 @@ import { } from "@proto-kit/common"; import { SmartContract } from "o1js"; +import type { CompileRegistry } from "../utils/CompileRegistry"; + export type SmartContractClassFromInterface = typeof SmartContract & TypedClass; @@ -23,5 +25,7 @@ export abstract class ContractModule< > extends ConfigurableModule { public abstract contractFactory(): SmartContractClassFromInterface; - public abstract compile(): Promise>; + public abstract compile( + registry: CompileRegistry + ): Promise>; } diff --git a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts index 0f76593a2..81ed92814 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts @@ -7,6 +7,7 @@ import { BridgeContractBase, BridgeContractType, } from "./BridgeContract"; +import { CompileRegistry } from "../../utils/CompileRegistry"; export type BridgeContractConfig = { withdrawalStatePath: `${string}.${string}`; @@ -34,8 +35,8 @@ export class BridgeContractProtocolModule extends ContractModule< return BridgeContract; } - public async compile() { - const bridgeVK = await BridgeContract.compile(); + public async compile(registry: CompileRegistry) { + const bridgeVK = await registry.compileSmartContract(BridgeContract); return { BridgeContract: bridgeVK, }; diff --git a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts index 32abac3b3..a653a5c4d 100644 --- a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts @@ -6,6 +6,7 @@ import { ContractModule, SmartContractClassFromInterface, } from "../ContractModule"; +import { CompileRegistry } from "../../utils/CompileRegistry"; import { DispatchSmartContract, @@ -43,8 +44,11 @@ export class DispatchContractProtocolModule extends ContractModule< return DispatchSmartContract; } - public async compile() { - const contractVk = await DispatchSmartContract.compile(); + public async compile(registry: CompileRegistry) { + const contractVk = await registry.compileSmartContract( + DispatchSmartContract + ); + return { DispatchSmartContract: contractVk, }; diff --git a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts index 3b4a7c175..d59269169 100644 --- a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts @@ -18,6 +18,7 @@ import { import { BridgeContractBase } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; import { BridgeContractProtocolModule } from "./BridgeContractProtocolModule"; +import { CompileRegistry } from "../../utils/CompileRegistry"; export type SettlementContractConfig = { escapeHatchSlotsInterval?: number; @@ -74,8 +75,12 @@ export class SettlementContractProtocolModule extends ContractModule< return SettlementSmartContract; } - public async compile(): Promise> { - const settlementVK = await SettlementSmartContract.compile(); + public async compile( + registry: CompileRegistry + ): Promise> { + const settlementVK = await registry.compileSmartContract( + SettlementSmartContract + ); return { SettlementContract: settlementVK, }; diff --git a/packages/protocol/src/utils/CompileRegistry.ts b/packages/protocol/src/utils/CompileRegistry.ts new file mode 100644 index 000000000..333ea6a7e --- /dev/null +++ b/packages/protocol/src/utils/CompileRegistry.ts @@ -0,0 +1,79 @@ +import { inject, injectable, singleton } from "tsyringe"; +import { + AreProofsEnabled, + CompileArtifact, + log, + mapSequential, + ZkProgrammable, +} from "@proto-kit/common"; + +/** + * The CompileRegistry compiles "compilable modules" + * (i.e. zkprograms, contracts or contractmodules) + * while making sure they don't get compiled twice in the same process in parallel. + */ +@injectable() +@singleton() +export class CompileRegistry { + public constructor( + @inject("AreProofsEnabled") + private readonly areProofsEnabled: AreProofsEnabled + ) {} + + private compilationPromises: { + [key: string]: Promise; + } = {}; + + // Use only the compile interface here, to avoid type issues + public async compile(zkProgram: { + compile: () => Promise; + name: string; + }) { + let newPromise = false; + const { name } = zkProgram; + if (this.compilationPromises[name] === undefined) { + log.time(`Compiling ${name}`); + this.compilationPromises[name] = zkProgram.compile(); + newPromise = true; + } + const result = await this.compilationPromises[name]; + if (newPromise) { + log.timeEnd.info(`Compiling ${name}`); + } + return result; + } + + // Generic params for zkProgrammable should be unknown, but verify makes those types invariant + public async compileZkProgrammable(zkProgrammable: ZkProgrammable) { + await mapSequential(zkProgrammable.zkProgram, (program) => + this.compile(program) + ); + } + + public async compileSmartContract( + contract: { + compile: () => Promise; + name: string; + }, + overrideProofsEnabled?: boolean + ) { + let newPromise = false; + const { name } = contract; + if (this.compilationPromises[name] === undefined) { + const proofsEnabled = + overrideProofsEnabled ?? this.areProofsEnabled.areProofsEnabled; + if (proofsEnabled) { + log.time(`Compiling ${name}`); + this.compilationPromises[name] = contract.compile(); + newPromise = true; + } else { + this.compilationPromises[name] = Promise.resolve(undefined); + } + } + const result = await this.compilationPromises[name]; + if (newPromise) { + log.timeEnd.info(`Compiling ${name}`); + } + return result; + } +} diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 2f9d45144..d06f4c8a2 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -22,7 +22,6 @@ export * from "./protocol/baselayer/NoopBaseLayer"; export * from "./protocol/production/helpers/UntypedOption"; export * from "./protocol/production/helpers/UntypedStateTransition"; export * from "./protocol/production/tasks/BlockProvingTask"; -export * from "./protocol/production/helpers/CompileRegistry"; export * from "./protocol/production/tasks/RuntimeProvingTask"; export * from "./protocol/production/tasks/RuntimeTaskParameters"; export * from "./protocol/production/tasks/StateTransitionTask"; diff --git a/packages/sequencer/src/protocol/production/helpers/CompileRegistry.ts b/packages/sequencer/src/protocol/production/helpers/CompileRegistry.ts deleted file mode 100644 index 3b21fd00e..000000000 --- a/packages/sequencer/src/protocol/production/helpers/CompileRegistry.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { injectable, singleton } from "tsyringe"; -import { CompileArtifact, log } from "@proto-kit/common"; - -export type ContractCompileArtifact = Record; - -/** - * The CompileRegistry compiles "compilable modules" - * (i.e. zkprograms, contracts or contractmodules) - * while making sure they don't get compiled twice in the same process in parallel. - */ -@injectable() -@singleton() -export class CompileRegistry { - private compilationPromises: { - [key: string]: Promise; - } = {}; - - // Use only the compile interface here, to avoid type issues - public async compile( - name: string, - zkProgram: { compile: () => Promise } - ) { - let newPromise = false; - if (this.compilationPromises[name] === undefined) { - log.time(`Compiling ${name}`); - this.compilationPromises[name] = zkProgram.compile(); - newPromise = true; - } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const result = (await this.compilationPromises[name]) as CompileArtifact; - if (newPromise) { - log.timeEnd.info(`Compiling ${name}`); - } - return result; - } - - public async compileSmartContract( - name: string, - contract: { - compile: () => Promise; - }, - proofsEnabled: boolean = true - ) { - let newPromise = false; - if (this.compilationPromises[name] === undefined) { - if (proofsEnabled) { - log.time(`Compiling ${name}`); - this.compilationPromises[name] = contract.compile(); - } else { - this.compilationPromises[name] = Promise.resolve({}); - } - } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const result = (await this.compilationPromises[ - name - ]) as ContractCompileArtifact; - if (newPromise) { - log.timeEnd.info(`Compiling ${name}`); - } - return result; - } -} diff --git a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts index 7b09c6fc7..5e995dd07 100644 --- a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts @@ -14,6 +14,7 @@ import { StateTransitionProvable, VKTreeWitness, DynamicRuntimeProof, + CompileRegistry, } from "@proto-kit/protocol"; import { Field, Proof } from "o1js"; import { Runtime } from "@proto-kit/module"; @@ -32,7 +33,6 @@ import { TaskWorkerModule } from "../../../worker/worker/TaskWorkerModule"; import { TaskStateRecord } from "../TransactionTraceService"; import { VerificationKeyService } from "../../runtime/RuntimeVerificationKeyService"; import { VerificationKeySerializer } from "../helpers/VerificationKeySerializer"; -import { CompileRegistry } from "../helpers/CompileRegistry"; import { JSONEncodableState } from "./RuntimeTaskParameters"; @@ -135,7 +135,6 @@ export class BlockReductionTask public async prepare(): Promise { await this.compileRegistry.compile( - "BlockProver", this.blockProver.zkProgrammable.zkProgram[0] ); } @@ -306,7 +305,6 @@ export class BlockProvingTask public async prepare(): Promise { // Compile await this.compileRegistry.compile( - "BlockProver", this.blockProver.zkProgrammable.zkProgram[0] ); } diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index d29638559..224906a7c 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -10,6 +10,7 @@ import { StateTransitionProvable, BlockHashMerkleTreeWitness, MandatoryProtocolModulesRecord, + CompileRegistry, } from "@proto-kit/protocol"; import { Proof } from "o1js"; import { ProvableMethodExecutionContext } from "@proto-kit/common"; @@ -20,7 +21,6 @@ import { PreFilledStateService } from "../../../state/prefilled/PreFilledStateSe import { TaskWorkerModule } from "../../../worker/worker/TaskWorkerModule"; import { PairingDerivedInput } from "../flow/ReductionTaskFlow"; import { TaskStateRecord } from "../TransactionTraceService"; -import { CompileRegistry } from "../helpers/CompileRegistry"; import { JSONEncodableState } from "./RuntimeTaskParameters"; import { DecodedStateSerializer } from "./BlockProvingTask"; @@ -187,9 +187,8 @@ export class NewBlockTask public async prepare(): Promise { // Compile - await this.compileRegistry.compile( - "BlockProver", - this.blockProver.zkProgrammable.zkProgram[0] + await this.compileRegistry.compileZkProgrammable( + this.blockProver.zkProgrammable ); } } diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index c024e84d8..acd98cee3 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -8,6 +8,7 @@ import { StateTransitionProvableBatch, StateTransitionProverPublicInput, StateTransitionProverPublicOutput, + CompileRegistry, } from "@proto-kit/protocol"; import { log, ProvableMethodExecutionContext } from "@proto-kit/common"; @@ -19,7 +20,6 @@ import { } from "../../../helpers/utils"; import { TaskWorkerModule } from "../../../worker/worker/TaskWorkerModule"; import { PreFilledWitnessProvider } from "../../../state/prefilled/PreFilledWitnessProvider"; -import { CompileRegistry } from "../helpers/CompileRegistry"; import { StateTransitionParametersSerializer, @@ -90,7 +90,6 @@ export class StateTransitionTask public async prepare(): Promise { await this.compileRegistry.compile( - "StateTransitionProver", this.stateTransitionProver.zkProgrammable.zkProgram[0] ); } @@ -144,7 +143,6 @@ export class StateTransitionReductionTask // eslint-disable-next-line sonarjs/no-identical-functions public async prepare(): Promise { await this.compileRegistry.compile( - "StateTransitionProver", this.stateTransitionProver.zkProgrammable.zkProgram[0] ); } diff --git a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts index 95cda66a3..b56118797 100644 --- a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts +++ b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts @@ -12,6 +12,7 @@ import { ReturnType, SettlementContractModule, Subclass, + CompileRegistry, } from "@proto-kit/protocol"; import { addCachedAccount, @@ -30,7 +31,6 @@ import { ProofTaskSerializer, DynamicProofTaskSerializer, } from "../../helpers/utils"; -import { CompileRegistry } from "../../protocol/production/helpers/CompileRegistry"; import { Task, TaskSerializer } from "../../worker/flow/Task"; import { TaskWorkerModule } from "../../worker/worker/TaskWorkerModule"; @@ -373,8 +373,6 @@ export class SettlementProvingTask return; } - const { areProofsEnabled } = this.areProofsEnabled; - const contractClasses: Record = {}; for (const key of this.settlementContractModule.moduleNames) { @@ -387,11 +385,7 @@ export class SettlementProvingTask contractClasses[key] = module.contractFactory(); // eslint-disable-next-line no-await-in-loop - await this.compileRegistry.compileSmartContract( - key, - module, - areProofsEnabled - ); + await module.compile(this.compileRegistry); } this.contractRegistry = new ContractRegistry(contractClasses); From 92f8420737db6138d01f4fefdd87090f831da18b Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 31 Oct 2024 20:55:58 +0100 Subject: [PATCH 2/7] Added new CompileRegistry with adaptive compiling and caching --- packages/common/src/utils.ts | 2 +- .../src/zkProgrammable/ZkProgrammable.ts | 16 ++++ .../src/compiling/AtomicCompileHelper.ts | 61 +++++++++++++ .../src/compiling/CompilableModule.ts | 6 ++ .../protocol/src/compiling/CompileRegistry.ts | 89 +++++++++++++++++++ packages/protocol/src/index.ts | 4 +- .../src/prover/block/BlockProvable.ts | 4 +- .../protocol/src/prover/block/BlockProver.ts | 19 +++- .../StateTransitionProvable.ts | 8 +- .../statetransition/StateTransitionProver.ts | 15 +++- .../protocol/src/settlement/ContractModule.ts | 15 ++-- .../contracts/BridgeContractProtocolModule.ts | 7 +- .../DispatchContractProtocolModule.ts | 11 +-- .../SettlementContractProtocolModule.ts | 31 +++++-- .../protocol/src/utils/CompileRegistry.ts | 79 ---------------- .../production/tasks/BlockProvingTask.ts | 9 +- .../production/tasks/CircuitCompilerTask.ts | 33 ++++--- .../protocol/production/tasks/NewBlockTask.ts | 4 +- .../production/tasks/StateTransitionTask.ts | 9 +- .../src/sequencer/SequencerStartupModule.ts | 10 ++- 20 files changed, 286 insertions(+), 146 deletions(-) create mode 100644 packages/protocol/src/compiling/AtomicCompileHelper.ts create mode 100644 packages/protocol/src/compiling/CompilableModule.ts create mode 100644 packages/protocol/src/compiling/CompileRegistry.ts delete mode 100644 packages/protocol/src/utils/CompileRegistry.ts diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index e79169ba7..35f5d64b9 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -56,7 +56,7 @@ export function reduceSequential( array: T[] ) => Promise, initialValue: U -) { +): Promise { return array.reduce>( async (previousPromise, current, index, arr) => { const previous = await previousPromise; diff --git a/packages/common/src/zkProgrammable/ZkProgrammable.ts b/packages/common/src/zkProgrammable/ZkProgrammable.ts index afe65b5ee..1dd683e9f 100644 --- a/packages/common/src/zkProgrammable/ZkProgrammable.ts +++ b/packages/common/src/zkProgrammable/ZkProgrammable.ts @@ -3,6 +3,7 @@ import { Memoize } from "typescript-memoize"; import { log } from "../log"; import { dummyVerificationKey } from "../dummyVerificationKey"; +import { reduceSequential } from "../utils"; import { MOCK_PROOF } from "./provableMethod"; @@ -126,6 +127,21 @@ export abstract class ZkProgrammable< }; }); } + + public async compile() { + return await reduceSequential( + this.zkProgram, + async (acc, program) => { + const result = await program.compile(); + return { + ...acc, + [program.name]: result, + }; + }, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + {} as Record + ); + } } export interface WithZkProgrammable< diff --git a/packages/protocol/src/compiling/AtomicCompileHelper.ts b/packages/protocol/src/compiling/AtomicCompileHelper.ts new file mode 100644 index 000000000..8facb9aac --- /dev/null +++ b/packages/protocol/src/compiling/AtomicCompileHelper.ts @@ -0,0 +1,61 @@ +import { + AreProofsEnabled, + log, + MOCK_VERIFICATION_KEY, +} from "@proto-kit/common"; + +export type Artifact = string | object | undefined; + +export type GenericCompileTarget = { + compile: () => Promise; +}; + +export class AtomicCompileHelper { + public constructor(private readonly areProofsEnabled: AreProofsEnabled) {} + + private compilationPromises: { + [key: string]: Promise; + } = {}; + + // Generic params for zkProgrammable should be unknown, but verify makes those types invariant + // public async zkProgrammable(zkProgrammable: ZkProgrammable) { + // await reduceSequential( + // zkProgrammable.zkProgram, + // async (acc, program) => { + // const res = await this.program(program); + // return { + // ...acc, + // [program.name]: res, + // }; + // }, + // {} + // ); + // } + + public async program( + name: string, + contract: GenericCompileTarget, + overrideProofsEnabled?: boolean + ): Promise { + let newPromise = false; + if (this.compilationPromises[name] === undefined) { + const proofsEnabled = + overrideProofsEnabled ?? this.areProofsEnabled.areProofsEnabled; + if (proofsEnabled) { + log.time(`Compiling ${name}`); + this.compilationPromises[name] = contract.compile(); + newPromise = true; + } else { + // TODO Mock VK here is not at all generalized and safe + // Better way would be to package the Smart contracts into a mock-compile as well + this.compilationPromises[name] = Promise.resolve(MOCK_VERIFICATION_KEY); + } + } + const result = await this.compilationPromises[name]; + if (newPromise) { + log.timeEnd.info(`Compiling ${name}`); + } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return result as ReturnArtifact; + } +} diff --git a/packages/protocol/src/compiling/CompilableModule.ts b/packages/protocol/src/compiling/CompilableModule.ts new file mode 100644 index 000000000..1459cb8e5 --- /dev/null +++ b/packages/protocol/src/compiling/CompilableModule.ts @@ -0,0 +1,6 @@ +import type { CompileRegistry } from "./CompileRegistry"; +import { Artifact } from "./AtomicCompileHelper"; + +export interface CompilableModule { + compile(registry: CompileRegistry): Promise; +} diff --git a/packages/protocol/src/compiling/CompileRegistry.ts b/packages/protocol/src/compiling/CompileRegistry.ts new file mode 100644 index 000000000..4a191d8b5 --- /dev/null +++ b/packages/protocol/src/compiling/CompileRegistry.ts @@ -0,0 +1,89 @@ +import { inject, injectable, singleton } from "tsyringe"; +import { + AreProofsEnabled, + mapSequential, + MOCK_VERIFICATION_KEY, +} from "@proto-kit/common"; + +import { + Artifact, + AtomicCompileHelper, + GenericCompileTarget, +} from "./AtomicCompileHelper"; +import { CompilableModule } from "./CompilableModule"; + +/** + * The CompileRegistry compiles "compilable modules" + * (i.e. zkprograms, contracts or contractmodules) + * while making sure they don't get compiled twice in the same process in parallel. + */ +@injectable() +@singleton() +export class CompileRegistry { + public constructor( + @inject("AreProofsEnabled") + private readonly areProofsEnabled: AreProofsEnabled + ) { + this.compile = new AtomicCompileHelper(this.areProofsEnabled); + } + + public compile: AtomicCompileHelper; + + private artifacts: Record = {}; + + public async compileModule( + // TODO Make name inferred by the module token + name: string, + compile: ( + registry: CompileRegistry, + ...args: unknown[] + ) => GenericCompileTarget, + dependencies: Record = {} + ): Promise { + const collectedArtifacts = await mapSequential( + Object.entries(dependencies), + async ([depName, dep]) => { + if (this.artifacts[depName] !== undefined) { + return this.artifacts[depName]; + } + const artifact = + (await dep.compile(this)) ?? "compiled-but-no-artifact"; + this.artifacts[depName] = artifact; + return artifact; + } + ); + + const target = compile(this, ...collectedArtifacts); + const artifact = await this.compile.program(name, target); + + this.artifacts[name] = artifact ?? "compiled-but-no-artifact"; + + return artifact; + } + + public getArtifact(name: string) { + if (this.artifacts[name] === undefined) { + throw new Error( + `Artifact for ${name} not available, did you compile it via the CompileRegistry?` + ); + } + if (!this.areProofsEnabled.areProofsEnabled) { + return MOCK_VERIFICATION_KEY; + } + + const artifact = this.artifacts[name]; + if (artifact === "compiled-but-no-artifact") { + throw new Error( + `Module ${name} didn't return the requested artifact even though proofs are enabled` + ); + } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return artifact as ArtifactType; + } + + public addArtifactsRaw(artifacts: Record) { + Object.entries(artifacts).forEach(([key, value]) => { + this.artifacts[key] = value; + }); + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 01ef39f85..d90737de8 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,3 +1,6 @@ +export * from "./compiling/AtomicCompileHelper"; +export * from "./compiling/CompileRegistry"; +export * from "./compiling/CompilableModule"; export * from "./hooks/AccountStateHook"; export * from "./hooks/BlockHeightHook"; export * from "./hooks/LastStateRootBlockHook"; @@ -17,7 +20,6 @@ export * from "./utils/MinaPrefixedProvableHashList"; export * from "./utils/ProvableReductionHashList"; export * from "./utils/StateTransitionReductionList"; export * from "./utils/utils"; -export * from "./utils/CompileRegistry"; export * from "./prover/block/BlockProver"; export * from "./prover/block/BlockProvable"; export * from "./prover/block/accummulators/RuntimeVerificationKeyTree"; diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index 74c88a346..ee4272418 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -16,6 +16,7 @@ import { NetworkState } from "../../model/network/NetworkState"; import { BlockHashMerkleTreeWitness } from "./accummulators/BlockHashMerkleTree"; import { RuntimeVerificationKeyAttestation } from "./accummulators/RuntimeVerificationKeyTree"; +import { CompilableModule } from "../../compiling/CompilableModule"; export class BlockProverPublicInput extends Struct({ transactionsHash: Field, @@ -77,7 +78,8 @@ export class DynamicRuntimeProof extends DynamicProof< } export interface BlockProvable - extends WithZkProgrammable { + extends WithZkProgrammable, + CompilableModule { proveTransaction: ( publicInput: BlockProverPublicInput, stateProof: StateTransitionProof, diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index f76e637ac..709ac31cc 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -12,6 +12,7 @@ import { import { container, inject, injectable, injectAll } from "tsyringe"; import { AreProofsEnabled, + CompileArtifact, PlainZkProgram, provableMethod, WithZkProgrammable, @@ -41,6 +42,7 @@ import { MinaActionsHashList, } from "../../utils/MinaPrefixedProvableHashList"; import { StateTransitionReductionList } from "../../utils/StateTransitionReductionList"; +import { CompileRegistry } from "../../compiling/CompileRegistry"; import { BlockProvable, @@ -60,6 +62,7 @@ import { RuntimeVerificationKeyAttestation, } from "./accummulators/RuntimeVerificationKeyTree"; import { RuntimeVerificationKeyRootService } from "./services/RuntimeVerificationKeyRootService"; +import { CompilableModule } from "../../compiling/CompilableModule"; const errors = { stateProofNotStartingAtZero: () => @@ -149,6 +152,8 @@ export class BlockProverProgrammable extends ZkProgrammable< super(); } + name = "BlockProver"; + public get areProofsEnabled(): AreProofsEnabled | undefined { return this.prover.areProofsEnabled; } @@ -889,7 +894,10 @@ export class BlockProverProgrammable extends ZkProgrammable< * then be merged to be committed to the base-layer contract */ @injectable() -export class BlockProver extends ProtocolModule implements BlockProvable { +export class BlockProver + extends ProtocolModule + implements BlockProvable, CompilableModule +{ public zkProgrammable: BlockProverProgrammable; public constructor( @@ -917,6 +925,15 @@ export class BlockProver extends ProtocolModule implements BlockProvable { ); } + public async compile( + registry: CompileRegistry + ): Promise | undefined> { + return await registry.compileModule( + "BlockProver", + () => this.zkProgrammable + ); + } + public proveTransaction( publicInput: BlockProverPublicInput, stateProof: StateTransitionProof, diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts index d6fc39c23..c3a9773a6 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts @@ -4,6 +4,7 @@ import { WithZkProgrammable } from "@proto-kit/common"; import { StateTransitionProvableBatch } from "../../model/StateTransitionProvableBatch"; import { StateTransitionWitnessProviderReference } from "./StateTransitionWitnessProviderReference"; +import { CompilableModule } from "../../compiling/CompilableModule"; export class StateTransitionProverPublicInput extends Struct({ stateTransitionsHash: Field, @@ -26,9 +27,10 @@ export type StateTransitionProof = Proof< export interface StateTransitionProvable extends WithZkProgrammable< - StateTransitionProverPublicInput, - StateTransitionProverPublicOutput - > { + StateTransitionProverPublicInput, + StateTransitionProverPublicOutput + >, + CompilableModule { witnessProviderReference: StateTransitionWitnessProviderReference; runBatch: ( diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 04bcc0874..cc032774c 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -29,6 +29,9 @@ import { } from "./StateTransitionProvable"; import { StateTransitionWitnessProvider } from "./StateTransitionWitnessProvider"; import { StateTransitionWitnessProviderReference } from "./StateTransitionWitnessProviderReference"; +import { CompilableModule } from "../../compiling/CompilableModule"; +import { Artifact } from "../../compiling/AtomicCompileHelper"; +import { CompileRegistry } from "../../compiling/CompileRegistry"; const errors = { propertyNotMatching: (property: string, step: string) => @@ -346,7 +349,10 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< @injectable() export class StateTransitionProver extends ProtocolModule - implements StateTransitionProvable, StateTransitionProverType + implements + StateTransitionProvable, + StateTransitionProverType, + CompilableModule { public zkProgrammable: StateTransitionProverProgrammable; @@ -361,6 +367,13 @@ export class StateTransitionProver ); } + public compile(registry: CompileRegistry): Promise { + return registry.compileModule( + "StateTransitionProver", + () => this.zkProgrammable + ); + } + public runBatch( publicInput: StateTransitionProverPublicInput, batch: StateTransitionProvableBatch diff --git a/packages/protocol/src/settlement/ContractModule.ts b/packages/protocol/src/settlement/ContractModule.ts index c4372130f..3f5d2d536 100644 --- a/packages/protocol/src/settlement/ContractModule.ts +++ b/packages/protocol/src/settlement/ContractModule.ts @@ -5,8 +5,9 @@ import { TypedClass, } from "@proto-kit/common"; import { SmartContract } from "o1js"; - -import type { CompileRegistry } from "../utils/CompileRegistry"; +import { CompilableModule } from "../compiling/CompilableModule"; +import type { CompileRegistry } from "../compiling/CompileRegistry"; +import { Artifact } from "../compiling/AtomicCompileHelper"; export type SmartContractClassFromInterface = typeof SmartContract & TypedClass; @@ -19,13 +20,13 @@ export type SmartContractClassFromInterface = typeof SmartContract & * of SmartContract and implements a certain interface as specified by the * ContractType generic. */ -export abstract class ContractModule< - ContractType, - Config = NoConfig, -> extends ConfigurableModule { +export abstract class ContractModule + extends ConfigurableModule + implements CompilableModule +{ public abstract contractFactory(): SmartContractClassFromInterface; public abstract compile( registry: CompileRegistry - ): Promise>; + ): Promise; } diff --git a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts index 81ed92814..3bfdc6b95 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts @@ -1,13 +1,13 @@ import { injectable } from "tsyringe"; import { ContractModule } from "../ContractModule"; +import { CompileRegistry } from "../../compiling/CompileRegistry"; import { BridgeContract, BridgeContractBase, BridgeContractType, } from "./BridgeContract"; -import { CompileRegistry } from "../../utils/CompileRegistry"; export type BridgeContractConfig = { withdrawalStatePath: `${string}.${string}`; @@ -36,9 +36,6 @@ export class BridgeContractProtocolModule extends ContractModule< } public async compile(registry: CompileRegistry) { - const bridgeVK = await registry.compileSmartContract(BridgeContract); - return { - BridgeContract: bridgeVK, - }; + return await registry.compileModule("BridgeContract", () => BridgeContract); } } diff --git a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts index a653a5c4d..ee58efc5d 100644 --- a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts @@ -6,7 +6,7 @@ import { ContractModule, SmartContractClassFromInterface, } from "../ContractModule"; -import { CompileRegistry } from "../../utils/CompileRegistry"; +import { CompileRegistry } from "../../compiling/CompileRegistry"; import { DispatchSmartContract, @@ -45,12 +45,9 @@ export class DispatchContractProtocolModule extends ContractModule< } public async compile(registry: CompileRegistry) { - const contractVk = await registry.compileSmartContract( - DispatchSmartContract + return await registry.compileModule( + "DispatchSmartContract", + () => DispatchSmartContract ); - - return { - DispatchSmartContract: contractVk, - }; } } diff --git a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts index d59269169..9556aacc7 100644 --- a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts @@ -1,5 +1,5 @@ -import { CompileArtifact } from "@proto-kit/common"; import { inject, injectable, injectAll } from "tsyringe"; +import { VerificationKey } from "o1js"; import { BlockProvable } from "../../prover/block/BlockProvable"; import { @@ -7,6 +7,8 @@ import { SmartContractClassFromInterface, } from "../ContractModule"; import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; +import { CompileRegistry } from "../../compiling/CompileRegistry"; +import { Artifact } from "../../compiling/AtomicCompileHelper"; import { DispatchSmartContractBase } from "./DispatchSmartContract"; import { @@ -18,7 +20,6 @@ import { import { BridgeContractBase } from "./BridgeContract"; import { DispatchContractProtocolModule } from "./DispatchContractProtocolModule"; import { BridgeContractProtocolModule } from "./BridgeContractProtocolModule"; -import { CompileRegistry } from "../../utils/CompileRegistry"; export type SettlementContractConfig = { escapeHatchSlotsInterval?: number; @@ -36,7 +37,7 @@ export class SettlementContractProtocolModule extends ContractModule< @injectAll("ProvableSettlementHook") private readonly hooks: ProvableSettlementHook[], @inject("BlockProver") - blockProver: BlockProvable, + private readonly blockProver: BlockProvable, @inject("DispatchContract") private readonly dispatchContractModule: DispatchContractProtocolModule, @inject("BridgeContract") @@ -77,12 +78,24 @@ export class SettlementContractProtocolModule extends ContractModule< public async compile( registry: CompileRegistry - ): Promise> { - const settlementVK = await registry.compileSmartContract( - SettlementSmartContract + ): Promise { + return await registry.compileModule( + "SettlementContract", + ( + registry2: CompileRegistry, + bridgeVk: unknown, + blockProverVk: unknown + ) => { + SettlementSmartContractBase.args.BridgeContractVerificationKey = + // TODO Infer type + bridgeVk as VerificationKey; + + return SettlementSmartContract; + }, + { + BridgeContract: this.bridgeContractModule, + BlockProver: this.blockProver.zkProgrammable, + } ); - return { - SettlementContract: settlementVK, - }; } } diff --git a/packages/protocol/src/utils/CompileRegistry.ts b/packages/protocol/src/utils/CompileRegistry.ts deleted file mode 100644 index 333ea6a7e..000000000 --- a/packages/protocol/src/utils/CompileRegistry.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { inject, injectable, singleton } from "tsyringe"; -import { - AreProofsEnabled, - CompileArtifact, - log, - mapSequential, - ZkProgrammable, -} from "@proto-kit/common"; - -/** - * The CompileRegistry compiles "compilable modules" - * (i.e. zkprograms, contracts or contractmodules) - * while making sure they don't get compiled twice in the same process in parallel. - */ -@injectable() -@singleton() -export class CompileRegistry { - public constructor( - @inject("AreProofsEnabled") - private readonly areProofsEnabled: AreProofsEnabled - ) {} - - private compilationPromises: { - [key: string]: Promise; - } = {}; - - // Use only the compile interface here, to avoid type issues - public async compile(zkProgram: { - compile: () => Promise; - name: string; - }) { - let newPromise = false; - const { name } = zkProgram; - if (this.compilationPromises[name] === undefined) { - log.time(`Compiling ${name}`); - this.compilationPromises[name] = zkProgram.compile(); - newPromise = true; - } - const result = await this.compilationPromises[name]; - if (newPromise) { - log.timeEnd.info(`Compiling ${name}`); - } - return result; - } - - // Generic params for zkProgrammable should be unknown, but verify makes those types invariant - public async compileZkProgrammable(zkProgrammable: ZkProgrammable) { - await mapSequential(zkProgrammable.zkProgram, (program) => - this.compile(program) - ); - } - - public async compileSmartContract( - contract: { - compile: () => Promise; - name: string; - }, - overrideProofsEnabled?: boolean - ) { - let newPromise = false; - const { name } = contract; - if (this.compilationPromises[name] === undefined) { - const proofsEnabled = - overrideProofsEnabled ?? this.areProofsEnabled.areProofsEnabled; - if (proofsEnabled) { - log.time(`Compiling ${name}`); - this.compilationPromises[name] = contract.compile(); - newPromise = true; - } else { - this.compilationPromises[name] = Promise.resolve(undefined); - } - } - const result = await this.compilationPromises[name]; - if (newPromise) { - log.timeEnd.info(`Compiling ${name}`); - } - return result; - } -} diff --git a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts index 5e995dd07..f27ebd36a 100644 --- a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts @@ -134,9 +134,7 @@ export class BlockReductionTask } public async prepare(): Promise { - await this.compileRegistry.compile( - this.blockProver.zkProgrammable.zkProgram[0] - ); + await this.blockProver.compile(this.compileRegistry); } } @@ -301,11 +299,8 @@ export class BlockProvingTask ); } - // eslint-disable-next-line sonarjs/no-identical-functions public async prepare(): Promise { // Compile - await this.compileRegistry.compile( - this.blockProver.zkProgrammable.zkProgram[0] - ); + await this.blockProver.compile(this.compileRegistry); } } diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index d63a9e083..8669ecf97 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -9,6 +9,8 @@ import { RuntimeMethodExecutionContext, RuntimeTransaction, SettlementContractModule, + Artifact, + CompileRegistry, } from "@proto-kit/protocol"; import { TaskSerializer } from "../../../worker/flow/Task"; @@ -21,15 +23,19 @@ export type CompiledCircuitsRecord = { runtimeCircuits: VKRecord; }; +export type CompilerTaskParams = { + existingArtifacts: Record; +}; + type VKRecordLite = Record; -export class UndefinedSerializer implements TaskSerializer { - public toJSON(parameters: undefined): string { - return ""; +export class SimpleJSONSerializer implements TaskSerializer { + public toJSON(parameters: Type): string { + return JSON.stringify(parameters); } - public fromJSON(json: string): undefined { - return undefined; + public fromJSON(json: string): Type { + return JSON.parse(json) as Type; } } @@ -64,20 +70,21 @@ export class VKResultSerializer { @injectable() @scoped(Lifecycle.ContainerScoped) export class CircuitCompilerTask extends UnpreparingTask< - undefined, + CompilerTaskParams, CompiledCircuitsRecord > { public name = "compiledCircuit"; public constructor( @inject("Runtime") protected readonly runtime: Runtime, - @inject("Protocol") protected readonly protocol: Protocol + @inject("Protocol") protected readonly protocol: Protocol, + private readonly compileRegistry: CompileRegistry ) { super(); } - public inputSerializer(): TaskSerializer { - return new UndefinedSerializer(); + public inputSerializer(): TaskSerializer { + return new SimpleJSONSerializer(); } public resultSerializer(): TaskSerializer { @@ -140,7 +147,7 @@ export class CircuitCompilerTask extends UnpreparingTask< public async compileProtocolCircuits(): Promise< [string, VerificationKey][][] > { - // We only care about the BridgeContract for now - later with cachine, + // We only care about the BridgeContract for now - later with caching, // we might want to expand that to all protocol circuits const container = this.protocol.dependencyContainer; if (container.isRegistered("SettlementContractModule")) { @@ -167,7 +174,11 @@ export class CircuitCompilerTask extends UnpreparingTask< }, {}); } - public async compute(): Promise { + public async compute( + input: CompilerTaskParams + ): Promise { + this.compileRegistry.addArtifactsRaw(input.existingArtifacts); + log.info("Computing VKs"); const runtimeTuples = await this.compileRuntimeMethods(); diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 224906a7c..3f0693110 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -187,8 +187,6 @@ export class NewBlockTask public async prepare(): Promise { // Compile - await this.compileRegistry.compileZkProgrammable( - this.blockProver.zkProgrammable - ); + await this.blockProver.compile(this.compileRegistry); } } diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index acd98cee3..757b8b0f7 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -89,9 +89,7 @@ export class StateTransitionTask } public async prepare(): Promise { - await this.compileRegistry.compile( - this.stateTransitionProver.zkProgrammable.zkProgram[0] - ); + await this.stateTransitionProver.compile(this.compileRegistry); } } @@ -140,10 +138,7 @@ export class StateTransitionReductionTask .result.prove(); } - // eslint-disable-next-line sonarjs/no-identical-functions public async prepare(): Promise { - await this.compileRegistry.compile( - this.stateTransitionProver.zkProgrammable.zkProgram[0] - ); + await this.stateTransitionProver.compile(this.compileRegistry); } } diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 402e12ad4..978ddc3d6 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -37,9 +37,13 @@ export class SequencerStartupModule extends SequencerModule { const vks = await flow.withFlow( async (res, rej) => { - await flow.pushTask(this.compileTask, undefined, async (result) => { - res(result); - }); + await flow.pushTask( + this.compileTask, + { existingArtifacts: {} }, + async (result) => { + res(result); + } + ); } ); From a45933fd64979e8216d6d7e9ee846a63d0a328a4 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 31 Oct 2024 23:08:08 +0100 Subject: [PATCH 3/7] Fixed new CompileRegistry to work --- packages/module/src/runtime/Runtime.ts | 20 +- .../src/compiling/AtomicCompileHelper.ts | 46 ++--- .../src/compiling/CompilableModule.ts | 4 +- .../protocol/src/compiling/CompileRegistry.ts | 67 ++++--- .../protocol/src/prover/block/BlockProver.ts | 3 +- .../statetransition/StateTransitionProver.ts | 7 +- .../protocol/src/settlement/ContractModule.ts | 4 +- .../contracts/BridgeContractProtocolModule.ts | 4 +- .../DispatchContractProtocolModule.ts | 9 +- .../SettlementContractProtocolModule.ts | 19 +- .../production/tasks/CircuitCompilerTask.ts | 175 ++++++------------ .../production/tasks/RuntimeProvingTask.ts | 9 +- .../runtime/RuntimeVerificationKeyService.ts | 42 ++++- .../src/sequencer/SequencerStartupModule.ts | 34 ++-- .../src/worker/worker/WorkerReadyModule.ts | 6 +- 15 files changed, 216 insertions(+), 233 deletions(-) diff --git a/packages/module/src/runtime/Runtime.ts b/packages/module/src/runtime/Runtime.ts index cf7573e03..6fa28bd6f 100644 --- a/packages/module/src/runtime/Runtime.ts +++ b/packages/module/src/runtime/Runtime.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument */ import { ZkProgram } from "o1js"; -import { DependencyContainer, injectable } from "tsyringe"; +import { container, DependencyContainer, injectable } from "tsyringe"; import { StringKeyOf, ModuleContainer, @@ -16,6 +16,11 @@ import { MethodPublicOutput, StateServiceProvider, SimpleAsyncStateService, + CompilableModule, + CompileRegistry, + RuntimeMethodExecutionContext, + RuntimeTransaction, + NetworkState, } from "@proto-kit/protocol"; import { @@ -264,7 +269,7 @@ export class RuntimeZkProgrammable< @injectable() export class Runtime extends ModuleContainer - implements RuntimeEnvironment + implements RuntimeEnvironment, CompilableModule { public static from( definition: RuntimeDefinition @@ -377,5 +382,16 @@ export class Runtime public get runtimeModuleNames() { return Object.keys(this.definition.modules); } + + public async compile(registry: CompileRegistry) { + return await registry.compileModule(async () => { + const context = container.resolve(RuntimeMethodExecutionContext); + context.setup({ + transaction: RuntimeTransaction.dummyTransaction(), + networkState: NetworkState.empty(), + }); + return await this.zkProgrammable.compile(); + }); + } } /* eslint-enable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument */ diff --git a/packages/protocol/src/compiling/AtomicCompileHelper.ts b/packages/protocol/src/compiling/AtomicCompileHelper.ts index 8facb9aac..c4dbf6cb8 100644 --- a/packages/protocol/src/compiling/AtomicCompileHelper.ts +++ b/packages/protocol/src/compiling/AtomicCompileHelper.ts @@ -1,61 +1,49 @@ import { AreProofsEnabled, + CompileArtifact, log, MOCK_VERIFICATION_KEY, } from "@proto-kit/common"; +import { SmartContract, VerificationKey } from "o1js"; -export type Artifact = string | object | undefined; +export type ArtifactRecord = Record; -export type GenericCompileTarget = { - compile: () => Promise; +export type CompileTarget = { + name: string; + compile: () => Promise; }; export class AtomicCompileHelper { public constructor(private readonly areProofsEnabled: AreProofsEnabled) {} private compilationPromises: { - [key: string]: Promise; + [key: string]: Promise; } = {}; - // Generic params for zkProgrammable should be unknown, but verify makes those types invariant - // public async zkProgrammable(zkProgrammable: ZkProgrammable) { - // await reduceSequential( - // zkProgrammable.zkProgram, - // async (acc, program) => { - // const res = await this.program(program); - // return { - // ...acc, - // [program.name]: res, - // }; - // }, - // {} - // ); - // } - - public async program( - name: string, - contract: GenericCompileTarget, + public async compileContract( + contract: CompileTarget, overrideProofsEnabled?: boolean - ): Promise { + ): Promise { let newPromise = false; + const { name } = contract; if (this.compilationPromises[name] === undefined) { const proofsEnabled = overrideProofsEnabled ?? this.areProofsEnabled.areProofsEnabled; - if (proofsEnabled) { + // This wierd any is necessary otherwise the compiler optimized that check away + if (proofsEnabled || !((contract as any) instanceof SmartContract)) { log.time(`Compiling ${name}`); this.compilationPromises[name] = contract.compile(); newPromise = true; } else { - // TODO Mock VK here is not at all generalized and safe - // Better way would be to package the Smart contracts into a mock-compile as well - this.compilationPromises[name] = Promise.resolve(MOCK_VERIFICATION_KEY); + this.compilationPromises[name] = Promise.resolve({ + verificationKey: MOCK_VERIFICATION_KEY, + }); } } const result = await this.compilationPromises[name]; if (newPromise) { log.timeEnd.info(`Compiling ${name}`); } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return result as ReturnArtifact; + return result; } } diff --git a/packages/protocol/src/compiling/CompilableModule.ts b/packages/protocol/src/compiling/CompilableModule.ts index 1459cb8e5..b967d1499 100644 --- a/packages/protocol/src/compiling/CompilableModule.ts +++ b/packages/protocol/src/compiling/CompilableModule.ts @@ -1,6 +1,6 @@ import type { CompileRegistry } from "./CompileRegistry"; -import { Artifact } from "./AtomicCompileHelper"; +import { ArtifactRecord } from "./AtomicCompileHelper"; export interface CompilableModule { - compile(registry: CompileRegistry): Promise; + compile(registry: CompileRegistry): Promise; } diff --git a/packages/protocol/src/compiling/CompileRegistry.ts b/packages/protocol/src/compiling/CompileRegistry.ts index 4a191d8b5..1125ca2b0 100644 --- a/packages/protocol/src/compiling/CompileRegistry.ts +++ b/packages/protocol/src/compiling/CompileRegistry.ts @@ -1,15 +1,11 @@ import { inject, injectable, singleton } from "tsyringe"; import { AreProofsEnabled, + CompileArtifact, mapSequential, - MOCK_VERIFICATION_KEY, } from "@proto-kit/common"; -import { - Artifact, - AtomicCompileHelper, - GenericCompileTarget, -} from "./AtomicCompileHelper"; +import { ArtifactRecord, AtomicCompileHelper } from "./AtomicCompileHelper"; import { CompilableModule } from "./CompilableModule"; /** @@ -29,61 +25,60 @@ export class CompileRegistry { public compile: AtomicCompileHelper; - private artifacts: Record = {}; + private artifacts: ArtifactRecord = {}; - public async compileModule( - // TODO Make name inferred by the module token - name: string, + public async compileModule( compile: ( - registry: CompileRegistry, + compiler: AtomicCompileHelper, ...args: unknown[] - ) => GenericCompileTarget, + ) => Promise, dependencies: Record = {} - ): Promise { + ): Promise { const collectedArtifacts = await mapSequential( Object.entries(dependencies), async ([depName, dep]) => { if (this.artifacts[depName] !== undefined) { return this.artifacts[depName]; } - const artifact = - (await dep.compile(this)) ?? "compiled-but-no-artifact"; - this.artifacts[depName] = artifact; + const artifact = await dep.compile(this); + if (artifact !== undefined) { + this.artifacts = { + ...this.artifacts, + ...artifact, + }; + } return artifact; } ); - const target = compile(this, ...collectedArtifacts); - const artifact = await this.compile.program(name, target); + const artifacts = await compile(this.compile, ...collectedArtifacts); - this.artifacts[name] = artifact ?? "compiled-but-no-artifact"; + this.artifacts = { + ...this.artifacts, + ...artifacts, + }; - return artifact; + return artifacts; } - public getArtifact(name: string) { + public getArtifact(name: string) { if (this.artifacts[name] === undefined) { throw new Error( `Artifact for ${name} not available, did you compile it via the CompileRegistry?` ); } - if (!this.areProofsEnabled.areProofsEnabled) { - return MOCK_VERIFICATION_KEY; - } - const artifact = this.artifacts[name]; - if (artifact === "compiled-but-no-artifact") { - throw new Error( - `Module ${name} didn't return the requested artifact even though proofs are enabled` - ); - } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return artifact as ArtifactType; + return this.artifacts[name]; + } + + public addArtifactsRaw(artifacts: ArtifactRecord) { + this.artifacts = { + ...this.artifacts, + ...artifacts, + }; } - public addArtifactsRaw(artifacts: Record) { - Object.entries(artifacts).forEach(([key, value]) => { - this.artifacts[key] = value; - }); + public getAllArtifacts() { + return this.artifacts; } } diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 709ac31cc..63b536559 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -929,8 +929,7 @@ export class BlockProver registry: CompileRegistry ): Promise | undefined> { return await registry.compileModule( - "BlockProver", - () => this.zkProgrammable + async () => await this.zkProgrammable.compile() ); } diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index cc032774c..7dc99227f 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -30,7 +30,7 @@ import { import { StateTransitionWitnessProvider } from "./StateTransitionWitnessProvider"; import { StateTransitionWitnessProviderReference } from "./StateTransitionWitnessProviderReference"; import { CompilableModule } from "../../compiling/CompilableModule"; -import { Artifact } from "../../compiling/AtomicCompileHelper"; +import { ArtifactRecord } from "../../compiling/AtomicCompileHelper"; import { CompileRegistry } from "../../compiling/CompileRegistry"; const errors = { @@ -367,10 +367,9 @@ export class StateTransitionProver ); } - public compile(registry: CompileRegistry): Promise { + public compile(registry: CompileRegistry): Promise { return registry.compileModule( - "StateTransitionProver", - () => this.zkProgrammable + async () => await this.zkProgrammable.compile() ); } diff --git a/packages/protocol/src/settlement/ContractModule.ts b/packages/protocol/src/settlement/ContractModule.ts index 3f5d2d536..4c4e4ef8e 100644 --- a/packages/protocol/src/settlement/ContractModule.ts +++ b/packages/protocol/src/settlement/ContractModule.ts @@ -7,7 +7,7 @@ import { import { SmartContract } from "o1js"; import { CompilableModule } from "../compiling/CompilableModule"; import type { CompileRegistry } from "../compiling/CompileRegistry"; -import { Artifact } from "../compiling/AtomicCompileHelper"; +import { ArtifactRecord } from "../compiling/AtomicCompileHelper"; export type SmartContractClassFromInterface = typeof SmartContract & TypedClass; @@ -28,5 +28,5 @@ export abstract class ContractModule public abstract compile( registry: CompileRegistry - ): Promise; + ): Promise; } diff --git a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts index 3bfdc6b95..097a40e74 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts @@ -36,6 +36,8 @@ export class BridgeContractProtocolModule extends ContractModule< } public async compile(registry: CompileRegistry) { - return await registry.compileModule("BridgeContract", () => BridgeContract); + return await registry.compileModule(async (compiler) => ({ + BridgeContract: await BridgeContract.compile(), + })); } } diff --git a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts index ee58efc5d..408161e85 100644 --- a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts @@ -45,9 +45,10 @@ export class DispatchContractProtocolModule extends ContractModule< } public async compile(registry: CompileRegistry) { - return await registry.compileModule( - "DispatchSmartContract", - () => DispatchSmartContract - ); + return await registry.compileModule(async (compiler) => ({ + DispatchSmartContract: await compiler.compileContract( + DispatchSmartContract + ), + })); } } diff --git a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts index 9556aacc7..a33e6cc6f 100644 --- a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts @@ -8,7 +8,7 @@ import { } from "../ContractModule"; import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; import { CompileRegistry } from "../../compiling/CompileRegistry"; -import { Artifact } from "../../compiling/AtomicCompileHelper"; +import { ArtifactRecord } from "../../compiling/AtomicCompileHelper"; import { DispatchSmartContractBase } from "./DispatchSmartContract"; import { @@ -78,23 +78,22 @@ export class SettlementContractProtocolModule extends ContractModule< public async compile( registry: CompileRegistry - ): Promise { + ): Promise { return await registry.compileModule( - "SettlementContract", - ( - registry2: CompileRegistry, - bridgeVk: unknown, - blockProverVk: unknown - ) => { + async (compiler, bridgeVk: unknown, blockProverVk: unknown) => { SettlementSmartContractBase.args.BridgeContractVerificationKey = // TODO Infer type bridgeVk as VerificationKey; - return SettlementSmartContract; + return { + SettlementSmartContract: await compiler.compileContract( + SettlementSmartContract + ), + }; }, { BridgeContract: this.bridgeContractModule, - BlockProver: this.blockProver.zkProgrammable, + BlockProver: this.blockProver, } ); } diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index 8669ecf97..d5e03750b 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -1,33 +1,29 @@ import { inject, injectable, Lifecycle, scoped } from "tsyringe"; import { Runtime } from "@proto-kit/module"; -import { VerificationKey } from "o1js"; import { log, mapSequential } from "@proto-kit/common"; import { MandatorySettlementModulesRecord, - NetworkState, Protocol, - RuntimeMethodExecutionContext, - RuntimeTransaction, SettlementContractModule, - Artifact, + ArtifactRecord, CompileRegistry, + CompilableModule, + BridgeContractProtocolModule, } from "@proto-kit/protocol"; import { TaskSerializer } from "../../../worker/flow/Task"; -import { VKRecord } from "../../runtime/RuntimeVerificationKeyService"; import { UnpreparingTask } from "../../../worker/flow/UnpreparingTask"; import { VerificationKeySerializer } from "../helpers/VerificationKeySerializer"; -export type CompiledCircuitsRecord = { - protocolCircuits: VKRecord; - runtimeCircuits: VKRecord; -}; - export type CompilerTaskParams = { - existingArtifacts: Record; + existingArtifacts: ArtifactRecord; + targets: string[]; }; -type VKRecordLite = Record; +type SerializedArtifactRecord = Record< + string, + { verificationKey: { hash: string; data: string } } +>; export class SimpleJSONSerializer implements TaskSerializer { public toJSON(parameters: Type): string { @@ -39,31 +35,34 @@ export class SimpleJSONSerializer implements TaskSerializer { } } -export class VKResultSerializer { - public toJSON(input: VKRecord): VKRecordLite { - const temp: VKRecordLite = Object.keys(input).reduce( - (accum, key) => { - return { - ...accum, - [key]: { - vk: VerificationKeySerializer.toJSON(input[key].vk), - }, - }; - }, - {} - ); +export class ArtifactRecordSerializer { + public toJSON(input: ArtifactRecord): SerializedArtifactRecord { + const temp: SerializedArtifactRecord = Object.keys( + input + ).reduce((accum, key) => { + return { + ...accum, + [key]: { + verificationKey: VerificationKeySerializer.toJSON( + input[key].verificationKey + ), + }, + }; + }, {}); return temp; } - public fromJSON(json: VKRecordLite): VKRecord { - return Object.keys(json).reduce((accum, key) => { + public fromJSON(json: SerializedArtifactRecord): ArtifactRecord { + return Object.keys(json).reduce((accum, key) => { return { ...accum, [key]: { - vk: VerificationKeySerializer.fromJSON(json[key].vk), + verificationKey: VerificationKeySerializer.fromJSON( + json[key].verificationKey + ), }, }; - }, {}); + }, {} as ArtifactRecord); } } @@ -71,7 +70,7 @@ export class VKResultSerializer { @scoped(Lifecycle.ContainerScoped) export class CircuitCompilerTask extends UnpreparingTask< CompilerTaskParams, - CompiledCircuitsRecord + ArtifactRecord > { public name = "compiledCircuit"; @@ -87,109 +86,57 @@ export class CircuitCompilerTask extends UnpreparingTask< return new SimpleJSONSerializer(); } - public resultSerializer(): TaskSerializer { - const vkRecordSerializer = new VKResultSerializer(); + public resultSerializer(): TaskSerializer { + const serializer = new ArtifactRecordSerializer(); return { - fromJSON: (json) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const temp: { - runtimeCircuits: VKRecordLite; - protocolCircuits: VKRecordLite; - } = JSON.parse(json); - return { - runtimeCircuits: vkRecordSerializer.fromJSON(temp.runtimeCircuits), - protocolCircuits: vkRecordSerializer.fromJSON(temp.protocolCircuits), - }; - }, - toJSON: (input) => { - return JSON.stringify({ - runtimeCircuits: vkRecordSerializer.toJSON(input.runtimeCircuits), - protocolCircuits: vkRecordSerializer.toJSON(input.protocolCircuits), - }); - }, + toJSON: (input) => JSON.stringify(serializer.toJSON(input)), + fromJSON: (input) => serializer.fromJSON(JSON.parse(input)), }; } - public async compileRuntimeMethods() { - log.time("Compiling runtime circuits"); - - const context = this.runtime.dependencyContainer.resolve( - RuntimeMethodExecutionContext - ); - context.setup({ - transaction: RuntimeTransaction.dummyTransaction(), - networkState: NetworkState.empty(), - }); - - const result = await mapSequential( - this.runtime.zkProgrammable.zkProgram, - async (program) => { - const vk = (await program.compile()).verificationKey; - - return Object.keys(program.methods).map((combinedMethodName) => { - const [moduleName, methodName] = combinedMethodName.split("."); - const methodId = this.runtime.methodIdResolver.getMethodId( - moduleName, - methodName - ); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return [methodId.toString(), new VerificationKey(vk)] as [ - string, - VerificationKey, - ]; - }); - } - ); - log.timeEnd.info("Compiling runtime circuits"); - return result; - } - - public async compileProtocolCircuits(): Promise< - [string, VerificationKey][][] - > { + public getSettlementTargets(): Record { // We only care about the BridgeContract for now - later with caching, // we might want to expand that to all protocol circuits const container = this.protocol.dependencyContainer; if (container.isRegistered("SettlementContractModule")) { - log.time("Compiling protocol circuits"); - const settlementModule = container.resolve< SettlementContractModule >("SettlementContractModule"); - const BridgeClass = settlementModule.getContractClasses().bridge; - const artifact = await BridgeClass.compile(); - - log.timeEnd.info("Compiling protocol circuits"); - - return [[["BridgeContract", artifact.verificationKey]]]; + const bridge = settlementModule.resolveOrFail( + "BridgeContract", + BridgeContractProtocolModule + ); + return { + bridge, + }; } - return [[]]; - } - - public collectRecord(tuples: [string, VerificationKey][][]): VKRecord { - return tuples.flat().reduce((acc, step) => { - acc[step[0]] = { vk: step[1] }; - return acc; - }, {}); + return {}; } - public async compute( - input: CompilerTaskParams - ): Promise { + public async compute(input: CompilerTaskParams): Promise { this.compileRegistry.addArtifactsRaw(input.existingArtifacts); log.info("Computing VKs"); - const runtimeTuples = await this.compileRuntimeMethods(); - const runtimeRecord = this.collectRecord(runtimeTuples); + // TODO make adaptive + const targets: Record = { + runtime: this.runtime, + protocol: this.protocol.blockProver, + ...this.getSettlementTargets(), + }; - const protocolTuples = await this.compileProtocolCircuits(); - const protocolRecord = this.collectRecord(protocolTuples); + const msg = `Compiling targets ${targets}`; + log.time(msg); + await mapSequential(input.targets, async (target) => { + if (target in targets) { + await targets[target].compile(this.compileRegistry); + } else { + throw new Error(`Compile target ${target} not found`); + } + }); + log.timeEnd.info(msg); - return { - runtimeCircuits: runtimeRecord, - protocolCircuits: protocolRecord, - }; + return this.compileRegistry.getAllArtifacts(); } } diff --git a/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts index 600a548f5..66a224b83 100644 --- a/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts @@ -5,6 +5,7 @@ import { Runtime, } from "@proto-kit/module"; import { + CompileRegistry, MethodPublicOutput, RuntimeMethodExecutionContext, } from "@proto-kit/protocol"; @@ -35,7 +36,8 @@ export class RuntimeProvingTask public constructor( @inject("Runtime") protected readonly runtime: Runtime, - private readonly executionContext: RuntimeMethodExecutionContext + private readonly executionContext: RuntimeMethodExecutionContext, + private readonly compileRegistry: CompileRegistry ) { super(); } @@ -95,9 +97,6 @@ export class RuntimeProvingTask } public async prepare(): Promise { - for (const zkProgram of this.runtimeZkProgrammable) { - // eslint-disable-next-line no-await-in-loop - await zkProgram.compile(); - } + await this.runtime.compile(this.compileRegistry); } } diff --git a/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts b/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts index 43d722e79..b6d09ef3b 100644 --- a/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts +++ b/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts @@ -1,7 +1,9 @@ import { Field, VerificationKey } from "o1js"; import { + CompileArtifact, ConfigurableModule, InMemoryMerkleTreeStorage, + mapSequential, ZkProgrammable, } from "@proto-kit/common"; import { inject, injectable, Lifecycle, scoped } from "tsyringe"; @@ -53,7 +55,45 @@ export class VerificationKeyService extends ConfigurableModule<{}> { [methodId: string]: VerificationKey; }; - public async initializeVKTree(verificationKeys: VKRecord) { + public collectRecord(tuples: [string, VerificationKey][][]): VKRecord { + return tuples.flat().reduce((acc, step) => { + acc[step[0]] = { vk: step[1] }; + return acc; + }, {}); + } + + public async initializeVKTree(artifacts: Record) { + const mappings = await mapSequential( + this.runtime.zkProgrammable.zkProgram, + async (program) => { + const artifact = artifacts[program.name]; + + if (artifact === undefined) { + throw new Error( + `Compiled artifact for runtime program ${program.name} not found` + ); + } + + return Object.keys(program.methods).map((combinedMethodName) => { + const [moduleName, methodName] = combinedMethodName.split("."); + const methodId = this.runtime.methodIdResolver.getMethodId( + moduleName, + methodName + ); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return [ + methodId.toString(), + new VerificationKey(artifact.verificationKey), + ] as [string, VerificationKey]; + }); + } + ); + return await this.initializeVKTreeFromMethodMappings( + this.collectRecord(mappings) + ); + } + + public async initializeVKTreeFromMethodMappings(verificationKeys: VKRecord) { const tree = new VKTree(new InMemoryMerkleTreeStorage()); const valuesVK: Record = {}; const indexes: VKIndexes = {}; diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 978ddc3d6..3c24af6a8 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -1,5 +1,6 @@ import { inject } from "tsyringe"; import { + ArtifactRecord, MandatoryProtocolModulesRecord, Protocol, RuntimeVerificationKeyRootService, @@ -9,10 +10,7 @@ import { log } from "@proto-kit/common"; import { FlowCreator } from "../worker/flow/Flow"; import { WorkerRegistrationFlow } from "../worker/worker/startup/WorkerRegistrationFlow"; -import { - CircuitCompilerTask, - CompiledCircuitsRecord, -} from "../protocol/production/tasks/CircuitCompilerTask"; +import { CircuitCompilerTask } from "../protocol/production/tasks/CircuitCompilerTask"; import { VerificationKeyService } from "../protocol/runtime/RuntimeVerificationKeyService"; import { SequencerModule, sequencerModule } from "./builder/SequencerModule"; @@ -35,22 +33,20 @@ export class SequencerStartupModule extends SequencerModule { log.info("Compiling Protocol circuits, this can take a few minutes"); - const vks = await flow.withFlow( - async (res, rej) => { - await flow.pushTask( - this.compileTask, - { existingArtifacts: {} }, - async (result) => { - res(result); - } - ); - } - ); + const artifacts = await flow.withFlow(async (res, rej) => { + await flow.pushTask( + this.compileTask, + { existingArtifacts: {}, targets: ["runtime"] }, + async (result) => { + res(result); + } + ); + }); log.info("Protocol circuits compiled"); // Init runtime VK tree - await this.verificationKeyService.initializeVKTree(vks.runtimeCircuits); + await this.verificationKeyService.initializeVKTree(artifacts); const root = this.verificationKeyService.getRoot(); @@ -59,15 +55,15 @@ export class SequencerStartupModule extends SequencerModule { .setRoot(root); // Init BridgeContract vk for settlement contract - const bridgeVk = vks.protocolCircuits.BridgeContract; + const bridgeVk = artifacts.BridgeContract; if (bridgeVk !== undefined) { SettlementSmartContractBase.args.BridgeContractVerificationKey = - bridgeVk.vk; + bridgeVk.verificationKey; } await this.registrationFlow.start({ runtimeVerificationKeyRoot: root, - bridgeContractVerificationKey: bridgeVk?.vk, + bridgeContractVerificationKey: bridgeVk.verificationKey, }); log.info("Protocol circuits compiled successfully, commencing startup"); diff --git a/packages/sequencer/src/worker/worker/WorkerReadyModule.ts b/packages/sequencer/src/worker/worker/WorkerReadyModule.ts index 1fd2516d8..fe9d060d4 100644 --- a/packages/sequencer/src/worker/worker/WorkerReadyModule.ts +++ b/packages/sequencer/src/worker/worker/WorkerReadyModule.ts @@ -1,5 +1,6 @@ import { injectable } from "tsyringe"; import { injectOptional } from "@proto-kit/common"; + import { LocalTaskWorkerModule } from "./LocalTaskWorkerModule"; /** @@ -15,10 +16,11 @@ export class WorkerReadyModule { | undefined ) {} - public async waitForReady(): Promise { + // eslint-disable-next-line consistent-return + public async waitForReady() { if (this.localTaskWorkerModule !== undefined) { const module = this.localTaskWorkerModule; - return new Promise((res, rej) => { + return await new Promise((res, rej) => { module.containerEvents.on("ready", (ready) => { if (ready) { res(); From 42e316c5a072ac4a59fd5948127b8880513e3fe5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 4 Nov 2024 16:35:51 +0100 Subject: [PATCH 4/7] Added ChildVerificationKeyService to allow for sideloading in protocol circuits --- .../services/ChildVerificationKeyService.ts | 26 +++++ packages/protocol/src/index.ts | 1 + .../production/tasks/CircuitCompilerTask.ts | 15 ++- .../runtime/RuntimeVerificationKeyService.ts | 2 +- .../src/sequencer/SequencerStartupModule.ts | 109 +++++++++++++++--- .../worker/startup/WorkerRegistrationTask.ts | 20 +++- 6 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 packages/protocol/src/compiling/services/ChildVerificationKeyService.ts diff --git a/packages/protocol/src/compiling/services/ChildVerificationKeyService.ts b/packages/protocol/src/compiling/services/ChildVerificationKeyService.ts new file mode 100644 index 000000000..1e90738d1 --- /dev/null +++ b/packages/protocol/src/compiling/services/ChildVerificationKeyService.ts @@ -0,0 +1,26 @@ +import { injectable, Lifecycle, scoped } from "tsyringe"; + +import { CompileRegistry } from "../CompileRegistry"; + +@injectable() +@scoped(Lifecycle.ContainerScoped) +export class ChildVerificationKeyService { + private compileRegistry?: CompileRegistry; + + public setCompileRegistry(registry: CompileRegistry) { + this.compileRegistry = registry; + } + + public getVerificationKey(name: string) { + if (this.compileRegistry === undefined) { + throw new Error("CompileRegistry hasn't been set yet"); + } + const artifact = this.compileRegistry.getArtifact(name); + if (artifact === undefined) { + throw new Error( + `Verification Key for child program ${name} not found in registry` + ); + } + return artifact.verificationKey; + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index d90737de8..52073b13e 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,6 +1,7 @@ export * from "./compiling/AtomicCompileHelper"; export * from "./compiling/CompileRegistry"; export * from "./compiling/CompilableModule"; +export * from "./compiling/services/ChildVerificationKeyService"; export * from "./hooks/AccountStateHook"; export * from "./hooks/BlockHeightHook"; export * from "./hooks/LastStateRootBlockHook"; diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index d5e03750b..b3a78c479 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -9,6 +9,7 @@ import { CompileRegistry, CompilableModule, BridgeContractProtocolModule, + RuntimeVerificationKeyRootService, } from "@proto-kit/protocol"; import { TaskSerializer } from "../../../worker/flow/Task"; @@ -18,6 +19,7 @@ import { VerificationKeySerializer } from "../helpers/VerificationKeySerializer" export type CompilerTaskParams = { existingArtifacts: ArtifactRecord; targets: string[]; + runtimeVKRoot?: string; }; type SerializedArtifactRecord = Record< @@ -117,6 +119,14 @@ export class CircuitCompilerTask extends UnpreparingTask< public async compute(input: CompilerTaskParams): Promise { this.compileRegistry.addArtifactsRaw(input.existingArtifacts); + // We need to initialize the VK tree root if we have it, so that + // the BlockProver can bake in that root + if (input.runtimeVKRoot !== undefined) { + this.protocol.dependencyContainer + .resolve(RuntimeVerificationKeyRootService) + .setRoot(BigInt(input.runtimeVKRoot)); + } + log.info("Computing VKs"); // TODO make adaptive @@ -132,7 +142,10 @@ export class CircuitCompilerTask extends UnpreparingTask< if (target in targets) { await targets[target].compile(this.compileRegistry); } else { - throw new Error(`Compile target ${target} not found`); + log.info( + // TODO Is that right? Or should we check that the bridge exists on the sequencer side? + `Compile target ${target} not found, skipping` + ); } }); log.timeEnd.info(msg); diff --git a/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts b/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts index b6d09ef3b..5445d6e63 100644 --- a/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts +++ b/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts @@ -93,7 +93,7 @@ export class VerificationKeyService extends ConfigurableModule<{}> { ); } - public async initializeVKTreeFromMethodMappings(verificationKeys: VKRecord) { + private async initializeVKTreeFromMethodMappings(verificationKeys: VKRecord) { const tree = new VKTree(new InMemoryMerkleTreeStorage()); const valuesVK: Record = {}; const indexes: VKIndexes = {}; diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 3c24af6a8..8915311e8 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -1,16 +1,20 @@ import { inject } from "tsyringe"; import { ArtifactRecord, + CompileRegistry, MandatoryProtocolModulesRecord, Protocol, RuntimeVerificationKeyRootService, SettlementSmartContractBase, } from "@proto-kit/protocol"; -import { log } from "@proto-kit/common"; +import { CompileArtifact, log } from "@proto-kit/common"; -import { FlowCreator } from "../worker/flow/Flow"; +import { Flow, FlowCreator } from "../worker/flow/Flow"; import { WorkerRegistrationFlow } from "../worker/worker/startup/WorkerRegistrationFlow"; -import { CircuitCompilerTask } from "../protocol/production/tasks/CircuitCompilerTask"; +import { + CircuitCompilerTask, + CompilerTaskParams, +} from "../protocol/production/tasks/CircuitCompilerTask"; import { VerificationKeyService } from "../protocol/runtime/RuntimeVerificationKeyService"; import { SequencerModule, sequencerModule } from "./builder/SequencerModule"; @@ -23,47 +27,114 @@ export class SequencerStartupModule extends SequencerModule { private readonly protocol: Protocol, private readonly compileTask: CircuitCompilerTask, private readonly verificationKeyService: VerificationKeyService, - private readonly registrationFlow: WorkerRegistrationFlow + private readonly registrationFlow: WorkerRegistrationFlow, + private readonly compileRegistry: CompileRegistry ) { super(); } - public async start() { - const flow = this.flowCreator.createFlow("compile-circuits", {}); + private async pushCompileTask( + flow: Flow<{}>, + payload: CompilerTaskParams + ): Promise { + return await flow.withFlow(async (res, rej) => { + await flow.pushTask(this.compileTask, payload, async (result) => { + res(result); + }); + }); + } - log.info("Compiling Protocol circuits, this can take a few minutes"); + public async compileRuntime(flow: Flow<{}>) { + const artifacts = await this.pushCompileTask(flow, { + existingArtifacts: {}, + targets: ["runtime"], + runtimeVKRoot: undefined, + }); + + // Init runtime VK tree + await this.verificationKeyService.initializeVKTree(artifacts); + + const root = this.verificationKeyService.getRoot(); + + this.protocol.dependencyContainer + .resolve(RuntimeVerificationKeyRootService) + .setRoot(root); + + this.compileRegistry.addArtifactsRaw(artifacts); + + return root; + } + + private async compileProtocolAndBridge(flow: Flow<{}>) { + // Can happen in parallel + type ParallelResult = { + protocol?: ArtifactRecord; + bridge?: ArtifactRecord; + }; + const result = await flow.withFlow(async (res, rej) => { + const results: ParallelResult = {}; + + const resolveIfPossible = () => { + const { bridge, protocol } = results; + if (bridge !== undefined && protocol !== undefined) { + res({ ...protocol, ...bridge }); + } + }; - const artifacts = await flow.withFlow(async (res, rej) => { await flow.pushTask( this.compileTask, - { existingArtifacts: {}, targets: ["runtime"] }, + { + existingArtifacts: {}, + targets: ["protocol"], + runtimeVKRoot: undefined, + }, async (result) => { - res(result); + results.protocol = result; + resolveIfPossible(); + } + ); + + await flow.pushTask( + this.compileTask, + { + existingArtifacts: {}, + targets: ["bridge"], + runtimeVKRoot: undefined, + }, + async (result) => { + results.bridge = result; + resolveIfPossible(); } ); }); + this.compileRegistry.addArtifactsRaw(result); + return result; + } - log.info("Protocol circuits compiled"); + public async start() { + const flow = this.flowCreator.createFlow("compile-circuits", {}); - // Init runtime VK tree - await this.verificationKeyService.initializeVKTree(artifacts); + log.info("Compiling Protocol circuits, this can take a few minutes"); - const root = this.verificationKeyService.getRoot(); + const root = await this.compileRuntime(flow); - this.protocol.dependencyContainer - .resolve(RuntimeVerificationKeyRootService) - .setRoot(root); + const protocolBridgeArtifacts = await this.compileProtocolAndBridge(flow); + + log.info("Protocol circuits compiled"); // Init BridgeContract vk for settlement contract - const bridgeVk = artifacts.BridgeContract; + const bridgeVk = protocolBridgeArtifacts.BridgeContract; if (bridgeVk !== undefined) { SettlementSmartContractBase.args.BridgeContractVerificationKey = bridgeVk.verificationKey; } + // TODO Add vk record to start params + await this.registrationFlow.start({ runtimeVerificationKeyRoot: root, - bridgeContractVerificationKey: bridgeVk.verificationKey, + bridgeContractVerificationKey: bridgeVk?.verificationKey, + compiledArtifacts: this.compileRegistry.getAllArtifacts(), }); log.info("Protocol circuits compiled successfully, commencing startup"); diff --git a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts index f1cf74672..0ce7864e8 100644 --- a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts +++ b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts @@ -1,6 +1,9 @@ import { log, noop } from "@proto-kit/common"; import { inject, injectable } from "tsyringe"; import { + ArtifactRecord, + ChildVerificationKeyService, + CompileRegistry, Protocol, RuntimeVerificationKeyRootService, SettlementSmartContractBase, @@ -12,11 +15,13 @@ import { AbstractStartupTask } from "../../flow/AbstractStartupTask"; import { VerificationKeySerializer } from "../../../protocol/production/helpers/VerificationKeySerializer"; import { CloseWorkerError } from "./CloseWorkerError"; +import { ArtifactRecordSerializer } from "../../../protocol/production/tasks/CircuitCompilerTask"; export type WorkerStartupPayload = { runtimeVerificationKeyRoot: bigint; // This has to be nullable, since bridgeContractVerificationKey?: VerificationKey; + compiledArtifacts: ArtifactRecord; }; @injectable() @@ -28,7 +33,8 @@ export class WorkerRegistrationTask private done = false; public constructor( - @inject("Protocol") private readonly protocol: Protocol + @inject("Protocol") private readonly protocol: Protocol, + private readonly compileRegistry: CompileRegistry ) { super(); } @@ -55,6 +61,11 @@ export class WorkerRegistrationTask input.bridgeContractVerificationKey; } + this.compileRegistry.addArtifactsRaw(input.compiledArtifacts); + this.protocol.dependencyContainer + .resolve(ChildVerificationKeyService) + .setCompileRegistry(this.compileRegistry); + this.events.emit("startup-task-finished"); this.done = true; @@ -62,6 +73,7 @@ export class WorkerRegistrationTask } public inputSerializer() { + const artifactSerializer = new ArtifactRecordSerializer(); return { toJSON: (payload: WorkerStartupPayload) => { return JSON.stringify({ @@ -73,6 +85,9 @@ export class WorkerRegistrationTask payload.bridgeContractVerificationKey ) : undefined, + compiledArtifacts: artifactSerializer.toJSON( + payload.compiledArtifacts + ), }); }, fromJSON: (payload: string) => { @@ -91,6 +106,9 @@ export class WorkerRegistrationTask jsonObject.bridgeContractVerificationKey ) : undefined, + compiledArtifacts: artifactSerializer.fromJSON( + jsonObject.compiledArtifacts + ), }; }, }; From 76b4fa864e7ea122451381b5bbe1b6ce8f9e05e6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 8 Nov 2024 01:33:21 +0700 Subject: [PATCH 5/7] Smart Contracts compiling --- .../src/compiling/AtomicCompileHelper.ts | 23 +++- .../src/compiling/CompilableModule.ts | 2 +- .../common/src/compiling/CompileRegistry.ts | 118 ++++++++++++++++++ .../services/ChildVerificationKeyService.ts | 0 packages/common/src/index.ts | 4 + packages/common/src/types.ts | 11 +- packages/common/src/utils.ts | 18 +++ .../src/zkProgrammable/ZkProgrammable.ts | 7 +- packages/module/src/runtime/Runtime.ts | 16 ++- .../protocol/src/compiling/CompileRegistry.ts | 84 ------------- packages/protocol/src/index.ts | 4 - .../src/prover/block/BlockProvable.ts | 3 +- .../protocol/src/prover/block/BlockProver.ts | 18 +-- .../StateTransitionProvable.ts | 3 +- .../statetransition/StateTransitionProver.ts | 14 +-- .../protocol/src/settlement/ContractModule.ts | 7 +- .../settlement/SettlementContractModule.ts | 54 +++----- .../contracts/BridgeContractProtocolModule.ts | 10 +- .../DispatchContractProtocolModule.ts | 33 +++-- .../SettlementContractProtocolModule.ts | 54 ++++---- .../contracts/SettlementSmartContract.ts | 78 ++++++++---- .../src/settlement/modularity/types.ts | 22 ++++ packages/protocol/test/compiling/types.ts | 28 +++++ packages/protocol/test/modularity/types.ts | 35 ++++++ .../production/tasks/BlockProvingTask.ts | 6 +- .../production/tasks/CircuitCompilerTask.ts | 61 ++++++--- .../protocol/production/tasks/NewBlockTask.ts | 6 +- .../production/tasks/RuntimeProvingTask.ts | 2 +- .../production/tasks/StateTransitionTask.ts | 7 +- .../src/sequencer/SequencerStartupModule.ts | 22 +++- .../src/settlement/SettlementModule.ts | 18 +-- .../settlement/tasks/SettlementProvingTask.ts | 43 +++++-- .../src/worker/worker/FlowTaskWorker.ts | 1 + .../worker/startup/WorkerRegistrationTask.ts | 11 +- .../integration/BlockProductionSize.test.ts | 2 +- .../sequencer/test/integration/Proven.test.ts | 113 ++++++++++++++--- .../test/integration/mocks/ProvenBalance.ts | 8 +- 37 files changed, 650 insertions(+), 296 deletions(-) rename packages/{protocol => common}/src/compiling/AtomicCompileHelper.ts (62%) rename packages/{protocol => common}/src/compiling/CompilableModule.ts (73%) create mode 100644 packages/common/src/compiling/CompileRegistry.ts rename packages/{protocol => common}/src/compiling/services/ChildVerificationKeyService.ts (100%) delete mode 100644 packages/protocol/src/compiling/CompileRegistry.ts create mode 100644 packages/protocol/src/settlement/modularity/types.ts create mode 100644 packages/protocol/test/compiling/types.ts create mode 100644 packages/protocol/test/modularity/types.ts diff --git a/packages/protocol/src/compiling/AtomicCompileHelper.ts b/packages/common/src/compiling/AtomicCompileHelper.ts similarity index 62% rename from packages/protocol/src/compiling/AtomicCompileHelper.ts rename to packages/common/src/compiling/AtomicCompileHelper.ts index c4dbf6cb8..19bdff07b 100644 --- a/packages/protocol/src/compiling/AtomicCompileHelper.ts +++ b/packages/common/src/compiling/AtomicCompileHelper.ts @@ -1,10 +1,11 @@ import { AreProofsEnabled, CompileArtifact, - log, MOCK_VERIFICATION_KEY, -} from "@proto-kit/common"; -import { SmartContract, VerificationKey } from "o1js"; +} from "../zkProgrammable/ZkProgrammable"; +import { isSubtypeOfName } from "../utils"; +import { TypedClass } from "../types"; +import { log } from "../log"; export type ArtifactRecord = Record; @@ -29,12 +30,24 @@ export class AtomicCompileHelper { if (this.compilationPromises[name] === undefined) { const proofsEnabled = overrideProofsEnabled ?? this.areProofsEnabled.areProofsEnabled; - // This wierd any is necessary otherwise the compiler optimized that check away - if (proofsEnabled || !((contract as any) instanceof SmartContract)) { + + // We only care about proofs enabled here if it's a contract, because + // in all other cases, ZkProgrammable already handles this switch, and we + // want to preserve the artifact layout (which might be more than one + // entry for ZkProgrammables) + if ( + proofsEnabled || + !isSubtypeOfName( + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + contract as unknown as TypedClass, + "SmartContract" + ) + ) { log.time(`Compiling ${name}`); this.compilationPromises[name] = contract.compile(); newPromise = true; } else { + log.trace(`Compiling ${name} - mock`); this.compilationPromises[name] = Promise.resolve({ verificationKey: MOCK_VERIFICATION_KEY, }); diff --git a/packages/protocol/src/compiling/CompilableModule.ts b/packages/common/src/compiling/CompilableModule.ts similarity index 73% rename from packages/protocol/src/compiling/CompilableModule.ts rename to packages/common/src/compiling/CompilableModule.ts index b967d1499..ba695aeba 100644 --- a/packages/protocol/src/compiling/CompilableModule.ts +++ b/packages/common/src/compiling/CompilableModule.ts @@ -1,5 +1,5 @@ import type { CompileRegistry } from "./CompileRegistry"; -import { ArtifactRecord } from "./AtomicCompileHelper"; +import type { ArtifactRecord } from "./AtomicCompileHelper"; export interface CompilableModule { compile(registry: CompileRegistry): Promise; diff --git a/packages/common/src/compiling/CompileRegistry.ts b/packages/common/src/compiling/CompileRegistry.ts new file mode 100644 index 000000000..adc91fa40 --- /dev/null +++ b/packages/common/src/compiling/CompileRegistry.ts @@ -0,0 +1,118 @@ +import { inject, injectable, singleton } from "tsyringe"; + +import { AreProofsEnabled } from "../zkProgrammable/ZkProgrammable"; + +import { + ArtifactRecord, + AtomicCompileHelper, + CompileTarget, +} from "./AtomicCompileHelper"; +import { CompilableModule } from "./CompilableModule"; + +interface GenericCompilableModule { + compile(registry: CompileRegistry): Promise; +} + +export type InferDependencyArtifacts< + Dependencies extends Record, +> = { + [Key in keyof Dependencies]: Dependencies[Key] extends GenericCompilableModule< + infer Artifact + > + ? Artifact + : void; +}; +/** + * The CompileRegistry compiles "compilable modules" + * (i.e. zkprograms, contracts or contractmodules) + * while making sure they don't get compiled twice in the same process in parallel. + */ +@injectable() +@singleton() +export class CompileRegistry { + public constructor( + @inject("AreProofsEnabled") + private readonly areProofsEnabled: AreProofsEnabled + ) { + this.compiler = new AtomicCompileHelper(this.areProofsEnabled); + } + + private compiler: AtomicCompileHelper; + + private artifacts: ArtifactRecord = {}; + + // private cachedModuleOutputs: Record = {}; + + // TODO Add possibility to force recompilation for non-sideloaded dependencies + + public async compile(target: CompileTarget) { + if (this.artifacts[target.name] === undefined) { + const artifact = await this.compiler.compileContract(target); + this.artifacts[target.name] = artifact; + return artifact; + } + return this.artifacts[target.name]; + } + + // public async compileModule< + // ReturnType extends ArtifactRecord, + // Dependencies extends Record, + // >( + // compile: ( + // compiler: AtomicCompileHelper, + // args: InferDependencyArtifacts + // ) => Promise, + // dependencies?: Dependencies + // ): Promise { + // const collectedArtifacts = await mapSequential( + // Object.entries(dependencies ?? {}), + // async ([depName, dep]) => { + // if (this.cachedModuleOutputs[depName] !== undefined) { + // return [depName, this.cachedModuleOutputs[depName]]; + // } + // const artifact = await dep.compile(this); + // if (artifact !== undefined) { + // this.artifacts = { + // ...this.artifacts, + // ...artifact, + // }; + // } + // this.cachedModuleOutputs[depName] = artifact; + // return [depName, artifact]; + // } + // ); + // + // const artifacts = await compile( + // this.compile, + // Object.fromEntries(collectedArtifacts) + // ); + // + // this.artifacts = { + // ...this.artifacts, + // ...artifacts, + // }; + // + // return artifacts; + // } + + public getArtifact(name: string) { + if (this.artifacts[name] === undefined) { + throw new Error( + `Artifact for ${name} not available, did you compile it via the CompileRegistry?` + ); + } + + return this.artifacts[name]; + } + + public addArtifactsRaw(artifacts: ArtifactRecord) { + this.artifacts = { + ...this.artifacts, + ...artifacts, + }; + } + + public getAllArtifacts() { + return this.artifacts; + } +} diff --git a/packages/protocol/src/compiling/services/ChildVerificationKeyService.ts b/packages/common/src/compiling/services/ChildVerificationKeyService.ts similarity index 100% rename from packages/protocol/src/compiling/services/ChildVerificationKeyService.ts rename to packages/common/src/compiling/services/ChildVerificationKeyService.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 42f98934d..9b293c8ef 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -18,3 +18,7 @@ export * from "./trees/RollupMerkleTree"; export * from "./events/EventEmitterProxy"; export * from "./events/ReplayingSingleUseEventEmitter"; export * from "./trees/MockAsyncMerkleStore"; +export * from "./compiling/AtomicCompileHelper"; +export * from "./compiling/CompileRegistry"; +export * from "./compiling/CompilableModule"; +export * from "./compiling/services/ChildVerificationKeyService"; diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index e81f01515..4d25b1192 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,5 +1,5 @@ // allows to reference interfaces as 'classes' rather than instances -import { Bool, Field, PublicKey } from "o1js"; +import { Bool, DynamicProof, Field, Proof, ProofBase, PublicKey } from "o1js"; export type TypedClass = new (...args: any[]) => Class; @@ -47,3 +47,12 @@ export const EMPTY_PUBLICKEY = PublicKey.fromObject({ export type OverwriteObjectType = { [Key in keyof Base]: Key extends keyof New ? New[Key] : Base[Key]; } & New; + +export type InferProofBase< + ProofType extends Proof | DynamicProof, +> = + ProofType extends Proof + ? ProofBase + : ProofType extends DynamicProof + ? ProofBase + : undefined; diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 35f5d64b9..32a3530f5 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -6,6 +6,8 @@ import { Proof, } from "o1js"; +import { TypedClass } from "./types"; + export function requireTrue( condition: boolean, errorOrFunction: Error | (() => Error) @@ -164,3 +166,19 @@ type NonMethodKeys = { [Key in keyof Type]: Type[Key] extends Function ? never : Key; }[keyof Type]; export type NonMethods = Pick>; + +/** + * Returns a boolean indicating whether a given class is a subclass of another class, + * indicated by the name parameter. + */ +// TODO Change to class reference based comparisons +export function isSubtypeOfName( + clas: TypedClass, + name: string +): boolean { + if (clas.name === name) { + return true; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + return isSubtypeOfName(Object.getPrototypeOf(clas), name); +} diff --git a/packages/common/src/zkProgrammable/ZkProgrammable.ts b/packages/common/src/zkProgrammable/ZkProgrammable.ts index 1dd683e9f..fc728bf31 100644 --- a/packages/common/src/zkProgrammable/ZkProgrammable.ts +++ b/packages/common/src/zkProgrammable/ZkProgrammable.ts @@ -4,6 +4,7 @@ import { Memoize } from "typescript-memoize"; import { log } from "../log"; import { dummyVerificationKey } from "../dummyVerificationKey"; import { reduceSequential } from "../utils"; +import type { CompileRegistry } from "../compiling/CompileRegistry"; import { MOCK_PROOF } from "./provableMethod"; @@ -74,8 +75,6 @@ export function verifyToMockable( return verified; } - console.log("VerifyMocked"); - return proof.proof === MOCK_PROOF; }; } @@ -128,11 +127,11 @@ export abstract class ZkProgrammable< }); } - public async compile() { + public async compile(registry: CompileRegistry) { return await reduceSequential( this.zkProgram, async (acc, program) => { - const result = await program.compile(); + const result = await registry.compile(program); return { ...acc, [program.name]: result, diff --git a/packages/module/src/runtime/Runtime.ts b/packages/module/src/runtime/Runtime.ts index 6fa28bd6f..264c57e8f 100644 --- a/packages/module/src/runtime/Runtime.ts +++ b/packages/module/src/runtime/Runtime.ts @@ -11,13 +11,13 @@ import { PlainZkProgram, AreProofsEnabled, ChildContainerProvider, + CompilableModule, + CompileRegistry, } from "@proto-kit/common"; import { MethodPublicOutput, StateServiceProvider, SimpleAsyncStateService, - CompilableModule, - CompileRegistry, RuntimeMethodExecutionContext, RuntimeTransaction, NetworkState, @@ -384,14 +384,12 @@ export class Runtime } public async compile(registry: CompileRegistry) { - return await registry.compileModule(async () => { - const context = container.resolve(RuntimeMethodExecutionContext); - context.setup({ - transaction: RuntimeTransaction.dummyTransaction(), - networkState: NetworkState.empty(), - }); - return await this.zkProgrammable.compile(); + const context = container.resolve(RuntimeMethodExecutionContext); + context.setup({ + transaction: RuntimeTransaction.dummyTransaction(), + networkState: NetworkState.empty(), }); + return await this.zkProgrammable.compile(registry); } } /* eslint-enable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument */ diff --git a/packages/protocol/src/compiling/CompileRegistry.ts b/packages/protocol/src/compiling/CompileRegistry.ts deleted file mode 100644 index 1125ca2b0..000000000 --- a/packages/protocol/src/compiling/CompileRegistry.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { inject, injectable, singleton } from "tsyringe"; -import { - AreProofsEnabled, - CompileArtifact, - mapSequential, -} from "@proto-kit/common"; - -import { ArtifactRecord, AtomicCompileHelper } from "./AtomicCompileHelper"; -import { CompilableModule } from "./CompilableModule"; - -/** - * The CompileRegistry compiles "compilable modules" - * (i.e. zkprograms, contracts or contractmodules) - * while making sure they don't get compiled twice in the same process in parallel. - */ -@injectable() -@singleton() -export class CompileRegistry { - public constructor( - @inject("AreProofsEnabled") - private readonly areProofsEnabled: AreProofsEnabled - ) { - this.compile = new AtomicCompileHelper(this.areProofsEnabled); - } - - public compile: AtomicCompileHelper; - - private artifacts: ArtifactRecord = {}; - - public async compileModule( - compile: ( - compiler: AtomicCompileHelper, - ...args: unknown[] - ) => Promise, - dependencies: Record = {} - ): Promise { - const collectedArtifacts = await mapSequential( - Object.entries(dependencies), - async ([depName, dep]) => { - if (this.artifacts[depName] !== undefined) { - return this.artifacts[depName]; - } - const artifact = await dep.compile(this); - if (artifact !== undefined) { - this.artifacts = { - ...this.artifacts, - ...artifact, - }; - } - return artifact; - } - ); - - const artifacts = await compile(this.compile, ...collectedArtifacts); - - this.artifacts = { - ...this.artifacts, - ...artifacts, - }; - - return artifacts; - } - - public getArtifact(name: string) { - if (this.artifacts[name] === undefined) { - throw new Error( - `Artifact for ${name} not available, did you compile it via the CompileRegistry?` - ); - } - - return this.artifacts[name]; - } - - public addArtifactsRaw(artifacts: ArtifactRecord) { - this.artifacts = { - ...this.artifacts, - ...artifacts, - }; - } - - public getAllArtifacts() { - return this.artifacts; - } -} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 52073b13e..93ed646f4 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,7 +1,3 @@ -export * from "./compiling/AtomicCompileHelper"; -export * from "./compiling/CompileRegistry"; -export * from "./compiling/CompilableModule"; -export * from "./compiling/services/ChildVerificationKeyService"; export * from "./hooks/AccountStateHook"; export * from "./hooks/BlockHeightHook"; export * from "./hooks/LastStateRootBlockHook"; diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index ee4272418..1556adeae 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -7,7 +7,7 @@ import { Struct, Void, } from "o1js"; -import { WithZkProgrammable } from "@proto-kit/common"; +import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; import { StateTransitionProof } from "../statetransition/StateTransitionProvable"; import { MethodPublicOutput } from "../../model/MethodPublicOutput"; @@ -16,7 +16,6 @@ import { NetworkState } from "../../model/network/NetworkState"; import { BlockHashMerkleTreeWitness } from "./accummulators/BlockHashMerkleTree"; import { RuntimeVerificationKeyAttestation } from "./accummulators/RuntimeVerificationKeyTree"; -import { CompilableModule } from "../../compiling/CompilableModule"; export class BlockProverPublicInput extends Struct({ transactionsHash: Field, diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 63b536559..344dad06b 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -12,7 +12,9 @@ import { import { container, inject, injectable, injectAll } from "tsyringe"; import { AreProofsEnabled, + CompilableModule, CompileArtifact, + CompileRegistry, PlainZkProgram, provableMethod, WithZkProgrammable, @@ -24,6 +26,7 @@ import { MethodPublicOutput } from "../../model/MethodPublicOutput"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { StateTransitionProof, + StateTransitionProvable, StateTransitionProverPublicInput, StateTransitionProverPublicOutput, } from "../statetransition/StateTransitionProvable"; @@ -42,7 +45,6 @@ import { MinaActionsHashList, } from "../../utils/MinaPrefixedProvableHashList"; import { StateTransitionReductionList } from "../../utils/StateTransitionReductionList"; -import { CompileRegistry } from "../../compiling/CompileRegistry"; import { BlockProvable, @@ -62,7 +64,6 @@ import { RuntimeVerificationKeyAttestation, } from "./accummulators/RuntimeVerificationKeyTree"; import { RuntimeVerificationKeyRootService } from "./services/RuntimeVerificationKeyRootService"; -import { CompilableModule } from "../../compiling/CompilableModule"; const errors = { stateProofNotStartingAtZero: () => @@ -905,9 +906,11 @@ export class BlockProver public readonly stateTransitionProver: WithZkProgrammable< StateTransitionProverPublicInput, StateTransitionProverPublicOutput - >, + > & + StateTransitionProvable, @inject("Runtime") - public readonly runtime: WithZkProgrammable, + public readonly runtime: WithZkProgrammable & + CompilableModule, @injectAll("ProvableTransactionHook") transactionHooks: ProvableTransactionHook[], @injectAll("ProvableBlockHook") @@ -928,9 +931,10 @@ export class BlockProver public async compile( registry: CompileRegistry ): Promise | undefined> { - return await registry.compileModule( - async () => await this.zkProgrammable.compile() - ); + await this.stateTransitionProver.compile(registry); + await this.runtime.compile(registry); + + return await this.zkProgrammable.compile(registry); } public proveTransaction( diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts index c3a9773a6..0b61e5ce4 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProvable.ts @@ -1,10 +1,9 @@ import { Field, Proof, Struct } from "o1js"; -import { WithZkProgrammable } from "@proto-kit/common"; +import { WithZkProgrammable, CompilableModule } from "@proto-kit/common"; import { StateTransitionProvableBatch } from "../../model/StateTransitionProvableBatch"; import { StateTransitionWitnessProviderReference } from "./StateTransitionWitnessProviderReference"; -import { CompilableModule } from "../../compiling/CompilableModule"; export class StateTransitionProverPublicInput extends Struct({ stateTransitionsHash: Field, diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index 7dc99227f..93a6fd7a3 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -4,6 +4,9 @@ import { provableMethod, RollupMerkleTreeWitness, ZkProgrammable, + CompilableModule, + type ArtifactRecord, + type CompileRegistry, } from "@proto-kit/common"; import { Field, Provable, SelfProof, ZkProgram } from "o1js"; import { injectable } from "tsyringe"; @@ -29,9 +32,6 @@ import { } from "./StateTransitionProvable"; import { StateTransitionWitnessProvider } from "./StateTransitionWitnessProvider"; import { StateTransitionWitnessProviderReference } from "./StateTransitionWitnessProviderReference"; -import { CompilableModule } from "../../compiling/CompilableModule"; -import { ArtifactRecord } from "../../compiling/AtomicCompileHelper"; -import { CompileRegistry } from "../../compiling/CompileRegistry"; const errors = { propertyNotMatching: (property: string, step: string) => @@ -367,10 +367,10 @@ export class StateTransitionProver ); } - public compile(registry: CompileRegistry): Promise { - return registry.compileModule( - async () => await this.zkProgrammable.compile() - ); + public async compile( + registry: CompileRegistry + ): Promise { + return await this.zkProgrammable.compile(registry); } public runBatch( diff --git a/packages/protocol/src/settlement/ContractModule.ts b/packages/protocol/src/settlement/ContractModule.ts index 4c4e4ef8e..295333372 100644 --- a/packages/protocol/src/settlement/ContractModule.ts +++ b/packages/protocol/src/settlement/ContractModule.ts @@ -1,13 +1,12 @@ import { - CompileArtifact, + ArtifactRecord, + type CompilableModule, + CompileRegistry, ConfigurableModule, NoConfig, TypedClass, } from "@proto-kit/common"; import { SmartContract } from "o1js"; -import { CompilableModule } from "../compiling/CompilableModule"; -import type { CompileRegistry } from "../compiling/CompileRegistry"; -import { ArtifactRecord } from "../compiling/AtomicCompileHelper"; export type SmartContractClassFromInterface = typeof SmartContract & TypedClass; diff --git a/packages/protocol/src/settlement/SettlementContractModule.ts b/packages/protocol/src/settlement/SettlementContractModule.ts index d832f2cb6..931c1667b 100644 --- a/packages/protocol/src/settlement/SettlementContractModule.ts +++ b/packages/protocol/src/settlement/SettlementContractModule.ts @@ -5,6 +5,7 @@ import { ModulesRecord, TypedClass, noop, + StringKeyOf, } from "@proto-kit/common"; import { Field, PublicKey, SmartContract } from "o1js"; import { injectable } from "tsyringe"; @@ -12,10 +13,7 @@ import { injectable } from "tsyringe"; import { ProtocolEnvironment } from "../protocol/ProtocolEnvironment"; import { ProtocolModule } from "../protocol/ProtocolModule"; -import { - ContractModule, - SmartContractClassFromInterface, -} from "./ContractModule"; +import { ContractModule } from "./ContractModule"; import { DispatchContractProtocolModule } from "./contracts/DispatchContractProtocolModule"; import { DispatchContractType } from "./contracts/DispatchSmartContract"; import { @@ -28,6 +26,7 @@ import { BridgeContractConfig, BridgeContractProtocolModule, } from "./contracts/BridgeContractProtocolModule"; +import { GetContracts } from "./modularity/types"; export type SettlementModulesRecord = ModulesRecord< TypedClass> @@ -107,32 +106,14 @@ export class SettlementContractModule< noop(); } - public getContractClasses(): { - settlement: SmartContractClassFromInterface; - dispatch: SmartContractClassFromInterface; - bridge: SmartContractClassFromInterface; - } { - // TODO Make that dynamic - const settlementContractKey = "SettlementContract"; - const dispatchContractKey = "DispatchContract"; - const bridgeContractKey = "BridgeContract"; - this.assertIsValidModuleName(settlementContractKey); - this.assertIsValidModuleName(dispatchContractKey); - this.assertIsValidModuleName(bridgeContractKey); - - const settlementModule = this.resolve(settlementContractKey); - const dispatchModule = this.resolve(dispatchContractKey); - const bridgeModule = this.resolve(bridgeContractKey); - - const dispatch = dispatchModule.contractFactory(); - const bridge = bridgeModule.contractFactory(); - const settlement = settlementModule.contractFactory(); - - return { - settlement, - dispatch, - bridge, - }; + public getContractClasses(): GetContracts { + const contracts = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (this.moduleNames as StringKeyOf[]).map((name) => { + const module = this.resolve(name); + return [name, module.contractFactory()]; + }); + return Object.fromEntries(contracts); } public createContracts(addresses: { @@ -142,12 +123,10 @@ export class SettlementContractModule< settlement: SettlementContractType & SmartContract; dispatch: DispatchContractType & SmartContract; } { - const { dispatch, settlement } = this.getContractClasses(); + const { DispatchContract, SettlementContract } = this.getContractClasses(); - // eslint-disable-next-line new-cap - const dispatchInstance = new dispatch(addresses.dispatch); - // eslint-disable-next-line new-cap - const settlementInstance = new settlement(addresses.settlement); + const dispatchInstance = new DispatchContract(addresses.dispatch); + const settlementInstance = new SettlementContract(addresses.settlement); return { dispatch: dispatchInstance, @@ -159,9 +138,8 @@ export class SettlementContractModule< address: PublicKey, tokenId?: Field ): BridgeContractType & SmartContract { - const { bridge } = this.getContractClasses(); + const { BridgeContract } = this.getContractClasses(); - // eslint-disable-next-line new-cap - return new bridge(address, tokenId); + return new BridgeContract(address, tokenId); } } diff --git a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts index 097a40e74..00dd2de4f 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContractProtocolModule.ts @@ -1,7 +1,7 @@ import { injectable } from "tsyringe"; +import { CompileRegistry } from "@proto-kit/common"; import { ContractModule } from "../ContractModule"; -import { CompileRegistry } from "../../compiling/CompileRegistry"; import { BridgeContract, @@ -29,15 +29,15 @@ export class BridgeContractProtocolModule extends ContractModule< BridgeContractBase.args = { withdrawalStatePath: withdrawalStatePathSplit, - SettlementContract: undefined, + SettlementContract: BridgeContractBase.args?.SettlementContract, }; return BridgeContract; } public async compile(registry: CompileRegistry) { - return await registry.compileModule(async (compiler) => ({ - BridgeContract: await BridgeContract.compile(), - })); + return { + BridgeContract: await registry.compile(BridgeContract), + }; } } diff --git a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts index 408161e85..2b7dc32f9 100644 --- a/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/DispatchContractProtocolModule.ts @@ -1,12 +1,12 @@ import { inject, injectable } from "tsyringe"; import { PublicKey } from "o1js"; +import { CompileRegistry } from "@proto-kit/common"; -import { RuntimeLike } from "../../model/RuntimeLike"; +import { RuntimeLike, RuntimeMethodIdMapping } from "../../model/RuntimeLike"; import { ContractModule, SmartContractClassFromInterface, } from "../ContractModule"; -import { CompileRegistry } from "../../compiling/CompileRegistry"; import { DispatchSmartContract, @@ -32,23 +32,42 @@ export class DispatchContractProtocolModule extends ContractModule< .events; } + private checkConfigIntegrity( + incomingMessagesMethods: Record, + runtimeMethodIds: RuntimeMethodIdMapping + ) { + const missing = Object.values(incomingMessagesMethods).filter( + (method) => runtimeMethodIds[method] === undefined + ); + if (missing.length > 0) { + throw new Error( + `Incoming messages config references a unknown methods: [${missing}]` + ); + } + } + public contractFactory(): SmartContractClassFromInterface { const { incomingMessagesMethods } = this.config; const methodIdMappings = this.runtime.methodIdResolver.methodIdMap(); + this.checkConfigIntegrity(incomingMessagesMethods, methodIdMappings); + DispatchSmartContractBase.args = { incomingMessagesPaths: incomingMessagesMethods, methodIdMappings, + settlementContractClass: + DispatchSmartContractBase.args?.settlementContractClass, }; return DispatchSmartContract; } public async compile(registry: CompileRegistry) { - return await registry.compileModule(async (compiler) => ({ - DispatchSmartContract: await compiler.compileContract( - DispatchSmartContract - ), - })); + if (DispatchSmartContractBase.args.settlementContractClass === undefined) { + throw new Error("Reference to Settlement Contract not set"); + } + return { + DispatchSmartContract: await registry.compile(DispatchSmartContract), + }; } } diff --git a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts index a33e6cc6f..b44808080 100644 --- a/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts +++ b/packages/protocol/src/settlement/contracts/SettlementContractProtocolModule.ts @@ -1,5 +1,10 @@ import { inject, injectable, injectAll } from "tsyringe"; -import { VerificationKey } from "o1js"; +import { + ArtifactRecord, + ChildVerificationKeyService, + CompileRegistry, + log, +} from "@proto-kit/common"; import { BlockProvable } from "../../prover/block/BlockProvable"; import { @@ -7,12 +12,9 @@ import { SmartContractClassFromInterface, } from "../ContractModule"; import { ProvableSettlementHook } from "../modularity/ProvableSettlementHook"; -import { CompileRegistry } from "../../compiling/CompileRegistry"; -import { ArtifactRecord } from "../../compiling/AtomicCompileHelper"; import { DispatchSmartContractBase } from "./DispatchSmartContract"; import { - LazyBlockProof, SettlementContractType, SettlementSmartContract, SettlementSmartContractBase, @@ -41,9 +43,9 @@ export class SettlementContractProtocolModule extends ContractModule< @inject("DispatchContract") private readonly dispatchContractModule: DispatchContractProtocolModule, @inject("BridgeContract") - private readonly bridgeContractModule: BridgeContractProtocolModule + private readonly bridgeContractModule: BridgeContractProtocolModule, + private readonly childVerificationKeyService: ChildVerificationKeyService ) { - LazyBlockProof.tag = blockProver.zkProgrammable.zkProgram[0].Proof.tag; super(); } @@ -64,6 +66,7 @@ export class SettlementContractProtocolModule extends ContractModule< BridgeContractVerificationKey: args?.BridgeContractVerificationKey, BridgeContractPermissions: args?.BridgeContractPermissions, signedSettlements: args?.signedSettlements, + ChildVerificationKeyService: this.childVerificationKeyService, }; // Ideally we don't want to have this cyclic dependency, but we have it in the protocol, @@ -79,22 +82,27 @@ export class SettlementContractProtocolModule extends ContractModule< public async compile( registry: CompileRegistry ): Promise { - return await registry.compileModule( - async (compiler, bridgeVk: unknown, blockProverVk: unknown) => { - SettlementSmartContractBase.args.BridgeContractVerificationKey = - // TODO Infer type - bridgeVk as VerificationKey; - - return { - SettlementSmartContract: await compiler.compileContract( - SettlementSmartContract - ), - }; - }, - { - BridgeContract: this.bridgeContractModule, - BlockProver: this.blockProver, - } - ); + // Dependencies + const bridgeArtifact = await this.bridgeContractModule.compile(registry); + + await this.blockProver.compile(registry); + + // Init params + SettlementSmartContractBase.args.BridgeContractVerificationKey = + bridgeArtifact.BridgeContract.verificationKey; + + if (SettlementSmartContractBase.args.signedSettlements === undefined) { + throw new Error( + "Args not fully initialized - make sure to also include the SettlementModule in the sequencer" + ); + } + + log.debug("Compiling Settlement Contract"); + + const artifact = await registry.compile(SettlementSmartContract); + + return { + SettlementSmartContract: artifact, + }; } } diff --git a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts index cf6cb276e..3f1fe412f 100644 --- a/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts +++ b/packages/protocol/src/settlement/contracts/SettlementSmartContract.ts @@ -3,13 +3,13 @@ import { RollupMerkleTree, TypedClass, mapSequential, + ChildVerificationKeyService, } from "@proto-kit/common"; import { AccountUpdate, Bool, Field, method, - Proof, PublicKey, Signature, SmartContract, @@ -24,8 +24,8 @@ import { Struct, Provable, TokenId, + DynamicProof, } from "o1js"; -import { singleton } from "tsyringe"; import { NetworkState } from "../../model/network/NetworkState"; import { BlockHashMerkleTree } from "../../prover/block/accummulators/BlockHashMerkleTree"; @@ -46,7 +46,7 @@ import { UpdateMessagesHashAuth } from "./authorizations/UpdateMessagesHashAuth" /* eslint-disable @typescript-eslint/lines-between-class-members */ -export class LazyBlockProof extends Proof< +export class DynamicBlockProof extends DynamicProof< BlockProverPublicInput, BlockProverPublicOutput > { @@ -54,9 +54,7 @@ export class LazyBlockProof extends Proof< public static publicOutputType = BlockProverPublicOutput; - public static tag: () => { name: string } = () => { - throw new Error("Tag not initialized yet"); - }; + public static maxProofsVerified = 2 as const; } export class TokenMapping extends Struct({ @@ -75,7 +73,7 @@ export interface SettlementContractType { ) => Promise; assertStateRoot: (root: Field) => AccountUpdate; settle: ( - blockProof: LazyBlockProof, + blockProof: DynamicBlockProof, signature: Signature, dispatchContractAddress: PublicKey, publicKey: PublicKey, @@ -93,19 +91,19 @@ export interface SettlementContractType { // Some random prefix for the sequencer signature export const BATCH_SIGNATURE_PREFIX = prefixToField("pk-batchSignature"); -@singleton() -export class SettlementSmartContractStaticArgs { - public args?: { - DispatchContract: TypedClass; - hooks: ProvableSettlementHook[]; - escapeHatchSlotsInterval: number; - BridgeContract: TypedClass & typeof SmartContract; - // Lazily initialized - BridgeContractVerificationKey: VerificationKey | undefined; - BridgeContractPermissions: Permissions | undefined; - signedSettlements: boolean | undefined; - }; -} +// @singleton() +// export class SettlementSmartContractStaticArgs { +// public args?: { +// DispatchContract: TypedClass; +// hooks: ProvableSettlementHook[]; +// escapeHatchSlotsInterval: number; +// BridgeContract: TypedClass & typeof SmartContract; +// // Lazily initialized +// BridgeContractVerificationKey: VerificationKey | undefined; +// BridgeContractPermissions: Permissions | undefined; +// signedSettlements: boolean | undefined; +// }; +// } export abstract class SettlementSmartContractBase extends TokenContractV2 { // This pattern of injecting args into a smartcontract is currently the only @@ -120,10 +118,10 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { BridgeContractVerificationKey: VerificationKey | undefined; BridgeContractPermissions: Permissions | undefined; signedSettlements: boolean | undefined; + ChildVerificationKeyService: ChildVerificationKeyService; }; events = { - "announce-private-key": PrivateKey, "token-bridge-deployed": TokenMapping, }; @@ -177,6 +175,28 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { const BridgeContractClass = args.BridgeContract; const bridgeContract = new BridgeContractClass(address, tokenId); + const { + BridgeContractVerificationKey, + signedSettlements, + BridgeContractPermissions, + } = args; + + if ( + signedSettlements === undefined || + BridgeContractPermissions === undefined + ) { + throw new Error( + "Static arguments for SettlementSmartContract not initialized" + ); + } + + if ( + BridgeContractVerificationKey !== undefined && + !BridgeContractVerificationKey.hash.isConstant() + ) { + throw new Error("Bridge contract verification key has to be constants"); + } + // This function is not a zkapps method, therefore it will be part of this methods execution // The returning account update (owner.self) is therefore part of this circuit and is assertable const deploymentAccountUpdate = await bridgeContract.deployProvable( @@ -254,11 +274,10 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { ); contractKey.toPublicKey().assertEquals(this.address); - this.emitEvent("announce-private-key", contractKey); } protected async settleBase( - blockProof: LazyBlockProof, + blockProof: DynamicBlockProof, signature: Signature, dispatchContractAddress: PublicKey, publicKey: PublicKey, @@ -266,8 +285,17 @@ export abstract class SettlementSmartContractBase extends TokenContractV2 { outputNetworkState: NetworkState, newPromisedMessagesHash: Field ) { + // Brought in as a constant + const blockProofVk = + SettlementSmartContractBase.args.ChildVerificationKeyService.getVerificationKey( + "BlockProver" + ); + if (!blockProofVk.hash.isConstant()) { + throw new Error("Sanity check - vk hash has to be constant"); + } + // Verify the blockproof - blockProof.verify(); + blockProof.verify(blockProofVk); // Get and assert on-chain values const stateRoot = this.stateRoot.getAndRequireEquals(); @@ -447,7 +475,7 @@ export class SettlementSmartContract @method public async settle( - blockProof: LazyBlockProof, + blockProof: DynamicBlockProof, signature: Signature, dispatchContractAddress: PublicKey, publicKey: PublicKey, diff --git a/packages/protocol/src/settlement/modularity/types.ts b/packages/protocol/src/settlement/modularity/types.ts new file mode 100644 index 000000000..8f7c706e6 --- /dev/null +++ b/packages/protocol/src/settlement/modularity/types.ts @@ -0,0 +1,22 @@ +import { TypedClass } from "@proto-kit/common"; + +import { + ContractModule, + SmartContractClassFromInterface, +} from "../ContractModule"; +import type { SettlementModulesRecord } from "../SettlementContractModule"; + +export type InferContractType< + Module extends TypedClass>, +> = + Module extends TypedClass + ? ConcreteModule extends ContractModule + ? Contract + : never + : never; + +export type GetContracts = { + [Key in keyof SettlementModules]: SmartContractClassFromInterface< + InferContractType + >; +}; diff --git a/packages/protocol/test/compiling/types.ts b/packages/protocol/test/compiling/types.ts new file mode 100644 index 000000000..fe1f231a0 --- /dev/null +++ b/packages/protocol/test/compiling/types.ts @@ -0,0 +1,28 @@ +import { Field, VerificationKey } from "o1js"; + +import { CompileRegistry, InferDependencyArtifacts } from "../../src"; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +type TestModule = { + compile( + registry: CompileRegistry + ): Promise<{ bar: { verificationKey: VerificationKey } }>; +}; + +type Inferred = InferDependencyArtifacts<{ foo: TestModule }>; +const typeAssignmentTest: Inferred = { + foo: { + bar: { + verificationKey: { + data: "", + hash: Field(1), + }, + }, + }, +}; + +const typePropertyTest: Inferred["foo"]["bar"]["verificationKey"] extends VerificationKey + ? true + : false = true; + +/* eslint-enable @typescript-eslint/no-unused-vars */ diff --git a/packages/protocol/test/modularity/types.ts b/packages/protocol/test/modularity/types.ts new file mode 100644 index 000000000..233214b11 --- /dev/null +++ b/packages/protocol/test/modularity/types.ts @@ -0,0 +1,35 @@ +import { SmartContract } from "o1js"; +import { TypedClass } from "@proto-kit/common"; + +import { GetContracts } from "../../src/settlement/modularity/types"; +import { + BridgeContractType, + MandatorySettlementModulesRecord, + SettlementContractType, +} from "../../src"; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +// Goal of this "test" is that it compiles. By compiling this file checks that +// certain types are inferred correctly + +type Inferred = GetContracts; + +// Get inferred Bridge Type +type Bridge = Inferred["BridgeContract"]; +// Get inferred Settlement Contract Type +type Settlement = Inferred["SettlementContract"]; + +// Check that the Bridge type is of the correct type +const bridgeSuccessful: Bridge extends TypedClass< + SmartContract & BridgeContractType +> + ? true + : false = true; + +const settlementSuccessful: Settlement extends TypedClass< + SmartContract & SettlementContractType +> + ? true + : false = true; + +/* eslint-enable @typescript-eslint/no-unused-vars */ diff --git a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts index f27ebd36a..f7da13a90 100644 --- a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts @@ -14,12 +14,14 @@ import { StateTransitionProvable, VKTreeWitness, DynamicRuntimeProof, - CompileRegistry, } from "@proto-kit/protocol"; import { Field, Proof } from "o1js"; import { Runtime } from "@proto-kit/module"; import { inject, injectable, Lifecycle, scoped } from "tsyringe"; -import { ProvableMethodExecutionContext } from "@proto-kit/common"; +import { + ProvableMethodExecutionContext, + CompileRegistry, +} from "@proto-kit/common"; import { PairProofTaskSerializer, diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index b3a78c479..7b8938251 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -1,14 +1,17 @@ import { inject, injectable, Lifecycle, scoped } from "tsyringe"; import { Runtime } from "@proto-kit/module"; -import { log, mapSequential } from "@proto-kit/common"; import { - MandatorySettlementModulesRecord, - Protocol, - SettlementContractModule, + log, + mapSequential, + StringKeyOf, ArtifactRecord, CompileRegistry, CompilableModule, - BridgeContractProtocolModule, +} from "@proto-kit/common"; +import { + MandatorySettlementModulesRecord, + Protocol, + SettlementContractModule, RuntimeVerificationKeyRootService, } from "@proto-kit/protocol"; @@ -55,6 +58,8 @@ export class ArtifactRecordSerializer { } public fromJSON(json: SerializedArtifactRecord): ArtifactRecord { + if (json === undefined || json === null) return {}; + return Object.keys(json).reduce((accum, key) => { return { ...accum, @@ -85,7 +90,23 @@ export class CircuitCompilerTask extends UnpreparingTask< } public inputSerializer(): TaskSerializer { - return new SimpleJSONSerializer(); + const serializer = new ArtifactRecordSerializer(); + return { + toJSON: (input) => + JSON.stringify({ + targets: input.targets, + root: input.runtimeVKRoot, + existingArtifacts: serializer.toJSON(input.existingArtifacts), + }), + fromJSON: (input) => { + const json = JSON.parse(input); + return { + targets: json.targets, + root: json.runtimeVKRoot, + existingArtifacts: serializer.fromJSON(json.existingArtifacts), + }; + }, + }; } public resultSerializer(): TaskSerializer { @@ -105,13 +126,20 @@ export class CircuitCompilerTask extends UnpreparingTask< SettlementContractModule >("SettlementContractModule"); - const bridge = settlementModule.resolveOrFail( - "BridgeContract", - BridgeContractProtocolModule - ); - return { - bridge, - }; + // Needed so that all contractFactory functions are called, because + // they set static args on the contracts + settlementModule.getContractClasses(); + + const moduleNames = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + settlementModule.moduleNames as StringKeyOf[]; + + const modules = moduleNames.map((name) => [ + `Settlement.${name}`, + settlementModule.resolve(name), + ]); + + return Object.fromEntries(modules); } return {}; } @@ -136,7 +164,7 @@ export class CircuitCompilerTask extends UnpreparingTask< ...this.getSettlementTargets(), }; - const msg = `Compiling targets ${targets}`; + const msg = `Compiling targets [${input.targets}]`; log.time(msg); await mapSequential(input.targets, async (target) => { if (target in targets) { @@ -150,6 +178,9 @@ export class CircuitCompilerTask extends UnpreparingTask< }); log.timeEnd.info(msg); - return this.compileRegistry.getAllArtifacts(); + const newEntries = Object.entries( + this.compileRegistry.getAllArtifacts() + ).filter(([key]) => !(key in input.existingArtifacts)); + return Object.fromEntries(newEntries); } } diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 3f0693110..b58b04f29 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -10,10 +10,12 @@ import { StateTransitionProvable, BlockHashMerkleTreeWitness, MandatoryProtocolModulesRecord, - CompileRegistry, } from "@proto-kit/protocol"; import { Proof } from "o1js"; -import { ProvableMethodExecutionContext } from "@proto-kit/common"; +import { + ProvableMethodExecutionContext, + CompileRegistry, +} from "@proto-kit/common"; import { Task, TaskSerializer } from "../../../worker/flow/Task"; import { ProofTaskSerializer } from "../../../helpers/utils"; diff --git a/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts index 66a224b83..e74f3451b 100644 --- a/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts @@ -5,11 +5,11 @@ import { Runtime, } from "@proto-kit/module"; import { - CompileRegistry, MethodPublicOutput, RuntimeMethodExecutionContext, } from "@proto-kit/protocol"; import { Proof } from "o1js"; +import { CompileRegistry } from "@proto-kit/common"; import { Task, TaskSerializer } from "../../../worker/flow/Task"; import { ProofTaskSerializer } from "../../../helpers/utils"; diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index 757b8b0f7..eacbecf8e 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -8,9 +8,12 @@ import { StateTransitionProvableBatch, StateTransitionProverPublicInput, StateTransitionProverPublicOutput, - CompileRegistry, } from "@proto-kit/protocol"; -import { log, ProvableMethodExecutionContext } from "@proto-kit/common"; +import { + log, + ProvableMethodExecutionContext, + CompileRegistry, +} from "@proto-kit/common"; import { Task, TaskSerializer } from "../../../worker/flow/Task"; import { diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 8915311e8..6198735e1 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -1,13 +1,16 @@ import { inject } from "tsyringe"; import { - ArtifactRecord, - CompileRegistry, MandatoryProtocolModulesRecord, Protocol, RuntimeVerificationKeyRootService, SettlementSmartContractBase, } from "@proto-kit/protocol"; -import { CompileArtifact, log } from "@proto-kit/common"; +import { + log, + ArtifactRecord, + ChildVerificationKeyService, + CompileRegistry, +} from "@proto-kit/common"; import { Flow, FlowCreator } from "../worker/flow/Flow"; import { WorkerRegistrationFlow } from "../worker/worker/startup/WorkerRegistrationFlow"; @@ -98,7 +101,7 @@ export class SequencerStartupModule extends SequencerModule { this.compileTask, { existingArtifacts: {}, - targets: ["bridge"], + targets: ["Settlement.BridgeContract"], runtimeVKRoot: undefined, }, async (result) => { @@ -114,6 +117,10 @@ export class SequencerStartupModule extends SequencerModule { public async start() { const flow = this.flowCreator.createFlow("compile-circuits", {}); + this.protocol.dependencyContainer + .resolve(ChildVerificationKeyService) + .setCompileRegistry(this.compileRegistry); + log.info("Compiling Protocol circuits, this can take a few minutes"); const root = await this.compileRuntime(flow); @@ -129,7 +136,12 @@ export class SequencerStartupModule extends SequencerModule { bridgeVk.verificationKey; } - // TODO Add vk record to start params + const record = await this.pushCompileTask(flow, { + existingArtifacts: this.compileRegistry.getAllArtifacts(), + targets: ["Settlement.SettlementContract"], + }); + + this.compileRegistry.addArtifactsRaw(record); await this.registrationFlow.start({ runtimeVerificationKeyRoot: root, diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 6484bcfdd..1dd61dd37 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -8,6 +8,7 @@ import { MandatoryProtocolModulesRecord, BlockProverPublicOutput, SettlementSmartContractBase, + DynamicBlockProof, } from "@proto-kit/protocol"; import { AccountUpdate, @@ -26,6 +27,7 @@ import { log, AreProofsEnabled, DependencyFactory, + CompileRegistry, } from "@proto-kit/common"; import truncate from "lodash/truncate"; @@ -94,7 +96,8 @@ export class SettlementModule private readonly transactionSender: MinaTransactionSender, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @inject("FeeStrategy") - private readonly feeStrategy: FeeStrategy + private readonly feeStrategy: FeeStrategy, + private readonly compileRegistry: CompileRegistry ) { super(); this.utils = new SettlementUtils(areProofsEnabled, baseLayer); @@ -201,6 +204,8 @@ export class SettlementModule .getBlockProofSerializer() .fromJSONProof(batch.proof); + const dynamicBlockProof = DynamicBlockProof.fromProof(blockProof); + const tx = await Mina.transaction( { sender: feepayer.toPublicKey(), @@ -210,7 +215,7 @@ export class SettlementModule }, async () => { await settlement.settle( - blockProof, + dynamicBlockProof, signature, dispatch.address, feepayer.toPublicKey(), @@ -248,6 +253,8 @@ export class SettlementModule const nonce = options?.nonce ?? 0; + // const verificationKey: + const sm = this.protocol.dependencyContainer.resolve< SettlementContractModule >("SettlementContractModule"); @@ -378,8 +385,6 @@ export class SettlementModule public async start(): Promise { const contractArgs = SettlementSmartContractBase.args; - // const dummyVk = MOCK_VERIFICATION_KEY; - SettlementSmartContractBase.args = { ...contractArgs, signedSettlements: this.utils.isSignedSettlement(), @@ -388,11 +393,6 @@ export class SettlementModule ? new SignedSettlementPermissions() : new ProvenSettlementPermissions() ).bridgeContractMina(), - // BridgeContractVerificationKey: this.utils.isSignedSettlement() - // ? undefined - // : dummyVk, }; - - // TODO Add task to compute verification key } } diff --git a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts index b56118797..4568b8385 100644 --- a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts +++ b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts @@ -3,16 +3,15 @@ import { MOCK_PROOF, AreProofsEnabled, log, + CompileRegistry, } from "@proto-kit/common"; import { - ContractModule, MandatoryProtocolModulesRecord, MandatorySettlementModulesRecord, Protocol, ReturnType, SettlementContractModule, Subclass, - CompileRegistry, } from "@proto-kit/protocol"; import { addCachedAccount, @@ -368,24 +367,42 @@ export class SettlementProvingTask } public async prepare(): Promise { + const { settlementContractModule } = this; // Guard in case the task is configured but settlement is not - if (this.settlementContractModule === undefined) { - return; + if (settlementContractModule === undefined) { + throw new Error( + "Settlement task is configured, but Settlement Contracts aren't" + ); } const contractClasses: Record = {}; - for (const key of this.settlementContractModule.moduleNames) { - const module: ContractModule = - this.settlementContractModule.resolve( - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - key as keyof MandatorySettlementModulesRecord - ); - + const modules = settlementContractModule.moduleNames.map( + (key) => + [ + key, + settlementContractModule.resolve( + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + key as keyof MandatorySettlementModulesRecord + ), + ] as const + ); + + // First, create all contract classes (with static args), then compile them + for (const [key, module] of modules) { contractClasses[key] = module.contractFactory(); + } - // eslint-disable-next-line no-await-in-loop - await module.compile(this.compileRegistry); + try { + for (const [key, module] of modules) { + log.debug(`Compiling Settlement Module ${key}`); + + // eslint-disable-next-line no-await-in-loop + await module.compile(this.compileRegistry); + } + } catch (e) { + console.error(e); + throw e; } this.contractRegistry = new ContractRegistry(contractClasses); diff --git a/packages/sequencer/src/worker/worker/FlowTaskWorker.ts b/packages/sequencer/src/worker/worker/FlowTaskWorker.ts index 704bd5e44..8db8a0c28 100644 --- a/packages/sequencer/src/worker/worker/FlowTaskWorker.ts +++ b/packages/sequencer/src/worker/worker/FlowTaskWorker.ts @@ -91,6 +91,7 @@ export class FlowTaskWorker[]> log.debug(`Preparing task ${task.constructor.name}`); // eslint-disable-next-line no-await-in-loop await task.prepare(); + log.trace(`${task.constructor.name} prepared`); } const newWorkers = Object.fromEntries( diff --git a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts index 0ce7864e8..53c7966c1 100644 --- a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts +++ b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts @@ -1,9 +1,12 @@ -import { log, noop } from "@proto-kit/common"; -import { inject, injectable } from "tsyringe"; import { + log, + noop, ArtifactRecord, ChildVerificationKeyService, CompileRegistry, +} from "@proto-kit/common"; +import { inject, injectable } from "tsyringe"; +import { Protocol, RuntimeVerificationKeyRootService, SettlementSmartContractBase, @@ -13,9 +16,9 @@ import { VerificationKey } from "o1js"; import { Task } from "../../flow/Task"; import { AbstractStartupTask } from "../../flow/AbstractStartupTask"; import { VerificationKeySerializer } from "../../../protocol/production/helpers/VerificationKeySerializer"; +import { ArtifactRecordSerializer } from "../../../protocol/production/tasks/CircuitCompilerTask"; import { CloseWorkerError } from "./CloseWorkerError"; -import { ArtifactRecordSerializer } from "../../../protocol/production/tasks/CircuitCompilerTask"; export type WorkerStartupPayload = { runtimeVerificationKeyRoot: bigint; @@ -106,7 +109,9 @@ export class WorkerRegistrationTask jsonObject.bridgeContractVerificationKey ) : undefined, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment compiledArtifacts: artifactSerializer.fromJSON( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument jsonObject.compiledArtifacts ), }; diff --git a/packages/sequencer/test/integration/BlockProductionSize.test.ts b/packages/sequencer/test/integration/BlockProductionSize.test.ts index 7ca9fd89e..19217559b 100644 --- a/packages/sequencer/test/integration/BlockProductionSize.test.ts +++ b/packages/sequencer/test/integration/BlockProductionSize.test.ts @@ -97,7 +97,7 @@ describe("block limit", () => { }); // Start AppChain - await app.start(container.createChildContainer()); + await app.start(false, container.createChildContainer()); ({ runtime, sequencer } = app); diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 64bf1654b..34bfef04e 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -1,17 +1,39 @@ import "reflect-metadata"; -import { expectDefined, log } from "@proto-kit/common"; +import { + expectDefined, + log, + MOCK_VERIFICATION_KEY, + ChildVerificationKeyService, + CompileRegistry, +} from "@proto-kit/common"; import { Runtime } from "@proto-kit/module"; -import { Protocol } from "@proto-kit/protocol"; +import { + BridgeContract, + DispatchSmartContract, + Protocol, + SettlementContractModule, + SettlementSmartContract, + SettlementSmartContractBase, +} from "@proto-kit/protocol"; import { VanillaProtocolModules } from "@proto-kit/library"; -import { AppChain } from "@proto-kit/sdk"; +import { AppChain, InMemoryAreProofsEnabled } from "@proto-kit/sdk"; import { container } from "tsyringe"; -import { PrivateKey, UInt64 } from "o1js"; +import { PrivateKey, UInt64, VerificationKey } from "o1js"; import { testingSequencerFromModules } from "../TestingSequencer"; import { ProtocolStateTestHook } from "./mocks/ProtocolStateTestHook"; import { BlockTestService } from "./services/BlockTestService"; import { ProvenBalance } from "./mocks/ProvenBalance"; +import { FungibleTokenContractModule } from "../../src/settlement/utils/FungibleTokenContractModule"; +import { FungibleTokenAdminContractModule } from "../../src/settlement/utils/FungibleTokenAdminContractModule"; +import { + MinaBaseLayer, + ProvenSettlementPermissions, + SettlementModule, + SettlementProvingTask, + WithdrawalQueue, +} from "../../src"; const timeout = 300000; @@ -24,22 +46,37 @@ describe("Proven", () => { log.setLevel(log.levels.DEBUG); const runtimeClass = Runtime.from({ modules: { - Balance: ProvenBalance, + Balances: ProvenBalance, }, config: { - Balance: {}, + Balances: {}, }, }); - const sequencerClass = testingSequencerFromModules({}); + const sequencerClass = testingSequencerFromModules( + { + BaseLayer: MinaBaseLayer, + SettlementModule, + OutgoingMessageQueue: WithdrawalQueue, + }, + { + SettlementProvingTask, + } + ); // TODO Analyze how we can get rid of the library import for mandatory modules const protocolClass = Protocol.from({ - modules: VanillaProtocolModules.mandatoryModules({ - ProtocolStateTestHook, - // ProtocolStateTestHook2, - }), + modules: { + ...VanillaProtocolModules.mandatoryModules({ + ProtocolStateTestHook, + // ProtocolStateTestHook2, + }), + SettlementContractModule: SettlementContractModule.with({ + // FungibleToken: FungibleTokenContractModule, + // FungibleTokenAdmin: FungibleTokenAdminContractModule, + }), + }, // modules: VanillaProtocolModules.with({}), }); @@ -58,13 +95,19 @@ describe("Proven", () => { BatchProducerModule: {}, BlockProducerModule: {}, LocalTaskWorkerModule: {}, - BaseLayer: {}, TaskQueue: {}, FeeStrategy: {}, SequencerStartupModule: {}, + BaseLayer: { + network: { + type: "local", + }, + }, + SettlementModule: {}, + OutgoingMessageQueue: {}, }, Runtime: { - Balance: {}, + Balances: {}, }, Protocol: { AccountState: {}, @@ -73,6 +116,18 @@ describe("Proven", () => { BlockHeight: {}, LastStateRoot: {}, ProtocolStateTestHook: {}, + SettlementContractModule: { + SettlementContract: {}, + BridgeContract: { + withdrawalStatePath: "Withdrawals.withdrawals", + withdrawalEventName: "withdrawal", + }, + DispatchContract: { + incomingMessagesMethods: { + deposit: "Balances.deposit", + }, + }, + }, // ProtocolStateTestHook2: {}, }, }); @@ -91,6 +146,36 @@ describe("Proven", () => { timeout ); + it.skip("Hello", async () => { + try { + const vkService = new ChildVerificationKeyService(); + const proofs = new InMemoryAreProofsEnabled(); + proofs.setProofsEnabled(true); + const registry = new CompileRegistry(proofs); + registry.addArtifactsRaw({ + BlockProver: { + verificationKey: MOCK_VERIFICATION_KEY, + }, + }); + vkService.setCompileRegistry(registry); + SettlementSmartContractBase.args = { + DispatchContract: DispatchSmartContract, + ChildVerificationKeyService: vkService, + BridgeContractVerificationKey: MOCK_VERIFICATION_KEY, + signedSettlements: false, + BridgeContract: BridgeContract, + hooks: [], + BridgeContractPermissions: + new ProvenSettlementPermissions().bridgeContractMina(), + escapeHatchSlotsInterval: 1000, + }; + const vk = await SettlementSmartContract.compile(); + console.log(vk.verificationKey); + } catch (e) { + console.error(e); + } + }, 500000); + it( "should produce simple block", async () => { @@ -101,7 +186,7 @@ describe("Proven", () => { const privateKey = PrivateKey.random(); await test.addTransaction({ - method: ["Balance", "addBalance"], + method: ["Balances", "addBalance"], privateKey, args: [PrivateKey.random().toPublicKey(), UInt64.from(100)], }); diff --git a/packages/sequencer/test/integration/mocks/ProvenBalance.ts b/packages/sequencer/test/integration/mocks/ProvenBalance.ts index ff6cbbeca..f579993ff 100644 --- a/packages/sequencer/test/integration/mocks/ProvenBalance.ts +++ b/packages/sequencer/test/integration/mocks/ProvenBalance.ts @@ -1,4 +1,5 @@ import { + runtimeMessage, runtimeMethod, runtimeModule, RuntimeModule, @@ -7,7 +8,7 @@ import { import { log, Presets } from "@proto-kit/common"; import { PublicKey, UInt64 } from "o1js"; import { Admin } from "@proto-kit/module/test/modules/Admin"; -import { State, StateMap } from "@proto-kit/protocol"; +import { Deposit, State, StateMap } from "@proto-kit/protocol"; @runtimeModule() export class ProvenBalance extends RuntimeModule { @@ -36,4 +37,9 @@ export class ProvenBalance extends RuntimeModule { const newBalance = balance.orElse(UInt64.zero).add(value); await this.balances.set(address, newBalance); } + + @runtimeMessage() + public async deposit(deposit: Deposit) { + await this.addBalance(deposit.address, deposit.amount); + } } From b58433cdedc0bb0af586c860b0f10fa6aad98a68 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 8 Nov 2024 02:00:04 +0700 Subject: [PATCH 6/7] Refactoring --- .../common/src/compiling/CompileRegistry.ts | 43 ------------------- packages/common/src/utils.ts | 6 +++ .../modularity/ProvableSettlementHook.ts | 5 ++- .../production/tasks/CircuitCompilerTask.ts | 32 +++++++------- .../src/sequencer/SequencerStartupModule.ts | 11 +++-- .../settlement/tasks/SettlementProvingTask.ts | 13 ++---- .../worker/startup/WorkerRegistrationTask.ts | 26 +++++++---- .../sequencer/test/integration/Proven.test.ts | 12 +++--- 8 files changed, 58 insertions(+), 90 deletions(-) diff --git a/packages/common/src/compiling/CompileRegistry.ts b/packages/common/src/compiling/CompileRegistry.ts index adc91fa40..6c2c84da5 100644 --- a/packages/common/src/compiling/CompileRegistry.ts +++ b/packages/common/src/compiling/CompileRegistry.ts @@ -41,8 +41,6 @@ export class CompileRegistry { private artifacts: ArtifactRecord = {}; - // private cachedModuleOutputs: Record = {}; - // TODO Add possibility to force recompilation for non-sideloaded dependencies public async compile(target: CompileTarget) { @@ -54,47 +52,6 @@ export class CompileRegistry { return this.artifacts[target.name]; } - // public async compileModule< - // ReturnType extends ArtifactRecord, - // Dependencies extends Record, - // >( - // compile: ( - // compiler: AtomicCompileHelper, - // args: InferDependencyArtifacts - // ) => Promise, - // dependencies?: Dependencies - // ): Promise { - // const collectedArtifacts = await mapSequential( - // Object.entries(dependencies ?? {}), - // async ([depName, dep]) => { - // if (this.cachedModuleOutputs[depName] !== undefined) { - // return [depName, this.cachedModuleOutputs[depName]]; - // } - // const artifact = await dep.compile(this); - // if (artifact !== undefined) { - // this.artifacts = { - // ...this.artifacts, - // ...artifact, - // }; - // } - // this.cachedModuleOutputs[depName] = artifact; - // return [depName, artifact]; - // } - // ); - // - // const artifacts = await compile( - // this.compile, - // Object.fromEntries(collectedArtifacts) - // ); - // - // this.artifacts = { - // ...this.artifacts, - // ...artifacts, - // }; - // - // return artifacts; - // } - public getArtifact(name: string) { if (this.artifacts[name] === undefined) { throw new Error( diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 32a3530f5..23b351d0c 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -182,3 +182,9 @@ export function isSubtypeOfName( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return isSubtypeOfName(Object.getPrototypeOf(clas), name); } + +// TODO Eventually, replace this by a schema validation library +export function safeParseJson(json: string) { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return JSON.parse(json) as T; +} diff --git a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts index 1a579ea34..ecb117129 100644 --- a/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts +++ b/packages/protocol/src/settlement/modularity/ProvableSettlementHook.ts @@ -1,10 +1,13 @@ import { Field, PublicKey, UInt32 } from "o1js"; +import { InferProofBase } from "@proto-kit/common"; import { ProtocolModule } from "../../protocol/ProtocolModule"; import { NetworkState } from "../../model/network/NetworkState"; import type { BlockProof } from "../../prover/block/BlockProver"; import type { SettlementSmartContractBase } from "../contracts/SettlementSmartContract"; +export type InputBlockProof = InferProofBase; + export type SettlementStateRecord = { sequencerKey: PublicKey; lastSettlementL1BlockHeight: UInt32; @@ -15,7 +18,7 @@ export type SettlementStateRecord = { }; export type SettlementHookInputs = { - blockProof: BlockProof; + blockProof: InputBlockProof; fromNetworkState: NetworkState; toNetworkState: NetworkState; newPromisedMessagesHash: Field; diff --git a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts index 7b8938251..5d02c5e8f 100644 --- a/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/CircuitCompilerTask.ts @@ -7,6 +7,7 @@ import { ArtifactRecord, CompileRegistry, CompilableModule, + safeParseJson, } from "@proto-kit/common"; import { MandatorySettlementModulesRecord, @@ -25,21 +26,11 @@ export type CompilerTaskParams = { runtimeVKRoot?: string; }; -type SerializedArtifactRecord = Record< +export type SerializedArtifactRecord = Record< string, { verificationKey: { hash: string; data: string } } >; -export class SimpleJSONSerializer implements TaskSerializer { - public toJSON(parameters: Type): string { - return JSON.stringify(parameters); - } - - public fromJSON(json: string): Type { - return JSON.parse(json) as Type; - } -} - export class ArtifactRecordSerializer { public toJSON(input: ArtifactRecord): SerializedArtifactRecord { const temp: SerializedArtifactRecord = Object.keys( @@ -69,7 +60,7 @@ export class ArtifactRecordSerializer { ), }, }; - }, {} as ArtifactRecord); + }, {}); } } @@ -90,19 +81,25 @@ export class CircuitCompilerTask extends UnpreparingTask< } public inputSerializer(): TaskSerializer { + type CompilerTaskParamsJSON = { + targets: string[]; + runtimeVKRoot?: string; + existingArtifacts: SerializedArtifactRecord; + }; + const serializer = new ArtifactRecordSerializer(); return { toJSON: (input) => JSON.stringify({ targets: input.targets, - root: input.runtimeVKRoot, + runtimeVKRoot: input.runtimeVKRoot, existingArtifacts: serializer.toJSON(input.existingArtifacts), - }), + } satisfies CompilerTaskParamsJSON), fromJSON: (input) => { - const json = JSON.parse(input); + const json = safeParseJson(input); return { targets: json.targets, - root: json.runtimeVKRoot, + runtimeVKRoot: json.runtimeVKRoot, existingArtifacts: serializer.fromJSON(json.existingArtifacts), }; }, @@ -113,7 +110,8 @@ export class CircuitCompilerTask extends UnpreparingTask< const serializer = new ArtifactRecordSerializer(); return { toJSON: (input) => JSON.stringify(serializer.toJSON(input)), - fromJSON: (input) => serializer.fromJSON(JSON.parse(input)), + fromJSON: (input) => + serializer.fromJSON(safeParseJson(input)), }; } diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 6198735e1..0c9b4a9c7 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -91,8 +91,8 @@ export class SequencerStartupModule extends SequencerModule { targets: ["protocol"], runtimeVKRoot: undefined, }, - async (result) => { - results.protocol = result; + async (protocolResult) => { + results.protocol = protocolResult; resolveIfPossible(); } ); @@ -104,8 +104,8 @@ export class SequencerStartupModule extends SequencerModule { targets: ["Settlement.BridgeContract"], runtimeVKRoot: undefined, }, - async (result) => { - results.bridge = result; + async (bridgeResult) => { + results.bridge = bridgeResult; resolveIfPossible(); } ); @@ -143,6 +143,9 @@ export class SequencerStartupModule extends SequencerModule { this.compileRegistry.addArtifactsRaw(record); + // TODO Compile all contracts and retrieve artifacts to enable crafting of + // the deployments - edit: can also be done on-demand with the CompileTask + await this.registrationFlow.start({ runtimeVerificationKeyRoot: root, bridgeContractVerificationKey: bridgeVk?.verificationKey, diff --git a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts index 4568b8385..38782a60b 100644 --- a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts +++ b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts @@ -393,16 +393,11 @@ export class SettlementProvingTask contractClasses[key] = module.contractFactory(); } - try { - for (const [key, module] of modules) { - log.debug(`Compiling Settlement Module ${key}`); + for (const [key, module] of modules) { + log.debug(`Compiling Settlement Module ${key}`); - // eslint-disable-next-line no-await-in-loop - await module.compile(this.compileRegistry); - } - } catch (e) { - console.error(e); - throw e; + // eslint-disable-next-line no-await-in-loop + await module.compile(this.compileRegistry); } this.contractRegistry = new ContractRegistry(contractClasses); diff --git a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts index 53c7966c1..da0858839 100644 --- a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts +++ b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationTask.ts @@ -4,6 +4,7 @@ import { ArtifactRecord, ChildVerificationKeyService, CompileRegistry, + safeParseJson, } from "@proto-kit/common"; import { inject, injectable } from "tsyringe"; import { @@ -15,8 +16,14 @@ import { VerificationKey } from "o1js"; import { Task } from "../../flow/Task"; import { AbstractStartupTask } from "../../flow/AbstractStartupTask"; -import { VerificationKeySerializer } from "../../../protocol/production/helpers/VerificationKeySerializer"; -import { ArtifactRecordSerializer } from "../../../protocol/production/tasks/CircuitCompilerTask"; +import { + VerificationKeyJSON, + VerificationKeySerializer, +} from "../../../protocol/production/helpers/VerificationKeySerializer"; +import { + ArtifactRecordSerializer, + SerializedArtifactRecord, +} from "../../../protocol/production/tasks/CircuitCompilerTask"; import { CloseWorkerError } from "./CloseWorkerError"; @@ -76,6 +83,12 @@ export class WorkerRegistrationTask } public inputSerializer() { + type WorkerStartupPayloadJSON = { + runtimeVerificationKeyRoot: string; + bridgeContractVerificationKey: VerificationKeyJSON | undefined; + compiledArtifacts: SerializedArtifactRecord; + }; + const artifactSerializer = new ArtifactRecordSerializer(); return { toJSON: (payload: WorkerStartupPayload) => { @@ -91,27 +104,22 @@ export class WorkerRegistrationTask compiledArtifacts: artifactSerializer.toJSON( payload.compiledArtifacts ), - }); + } satisfies WorkerStartupPayloadJSON); }, fromJSON: (payload: string) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const jsonObject = JSON.parse(payload); + const jsonObject = safeParseJson(payload); return { runtimeVerificationKeyRoot: BigInt( - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument jsonObject.runtimeVerificationKeyRoot ), bridgeContractVerificationKey: jsonObject.bridgeContractVerificationKey !== undefined ? VerificationKeySerializer.fromJSON( - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument jsonObject.bridgeContractVerificationKey ) : undefined, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment compiledArtifacts: artifactSerializer.fromJSON( - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument jsonObject.compiledArtifacts ), }; diff --git a/packages/sequencer/test/integration/Proven.test.ts b/packages/sequencer/test/integration/Proven.test.ts index 34bfef04e..4f0195b74 100644 --- a/packages/sequencer/test/integration/Proven.test.ts +++ b/packages/sequencer/test/integration/Proven.test.ts @@ -18,15 +18,9 @@ import { import { VanillaProtocolModules } from "@proto-kit/library"; import { AppChain, InMemoryAreProofsEnabled } from "@proto-kit/sdk"; import { container } from "tsyringe"; -import { PrivateKey, UInt64, VerificationKey } from "o1js"; +import { PrivateKey, UInt64 } from "o1js"; import { testingSequencerFromModules } from "../TestingSequencer"; - -import { ProtocolStateTestHook } from "./mocks/ProtocolStateTestHook"; -import { BlockTestService } from "./services/BlockTestService"; -import { ProvenBalance } from "./mocks/ProvenBalance"; -import { FungibleTokenContractModule } from "../../src/settlement/utils/FungibleTokenContractModule"; -import { FungibleTokenAdminContractModule } from "../../src/settlement/utils/FungibleTokenAdminContractModule"; import { MinaBaseLayer, ProvenSettlementPermissions, @@ -35,6 +29,10 @@ import { WithdrawalQueue, } from "../../src"; +import { ProtocolStateTestHook } from "./mocks/ProtocolStateTestHook"; +import { BlockTestService } from "./services/BlockTestService"; +import { ProvenBalance } from "./mocks/ProvenBalance"; + const timeout = 300000; describe("Proven", () => { From 4d63b5a4431134c6691253966f14b716830cc63d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 8 Nov 2024 16:48:48 +0700 Subject: [PATCH 7/7] Cleanup --- packages/common/src/compiling/CompileRegistry.ts | 14 -------------- .../test-integration/SequencerRestart.test.ts | 2 +- .../sdk/test/networkstate/NetworkState.test.ts | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/common/src/compiling/CompileRegistry.ts b/packages/common/src/compiling/CompileRegistry.ts index 6c2c84da5..c43c50a94 100644 --- a/packages/common/src/compiling/CompileRegistry.ts +++ b/packages/common/src/compiling/CompileRegistry.ts @@ -7,21 +7,7 @@ import { AtomicCompileHelper, CompileTarget, } from "./AtomicCompileHelper"; -import { CompilableModule } from "./CompilableModule"; -interface GenericCompilableModule { - compile(registry: CompileRegistry): Promise; -} - -export type InferDependencyArtifacts< - Dependencies extends Record, -> = { - [Key in keyof Dependencies]: Dependencies[Key] extends GenericCompilableModule< - infer Artifact - > - ? Artifact - : void; -}; /** * The CompileRegistry compiles "compilable modules" * (i.e. zkprograms, contracts or contractmodules) diff --git a/packages/persistance/test-integration/SequencerRestart.test.ts b/packages/persistance/test-integration/SequencerRestart.test.ts index dfb7b9987..b3e701a80 100644 --- a/packages/persistance/test-integration/SequencerRestart.test.ts +++ b/packages/persistance/test-integration/SequencerRestart.test.ts @@ -32,7 +32,7 @@ describe("sequencer restart", () => { }, }); - await appChain.start(container.createChildContainer()); + await appChain.start(false, container.createChildContainer()); }; const teardown = async () => { diff --git a/packages/sdk/test/networkstate/NetworkState.test.ts b/packages/sdk/test/networkstate/NetworkState.test.ts index 36548f535..53d8182dd 100644 --- a/packages/sdk/test/networkstate/NetworkState.test.ts +++ b/packages/sdk/test/networkstate/NetworkState.test.ts @@ -60,7 +60,7 @@ describe.skip("block production", () => { }); // Start AppChain - await app.start(container.createChildContainer()); + await app.start(false, container.createChildContainer()); appchain = app; ({ runtime, protocol } = app);