diff --git a/package-lock.json b/package-lock.json index 1d57189ac..b669fd4d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29412,7 +29412,8 @@ "lodash-es": "^4.17.21", "mina-fungible-token": "^1.1.0", "reflect-metadata": "^0.1.13", - "ts-pattern": "^4.3.0" + "ts-pattern": "^4.3.0", + "typescript-memoize": "^1.1.1" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 27a0a036c..56c64ec3f 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -311,3 +311,10 @@ export function assertDefined( throw new Error(msg ?? "Value is undefined"); } } + +export function takeFirst(arr: T[]): T { + if (arr.length === 0) { + throw new Error("takeFirst called with empty array"); + } + return arr[0]; +} diff --git a/packages/common/src/zkProgrammable/FeatureFlagsExtension.ts b/packages/common/src/zkProgrammable/FeatureFlagsExtension.ts new file mode 100644 index 000000000..f0338ee9f --- /dev/null +++ b/packages/common/src/zkProgrammable/FeatureFlagsExtension.ts @@ -0,0 +1,27 @@ +import { FeatureFlags } from "o1js"; + +function combineFeatureFlag(a: boolean | undefined, b: boolean | undefined) { + if (a === true || b === true) { + return true; + } else if (a === undefined || b === undefined) { + return undefined; + } else { + return false; + } +} + +export function combineFeatureFlags( + a: FeatureFlags, + b: FeatureFlags +): FeatureFlags { + return { + xor: combineFeatureFlag(a.xor, b.xor), + rot: combineFeatureFlag(a.rot, b.rot), + lookup: combineFeatureFlag(a.lookup, b.lookup), + foreignFieldAdd: combineFeatureFlag(a.foreignFieldAdd, b.foreignFieldAdd), + foreignFieldMul: combineFeatureFlag(a.foreignFieldMul, b.foreignFieldMul), + rangeCheck0: combineFeatureFlag(a.rangeCheck0, b.rangeCheck0), + rangeCheck1: combineFeatureFlag(a.rangeCheck1, b.rangeCheck1), + runtimeTables: combineFeatureFlag(a.runtimeTables, b.runtimeTables), + }; +} diff --git a/packages/common/src/zkProgrammable/ZkProgrammable.ts b/packages/common/src/zkProgrammable/ZkProgrammable.ts index 1736af95f..4983cad40 100644 --- a/packages/common/src/zkProgrammable/ZkProgrammable.ts +++ b/packages/common/src/zkProgrammable/ZkProgrammable.ts @@ -5,15 +5,19 @@ import { Field, Provable, Cache as O1Cache, + DynamicProof, + FlexibleProvable, + FeatureFlags, } from "o1js"; import { Memoize } from "typescript-memoize"; import { log } from "../log"; import { dummyVerificationKey } from "../dummyVerificationKey"; -import { reduceSequential } from "../utils"; +import { mapSequential, reduceSequential } from "../utils"; import type { CompileRegistry } from "../compiling/CompileRegistry"; import { MOCK_PROOF } from "./provableMethod"; +import { combineFeatureFlags } from "./FeatureFlagsExtension"; const errors = { areProofsEnabledNotSet: (name: string) => @@ -52,6 +56,8 @@ export interface PlainZkProgram< PublicOutput = undefined, > { name: string; + publicInputType: FlexibleProvable; + publicOutputType: FlexibleProvable; compile: Compile; verify: Verify; Proof: ReturnType< @@ -75,8 +81,15 @@ export interface PlainZkProgram< }>) >; analyzeMethods: () => Promise< - Record>> + Record< + string, + Awaited> & { + // TODO Properly model ProofClass here + proofs: any[]; + } + > >; + maxProofsVerified: () => Promise<0 | 1 | 2>; } export function verifyToMockable( @@ -125,17 +138,18 @@ export abstract class ZkProgrammable< > { public abstract get areProofsEnabled(): AreProofsEnabled | undefined; - public abstract zkProgramFactory(): PlainZkProgram< - PublicInput, - PublicOutput - >[]; + public abstract zkProgramFactory(): Promise< + PlainZkProgram[] + >; private zkProgramSingleton?: PlainZkProgram[]; @Memoize() - public get zkProgram(): PlainZkProgram[] { + public async zkProgram(): Promise< + PlainZkProgram[] + > { if (this.zkProgramSingleton === undefined) { - this.zkProgramSingleton = this.zkProgramFactory(); + this.zkProgramSingleton = await this.zkProgramFactory(); } return this.zkProgramSingleton.map((bucket) => { @@ -150,9 +164,76 @@ export abstract class ZkProgrammable< }); } + @Memoize() + public async proofType(): Promise> { + const programs = await this.zkProgram(); + + const Template = programs[0].Proof; + const maxProofsVerifeds = await mapSequential(programs, (p) => + p.maxProofsVerified() + ); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const maxProofsVerified = Math.max(...maxProofsVerifeds) as 0 | 1 | 2; + + return class ZkProgrammableProofType extends Proof< + PublicInput, + PublicOutput + > { + static publicInputType = Template.publicInputType; + + static publicOutputType = Template.publicOutputType; + + static maxProofsVerified = maxProofsVerified; + }; + } + + @Memoize() + public async dynamicProofType(): Promise< + typeof DynamicProof + > { + const programs = await this.zkProgram(); + + let maxProofsVerified: 0 | 1 | 2; + let featureFlags: FeatureFlags; + + // We actually only need to compute maxProofsVerified and featuresflags if proofs + // are enabled, otherwise o1js will ignore it anyways. This way startup is a bit + // faster for non-proof environments + if (this.areProofsEnabled?.areProofsEnabled === true) { + const maxProofsVerifieds = await mapSequential( + programs, + async (zkProgram) => await zkProgram.maxProofsVerified() + ); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + maxProofsVerified = Math.max(...maxProofsVerifieds) as 0 | 1 | 2; + const featureFlagsSet = await mapSequential( + programs, + async (zkProgram) => await FeatureFlags.fromZkProgram(zkProgram) + ); + featureFlags = featureFlagsSet.reduce(combineFeatureFlags); + } else { + featureFlags = FeatureFlags.allNone; + maxProofsVerified = 0; + } + + return class DynamicProofType extends DynamicProof< + PublicInput, + PublicOutput + > { + static publicInputType = programs[0].publicInputType; + + static publicOutputType = programs[0].publicOutputType; + + static maxProofsVerified = maxProofsVerified; + + static featureFlags = featureFlags; + }; + } + public async compile(registry: CompileRegistry) { + const programs = await this.zkProgram(); return await reduceSequential( - this.zkProgram, + programs, async (acc, program) => { const result = await registry.compile(program); return { diff --git a/packages/common/src/zkProgrammable/provableMethod.ts b/packages/common/src/zkProgrammable/provableMethod.ts index d01b1fef6..595a954a8 100644 --- a/packages/common/src/zkProgrammable/provableMethod.ts +++ b/packages/common/src/zkProgrammable/provableMethod.ts @@ -26,7 +26,7 @@ export function toProver( return async function prover(this: ZkProgrammable) { const { areProofsEnabled } = this.areProofsEnabled!; - const zkProgram = this.zkProgram.find((prog) => + const zkProgram = (await this.zkProgram()).find((prog) => Object.keys(prog.methods).includes(methodName) ); diff --git a/packages/common/test/zkProgrammable/ZkProgrammable.test.ts b/packages/common/test/zkProgrammable/ZkProgrammable.test.ts index e5bf7cb5c..500ecb2c7 100644 --- a/packages/common/test/zkProgrammable/ZkProgrammable.test.ts +++ b/packages/common/test/zkProgrammable/ZkProgrammable.test.ts @@ -11,6 +11,7 @@ import { MOCK_VERIFICATION_KEY, ZkProgrammable, ProvableMethodExecutionContext, + takeFirst, } from "../../src"; const appChainMock: AreProofsEnabled = { @@ -60,7 +61,7 @@ class TestProgrammable extends ZkProgrammable< }; } - public zkProgramFactory() { + public async zkProgramFactory() { const program = ZkProgram({ name: "testprogram", publicInput: TestPublicInput, @@ -89,9 +90,12 @@ class TestProgrammable extends ZkProgrammable< return [ { name: program.name, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), + maxProofsVerified: program.maxProofsVerified.bind(program), Proof: SelfProof, methods, }, @@ -106,19 +110,21 @@ class OtherTestProgrammable extends ZkProgrammable { super(); } - proofType = this.testProgrammable.zkProgram[0].Proof; - @provableMethod() - public async bar(testProgrammableProof: InstanceType) { + public async bar( + testProgrammableProof: InstanceType< + Awaited> + > + ) { testProgrammableProof.verify(); } - public zkProgramFactory() { + public async zkProgramFactory() { const program = ZkProgram({ name: "testprogram2", methods: { bar: { - privateInputs: [this.testProgrammable.zkProgram[0].Proof], + privateInputs: [await this.testProgrammable.proofType()], method: this.bar.bind(this), }, }, @@ -133,9 +139,12 @@ class OtherTestProgrammable extends ZkProgrammable { return [ { name: program.name, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), + maxProofsVerified: program.maxProofsVerified.bind(program), Proof: SelfProof, methods, }, @@ -189,7 +198,11 @@ describe("zkProgrammable", () => { testProgrammable = new TestProgrammable(); testProgrammable.areProofsEnabled.setProofsEnabled(areProofsEnabled); zkProgramFactorySpy = jest.spyOn(testProgrammable, "zkProgramFactory"); - artifact = await testProgrammable.zkProgram[0].compile(); + + artifact = await testProgrammable + .zkProgram() + .then((p) => takeFirst(p)) + .then((p) => p.compile()); }, 500_000); describe("zkProgramFactory", () => { @@ -216,7 +229,8 @@ describe("zkProgrammable", () => { it("if proofs are disabled, it should successfully verify mock proofs", async () => { expect.assertions(1); - const proof = new testProgrammable.zkProgram[0].Proof({ + const program = await testProgrammable.zkProgram().then(takeFirst); + const proof = new program.Proof({ proof: MOCK_PROOF, publicInput: new TestPublicInput({ @@ -230,7 +244,7 @@ describe("zkProgrammable", () => { maxProofsVerified: 0, }); - const verified = await testProgrammable.zkProgram[0].verify(proof); + const verified = await program.verify(proof); expect(verified).toBe(shouldVerifyMockProofs); @@ -254,7 +268,10 @@ describe("zkProgrammable", () => { describe("zkProgram interoperability", () => { beforeAll(async () => { otherTestProgrammable = new OtherTestProgrammable(testProgrammable); - await otherTestProgrammable.zkProgram[0].compile(); + await otherTestProgrammable + .zkProgram() + .then(takeFirst) + .then((p) => p.compile()); }, 500_000); it("should successfully pass proof of one zkProgram as input to another zkProgram", async () => { @@ -267,8 +284,10 @@ describe("zkProgrammable", () => { const testProof = await executionContext .current() .result.prove>(); - const testProofVerified = - await testProgrammable.zkProgram[0].verify(testProof); + const zkProgram = await testProgrammable + .zkProgram() + .then(takeFirst); + const testProofVerified = await zkProgram.verify(testProof); // execute bar await otherTestProgrammable.bar(testProof); @@ -277,8 +296,11 @@ describe("zkProgrammable", () => { const otherTestProof = await executionContext .current() .result.prove>(); + const otherZkProgram = await otherTestProgrammable + .zkProgram() + .then(takeFirst); const otherTestProofVerified = - await otherTestProgrammable.zkProgram[0].verify(otherTestProof); + await otherZkProgram.verify(otherTestProof); expect(testProof.publicOutput.bar.toString()).toBe( testPublicInput.foo.toString() diff --git a/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts b/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts index 1e8d2581c..6bb8e13a3 100644 --- a/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts +++ b/packages/library/src/hooks/RuntimeFeeAnalyzerService.ts @@ -82,66 +82,66 @@ export class RuntimeFeeAnalyzerService extends ConfigurableModule - >( - async (accum, program) => { - const [valuesProg, indexesProg] = await accum; - const analyzedMethods = await program.analyzeMethods(); - const [valuesMeth, indexesMeth] = Object.keys(program.methods).reduce< - [FeeTreeValues, FeeIndexes] - >( - // eslint-disable-next-line @typescript-eslint/no-shadow - ([values, indexes], combinedMethodName) => { - const { rows } = analyzedMethods[combinedMethodName]; - // const rows = 1000; - const [moduleName, methodName] = combinedMethodName.split("."); - const methodId = this.runtime.methodIdResolver.getMethodId( - moduleName, - methodName - ); - - /** - * Determine the fee config for the given method id, and merge it with - * the default fee config. - */ - return [ - { - ...values, - - [methodId.toString()]: { - methodId, - - baseFee: - this.config.methods[combinedMethodName]?.baseFee ?? - this.config.baseFee, - - perWeightUnitFee: - this.config.methods[combinedMethodName] - ?.perWeightUnitFee ?? this.config.perWeightUnitFee, - - weight: - this.config.methods[combinedMethodName]?.weight ?? - BigInt(rows), - }, + const programs = await this.runtime.zkProgrammable.zkProgram(); + const [values, indexes] = await programs.reduce< + Promise<[FeeTreeValues, FeeIndexes]> + >( + async (accum, program) => { + const [valuesProg, indexesProg] = await accum; + const analyzedMethods = await program.analyzeMethods(); + const [valuesMeth, indexesMeth] = Object.keys(program.methods).reduce< + [FeeTreeValues, FeeIndexes] + >( + // eslint-disable-next-line @typescript-eslint/no-shadow + ([values, indexes], combinedMethodName) => { + const { rows } = analyzedMethods[combinedMethodName]; + // const rows = 1000; + const [moduleName, methodName] = combinedMethodName.split("."); + const methodId = this.runtime.methodIdResolver.getMethodId( + moduleName, + methodName + ); + + /** + * Determine the fee config for the given method id, and merge it with + * the default fee config. + */ + return [ + { + ...values, + + [methodId.toString()]: { + methodId, + + baseFee: + this.config.methods[combinedMethodName]?.baseFee ?? + this.config.baseFee, + + perWeightUnitFee: + this.config.methods[combinedMethodName]?.perWeightUnitFee ?? + this.config.perWeightUnitFee, + + weight: + this.config.methods[combinedMethodName]?.weight ?? + BigInt(rows), }, - { - ...indexes, - // eslint-disable-next-line no-plusplus - [methodId.toString()]: BigInt(methodCounter++), - }, - ]; - }, - [{}, {}] - ); - return [ - { ...valuesProg, ...valuesMeth }, - { ...indexesProg, ...indexesMeth }, - ]; - }, - Promise.resolve([{}, {}]) - ); + }, + { + ...indexes, + // eslint-disable-next-line no-plusplus + [methodId.toString()]: BigInt(methodCounter++), + }, + ]; + }, + [{}, {}] + ); + return [ + { ...valuesProg, ...valuesMeth }, + { ...indexesProg, ...indexesMeth }, + ]; + }, + Promise.resolve([{}, {}]) + ); const tree = new FeeTree(new InMemoryMerkleTreeStorage()); diff --git a/packages/module/src/runtime/Runtime.ts b/packages/module/src/runtime/Runtime.ts index 24a0f0a43..811fcf380 100644 --- a/packages/module/src/runtime/Runtime.ts +++ b/packages/module/src/runtime/Runtime.ts @@ -76,7 +76,9 @@ export class RuntimeZkProgrammable< return this.runtime.areProofsEnabled; } - public zkProgramFactory(): PlainZkProgram[] { + public async zkProgramFactory(): Promise< + PlainZkProgram[] + > { type Methods = Record< string, { @@ -253,9 +255,12 @@ export class RuntimeZkProgrammable< return { name, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), + maxProofsVerified: program.maxProofsVerified.bind(program), Proof: SelfProof, methods, }; diff --git a/packages/module/test/modules/Balances.test.ts b/packages/module/test/modules/Balances.test.ts index d61f6a3d9..0217683e6 100644 --- a/packages/module/test/modules/Balances.test.ts +++ b/packages/module/test/modules/Balances.test.ts @@ -81,7 +81,8 @@ describe("balances", () => { "1439144406936083177718146178121957896974210157062549589517697792374542035761"; const expectedStatus = true; - await runtime.zkProgrammable.zkProgram[0].compile(); + const runtimeProgram = await runtime.zkProgrammable.zkProgram(); + await runtimeProgram[0].compile(); await balances.getTotalSupply(); @@ -89,7 +90,7 @@ describe("balances", () => { const proof = await result.prove>(); - const verified = await runtime.zkProgrammable.zkProgram[0].verify(proof); + const verified = await runtimeProgram[0].verify(proof); runtime.zkProgrammable.areProofsEnabled?.setProofsEnabled(false); diff --git a/packages/protocol/src/prover/block/BlockProvable.ts b/packages/protocol/src/prover/block/BlockProvable.ts index 361df387e..af1742558 100644 --- a/packages/protocol/src/prover/block/BlockProvable.ts +++ b/packages/protocol/src/prover/block/BlockProvable.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line max-classes-per-file import { Bool, DynamicProof, @@ -360,27 +359,15 @@ export class BlockProverState { } } -export class DynamicSTProof extends DynamicProof< +export type DynamicSTProof = DynamicProof< StateTransitionProverPublicInput, StateTransitionProverPublicOutput -> { - static publicInputType = StateTransitionProverPublicInput; +>; - static publicOutputType = StateTransitionProverPublicOutput; - - static maxProofsVerified = 2 as const; -} - -export class DynamicTransactionProof extends DynamicProof< +export type DynamicTransactionProof = DynamicProof< TransactionProverPublicInput, TransactionProverPublicOutput -> { - static publicInputType = TransactionProverPublicInput; - - static publicOutputType = TransactionProverPublicOutput; - - static maxProofsVerified = 2 as const; -} +>; export type BlockProof = Proof; diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 643229794..561609365 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -75,6 +75,14 @@ export class BlockProverProgrammable extends ZkProgrammable< > { public constructor( private readonly prover: BlockProver, + public readonly stateTransitionProver: ZkProgrammable< + StateTransitionProverPublicInput, + StateTransitionProverPublicOutput + >, + public readonly transactionProver: ZkProgrammable< + TransactionProverPublicInput, + TransactionProverPublicOutput + >, private readonly blockHooks: ProvableBlockHook[], private readonly stateServiceProvider: StateServiceProvider, private readonly childVerificationKeyService: ChildVerificationKeyService @@ -259,9 +267,8 @@ export class BlockProverProgrammable extends ZkProgrammable< ); // Brought in as a constant - const transactionProofVk = this.childVerificationKeyService.getAsConstant( - "StateTransitionProver" - ); + const transactionProofVk = + this.childVerificationKeyService.getAsConstant("TransactionProver"); transactionProof.verifyIf(transactionProofVk, verifyTransactionProof); // Fast-forward transaction trackers by the results of the aggregated transaction proof @@ -435,6 +442,7 @@ export class BlockProverProgrammable extends ZkProgrammable< deferSTProof: Bool, deferTransactionProof: Bool, finalize: Bool, + // TODO Add typing such that it is clear that either both are undefined or none is stateTransitionProof?: DynamicSTProof, transactionProof?: DynamicTransactionProof ): Promise { @@ -620,16 +628,19 @@ export class BlockProverProgrammable extends ZkProgrammable< * Recursive linking of proofs is done via the previously * injected StateTransitionProver and the required AppChainProof class */ - public zkProgramFactory(): PlainZkProgram< - BlockProverPublicInput, - BlockProverPublicOutput - >[] { + public async zkProgramFactory(): Promise< + PlainZkProgram[] + > { const { prover } = this; const proveBlockBatchWithProofs = prover.proveBlockBatchWithProofs.bind(prover); const proveBlockBatchNoProofs = prover.proveBlockBatchNoProofs.bind(prover); const merge = prover.merge.bind(prover); + const dynamicStProofType = + await this.stateTransitionProver.dynamicProofType(); + const dynamicTxProofType = await this.transactionProver.dynamicProofType(); + const program = ZkProgram({ name: "BlockProver", publicInput: BlockProverPublicInput, @@ -644,8 +655,8 @@ export class BlockProverProgrammable extends ZkProgrammable< BlockArgumentsBatch, Bool, Bool, - DynamicSTProof, - DynamicTransactionProof, + dynamicStProofType, + dynamicTxProofType, ], async method( publicInput: BlockProverPublicInput, @@ -731,11 +742,14 @@ export class BlockProverProgrammable extends ZkProgrammable< return [ { name: program.name, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), Proof: SelfProofClass, methods, + maxProofsVerified: program.maxProofsVerified.bind(program), }, ]; } @@ -775,6 +789,8 @@ export class BlockProver super(); this.zkProgrammable = new BlockProverProgrammable( this, + stateTransitionProver.zkProgrammable, + transactionProver.zkProgrammable, blockHooks, stateServiceProvider, childVerificationKeyService diff --git a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts index e0f821cf0..ac7d1eda3 100644 --- a/packages/protocol/src/prover/statetransition/StateTransitionProver.ts +++ b/packages/protocol/src/prover/statetransition/StateTransitionProver.ts @@ -74,10 +74,12 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< return this.stateTransitionProver.areProofsEnabled; } - public zkProgramFactory(): PlainZkProgram< - StateTransitionProverPublicInput, - StateTransitionProverPublicOutput - >[] { + public async zkProgramFactory(): Promise< + PlainZkProgram< + StateTransitionProverPublicInput, + StateTransitionProverPublicOutput + >[] + > { const instance = this; const program = ZkProgram({ @@ -144,6 +146,9 @@ export class StateTransitionProverProgrammable extends ZkProgrammable< analyzeMethods: program.analyzeMethods.bind(program), Proof: SelfProofClass, methods, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, + maxProofsVerified: program.maxProofsVerified.bind(program), }, ]; } diff --git a/packages/protocol/src/prover/transaction/TransactionProver.ts b/packages/protocol/src/prover/transaction/TransactionProver.ts index 1ef581beb..349e7b858 100644 --- a/packages/protocol/src/prover/transaction/TransactionProver.ts +++ b/packages/protocol/src/prover/transaction/TransactionProver.ts @@ -72,6 +72,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< > { public constructor( private readonly prover: TransactionProver, + public readonly runtime: WithZkProgrammable, private readonly transactionHooks: ProvableTransactionHook[], private readonly stateServiceProvider: StateServiceProvider, private readonly verificationKeyService: MinimalVKTreeService @@ -366,16 +367,21 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< * Recursive linking of proofs is done via the previously * injected StateTransitionProver and the required AppChainProof class */ - public zkProgramFactory(): PlainZkProgram< - TransactionProverPublicInput, - TransactionProverPublicOutput - >[] { + public async zkProgramFactory(): Promise< + PlainZkProgram< + TransactionProverPublicInput, + TransactionProverPublicOutput + >[] + > { const { prover } = this; const proveTransaction = prover.proveTransaction.bind(prover); const proveTransactions = prover.proveTransactions.bind(prover); const merge = prover.merge.bind(prover); const dummy = prover.dummy.bind(prover); + const runtimeProofType = + await this.runtime.zkProgrammable.dynamicProofType(); + const program = ZkProgram({ name: "TransactionProver", publicInput: TransactionProverPublicInput, @@ -383,7 +389,7 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< methods: { proveTransaction: { - privateInputs: [DynamicRuntimeProof, TransactionProverExecutionData], + privateInputs: [runtimeProofType, TransactionProverExecutionData], async method( publicInput: TransactionProverPublicInput, @@ -469,9 +475,12 @@ export class TransactionProverZkProgrammable extends ZkProgrammable< return [ { name: program.name, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), + maxProofsVerified: program.maxProofsVerified.bind(program), Proof: SelfProofClass, methods, }, @@ -504,6 +513,7 @@ export class TransactionProver super(); this.zkProgrammable = new TransactionProverZkProgrammable( this, + runtime, transactionHooks, stateServiceProvider, verificationKeyService diff --git a/packages/protocol/test/BlockProver.test.ts b/packages/protocol/test/BlockProver.test.ts index f06ac25a1..8219e9a2a 100644 --- a/packages/protocol/test/BlockProver.test.ts +++ b/packages/protocol/test/BlockProver.test.ts @@ -4,13 +4,12 @@ import { PlainZkProgram, ZkProgrammable, } from "@proto-kit/common"; -import { Bool, Field, Proof, UInt64, ZkProgram } from "o1js"; +import { Field, Proof, UInt64, ZkProgram } from "o1js"; import "reflect-metadata"; import { MethodPublicOutput, NetworkState, - AuthorizedTransaction, StateTransitionProverPublicInput, StateTransitionProverPublicOutput, } from "../src"; @@ -38,7 +37,9 @@ class RuntimeZkProgrammable extends ZkProgrammable< return new MockAppChain(); } - zkProgramFactory(): PlainZkProgram[] { + public async zkProgramFactory(): Promise< + PlainZkProgram[] + > { const program = ZkProgram({ name: "BlockProverTestProgram", publicOutput: MethodPublicOutput, @@ -48,9 +49,12 @@ class RuntimeZkProgrammable extends ZkProgrammable< return [ { name: program.name, + publicInputType: program.publicInputType, + publicOutputType: program.publicOutputType, compile: program.compile.bind(program), verify: program.verify.bind(program), analyzeMethods: program.analyzeMethods.bind(program), + maxProofsVerified: program.maxProofsVerified.bind(program), methods: {}, Proof: ZkProgram.Proof(program), }, diff --git a/packages/sequencer/package.json b/packages/sequencer/package.json index 421370c2d..06a0ae97b 100644 --- a/packages/sequencer/package.json +++ b/packages/sequencer/package.json @@ -38,7 +38,8 @@ "lodash-es": "^4.17.21", "mina-fungible-token": "^1.1.0", "reflect-metadata": "^0.1.13", - "ts-pattern": "^4.3.0" + "ts-pattern": "^4.3.0", + "typescript-memoize": "^1.1.1" }, "gitHead": "8a7eca319272a15162dc4ad04bdc134b1017716d" } diff --git a/packages/sequencer/src/helpers/CircuitAnalysisModule.ts b/packages/sequencer/src/helpers/CircuitAnalysisModule.ts index c9b33572f..7b38e1e80 100644 --- a/packages/sequencer/src/helpers/CircuitAnalysisModule.ts +++ b/packages/sequencer/src/helpers/CircuitAnalysisModule.ts @@ -35,10 +35,10 @@ export class CircuitAnalysisModule { const zkProgrammablePromises = await mapSequential( zkProgrammables, - (withZkProgrammable) => - mapSequential( + async (withZkProgrammable) => + await mapSequential( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - withZkProgrammable.zkProgrammable.zkProgramFactory() as PlainZkProgram< + (await withZkProgrammable.zkProgrammable.zkProgramFactory()) as PlainZkProgram< unknown, unknown >[], diff --git a/packages/sequencer/src/helpers/utils.ts b/packages/sequencer/src/helpers/utils.ts index 3e31f7013..ba8a55d41 100644 --- a/packages/sequencer/src/helpers/utils.ts +++ b/packages/sequencer/src/helpers/utils.ts @@ -1,6 +1,7 @@ import { Field, Proof, DynamicProof } from "o1js"; import { Subclass } from "@proto-kit/protocol"; -import { MOCK_PROOF, TypedClass } from "@proto-kit/common"; +import { mapSequential, MOCK_PROOF, TypedClass } from "@proto-kit/common"; +import { Memoize } from "typescript-memoize"; import { TaskSerializer } from "../worker/flow/Task"; @@ -30,28 +31,37 @@ export function distinctByString string }>( type JsonProof = ReturnType; -abstract class ProofTaskSerializerBase { +abstract class ProofTaskSerializerBase< + PublicInputType, + PublicOutputType, + Type extends + | (TypedClass> & + typeof Proof) + | (TypedClass> & + typeof DynamicProof), +> { protected constructor( - private readonly proofClassInternal: Subclass< - | typeof Proof - | typeof DynamicProof - > + private readonly proofClassInternalFun: () => Promise> ) {} - protected getDummy< - T extends - | Proof - | DynamicProof, - >(c: TypedClass, jsonProof: JsonProof): T { + @Memoize() + protected get proofClass() { + return this.proofClassInternalFun(); + } + + protected async getDummy( + c: Subclass, + jsonProof: JsonProof + ): Promise> { + const proofClass = await this.proofClass; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const publicInput: PublicInputType = - this.proofClassInternal.publicInputType.fromFields( - jsonProof.publicInput.map(Field), - [] - ); + const publicInput: PublicInputType = proofClass.publicInputType.fromFields( + jsonProof.publicInput.map(Field), + [] + ); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const publicOutput: PublicOutputType = - this.proofClassInternal.publicOutputType.fromFields( + proofClass.publicOutputType.fromFields( jsonProof.publicOutput.map(Field), [] ); @@ -64,28 +74,29 @@ abstract class ProofTaskSerializerBase { }); } - public toJSON( + public async toJSON( proof: | Proof | DynamicProof - ): string { - return JSON.stringify(this.toJSONProof(proof)); + ): Promise { + return JSON.stringify(await this.toJSONProof(proof)); } - public toJSONProof( + public async toJSONProof( proof: | Proof | DynamicProof - ): JsonProof { + ): Promise { if (proof.proof === MOCK_PROOF) { + const proofClass = await this.proofClass; return { - publicInput: this.proofClassInternal.publicInputType + publicInput: proofClass.publicInputType // eslint-disable-next-line max-len // eslint-disable-next-line @typescript-eslint/consistent-type-assertions,@typescript-eslint/no-unsafe-argument .toFields(proof.publicInput as any) .map(String), - publicOutput: this.proofClassInternal.publicOutputType + publicOutput: proofClass.publicOutputType // eslint-disable-next-line max-len // eslint-disable-next-line @typescript-eslint/consistent-type-assertions,@typescript-eslint/no-unsafe-argument .toFields(proof.publicOutput as any) @@ -100,13 +111,15 @@ abstract class ProofTaskSerializerBase { } export class ProofTaskSerializer - extends ProofTaskSerializerBase + extends ProofTaskSerializerBase< + PublicInputType, + PublicOutputType, + typeof Proof + > implements TaskSerializer> { public constructor( - private readonly proofClass: Subclass< - typeof Proof - > + proofClass: () => Promise> ) { super(proofClass); } @@ -122,20 +135,24 @@ export class ProofTaskSerializer jsonProof: JsonProof ): Promise> { if (jsonProof.proof === MOCK_PROOF) { - return this.getDummy(this.proofClass, jsonProof); + return await this.getDummy(await this.proofClass, jsonProof); } - return await this.proofClass.fromJSON(jsonProof); + return await (await this.proofClass).fromJSON(jsonProof); } } export class DynamicProofTaskSerializer - extends ProofTaskSerializerBase + extends ProofTaskSerializerBase< + PublicInputType, + PublicOutputType, + typeof DynamicProof + > implements TaskSerializer> { public constructor( - private readonly proofClass: Subclass< - typeof DynamicProof + proofClass: () => Promise< + Subclass> > ) { super(proofClass); @@ -151,12 +168,12 @@ export class DynamicProofTaskSerializer public async fromJSONProof( jsonProof: JsonProof ): Promise> { + const proofClass = await this.proofClass; + if (jsonProof.proof === MOCK_PROOF) { - return this.getDummy(this.proofClass, jsonProof); + return await this.getDummy(proofClass, jsonProof); } - const { proofClass } = this; - return await proofClass.fromJSON(jsonProof); } } @@ -169,11 +186,13 @@ export class PairProofTaskSerializer< > implements TaskSerializer< PairTuple> > { - private readonly proofSerializer = new ProofTaskSerializer(this.proofClass); + private readonly proofSerializer = new ProofTaskSerializer( + this.proofClassFun + ); public constructor( - private readonly proofClass: Subclass< - typeof Proof + private readonly proofClassFun: () => Promise< + Subclass> > ) {} @@ -188,11 +207,13 @@ export class PairProofTaskSerializer< ]; } - public toJSON( + public async toJSON( input: PairTuple> - ): string { + ): Promise { return JSON.stringify( - input.map((element) => this.proofSerializer.toJSONProof(element)) + await mapSequential(input, (element) => + this.proofSerializer.toJSONProof(element) + ) ); } } diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 81ee673be..6df8f2699 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -106,7 +106,7 @@ export class BatchProducerModule extends SequencerModule { const blockHashes = blocks.map((bundle) => bundle.block.hash.toString()); - const jsonProof = this.blockProofSerializer + const jsonProof = await this.blockProofSerializer .getBlockProofSerializer() .toJSONProof(batch.proof); diff --git a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts index e2267ec60..3cc2f9286 100644 --- a/packages/sequencer/src/protocol/production/flow/BatchFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/BatchFlow.ts @@ -65,16 +65,20 @@ export class BatchFlow { } } - private dummySTProof() { - return this.protocol.stateTransitionProver.zkProgrammable.zkProgram[0].Proof.dummy( + private async dummySTProof() { + const program = + await this.protocol.stateTransitionProver.zkProgrammable.zkProgram(); + return await program[0].Proof.dummy( StateTransitionProverPublicInput.empty(), StateTransitionProverPublicOutput.empty(), 2 ); } - private dummyTransactionProof() { - return this.protocol.transactionProver.zkProgrammable.zkProgram[0].Proof.dummy( + private async dummyTransactionProof() { + const program = + await this.protocol.transactionProver.zkProgrammable.zkProgram(); + return await program[0].Proof.dummy( TransactionProverPublicInput.empty(), TransactionProverPublicInput.empty(), 2 diff --git a/packages/sequencer/src/protocol/production/flow/StateTransitionFlow.ts b/packages/sequencer/src/protocol/production/flow/StateTransitionFlow.ts index 6321920b0..16071cfea 100644 --- a/packages/sequencer/src/protocol/production/flow/StateTransitionFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/StateTransitionFlow.ts @@ -37,11 +37,9 @@ export class StateTransitionFlow { witnessedRootsHash: Field(0), }; - return await this.protocol.stateTransitionProver.zkProgrammable.zkProgram[0].Proof.dummy( - emptyInputOutput, - emptyInputOutput, - 2 - ); + const program = + await this.protocol.stateTransitionProver.zkProgrammable.zkProgram(); + return await program[0].Proof.dummy(emptyInputOutput, emptyInputOutput, 2); } private createFlow(name: string, inputLength: number) { diff --git a/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts b/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts index 96f646569..5ab06d909 100644 --- a/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/BlockReductionTask.ts @@ -58,14 +58,14 @@ export class BlockReductionTask } public inputSerializer(): TaskSerializer> { - return new PairProofTaskSerializer( - this.blockProver.zkProgrammable.zkProgram[0].Proof + return new PairProofTaskSerializer(() => + this.blockProver.zkProgrammable.proofType() ); } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer( - this.blockProver.zkProgrammable.zkProgram[0].Proof + return new ProofTaskSerializer(() => + this.blockProver.zkProgrammable.proofType() ); } diff --git a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts index 8a9f05dc1..f40fbb1a6 100644 --- a/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/NewBlockTask.ts @@ -15,8 +15,6 @@ import { BlockArgumentsBatch, BlockProverStateInput, ProtocolConstants, - DynamicSTProof, - DynamicTransactionProof, } from "@proto-kit/protocol"; import { Bool } from "o1js"; import { @@ -97,12 +95,12 @@ export class NewBlockTask } public inputSerializer(): TaskSerializer { - const stProofSerializer = new ProofTaskSerializer( - this.stateTransitionProver.zkProgrammable.zkProgram[0].Proof + const stProofSerializer = new ProofTaskSerializer(() => + this.stateTransitionProver.zkProgrammable.proofType() ); - const transactionProofSerializer = new ProofTaskSerializer( - this.transactionProver.zkProgrammable.zkProgram[0].Proof + const transactionProofSerializer = new ProofTaskSerializer(() => + this.transactionProver.zkProgrammable.proofType() ); return new NewBlockProvingParametersSerializer( @@ -112,8 +110,8 @@ export class NewBlockTask } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer( - this.blockProver.zkProgrammable.zkProgram[0].Proof + return new ProofTaskSerializer(() => + this.blockProver.zkProgrammable.proofType() ); } @@ -157,11 +155,16 @@ export class NewBlockTask blockWitness, blockArgumentBatch, Bool(false) - // deferSTProof.or(deferTransactionProof) ); } else { + const DynamicSTProof = + await this.stateTransitionProver.zkProgrammable.dynamicProofType(); const stProof = DynamicSTProof.fromProof(input1); + + const DynamicTransactionProof = + await this.transactionProver.zkProgrammable.dynamicProofType(); const txProof = DynamicTransactionProof.fromProof(input2); + await this.blockProver.proveBlockBatchWithProofs( publicInput, stateWitness, diff --git a/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts index 4a15b3614..6cfdb9126 100644 --- a/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/RuntimeProvingTask.ts @@ -67,7 +67,9 @@ export class RuntimeProvingTask } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer(this.runtimeZkProgrammable[0].Proof); + return new ProofTaskSerializer(() => + this.runtime.zkProgrammable.proofType() + ); } public async compute(input: RuntimeProofParameters): Promise { diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionReductionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionReductionTask.ts index 7992f4178..abd98dbf1 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionReductionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionReductionTask.ts @@ -59,14 +59,14 @@ export class StateTransitionReductionTask } public inputSerializer(): TaskSerializer> { - return new PairProofTaskSerializer( - this.stateTransitionProver.zkProgrammable.zkProgram[0].Proof + return new PairProofTaskSerializer(() => + this.stateTransitionProver.zkProgrammable.proofType() ); } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer( - this.stateTransitionProver.zkProgrammable.zkProgram[0].Proof + return new ProofTaskSerializer(() => + this.stateTransitionProver.zkProgrammable.proofType() ); } diff --git a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts index 4ca6f489d..a942a551c 100644 --- a/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/StateTransitionTask.ts @@ -63,8 +63,8 @@ export class StateTransitionTask } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer( - this.stateTransitionProver.zkProgrammable.zkProgram[0].Proof + return new ProofTaskSerializer(() => + this.stateTransitionProver.zkProgrammable.proofType() ); } diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts index 60785c1ad..386a14b4d 100644 --- a/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/TransactionProvingTask.ts @@ -62,9 +62,6 @@ export class TransactionProvingTask { private readonly transactionProver: TransactionProvable; - private readonly runtimeProofType = - this.runtime.zkProgrammable.zkProgram[0].Proof; - public name = "transaction"; public constructor( @@ -88,9 +85,13 @@ export class TransactionProvingTask }; } + private async runtimeProofType() { + return await this.runtime.zkProgrammable.proofType(); + } + public inputSerializer(): TaskSerializer { const runtimeProofSerializer = new ProofTaskSerializer( - this.runtimeProofType + this.runtimeProofType.bind(this) ); return new TransactionProvingTaskParameterSerializer( runtimeProofSerializer @@ -98,8 +99,8 @@ export class TransactionProvingTask } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer( - this.transactionProver.zkProgrammable.zkProgram[0].Proof + return new ProofTaskSerializer(() => + this.transactionProver.zkProgrammable.proofType() ); } diff --git a/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts b/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts index 0e934bb13..7c7cb4d5a 100644 --- a/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/TransactionReductionTask.ts @@ -58,14 +58,14 @@ export class TransactionReductionTask } public inputSerializer(): TaskSerializer> { - return new PairProofTaskSerializer( - this.transactionProver.zkProgrammable.zkProgram[0].Proof + return new PairProofTaskSerializer(() => + this.transactionProver.zkProgrammable.proofType() ); } public resultSerializer(): TaskSerializer { - return new ProofTaskSerializer( - this.transactionProver.zkProgrammable.zkProgram[0].Proof + return new ProofTaskSerializer(() => + this.transactionProver.zkProgrammable.proofType() ); } diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/BlockProofSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/BlockProofSerializer.ts index 113db208a..650850729 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/BlockProofSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/BlockProofSerializer.ts @@ -24,8 +24,9 @@ export class BlockProofSerializer { public getBlockProofSerializer() { if (this.serializer === undefined) { const blockProver = this.protocol.resolve("BlockProver"); - const proofType = blockProver.zkProgrammable.zkProgram[0].Proof; - this.serializer = new ProofTaskSerializer(proofType); + this.serializer = new ProofTaskSerializer(() => + blockProver.zkProgrammable.proofType() + ); } return this.serializer; } diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts index f54771bf6..3ddf5b040 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/NewBlockProvingParametersSerializer.ts @@ -60,10 +60,10 @@ export class NewBlockProvingParametersSerializer implements TaskSerializer ) {} - public toJSON(input: NewBlockPayload) { + public async toJSON(input: NewBlockPayload) { return JSON.stringify({ - input1: this.stProofSerializer.toJSON(input.input1), - input2: this.transactionProofSerializer.toJSON(input.input2), + input1: await this.stProofSerializer.toJSON(input.input1), + input2: await this.transactionProofSerializer.toJSON(input.input2), params: { publicInput: BlockProverPublicInput.toJSON(input.params.publicInput), diff --git a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts index 24a7ce2cb..5c54735b2 100644 --- a/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts +++ b/packages/sequencer/src/protocol/production/tasks/serializers/TransactionProvingTaskParameterSerializer.ts @@ -81,17 +81,19 @@ export class TransactionProvingTaskParameterSerializer implements TaskSerializer }; } - public toJSON(inputs: TransactionProvingTaskParameters): string { + public async toJSON( + inputs: TransactionProvingTaskParameters + ): Promise { if (inputs === "dummy") { return "dummy"; } - const taskParamsJson: TransactionProvingTaskParametersJSON = inputs.map( - (input) => { + const taskParamsJson: TransactionProvingTaskParametersJSON = + await mapSequential(inputs, async (input) => { const { parameters, proof } = input; const { executionData } = parameters; - const proofJSON = this.runtimeProofSerializer.toJSONProof(proof); + const proofJSON = await this.runtimeProofSerializer.toJSONProof(proof); const parametersJSON: TransactionProverTaskParametersJSON = { publicInput: TransactionProverPublicInput.toJSON( @@ -112,8 +114,7 @@ export class TransactionProvingTaskParameterSerializer implements TaskSerializer }; return { parameters: parametersJSON, proof: proofJSON }; - } - ); + }); return JSON.stringify(taskParamsJson); } diff --git a/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts b/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts index 5445d6e63..2c62bd98e 100644 --- a/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts +++ b/packages/sequencer/src/protocol/runtime/RuntimeVerificationKeyService.ts @@ -64,7 +64,7 @@ export class VerificationKeyService extends ConfigurableModule<{}> { public async initializeVKTree(artifacts: Record) { const mappings = await mapSequential( - this.runtime.zkProgrammable.zkProgram, + await this.runtime.zkProgrammable.zkProgram(), async (program) => { const artifact = artifacts[program.name]; diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index d72f3683f..745bbab1e 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -104,13 +104,17 @@ export class SequencerStartupModule return root; } - private async compileBridge(flow: Flow<{}>, isSignedSettlement?: boolean) { + private async compileBridge( + flow: Flow<{}>, + runtimeVKRoot: bigint, + isSignedSettlement?: boolean + ) { const result = await flow.withFlow(async (res, rej) => { await flow.pushTask( this.settlementCompileTask, { existingArtifacts: this.compileRegistry.getAllArtifacts(), - runtimeVKRoot: undefined, + runtimeVKRoot: runtimeVKRoot.toString(), isSignedSettlement, }, async (bridgeResult) => { @@ -184,6 +188,7 @@ export class SequencerStartupModule if (this.settlementModule !== undefined) { const bridgeArtifacts = await this.compileBridge( flow, + root, isSignedSettlement ); diff --git a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts index a33d942ef..594f48799 100644 --- a/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts +++ b/packages/sequencer/src/settlement/tasks/SettlementProvingTask.ts @@ -182,11 +182,11 @@ export class SettlementProvingTask return proofType.prototype instanceof Proof ? new ProofTaskSerializer( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - proofType as Subclass> + async () => proofType as Subclass> ) : new DynamicProofTaskSerializer( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - proofType as Subclass> + async () => proofType as Subclass> ); } @@ -341,85 +341,85 @@ export class SettlementProvingTask }; }, - toJSON: (input: TransactionTaskArgs): string => { + toJSON: async (input: TransactionTaskArgs): Promise => { const transaction = input.transaction.toJSON(); - const lazyProofs = - input.transaction.transaction.accountUpdates.map( - (au) => { - if (au.lazyAuthorization?.kind === "lazy-proof") { - const lazyProof = au.lazyAuthorization; - - // eslint-disable-next-line no-underscore-dangle - const method = lazyProof.ZkappClass._methods?.find( - (methodInterface) => - methodInterface.methodName === lazyProof.methodName - ); - if (method === undefined) { - throw new Error("Method interface not found"); - } + const lazyProofs = await mapSequential( + input.transaction.transaction.accountUpdates, + async (au) => { + if (au.lazyAuthorization?.kind === "lazy-proof") { + const lazyProof = au.lazyAuthorization; + + // eslint-disable-next-line no-underscore-dangle + const method = lazyProof.ZkappClass._methods?.find( + (methodInterface) => + methodInterface.methodName === lazyProof.methodName + ); + if (method === undefined) { + throw new Error("Method interface not found"); + } + + // args are [public key, tokenId, ...args] + const args = method.args.slice(2); + + const encodedArgs = ( + await mapSequential(lazyProof.args, async (arg, index) => { + const argType = args[index]; + const argTypeProvable = ProvableType.get(argType); + const argProofs = this.extractProofTypes(argType); - // args are [public key, tokenId, ...args] - const args = method.args.slice(2); - - const encodedArgs = lazyProof.args - .map((arg, index) => { - const argType = args[index]; - const argTypeProvable = ProvableType.get(argType); - const argProofs = this.extractProofTypes(argType); - - if (argProofs.length === 0) { - // Special case for AUForest - if (arg instanceof AccountUpdateForest) { - const accountUpdates = AccountUpdateForest.toFlatArray( - arg - ).map((update) => AccountUpdate.toJSON(update)); - - return { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - fields: [] as string[], - aux: [ - JSON.stringify({ - accountUpdates, - typeName: "AccountUpdateForest", - }), - ], - }; - } - - const fields = argTypeProvable - .toFields(arg) - .map((f) => f.toString()); - const aux = argTypeProvable - .toAuxiliary(arg) - .map((x) => JSON.stringify(x)); + if (argProofs.length === 0) { + // Special case for AUForest + if (arg instanceof AccountUpdateForest) { + const accountUpdates = AccountUpdateForest.toFlatArray( + arg + ).map((update) => AccountUpdate.toJSON(update)); return { - fields, - aux, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + fields: [] as string[], + aux: [ + JSON.stringify({ + accountUpdates, + typeName: "AccountUpdateForest", + }), + ], }; - } else { - const serializer = this.getProofSerializer(argProofs[0]); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - return serializer.toJSON(arg); } - }) - .filter(filterNonUndefined); - - return { - methodName: lazyProof.methodName, - zkappClassName: lazyProof.ZkappClass.name, - args: encodedArgs, - blindingValue: lazyProof.blindingValue.toString(), - memoized: lazyProof.memoized.map((value) => ({ - fields: value.fields.map((f) => f.toString()), - aux: value.aux, - })), - }; - } - return null; + + const fields = argTypeProvable + .toFields(arg) + .map((f) => f.toString()); + const aux = argTypeProvable + .toAuxiliary(arg) + .map((x) => JSON.stringify(x)); + + return { + fields, + aux, + }; + } else { + const serializer = this.getProofSerializer(argProofs[0]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + return await serializer.toJSON(arg); + } + }) + ).filter(filterNonUndefined); + + return { + methodName: lazyProof.methodName, + zkappClassName: lazyProof.ZkappClass.name, + args: encodedArgs, + blindingValue: lazyProof.blindingValue.toString(), + memoized: lazyProof.memoized.map((value) => ({ + fields: value.fields.map((f) => f.toString()), + aux: value.aux, + })), + }; } - ); + return null; + } + ); const jsonObject: JsonInputObject = { transaction, diff --git a/packages/sequencer/test-proven/Proven.test.ts b/packages/sequencer/test-proven/Proven.test.ts index 14e41f9dd..19f00fe57 100644 --- a/packages/sequencer/test-proven/Proven.test.ts +++ b/packages/sequencer/test-proven/Proven.test.ts @@ -18,7 +18,7 @@ import { } from "@proto-kit/protocol"; import { VanillaProtocolModules } from "@proto-kit/library"; import { container } from "tsyringe"; -import { PrivateKey, UInt64 } from "o1js"; +import { PrivateKey, Provable, UInt64, VerificationKey } from "o1js"; import { testingSequencerModules } from "../test/TestingSequencer"; import { @@ -221,6 +221,7 @@ describe("Proven", () => { }, timeout ); + it( "should produce large block", async () => { @@ -260,6 +261,45 @@ describe("Proven", () => { timeout * 10 ); + it( + "should produce empty + 1 tx", + async () => { + log.setLevel("INFO"); + + const privateKey = PrivateKey.random(); + + // await test.produceBlock(); + // await test.produceBlock(); + // await test.produceBlock(); + + await test.addTransaction({ + method: ["Balances", "addBalance"], + privateKey, + args: [PrivateKey.random().toPublicKey(), UInt64.from(100)], + }); + + // Produce 6 blocks, 5 txs each into 1 batch + const block = await test.produceBlock(); + await test.produceBlock(); + await test.produceBlock(); + await test.produceBlock(); + + expectDefined(block); + expect(block.transactions).toHaveLength(1); + expect(block.transactions[0].status.toBoolean()).toBe(true); + + const batch = await test.produceBatch(); + + expectDefined(batch); + + console.log(batch.proof); + + expect(batch.blockHashes).toHaveLength(4); + expect(batch.proof.proof.length).toBeGreaterThan(50); + }, + timeout * 10 + ); + afterAll(async () => { await appChain.close(); }); diff --git a/packages/sequencer/test/integration/BlockProduction-test.ts b/packages/sequencer/test/integration/BlockProduction-test.ts index 8e2764a42..a2a97ebcf 100644 --- a/packages/sequencer/test/integration/BlockProduction-test.ts +++ b/packages/sequencer/test/integration/BlockProduction-test.ts @@ -189,7 +189,7 @@ export function testBlockProduction< app.sequencer.dependencyContainer.resolve( "UnprovenLinkedLeafStore" ); - }); + }, 30000); afterEach(async () => { await appChain.close();