Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## Unreleased
- Added RecursiveProofModule [#516](https://github.com/proto-kit/framework/pull/516)

### Added
- Added createdAt timestamp to block, batch and settlement models.[#502](https://github.com/proto-kit/framework/pull/502)
Expand Down
121 changes: 121 additions & 0 deletions packages/sequencer/src/sequencer/output/RecursiveProofModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {
filterNonUndefined,
mapSequential,
noop,
range,
} from "@proto-kit/common";
import { inject } from "tsyringe";
import { BlockProof } from "@proto-kit/protocol";
import { JsonProof } from "o1js";

import { sequencerModule, SequencerModule } from "../builder/SequencerModule";
import { BatchMergingFlow } from "../../settlement/tasks/BatchMergingFlow";
import { PropertyStorage } from "../../storage/repositories/PropertyStorage";
import { BatchStorage } from "../../storage/repositories/BatchStorage";
import { Batch } from "../../storage/model/Batch";
import { BatchProducerModule } from "../../protocol/production/BatchProducerModule";
import { BlockProofSerializer } from "../../protocol/production/tasks/serializers/BlockProofSerializer";

export interface RecursiveProofJson {
toBatchHeight: number;
proof: JsonProof;
}

export interface RecursiveProof {
toBatchHeight: number;
proof: BlockProof;
}

const BATCH_STORAGE_KEY = "batch-recursive";

@sequencerModule()
// implements ProofOutputModule
export class RecursiveProofModule extends SequencerModule {
public constructor(
@inject("PropertyStorage")
private readonly propertyStorage: PropertyStorage,
@inject("BatchStorage")
private readonly batchStorage: BatchStorage,
private readonly batchMergingFlow: BatchMergingFlow,
@inject("BatchProducerModule")
private readonly batchProducerModule: BatchProducerModule,
private readonly blockProofSerializer: BlockProofSerializer
) {
super();
}

public async start() {
noop();
}

public async storeProof(proof: RecursiveProofJson) {
const str = JSON.stringify(proof);

await this.propertyStorage.set(BATCH_STORAGE_KEY, str);
}

public async readProof(): Promise<RecursiveProof | undefined> {
const json = await this.propertyStorage.get(BATCH_STORAGE_KEY);

if (json === undefined) {
return undefined;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const jsonObject: RecursiveProofJson = JSON.parse(json);

const proof = await this.blockProofSerializer
.getBlockProofSerializer()
.fromJSONProof(jsonObject.proof);

return {
toBatchHeight: jsonObject.toBatchHeight,
proof,
};
}

public async proveRecursively() {
const previous = await this.readProof();
let previousBatch: Batch | undefined = undefined;
if (previous !== undefined) {
previousBatch = await this.batchStorage.getBatchAt(
previous.toBatchHeight
);
}

const currentHeight = await this.batchStorage.getCurrentBatchHeight();
const previousBatchesResults = await Promise.all(
range((previous?.toBatchHeight ?? -1) + 1, currentHeight).map((height) =>
this.batchStorage.getBatchAt(height)
)
);

const previousBatches = previousBatchesResults.filter(filterNonUndefined);
if (previousBatches.length < previousBatchesResults.length) {
throw new Error("Some previous batch retrieval failed");
}

const batches = [previousBatch, ...previousBatches].filter(
filterNonUndefined
);
// TODO This is very expensive (loads of DB-trips)
const settleableBatches = await mapSequential(
batches,
async (batch) =>
await this.batchProducerModule.recoverSettleableBatch(batch)
);

const batch = await this.batchMergingFlow.mergeBatches(settleableBatches);

await this.storeProof({ proof: batch.proof, toBatchHeight: batch.height });

return batch;
}

// public async settleBatch(batches: SettleableBatch[]): Promise<Settlement> {
//
// return {
//
// }
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class BatchMergingFlow {

return {
proof: await serializer.toJSONProof(result),
height: batches[0].height,
height: batches.at(-1)!.height,
blockHashes: batches.flatMap((batch) => batch.blockHashes),
createdAt: Date.now(),
fromNetworkState: batches[0].fromNetworkState,
Expand Down
153 changes: 153 additions & 0 deletions packages/sequencer/test/integration/RecursiveProofModules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { log } from "@proto-kit/common";
import { VanillaProtocolModules } from "@proto-kit/library";
import { Runtime } from "@proto-kit/module";
import { Protocol } from "@proto-kit/protocol";
import { Bool, PrivateKey, UInt64 } from "o1js";
import "reflect-metadata";
import { container } from "tsyringe";
import { afterEach, expect } from "@jest/globals";

import {
Sequencer,
VanillaTaskWorkerModules,
AppChain,
InMemoryDatabase,
} from "../../src";
import {
DefaultTestingSequencerModules,
testingSequencerModules,
} from "../TestingSequencer";
import { RecursiveProofModule } from "../../src/sequencer/output/RecursiveProofModule";

import { Balance } from "./mocks/Balance";
import { BlockTestService } from "./services/BlockTestService";

describe("RecursiveProofModule", () => {
let sequencer: Sequencer<
DefaultTestingSequencerModules & {
Database: typeof InMemoryDatabase;
RecursiveProofModule: typeof RecursiveProofModule;
}
>;

let appChain: AppChain<any>;

let test: BlockTestService;

beforeEach(async () => {
const runtimeClass = Runtime.from({
Balance,
});

const sequencerClass = Sequencer.from({
...testingSequencerModules({}),
Database: InMemoryDatabase,
RecursiveProofModule,
});

// TODO Analyze how we can get rid of the library import for mandatory modules
const protocolClass = Protocol.from(
VanillaProtocolModules.mandatoryModules({})
);

const app = AppChain.from({
Runtime: runtimeClass,
Sequencer: sequencerClass,
Protocol: protocolClass,
});

app.configure({
Sequencer: {
Database: {},
BlockTrigger: {},
Mempool: {},
BatchProducerModule: {},
BlockProducerModule: {},
WorkerModule: VanillaTaskWorkerModules.defaultConfig(),
BaseLayer: {},
TaskQueue: {},
FeeStrategy: {},
SequencerStartupModule: {},
RecursiveProofModule: {},
},
Runtime: {
Balance: {},
},
Protocol: {
...Protocol.defaultConfig(),
},
});

// Start AppChain
await app.start(false, container.createChildContainer());

appChain = app;

({ sequencer } = app);

test = app.sequencer.dependencyContainer.resolve(BlockTestService);
}, 30000);

afterEach(async () => {
await appChain.close();
});

it("should produce a dummy block proof", async () => {
log.setLevel("DEBUG");
expect.assertions(12);

const outputModule = sequencer.resolve("RecursiveProofModule");

const privateKey = PrivateKey.random();
const publicKey = privateKey.toPublicKey();

await test.addTransaction({
method: ["Balance", "setBalanceIf"],
privateKey,
args: [publicKey, UInt64.from(100), Bool(true)],
});

// let [block, batch] = await blockTrigger.produceBlockAndBatch();
const block = await test.produceBlock();

expect(block).toBeDefined();
expect(block!.transactions).toHaveLength(1);

const batch = await test.produceBatch();

expect(batch).toBeDefined();
expect(batch!.blockHashes).toHaveLength(1);

const recursiveBatch = await outputModule.proveRecursively();
expect(recursiveBatch.proof.publicInput).toStrictEqual(
batch!.proof.publicInput
);

// Second tx
await test.addTransaction({
method: ["Balance", "addBalanceToSelf"],
privateKey,
args: [UInt64.from(100), UInt64.from(1)],
});

log.info("Starting second block");

const [block2, batch2] = await test.produceBlockAndBatch();

expect(block2).toBeDefined();
expect(block2!.transactions).toHaveLength(1);
expect(batch2!.blockHashes).toHaveLength(1);

const recursiveBatch2 = await outputModule.proveRecursively();
expect(recursiveBatch2.proof.publicInput).toStrictEqual(
batch!.proof.publicInput
);
expect(recursiveBatch2.proof.publicOutput).toStrictEqual(
batch2!.proof.publicOutput
);

const storedProperty = await outputModule.readProof();
expect(storedProperty).toBeDefined();
expect(storedProperty!.toBatchHeight).toBe(1);
}, 60_000);
});
Loading