From 6ac88988566731956efa6cf6b2d8b727ccb29efa Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 23 Dec 2023 23:21:12 +0100 Subject: [PATCH 01/54] Change Block prod pipeline to Untyped ST object --- packages/protocol/src/model/Option.ts | 123 ++++++++++-------- packages/sequencer/src/index.ts | 2 + .../production/TransactionTraceService.ts | 17 +-- .../production/helpers/UntypedOption.ts | 43 ++++++ .../helpers/UntypedStateTransition.ts | 60 +++++++++ .../unproven/TransactionExecutionService.ts | 19 ++- 6 files changed, 195 insertions(+), 69 deletions(-) create mode 100644 packages/sequencer/src/protocol/production/helpers/UntypedOption.ts create mode 100644 packages/sequencer/src/protocol/production/helpers/UntypedStateTransition.ts diff --git a/packages/protocol/src/model/Option.ts b/packages/protocol/src/model/Option.ts index 2c9ec077e..f101b883c 100644 --- a/packages/protocol/src/model/Option.ts +++ b/packages/protocol/src/model/Option.ts @@ -17,10 +17,66 @@ export class ProvableOption extends Struct({ } } +export abstract class OptionBase { + protected abstract encodeValueToFields(): Field[]; + + protected abstract clone(): OptionBase; + + public constructor(public isSome: Bool, public isForcedSome: Bool) {} + + public forceSome() { + this.isForcedSome = Provable.if(this.isSome, Bool(false), Bool(true)); + this.isSome = Bool(true); + } + + /** + * @returns Tree representation of the current value + */ + public get treeValue() { + const treeValue = Poseidon.hash(this.encodeValueToFields()); + + return Provable.if( + this.isSome.and(this.isForcedSome.not()), + treeValue, + Field(0) + ); + } + + /** + * Returns the `to`-value as decoded as a list of fields + * Not in circuit + */ + public toFields(): Field[] { + if (this.isSome.toBoolean()) { + return this.encodeValueToFields(); + } + return [Field(0)]; + } + + /** + * @returns Provable representation of the current option. + */ + public toProvable() { + return new ProvableOption({ + isSome: this.isSome, + value: this.treeValue, + }); + } + + public toJSON() { + const value = this.encodeValueToFields().map((field) => field.toString()); + + return { + isSome: this.isSome.toBoolean(), + value, + }; + } +} + /** * Option facilitating in-circuit values that may or may not exist. */ -export class Option { +export class Option extends OptionBase { /** * Creates a new Option from the provided parameters * @@ -58,55 +114,21 @@ export class Option { return new Option(Bool(false), Field(0), Field); } - public isForcedSome = Bool(false); - public constructor( - public isSome: Bool, + isSome: Bool, public value: Value, - public valueType: FlexibleProvablePure - ) {} - - public clone() { - return new Option(this.isSome, this.value, this.valueType); - } - - public forceSome() { - this.isForcedSome = Provable.if(this.isSome, Bool(false), Bool(true)); - this.isSome = Bool(true); + public valueType: FlexibleProvablePure, + isForcedSome = Bool(false) + ) { + super(isSome, isForcedSome); } - /** - * @returns Tree representation of the current value - */ - public get treeValue() { - const treeValue = Poseidon.hash(this.valueType.toFields(this.value)); - - return Provable.if( - this.isSome.and(this.isForcedSome.not()), - treeValue, - Field(0) - ); + public encodeValueToFields(): Field[] { + return this.valueType.toFields(this.value); } - /** - * Returns the `to`-value as decoded as a list of fields - * Not in circuit - */ - public toFields(): Field[] { - if (this.isSome.toBoolean()) { - return this.valueType.toFields(this.value); - } - return [Field(0)]; - } - - /** - * @returns Provable representation of the current option. - */ - public toProvable() { - return new ProvableOption({ - isSome: this.isSome, - value: this.treeValue, - }); + public clone(): Option { + return new Option(this.isSome, this.value, this.valueType); } /** @@ -121,17 +143,4 @@ export class Option { defaultValue ); } - - public toJSON() { - const valueContent = this.valueType - .toFields(this.value) - .map((field) => field.toString()) - .reduce((a, b) => `${a}, ${b}`); - - return { - isSome: this.isSome.toBoolean(), - - value: `[${valueContent}]`, - }; - } } diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index ce8a0e9c5..a0106e371 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -19,6 +19,8 @@ export * from "./worker/worker/FlowTaskWorker"; export * from "./worker/worker/LocalTaskWorkerModule"; export * from "./protocol/baselayer/BaseLayer"; 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/tasks/CompileRegistry"; export * from "./protocol/production/tasks/RuntimeProvingTask"; diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index c478af8e1..84f230f09 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -23,11 +23,12 @@ import { SyncCachedMerkleTreeStore } from "../../state/merkle/SyncCachedMerkleTr import type { TransactionTrace } from "./BlockProducerModule"; import type { TransactionExecutionResult } from "./unproven/TransactionExecutionService"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; +import { UntypedStateTransition } from "./helpers/UntypedStateTransition"; @injectable() @scoped(Lifecycle.ContainerScoped) export class TransactionTraceService { - private allKeys(stateTransitions: StateTransition[]): Field[] { + private allKeys(stateTransitions: UntypedStateTransition[]): Field[] { // We have to do the distinct with strings because // array.indexOf() doesn't work with fields return stateTransitions.map((st) => st.path).filter(distinctByString); @@ -35,7 +36,7 @@ export class TransactionTraceService { private async collectStartingState( stateService: CachedStateService, - stateTransitions: StateTransition[] + stateTransitions: UntypedStateTransition[] ): Promise> { const keys = this.allKeys(stateTransitions); await stateService.preloadKeys(keys); @@ -48,14 +49,14 @@ export class TransactionTraceService { private applyTransitions( stateService: CachedStateService, - stateTransitions: StateTransition[] + stateTransitions: UntypedStateTransition[] ): void { // Use updated stateTransitions since only they will have the // right values stateTransitions - .filter((st) => st.to.isSome.toBoolean()) + .filter((st) => st.toValue.isSome.toBoolean()) .forEach((st) => { - stateService.set(st.path, st.to.toFields()); + stateService.set(st.path, st.toValue.toFields()); }); } @@ -148,8 +149,8 @@ export class TransactionTraceService { private async createMerkleTrace( merkleStore: CachedMerkleTreeStore, - stateTransitions: StateTransition[], - protocolTransitions: StateTransition[], + stateTransitions: UntypedStateTransition[], + protocolTransitions: UntypedStateTransition[], runtimeSuccess: boolean ): Promise<{ stParameters: StateTransitionProofParameters[]; @@ -180,7 +181,7 @@ export class TransactionTraceService { ); const allTransitions = protocolTransitions - .map<[StateTransition, boolean]>((protocolTransition) => [ + .map<[UntypedStateTransition, boolean]>((protocolTransition) => [ protocolTransition, StateTransitionType.protocol, ]) diff --git a/packages/sequencer/src/protocol/production/helpers/UntypedOption.ts b/packages/sequencer/src/protocol/production/helpers/UntypedOption.ts new file mode 100644 index 000000000..13cfcdb47 --- /dev/null +++ b/packages/sequencer/src/protocol/production/helpers/UntypedOption.ts @@ -0,0 +1,43 @@ +import { Bool, Field } from "o1js"; +import { Option, OptionBase } from "@proto-kit/protocol"; + +/** + * Option facilitating in-circuit values that may or may not exist. + */ +export class UntypedOption extends OptionBase { + public static fromOption(option: Option | Option) { + return new UntypedOption( + option.isSome, + option.encodeValueToFields(), + option.isForcedSome + ); + } + + public static fromJSON({ + isSome, + value, + isForcedSome, + }: { + isSome: boolean; + value: string[]; + isForcedSome: boolean; + }): UntypedOption { + return new UntypedOption( + Bool(isSome), + value.map((v) => Field(v)), + Bool(isForcedSome) + ); + } + + public constructor(isSome: Bool, public value: Field[], isForcedSome: Bool) { + super(isSome, isForcedSome); + } + + public clone() { + return new UntypedOption(this.isSome, this.value, this.isForcedSome); + } + + protected encodeValueToFields(): Field[] { + return this.value; + } +} diff --git a/packages/sequencer/src/protocol/production/helpers/UntypedStateTransition.ts b/packages/sequencer/src/protocol/production/helpers/UntypedStateTransition.ts new file mode 100644 index 000000000..5f4c28787 --- /dev/null +++ b/packages/sequencer/src/protocol/production/helpers/UntypedStateTransition.ts @@ -0,0 +1,60 @@ +import { Bool, Field } from "o1js"; +import { ProvableStateTransition, StateTransition } from "@proto-kit/protocol"; + +import { UntypedOption } from "./UntypedOption"; + +/** + * Generic state transition that constraints the current method circuit + * to external state, by providing a state anchor. + */ +export class UntypedStateTransition { + public static fromStateTransition(st: StateTransition) { + return new UntypedStateTransition( + st.path, + UntypedOption.fromOption(st.fromValue), + UntypedOption.fromOption(st.toValue) + ); + } + + public static fromJSON({ + path, + from, + to, + }: { + path: string; + from: Parameters[0]; + to: Parameters[0]; + }): UntypedStateTransition { + return new UntypedStateTransition( + Field(path), + UntypedOption.fromJSON(from), + UntypedOption.fromJSON(to) + ); + } + + public constructor( + public path: Field, + public fromValue: UntypedOption, + public toValue: UntypedOption + ) {} + + /** + * Converts a StateTransition to a ProvableStateTransition, + * while enforcing the 'from' property to be 'Some' in all cases. + */ + public toProvable(): ProvableStateTransition { + return new ProvableStateTransition({ + path: this.path, + from: this.fromValue.toProvable(), + to: this.fromValue.toProvable(), + }); + } + + public toJSON() { + return { + path: this.path.toString(), + from: this.fromValue.toJSON(), + to: this.toValue.toJSON(), + }; + } +} diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index cd15bc46a..1e32128cb 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -35,6 +35,7 @@ import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeSto import type { StateRecord } from "../BlockProducerModule"; import { RuntimeMethodExecution } from "./RuntimeMethodExecution"; +import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; const errors = { methodIdNotFound: (methodId: string) => @@ -43,10 +44,14 @@ const errors = { export interface TransactionExecutionResult { tx: PendingTransaction; - stateTransitions: StateTransition[]; - protocolTransitions: StateTransition[]; + stateTransitions: UntypedStateTransition[]; + protocolTransitions: UntypedStateTransition[]; status: Bool; statusMessage?: string; + /** + * TODO Remove + * @deprecated + */ stateDiff: StateRecord; } @@ -474,11 +479,17 @@ export class TransactionExecutionService { return { tx, - stateTransitions: runtimeResult.stateTransitions, - protocolTransitions: protocolResult.stateTransitions, status: runtimeResult.status, statusMessage: runtimeResult.statusMessage, + stateTransitions: runtimeResult.stateTransitions.map((st) => + UntypedStateTransition.fromStateTransition(st) + ), + + protocolTransitions: protocolResult.stateTransitions.map((st) => + UntypedStateTransition.fromStateTransition(st) + ), + stateDiff, }; } From 64b922c0c9c5fbaf8ddde0d2ece0f74e0ce365e7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 23 Dec 2023 23:26:45 +0100 Subject: [PATCH 02/54] Added Prisma and Redis connections --- package-lock.json | 183 ++++++++++++++++++ packages/persistance/.gitignore | 3 + packages/persistance/docker/.gitignore | 2 + .../persistance/docker/docker-compose.yml | 25 +++ packages/persistance/docker/redis.conf | 3 + packages/persistance/jest.config.cjs | 1 + packages/persistance/package.json | 38 ++++ packages/persistance/prisma/schema.prisma | 76 ++++++++ .../src/PrismaDatabaseConnection.ts | 5 + packages/persistance/src/RedisConnection.ts | 29 +++ packages/persistance/src/index.ts | 3 + packages/persistance/tsconfig.json | 8 + packages/persistance/tsconfig.test.json | 9 + 13 files changed, 385 insertions(+) create mode 100644 packages/persistance/.gitignore create mode 100644 packages/persistance/docker/.gitignore create mode 100644 packages/persistance/docker/docker-compose.yml create mode 100644 packages/persistance/docker/redis.conf create mode 100644 packages/persistance/jest.config.cjs create mode 100644 packages/persistance/package.json create mode 100644 packages/persistance/prisma/schema.prisma create mode 100644 packages/persistance/src/PrismaDatabaseConnection.ts create mode 100644 packages/persistance/src/RedisConnection.ts create mode 100644 packages/persistance/src/index.ts create mode 100644 packages/persistance/tsconfig.json create mode 100644 packages/persistance/tsconfig.test.json diff --git a/package-lock.json b/package-lock.json index fc10343fe..3f0659804 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3858,6 +3858,68 @@ "node": ">=14" } }, + "node_modules/@prisma/client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.7.1.tgz", + "integrity": "sha512-TUSa4nUcC4nf/e7X3jyO1pEd6XcI/TLRCA0KjkA46RDIpxUaRsBYEOqITwXRW2c0bMFyKcCRXrH4f7h4q9oOlg==", + "hasInstallScript": true, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.7.1.tgz", + "integrity": "sha512-yrVSO/YZOxdeIxcBtZ5BaNqUfPrZkNsAKQIQg36cJKMxj/VYK3Vk5jMKkI+gQLl0KReo1YvX8GWKfV788SELjw==", + "devOptional": true + }, + "node_modules/@prisma/engines": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.7.1.tgz", + "integrity": "sha512-R+Pqbra8tpLP2cvyiUpx+SIKglav3nTCpA+rn6826CThviQ8yvbNG0s8jNpo51vS9FuZO3pOkARqG062vKX7uA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.7.1", + "@prisma/engines-version": "5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5", + "@prisma/fetch-engine": "5.7.1", + "@prisma/get-platform": "5.7.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5.tgz", + "integrity": "sha512-dIR5IQK/ZxEoWRBDOHF87r1Jy+m2ih3Joi4vzJRP+FOj5yxCwS2pS5SBR3TWoVnEK1zxtLI/3N7BjHyGF84fgw==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.7.1.tgz", + "integrity": "sha512-9ELauIEBkIaEUpMIYPRlh5QELfoC6pyHolHVQgbNxglaINikZ9w9X7r1TIePAcm05pCNp2XPY1ObQIJW5nYfBQ==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.7.1", + "@prisma/engines-version": "5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5", + "@prisma/get-platform": "5.7.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.7.1.tgz", + "integrity": "sha512-eDlswr3a1m5z9D/55Iyt/nZqS5UpD+DZ9MooBB3hvrcPhDQrcf9m4Tl7buy4mvAtrubQ626ECtb8c6L/f7rGSQ==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.7.1" + } + }, "node_modules/@proto-kit/api": { "resolved": "packages/api", "link": true @@ -3874,6 +3936,10 @@ "resolved": "packages/module", "link": true }, + "node_modules/@proto-kit/persistance": { + "resolved": "packages/persistance", + "link": true + }, "node_modules/@proto-kit/protocol": { "resolved": "packages/protocol", "link": true @@ -6249,6 +6315,64 @@ "node": ">=16" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.13.tgz", + "integrity": "sha512-epkUM9D0Sdmt93/8Ozk43PNjLi36RZzG+d/T1Gdu5AI8jvghonTeLYV69WVWdilvFo+PYxbP0TZ0saMvr6nscQ==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", + "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", + "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@repeaterjs/repeater": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz", @@ -13757,6 +13881,14 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -22812,6 +22944,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prisma": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.7.1.tgz", + "integrity": "sha512-ekho7ziH0WEJvC4AxuJz+ewRTMskrebPcrKuBwcNzVDniYxx+dXOGcorNeIb9VEMO5vrKzwNYvhD271Ui2jnNw==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.7.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, "node_modules/proc-log": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", @@ -23781,6 +23929,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/redis": { + "version": "4.6.12", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.12.tgz", + "integrity": "sha512-41Xuuko6P4uH4VPe5nE3BqXHB7a9lkFL0J29AlxKaIfD6eWO8VO/5PDF9ad2oS+mswMsfFxaM5DlE3tnXT+P8Q==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.13", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.6", + "@redis/search": "1.1.6", + "@redis/time-series": "1.0.5" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -27641,6 +27802,28 @@ "tsyringe": "^4.7.0" } }, + "packages/persistance": { + "name": "@proto-kit/persistance", + "version": "0.1.1-develop.267+b252853", + "license": "MIT", + "dependencies": { + "@prisma/client": "^5.7.1", + "@proto-kit/common": "*", + "@proto-kit/sequencer": "*", + "lodash": "^4.17.21", + "redis": "^4.6.12", + "reflect-metadata": "^0.1.13" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/lodash": "^4.14.194", + "prisma": "^5.7.1" + }, + "peerDependencies": { + "o1js": "0.13.1", + "tsyringe": "^4.7.0" + } + }, "packages/protocol": { "name": "@proto-kit/protocol", "version": "0.1.1-develop.267+b252853", diff --git a/packages/persistance/.gitignore b/packages/persistance/.gitignore new file mode 100644 index 000000000..11ddd8dbe --- /dev/null +++ b/packages/persistance/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Keep environment variables out of version control +.env diff --git a/packages/persistance/docker/.gitignore b/packages/persistance/docker/.gitignore new file mode 100644 index 000000000..42b0ae238 --- /dev/null +++ b/packages/persistance/docker/.gitignore @@ -0,0 +1,2 @@ +postgres +redis \ No newline at end of file diff --git a/packages/persistance/docker/docker-compose.yml b/packages/persistance/docker/docker-compose.yml new file mode 100644 index 000000000..1e5e49feb --- /dev/null +++ b/packages/persistance/docker/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.9' + +services: + postgres: + image: postgres:14-alpine + container_name: protokit-db + ports: + - 5432:5432 + volumes: + - ./postgres:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=password + - POSTGRES_USER=admin + - POSTGRES_DB=protokit + + redis: + image: redis:6.2-alpine + container_name: protokit-redis + restart: unless-stopped + ports: + - '6379:6379' + command: redis-server /redis.conf --requirepass password + volumes: + - ./redis:/data + - ./redis.conf:/redis.conf \ No newline at end of file diff --git a/packages/persistance/docker/redis.conf b/packages/persistance/docker/redis.conf new file mode 100644 index 000000000..35470ba0e --- /dev/null +++ b/packages/persistance/docker/redis.conf @@ -0,0 +1,3 @@ +save 60 100 +appendonly yes +loglevel warning \ No newline at end of file diff --git a/packages/persistance/jest.config.cjs b/packages/persistance/jest.config.cjs new file mode 100644 index 000000000..87e143bf4 --- /dev/null +++ b/packages/persistance/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("../../jest.config.cjs"); diff --git a/packages/persistance/package.json b/packages/persistance/package.json new file mode 100644 index 000000000..739c39d32 --- /dev/null +++ b/packages/persistance/package.json @@ -0,0 +1,38 @@ +{ + "name": "@proto-kit/persistance", + "license": "MIT", + "private": false, + "type": "module", + "version": "0.1.1-develop.267+b252853", + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch", + "lint": "eslint ./src ./test", + "test:file": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads ../../node_modules/jest/bin/jest.js", + "test": "npm run test:file -- ./src/** ./test/**", + "test:watch": "npm run test:file -- ./src/** ./test/** --watch" + }, + "main": "dist/index.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@prisma/client": "^5.7.1", + "@proto-kit/common": "*", + "@proto-kit/protocol": "*", + "@proto-kit/sequencer": "*", + "lodash": "^4.17.21", + "redis": "^4.6.12", + "reflect-metadata": "^0.1.13" + }, + "peerDependencies": { + "o1js": "0.13.1", + "tsyringe": "^4.7.0" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/lodash": "^4.14.194", + "prisma": "^5.7.1" + }, + "gitHead": "b2528538c73747d000cc3ea99ee26ee415d8248d" +} diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma new file mode 100644 index 000000000..d80ef685c --- /dev/null +++ b/packages/persistance/prisma/schema.prisma @@ -0,0 +1,76 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model State { + path BigInt @id + values BigInt[] +} + +model TreeElement { + key Decimal @db.Decimal(78, 0) + level Int @db.SmallInt + value Decimal @db.Decimal(78, 0) + + @@id([key, level]) +} + +model Transaction { + hash String @id + + methodId String + sender String + nonce String + args String[] + signature_r String + signature_s String + + executionResult TransactionExecutionResult? +} + +model TransactionExecutionResult { + stateTransitions Json @db.Json + protocolTransitions Json @db.Json + status Boolean + statusMessage String? + + tx Transaction @relation(fields: [txHash], references: [hash]) + txHash String @id + + block Block @relation(fields: [blockHash], references: [transactionsHash]) + blockHash String +} + +model Block { + transactionsHash String @id + + networkState Json @db.Json + height Int + + transactions TransactionExecutionResult[] + + batch Batch? @relation(fields: [batchHeight], references: [height]) + batchHeight Int? +} + +model Batch { + height Int @id + + proof String + + blocks Block[] +} + +model UnprovenBlockMetadata { + height Int @id + resultingStateRoot String + resultingNetworkState Json @db.Json +} \ No newline at end of file diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts new file mode 100644 index 000000000..f407163f9 --- /dev/null +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from "@prisma/client"; + +export class PrismaDatabaseConnection { + public readonly client = new PrismaClient(); +} diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts new file mode 100644 index 000000000..55df61780 --- /dev/null +++ b/packages/persistance/src/RedisConnection.ts @@ -0,0 +1,29 @@ +import { createClient, RedisClientType } from "redis"; +import { SequencerModule } from "@proto-kit/sequencer"; + +export interface RedisConnectionConfig { + url: string; + password: string; +} + +export class RedisConnection extends SequencerModule { + private redisClient?: RedisClientType; + + public get client(): RedisClientType { + if (this.redisClient === undefined) { + throw new Error( + "Redis client not initialized yet, wait for .start() to be called" + ); + } + return this.redisClient; + } + + public async init() { + this.redisClient = createClient(this.config); + await this.redisClient.connect(); + } + + public async start(): Promise { + await this.init(); + } +} diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts new file mode 100644 index 000000000..fae6eff84 --- /dev/null +++ b/packages/persistance/src/index.ts @@ -0,0 +1,3 @@ +export * from "./PrismaDatabaseConnection"; +export * from "./services/prisma/PrismaMerkleTreeStore"; +export * from "./services/prisma/PrismaStateService"; diff --git a/packages/persistance/tsconfig.json b/packages/persistance/tsconfig.json new file mode 100644 index 000000000..7e1bf2a1c --- /dev/null +++ b/packages/persistance/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["./src/index.ts"], + "exclude": ["./dist/**/*.ts"] +} diff --git a/packages/persistance/tsconfig.test.json b/packages/persistance/tsconfig.test.json new file mode 100644 index 000000000..6f8322c29 --- /dev/null +++ b/packages/persistance/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./../../tsconfig.json", + "include": [ + "./src/**/*.test.ts", + "./test/**/*.ts", + "./test/*.ts", + "./src/**/*.ts" + ] +} From bcdbfb1e6e9bdae34a10a7a065a0692a47d3e957 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 23 Dec 2023 23:27:38 +0100 Subject: [PATCH 03/54] Added object mappers --- packages/persistance/src/ObjectMapper.ts | 4 ++ .../services/prisma/mappers/BlockMapper.ts | 36 +++++++++++ .../services/prisma/mappers/FieldMapper.ts | 14 +++++ .../prisma/mappers/StateTransitionMapper.ts | 39 ++++++++++++ .../prisma/mappers/TransactionMapper.ts | 63 +++++++++++++++++++ .../mappers/UnprovenBlockMetadataMapper.ts | 35 +++++++++++ 6 files changed, 191 insertions(+) create mode 100644 packages/persistance/src/ObjectMapper.ts create mode 100644 packages/persistance/src/services/prisma/mappers/BlockMapper.ts create mode 100644 packages/persistance/src/services/prisma/mappers/FieldMapper.ts create mode 100644 packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts create mode 100644 packages/persistance/src/services/prisma/mappers/TransactionMapper.ts create mode 100644 packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts diff --git a/packages/persistance/src/ObjectMapper.ts b/packages/persistance/src/ObjectMapper.ts new file mode 100644 index 000000000..6d4b9c5a3 --- /dev/null +++ b/packages/persistance/src/ObjectMapper.ts @@ -0,0 +1,4 @@ +export interface ObjectMapper { + mapOut: (input: SequenceObject) => DTO + mapIn: (input: DTO) => SequenceObject +} \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts new file mode 100644 index 000000000..9fe4c9147 --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -0,0 +1,36 @@ +import { singleton } from "tsyringe"; +import { UnprovenBlock } from "@proto-kit/sequencer"; +import { Block } from "@prisma/client"; +import { DefaultProvableHashList, NetworkState } from "@proto-kit/protocol"; +import { Field } from "o1js"; + +import { ObjectMapper } from "../../../ObjectMapper"; + +@singleton() +export class BlockMapper implements ObjectMapper { + public mapIn(input: Block): UnprovenBlock { + return { + transactions: [], + + networkState: new NetworkState( + NetworkState.fromJSON(input.networkState as any) + ), + + transactionsHash: Field(input.transactionsHash), + }; + } + + public mapOut(input: UnprovenBlock): Block { + const transactionsHash = new DefaultProvableHashList(Field); + input.transactions.forEach((tx) => { + transactionsHash.push(tx.tx.hash()); + }); + + return { + height: Number(input.networkState.block.height.toBigInt()), + networkState: NetworkState.toJSON(input.networkState), + transactionsHash: transactionsHash.commitment.toString(), + batchHeight: null, + }; + } +} diff --git a/packages/persistance/src/services/prisma/mappers/FieldMapper.ts b/packages/persistance/src/services/prisma/mappers/FieldMapper.ts new file mode 100644 index 000000000..703a9d90c --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/FieldMapper.ts @@ -0,0 +1,14 @@ +import { Field } from "o1js"; +import { singleton } from "tsyringe"; +import { ObjectMapper } from "../../../ObjectMapper"; + +@singleton() +export class FieldMapper implements ObjectMapper { + public mapIn(input: string): Field[] { + return input.split(";").map(s => Field(s)); + } + + public mapOut(input: Field[]): string { + return input.map(field => field.toString()).join(";"); + } +} \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts b/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts new file mode 100644 index 000000000..d8bafc0fa --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts @@ -0,0 +1,39 @@ +import { singleton } from "tsyringe"; +import { ObjectMapper } from "../../../ObjectMapper"; +import { UntypedStateTransition } from "@proto-kit/sequencer"; +import { Prisma } from "@prisma/client"; + +@singleton() +export class StateTransitionMapper + implements ObjectMapper +{ + public mapIn(input: Prisma.JsonObject): UntypedStateTransition { + return UntypedStateTransition.fromJSON(input as any); + } + + public mapOut(input: UntypedStateTransition): Prisma.JsonObject { + return input.toJSON(); + } +} + +@singleton() +export class StateTransitionArrayMapper implements ObjectMapper { + public constructor(private readonly stMapper: StateTransitionMapper) { + } + + public mapIn(input: Prisma.JsonValue | undefined): UntypedStateTransition[] { + if (input === undefined) return []; + + if(Array.isArray(input)){ + return (input as Prisma.JsonArray) + .map(stJson => this.stMapper.mapIn(stJson as Prisma.JsonObject)); + } else { + return [] + } + } + + public mapOut(input: UntypedStateTransition[]): Prisma.JsonValue { + return input.map(st => this.stMapper.mapOut(st)) as Prisma.JsonArray; + } + +} \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts new file mode 100644 index 000000000..3527f0368 --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts @@ -0,0 +1,63 @@ +import { singleton } from "tsyringe"; +import { ObjectMapper } from "../../../ObjectMapper"; +import { PendingTransaction, TransactionExecutionResult } from "@proto-kit/sequencer"; +import { Transaction as DBTransaction, TransactionExecutionResult as DBTransactionExecutionResult } from "@prisma/client"; +import { Bool } from "o1js"; +import { StateTransitionArrayMapper } from "./StateTransitionMapper"; + +@singleton() +export class TransactionMapper + implements ObjectMapper +{ + public mapIn(input: DBTransaction): PendingTransaction { + return PendingTransaction.fromJSON({ + ...input, + signature: { + r: input.signature_r, + s: input.signature_s, + }, + }); + } + + public mapOut(input: PendingTransaction): DBTransaction { + const json = input.toJSON(); + return { + ...json, + signature_r: json.signature.r, + signature_s: json.signature.s, + hash: input.hash().toString() + }; + } +} + +@singleton() +export class TransactionExecutionResultMapper + implements ObjectMapper, DBTransaction]> { + public constructor(private readonly transactionMapper: TransactionMapper, private readonly stArrayMapper: StateTransitionArrayMapper) { + } + + public mapIn(input: [Omit, DBTransaction]): TransactionExecutionResult { + const executionResult = input[0]; + return { + tx: this.transactionMapper.mapIn(input[1]), + status: Bool(executionResult.status), + statusMessage: executionResult.statusMessage ?? undefined, + stateTransitions: this.stArrayMapper.mapIn(executionResult.stateTransitions), + protocolTransitions: this.stArrayMapper.mapIn(executionResult.protocolTransitions), + stateDiff: {} + }; + } + + mapOut(input: TransactionExecutionResult): [Omit, DBTransaction] { + const tx = this.transactionMapper.mapOut(input.tx); + const executionResult = { + status: input.status.toBoolean(), + statusMessage: input.statusMessage ?? null, + stateTransitions: this.stArrayMapper.mapOut(input.stateTransitions), + protocolTransitions: this.stArrayMapper.mapOut(input.protocolTransitions), + txHash: tx.hash + } + return [executionResult, tx]; + } + +} \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts new file mode 100644 index 000000000..06ab15dee --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts @@ -0,0 +1,35 @@ +import { singleton } from "tsyringe"; +import { ObjectMapper } from "../../../ObjectMapper"; +import { UnprovenBlockMetadata } from "@proto-kit/sequencer"; +import { UnprovenBlockMetadata as DBUnprovenBlockMetadata } from "@prisma/client"; +import { NetworkState } from "@proto-kit/protocol"; + +@singleton() +export class UnprovenBlockMetadataMapper + implements + ObjectMapper< + UnprovenBlockMetadata, + Omit + > +{ + public mapIn( + input: Omit + ): UnprovenBlockMetadata { + return { + resultingStateRoot: BigInt(input.resultingStateRoot), + resultingNetworkState: new NetworkState( + NetworkState.fromJSON(input.resultingNetworkState as any) + ), + }; + } + + public mapOut( + input: UnprovenBlockMetadata + ): Omit { + return { + resultingStateRoot: input.resultingStateRoot.toString(), + + resultingNetworkState: NetworkState.toJSON(input.resultingNetworkState), + }; + } +} From b9c100e09af4348a092ad17a1bcef19586b57a65 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 23 Dec 2023 23:28:23 +0100 Subject: [PATCH 04/54] async stores: made commit and openTx async --- .../src/state/async/AsyncMerkleTreeStore.ts | 21 +++- .../src/state/async/AsyncStateService.ts | 4 +- .../src/state/merkle/CachedMerkleTreeStore.ts | 116 ++++++++++++------ .../src/state/state/CachedStateService.ts | 4 +- .../storage/MockStorageDependencyFactory.ts | 40 +++--- 5 files changed, 121 insertions(+), 64 deletions(-) diff --git a/packages/sequencer/src/state/async/AsyncMerkleTreeStore.ts b/packages/sequencer/src/state/async/AsyncMerkleTreeStore.ts index 30e0b8cdc..e6093e390 100644 --- a/packages/sequencer/src/state/async/AsyncMerkleTreeStore.ts +++ b/packages/sequencer/src/state/async/AsyncMerkleTreeStore.ts @@ -1,9 +1,20 @@ +export interface MerkleTreeNodeQuery { + key: bigint; + level: number; +} + +export interface MerkleTreeNode extends MerkleTreeNodeQuery { + value: bigint; +} + export interface AsyncMerkleTreeStore { - openTransaction: () => void; + openTransaction: () => Promise; - commit: () => void; + commit: () => Promise; - setNodeAsync: (key: bigint, level: number, value: bigint) => Promise; + writeNodes: (nodes: MerkleTreeNode[]) => void; - getNodeAsync: (key: bigint, level: number) => Promise; -} \ No newline at end of file + getNodesAsync: ( + nodes: MerkleTreeNodeQuery[] + ) => Promise<(bigint | undefined)[]>; +} diff --git a/packages/sequencer/src/state/async/AsyncStateService.ts b/packages/sequencer/src/state/async/AsyncStateService.ts index fe12fe841..6bc6304c1 100644 --- a/packages/sequencer/src/state/async/AsyncStateService.ts +++ b/packages/sequencer/src/state/async/AsyncStateService.ts @@ -6,9 +6,9 @@ import { Field } from "o1js"; * CachedStateService to preload keys for In-Circuit usage. */ export interface AsyncStateService { - openTransaction: () => void; + openTransaction: () => Promise; - commit: () => void; + commit: () => Promise; setAsync: (key: Field, value: Field[] | undefined) => Promise; diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index dcdabcbe6..bb41e0ce1 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -1,9 +1,13 @@ import { InMemoryMerkleTreeStorage, - RollupMerkleTree + RollupMerkleTree, } from "@proto-kit/protocol"; import { log, noop } from "@proto-kit/common"; -import { AsyncMerkleTreeStore } from "../async/AsyncMerkleTreeStore"; +import { + AsyncMerkleTreeStore, + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "../async/AsyncMerkleTreeStore"; export class CachedMerkleTreeStore extends InMemoryMerkleTreeStorage @@ -15,9 +19,13 @@ export class CachedMerkleTreeStore }; } = {}; - public openTransaction = noop; + public async openTransaction(): Promise { + noop(); + } - public commit = noop; + public async commit(): Promise { + noop(); + } public constructor(private readonly parent: AsyncMerkleTreeStore) { super(); @@ -53,8 +61,8 @@ export class CachedMerkleTreeStore index %= leafCount; } - // eslint-disable-next-line no-warning-comments,max-len - // TODO Not practical at the moment. Improve pattern when implementing DB storage + const nodesToRetrieve: MerkleTreeNodeQuery[] = []; + for (let level = 0; level < height; level++) { const key = index; @@ -64,25 +72,31 @@ export class CachedMerkleTreeStore // Only preload node if it is not already preloaded. // We also don't want to overwrite because changes will get lost (tracing) if (this.getNode(key, level) === undefined) { - // eslint-disable-next-line no-await-in-loop - const value = await this.parent.getNodeAsync(key, level); + nodesToRetrieve.push({ + key, + level, + }); if (level === 0) { - log.debug(`Preloaded ${key} @ ${level} -> ${value ?? "-"}`); - } - if (value !== undefined) { - this.setNode(key, level, value); + log.debug(`Queued preloading of ${key} @ ${level}`); } } if (this.getNode(siblingKey, level) === undefined) { - // eslint-disable-next-line no-await-in-loop - const sibling = await this.parent.getNodeAsync(siblingKey, level); - if (sibling !== undefined) { - this.setNode(siblingKey, level, sibling); - } + nodesToRetrieve.push({ + key: siblingKey, + level, + }); } index /= 2n; } + + const results = await this.parent.getNodesAsync(nodesToRetrieve); + nodesToRetrieve.forEach(({ key, level }, index) => { + const value = results[index]; + if (value !== undefined) { + this.setNode(key, level, value); + } + }); } public async mergeIntoParent(): Promise { @@ -91,19 +105,25 @@ export class CachedMerkleTreeStore return; } - this.parent.openTransaction(); + await this.parent.openTransaction(); const { height } = RollupMerkleTree; const nodes = this.getWrittenNodes(); - const promises = Array.from({ length: height }).flatMap((ignored, level) => - Object.entries(nodes[level]).map(async (entry) => { - await this.parent.setNodeAsync(BigInt(entry[0]), level, entry[1]); - }) - ); - - await Promise.all(promises); - - this.parent.commit(); + const writes = Array.from({ length: height }) + .fill(0) + .flatMap((ignored, level) => + Object.entries(nodes[level]).map(([key, value]) => { + return { + key: BigInt(key), + level, + value, + }; + }) + ); + + this.parent.writeNodes(writes); + + await this.parent.commit(); this.resetWrittenNodes(); } @@ -115,12 +135,38 @@ export class CachedMerkleTreeStore this.setNode(key, level, value); } - public async getNodeAsync( - key: bigint, - level: number - ): Promise { - return ( - this.getNode(key, level) ?? (await this.parent.getNodeAsync(key, level)) - ); + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + const results = Array( + nodes.length + ).fill(undefined); + + const toFetch: MerkleTreeNodeQuery[] = []; + + nodes.forEach((node, index) => { + const localResult = this.getNode(node.key, node.level); + if (localResult !== undefined) { + results[index] = localResult; + } else { + toFetch.push(node); + } + }); + + const fetchResult = (await this.parent.getNodesAsync(toFetch)).reverse(); + + results.forEach((result, index) => { + if (result === -1n) { + results[index] = fetchResult.pop(); + } + }); + + return results; + } + + public writeNodes(nodes: MerkleTreeNode[]): void { + nodes.forEach(({ key, level, value }) => { + this.setNode(key, level, value); + }); } -} \ No newline at end of file +} diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index 84d9097d7..530dfe2d4 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -28,11 +28,11 @@ export class CachedStateService } } - public commit(): void { + public async commit(): Promise { noop(); } - public openTransaction(): void { + public async openTransaction(): Promise { noop(); } diff --git a/packages/sequencer/src/storage/MockStorageDependencyFactory.ts b/packages/sequencer/src/storage/MockStorageDependencyFactory.ts index 45e6000c9..cb2b028e4 100644 --- a/packages/sequencer/src/storage/MockStorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/MockStorageDependencyFactory.ts @@ -10,7 +10,11 @@ import { noop, } from "@proto-kit/common"; -import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; +import { + AsyncMerkleTreeStore, + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "../state/async/AsyncMerkleTreeStore"; import { CachedStateService } from "../state/state/CachedStateService"; import { AsyncStateService } from "../state/async/AsyncStateService"; import { @@ -35,27 +39,24 @@ import { export class MockAsyncMerkleTreeStore implements AsyncMerkleTreeStore { private readonly store = new InMemoryMerkleTreeStorage(); - public commit(): void { + public async commit(): Promise { noop(); } - public openTransaction(): void { + public async openTransaction(): Promise { noop(); } - public async getNodeAsync( - key: bigint, - level: number - ): Promise { - return this.store.getNode(key, level); + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + return nodes.map(({ key, level }) => this.store.getNode(key, level)); } - public async setNodeAsync( - key: bigint, - level: number, - value: bigint - ): Promise { - this.store.setNode(key, level, value); + public writeNodes(nodes: MerkleTreeNode[]): void { + nodes.forEach(({ key, level, value }) => + this.store.setNode(key, level, value) + ); } } @@ -99,9 +100,7 @@ class MockUnprovenBlockStorage return await this.getBlockAt((await this.getCurrentBlockHeight()) - 1); } - public async popNewBlocks( - remove: boolean - ): Promise { + public async getNewBlocks(): Promise { const slice = this.blocks.slice(this.cursor); // eslint-disable-next-line putout/putout @@ -112,9 +111,10 @@ class MockUnprovenBlockStorage metadata = [undefined, ...metadata]; } - if (remove) { - this.cursor = this.blocks.length; - } + // This assumes that getNewBlocks() is only called once per block prod cycle + // TODO query batch storage which the last proven block was instead + this.cursor = this.blocks.length; + return slice.map((block, index) => ({ block, lastBlockMetadata: metadata[index], From cd508ea90657a8b01d5f7d87dd77fba6b0167a28 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 23 Dec 2023 23:28:57 +0100 Subject: [PATCH 05/54] Implemented first few Prisma stores --- .../services/prisma/PrismaMerkleTreeStore.ts | 70 +++++++ .../src/services/prisma/PrismaStateService.ts | 66 +++++++ .../prisma/PrismaUnprovenBlockQueue.ts | 185 ++++++++++++++++++ .../production/trigger/ManualBlockTrigger.ts | 2 +- .../production/trigger/TimedBlockTrigger.ts | 2 +- .../repositories/UnprovenBlockStorage.ts | 2 +- 6 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts create mode 100644 packages/persistance/src/services/prisma/PrismaStateService.ts create mode 100644 packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts diff --git a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts new file mode 100644 index 000000000..f03c15faf --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts @@ -0,0 +1,70 @@ +import { + AsyncMerkleTreeStore, + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "@proto-kit/sequencer"; + +import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { noop } from "@proto-kit/common"; +import { Prisma } from "@prisma/client"; + +/** + * @deprecated + */ +export class PrismaMerkleTreeStore implements AsyncMerkleTreeStore { + private cache: MerkleTreeNode[] = []; + + public constructor(private readonly connection: PrismaDatabaseConnection) {} + + public async openTransaction(): Promise { + noop(); + } + + public async commit(): Promise { + // TODO Filter distinct + + const start = Date.now(); + const array: [Prisma.Decimal, number, Prisma.Decimal][] = this.cache.map( + ({ key, level, value }) => [ + new Prisma.Decimal(key.toString()), + level, + new Prisma.Decimal(value.toString()), + ] + ); + console.log(`Took ${Date.now() - start} ms`) + + const start2 = Date.now(); + const rows = await this.connection.client.$executeRaw( + Prisma.sql`INSERT INTO "TreeElement" (key, level, value) VALUES ${Prisma.join( + array.map((entry) => Prisma.sql`(${Prisma.join(entry)})`) + )} ON CONFLICT ON CONSTRAINT "TreeElement_pkey" DO UPDATE SET value = EXCLUDED.value;` + ); + console.log(`Took ${Date.now() - start2} ms`) + console.log(`Rows: ${rows}`) + + this.cache = []; + } + + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + // this.connection.client.treeElement.create({ + // data: { + // key: new Prisma.Decimal(123) + // } + // }) + // const result = await this.connection.client.treeElement.findMany({ + // where: { + // + // key: { + // in: nodes.map(x => x.key) + // } + // } + // }) + return Array(nodes.length).fill(undefined); + } + + public writeNodes(nodes: MerkleTreeNode[]): void { + this.cache = this.cache.concat(nodes); + } +} diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts new file mode 100644 index 000000000..7757df13d --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -0,0 +1,66 @@ +import { AsyncStateService } from "@proto-kit/sequencer"; +import { Field } from "o1js"; +import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; + +export class PrismaStateService implements AsyncStateService { + private cache: [Field, Field[] | undefined][] = []; + + public constructor(private readonly connection: PrismaDatabaseConnection) {} + + public async commit(): Promise { + const { client } = this.connection; + + const data = this.cache + .filter((entry) => entry[1] !== undefined) + .map((entry) => { + return { + path: entry[0].toBigInt(), + values: entry[1]!.map((field) => field.toBigInt()), + }; + }); + + await client.$transaction([ + client.state.deleteMany({ + where: { + path: { + in: this.cache.map((x) => x[0].toBigInt()), + }, + }, + }), + client.state.createMany({ + data, + }), + ]); + + // const removeKeys = this.cache + // .filter((x) => x[1] === undefined) + // .map((x) => x[0]); + // + // if (removeKeys.length > 0) { + // await this.client.client.state.deleteMany({ + // where: { + // path: { + // in: removeKeys.map((x) => x.toBigInt()), + // }, + // }, + // }); + // } + + this.cache = []; + } + + public async getAsync(key: Field): Promise { + const record = await this.connection.client.state.findFirst({ + where: { + path: key.toBigInt(), + }, + }); + return record?.values.map((x) => Field(x)) ?? undefined; + } + + public async openTransaction(): Promise {} + + public async setAsync(key: Field, value: Field[] | undefined): Promise { + this.cache.push([key, value]); + } +} diff --git a/packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts b/packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts new file mode 100644 index 000000000..d65d4fd94 --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts @@ -0,0 +1,185 @@ +import { + HistoricalUnprovenBlockStorage, + TransactionExecutionResult, + UnprovenBlock, + UnprovenBlockMetadata, + UnprovenBlockQueue, + UnprovenBlockStorage, + UnprovenBlockWithPreviousMetadata, +} from "@proto-kit/sequencer"; +import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { TransactionExecutionResultMapper } from "./mappers/TransactionMapper"; +import { + Prisma, + TransactionExecutionResult as DBTransactionExecutionResult, +} from "@prisma/client"; +import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; +import { BlockMapper } from "./mappers/BlockMapper"; + +export class PrismaUnprovenBlockQueue + implements + UnprovenBlockQueue, + UnprovenBlockStorage, + HistoricalUnprovenBlockStorage +{ + public constructor( + private readonly connection: PrismaDatabaseConnection, + private readonly transactionResultMapper: TransactionExecutionResultMapper, + private readonly blockMetadataMapper: UnprovenBlockMetadataMapper, + private readonly blockMapper: BlockMapper + ) {} + + public async getBlockAt(height: number): Promise { + const result = await this.connection.client.block.findFirst({ + where: { + height, + }, + include: { + transactions: { + include: { + tx: true, + }, + }, + }, + }); + if (result === null) { + return undefined; + } + const transactions = result.transactions.map( + (txresult) => { + return this.transactionResultMapper.mapIn([txresult, txresult.tx]); + } + ); + return { + ...this.blockMapper.mapIn(result), + transactions, + }; + } + + public async pushBlock(block: UnprovenBlock): Promise { + const transactions = block.transactions.map( + (tx) => { + const encoded = this.transactionResultMapper.mapOut(tx); + return { + ...encoded[0], + blockHash: block.transactionsHash.toString(), + }; + } + ); + + const encodedBlock = this.blockMapper.mapOut(block); + + const result = await this.connection.client.block.create({ + data: { + ...encodedBlock, + networkState: encodedBlock.networkState as Prisma.InputJsonValue, + + transactions: { + createMany: { + data: transactions.map((tx) => { + return { + ...tx, + stateTransitions: tx.stateTransitions as Prisma.InputJsonValue, + protocolTransitions: + tx.protocolTransitions as Prisma.InputJsonValue, + }; + }), + }, + }, + + batchHeight: undefined, + }, + }); + } + + public async pushMetadata(metadata: UnprovenBlockMetadata): Promise { + // TODO Save this DB trip + const height = await this.getCurrentBlockHeight(); + + const encoded = this.blockMetadataMapper.mapOut(metadata); + + await this.connection.client.unprovenBlockMetadata.create({ + data: { + resultingNetworkState: + encoded.resultingNetworkState as Prisma.InputJsonValue, + + resultingStateRoot: encoded.resultingStateRoot, + height: height - 1, + }, + }); + } + + public async getCurrentBlockHeight(): Promise { + const result = await this.connection.client.block.aggregate({ + _max: { + height: true, + }, + }); + // TODO I have no idea what this should give in case no block are in the DB. Document properly + return (result?._max.height ?? -1) + 1; + } + + public async getLatestBlock(): Promise { + const height = await this.getCurrentBlockHeight(); + if (height > 0) { + return await this.getBlockAt(height - 1); + } + return undefined; + } + + public async getNewestMetadata(): Promise { + const height = await this.getCurrentBlockHeight(); + + const result = await this.connection.client.unprovenBlockMetadata.findFirst( + { + where: { + height, + }, + } + ); + + if (result === null) { + return undefined; + } + + return this.blockMetadataMapper.mapIn(result); + } + + public async getNewBlocks(): Promise { + const blocks = await this.connection.client.block.findMany({ + where: { + // eslint-disable-next-line unicorn/no-null + batch: null, + }, + orderBy: { + height: Prisma.SortOrder.asc, + }, + }); + + const minUnbatchedBlock = blocks + .map((b) => b.height) + .reduce((a, b) => (a < b ? a : b)); + + const metadata = + await this.connection.client.unprovenBlockMetadata.findMany({ + where: { + height: { + gte: minUnbatchedBlock, + }, + }, + }); + + return blocks.map((block, index) => { + const correspondingMetadata = metadata.find( + (entry) => entry.height == block.height - 1 + ); + return { + block: this.blockMapper.mapIn(block), + lastBlockMetadata: + correspondingMetadata !== undefined + ? this.blockMetadataMapper.mapIn(correspondingMetadata) + : undefined, + }; + }); + } +} diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index 3a2279f91..64abc3583 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -36,7 +36,7 @@ export class ManualBlockTrigger } public async produceProven(): Promise { - const blocks = await this.unprovenBlockQueue.popNewBlocks(true); + const blocks = await this.unprovenBlockQueue.getNewBlocks(); if (blocks.length > 0) { return await this.blockProducerModule.createBlock(blocks); } diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index 620bf7fe3..111944c6d 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -87,7 +87,7 @@ export class TimedBlockTrigger } private async produceBlock() { - const unprovenBlocks = await this.unprovenBlockQueue.popNewBlocks(true); + const unprovenBlocks = await this.unprovenBlockQueue.getNewBlocks(); if (unprovenBlocks.length > 0) { void this.blockProducerModule.createBlock(unprovenBlocks); } diff --git a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts index cbc11f9fd..0f6c29b08 100644 --- a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts +++ b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts @@ -9,7 +9,7 @@ import { export interface UnprovenBlockQueue { pushBlock: (block: UnprovenBlock) => Promise; pushMetadata: (metadata: UnprovenBlockMetadata) => Promise; - popNewBlocks: (remove: boolean) => Promise; + getNewBlocks: () => Promise; getNewestMetadata: () => Promise; } From a06107bb4818b28226eed6cef4b2a21fe149cfcb Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 23 Dec 2023 23:29:17 +0100 Subject: [PATCH 06/54] Added redis merkle tree store --- .../services/redis/RedisMerkleTreeStore.ts | 76 ++++++++++++++++ packages/persistance/test/run.test.ts | 89 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 packages/persistance/src/services/redis/RedisMerkleTreeStore.ts create mode 100644 packages/persistance/test/run.test.ts diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts new file mode 100644 index 000000000..12c0d99d0 --- /dev/null +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -0,0 +1,76 @@ +import { + AsyncMerkleTreeStore, + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "@proto-kit/sequencer"; + +import { noop } from "@proto-kit/common"; +import { Prisma } from "@prisma/client"; +import { RedisConnection } from "../../RedisConnection"; + +export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { + private cache: MerkleTreeNode[] = []; + + public constructor(private readonly connection: RedisConnection) {} + + public async openTransaction(): Promise { + noop(); + } + + public async commit(): Promise { + // TODO Filter distinct + + const start = Date.now(); + const array: [string, string][] = this.cache.map( + ({ key, level, value }) => { + const s = key.toString() + // const padded = Array(78 - s.length).fill("0").join("") + s; + return [ + level + ":" + s, + value.toString() + // Buffer.from(padded + ":" + level), + // Buffer.from(value.toString()) + ] + } + ); + console.log(`Took ${Date.now() - start} ms`); + + const start2 = Date.now(); + + if(array.length === 0){ + return; + } + + try { + console.log(array.slice(0, 5).flat(1)) + await this.connection.client!.mSet(array.flat(1)); + }catch(e){ + if(e instanceof Error){ + console.log(e.name) + console.log(e.message) + console.log(e.stack) + }else{ + console.log(e); + } + } + + console.log(`Took ${Date.now() - start2} ms`); + // console.log(`Rows: ${rows}`); + + this.cache = []; + } + + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + const keys = nodes.map(node => node.level + ":" + node.key.toString()) + + const result = await this.connection.client!.mGet(keys); + + return result.map(x => x !== null ? BigInt(x) : undefined) + } + + public writeNodes(nodes: MerkleTreeNode[]): void { + this.cache = this.cache.concat(nodes); + } +} diff --git a/packages/persistance/test/run.test.ts b/packages/persistance/test/run.test.ts new file mode 100644 index 000000000..f151eceb7 --- /dev/null +++ b/packages/persistance/test/run.test.ts @@ -0,0 +1,89 @@ +import { describe } from "@jest/globals"; +import { PrismaDatabaseConnection } from "../src/PrismaDatabaseConnection"; +import { PrismaStateService } from "../src/services/prisma/PrismaStateService"; +import { Field } from "o1js"; +import { PrismaMerkleTreeStore } from "../src"; +import { CachedMerkleTreeStore } from "@proto-kit/sequencer"; +import { RollupMerkleTree } from "@proto-kit/protocol"; +import { RedisConnection } from "../src/RedisConnection"; +import { RedisMerkleTreeStore } from "../src/services/redis/RedisMerkleTreeStore"; + +describe("prisma", () => { + it("merkle store", async () => { + const db = new RedisConnection(); + db.config = { + url: "redis://localhost:6379/", + password: "password", + } + await db.start(); + const store = new RedisMerkleTreeStore(db); + + const cached = new CachedMerkleTreeStore(store); + const tree = new RollupMerkleTree(cached); + + await cached.preloadKey(100n); + await cached.preloadKey(500n); + + tree.setLeaf(100n, Field(6)); + await cached.mergeIntoParent(); + + tree.setLeaf(500n, Field(100)); + await cached.mergeIntoParent(); + + console.log(`Root ${tree.getRoot().toBigInt()}`); + + const store2 = new RedisMerkleTreeStore(db); + + const cached2 = new CachedMerkleTreeStore(store2); + const tree2 = new RollupMerkleTree(cached2); + + await cached2.preloadKey(103n); + + expect(tree2.getRoot().toBigInt()).toStrictEqual(tree.getRoot().toBigInt()); + + await cached2.preloadKey(100n); + const witness = tree2.getWitness(100n); + + const witnessRoot = witness.calculateRoot(Field(6)); + expect(witnessRoot.toBigInt()).toStrictEqual(tree.getRoot().toBigInt()); + + await db.client!.disconnect(); + }); + + it.skip("merkle store", async () => { + const db = new PrismaDatabaseConnection(); + const store = new PrismaMerkleTreeStore(db); + + const cached = new CachedMerkleTreeStore(store); + const tree = new RollupMerkleTree(cached); + + tree.setLeaf(10n, Field(6)); + await cached.mergeIntoParent(); + + // store.writeNodes([{ key: 1n, level: 0, value: 15n }]); + // store.writeNodes([{ key: 2n, level: 0, value: 2n ** 244n }]); + // await store.commit(); + }); + + it.skip("fill and get", async () => { + const db = new PrismaDatabaseConnection(); + const service = new PrismaStateService(db); + + await service.setAsync(Field(5), [Field(100)]); + await service.commit(); + + const result = await service.getAsync(Field(5)); + console.log(`Received ${result?.map((x) => x.toString())}`); + + expect(result).toBeDefined(); + expect(result![0].toBigInt()).toBe(100n); + + await service.setAsync(Field(5), undefined); + await service.commit(); + + const result2 = await service.getAsync(Field(5)); + expect(result2).toBeUndefined(); + + await db.client.$disconnect(); + }); +}); From 097389081a24b06017042dc80842fb07afc5c241 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 28 Dec 2023 12:56:02 +0100 Subject: [PATCH 07/54] Added transactionsHash as id for blocks and references them in batch --- .eslintrc | 3 +- .../graphql/modules/BlockStorageResolver.ts | 45 ++++++++++--------- .../graphql/modules/UnprovenBlockResolver.ts | 29 +++++++++--- packages/persistance/prisma/schema.prisma | 2 +- .../production/BlockProducerModule.ts | 10 +---- .../storage/MockStorageDependencyFactory.ts | 4 ++ packages/sequencer/src/storage/model/Block.ts | 10 ++--- .../repositories/UnprovenBlockStorage.ts | 1 + 8 files changed, 60 insertions(+), 44 deletions(-) diff --git a/.eslintrc b/.eslintrc index 36a86fb5c..371a55224 100644 --- a/.eslintrc +++ b/.eslintrc @@ -131,7 +131,8 @@ "off" ], "nodejs/declare": "off", - "unicorn/prefer-event-target": "off" + "unicorn/prefer-event-target": "off", + "no-undef-init": "off" }, "overrides": [ diff --git a/packages/api/src/graphql/modules/BlockStorageResolver.ts b/packages/api/src/graphql/modules/BlockStorageResolver.ts index 9d37fe9d0..4f1dd6041 100644 --- a/packages/api/src/graphql/modules/BlockStorageResolver.ts +++ b/packages/api/src/graphql/modules/BlockStorageResolver.ts @@ -13,6 +13,10 @@ import { MOCK_PROOF } from "@proto-kit/common"; import { graphqlModule, GraphqlModule } from "../GraphqlModule"; import { TransactionObject } from "./MempoolResolver"; +import { + UnprovenBlockModel, + UnprovenBlockResolver, +} from "./UnprovenBlockResolver"; @ObjectType() export class ComputedBlockTransactionModel { @@ -48,32 +52,25 @@ export class ComputedBlockTransactionModel { @ObjectType() export class ComputedBlockModel { - public static fromServiceLayerModel({ - bundles, - proof, - }: ComputedBlock): ComputedBlockModel { + public static fromServiceLayerModel( + { bundles, proof }: ComputedBlock, + blocks: (UnprovenBlockModel | undefined)[] + ): ComputedBlockModel { return new ComputedBlockModel( - bundles.map((bundle) => - bundle.map((tx) => - ComputedBlockTransactionModel.fromServiceLayerModel(tx) - ) + bundles.map( + (bundle) => blocks.find((block) => block?.transactionsHash === bundle)! ), - proof.proof === MOCK_PROOF - ? "mock-proof" - : JSON.stringify(proof.toJSON()) + proof.proof === MOCK_PROOF ? "mock-proof" : JSON.stringify(proof) ); } - @Field(() => [[ComputedBlockTransactionModel]]) - public bundles: ComputedBlockTransactionModel[][]; + @Field(() => [UnprovenBlockModel]) + public bundles: UnprovenBlockModel[]; @Field() public proof: string; - public constructor( - bundles: ComputedBlockTransactionModel[][], - proof: string - ) { + public constructor(bundles: UnprovenBlockModel[], proof: string) { this.bundles = bundles; this.proof = proof; } @@ -84,7 +81,8 @@ export class BlockStorageResolver extends GraphqlModule { // TODO seperate these two block interfaces public constructor( @inject("BlockStorage") - private readonly blockStorage: BlockStorage & HistoricalBlockStorage + private readonly blockStorage: BlockStorage & HistoricalBlockStorage, + private readonly unprovenResolver: UnprovenBlockResolver ) { super(); } @@ -97,10 +95,15 @@ export class BlockStorageResolver extends GraphqlModule { const blockHeight = height ?? (await this.blockStorage.getCurrentBlockHeight()) - 1; - const block = await this.blockStorage.getBlockAt(blockHeight); + const batch = await this.blockStorage.getBlockAt(blockHeight); - if (block !== undefined) { - return ComputedBlockModel.fromServiceLayerModel(block); + if (batch !== undefined) { + const blocks = await Promise.all( + batch.bundles.map((bundle) => + this.unprovenResolver.block(undefined, bundle) + ) + ); + return ComputedBlockModel.fromServiceLayerModel(batch, blocks); } return undefined; } diff --git a/packages/api/src/graphql/modules/UnprovenBlockResolver.ts b/packages/api/src/graphql/modules/UnprovenBlockResolver.ts index f100c84b3..0e1d5f526 100644 --- a/packages/api/src/graphql/modules/UnprovenBlockResolver.ts +++ b/packages/api/src/graphql/modules/UnprovenBlockResolver.ts @@ -21,7 +21,8 @@ export class UnprovenBlockModel { status: tx.status.toBoolean(), statusMessage: tx.statusMessage, }) - ) + ), + unprovenBlock.transactionsHash.toString() ); } @@ -31,9 +32,17 @@ export class UnprovenBlockModel { @Field(() => [ComputedBlockTransactionModel]) txs: ComputedBlockTransactionModel[]; - private constructor(height: number, txs: ComputedBlockTransactionModel[]) { + @Field() + transactionsHash: string; + + private constructor( + height: number, + txs: ComputedBlockTransactionModel[], + transactionsHash: string + ) { this.height = height; this.txs = txs; + this.transactionsHash = transactionsHash; } } @@ -50,12 +59,20 @@ export class UnprovenBlockResolver extends GraphqlModule { @Query(() => UnprovenBlockModel, { nullable: true }) public async block( @Arg("height", () => Number, { nullable: true }) - height: number | undefined + height: number | undefined, + @Arg("transactionsHash", () => String, { nullable: true }) + transactionsHash: string | undefined ) { - const blockHeight = - height ?? (await this.blockStorage.getCurrentBlockHeight()) - 1; + let block: UnprovenBlock | undefined; - const block = await this.blockStorage.getBlockAt(blockHeight); + if (transactionsHash !== undefined) { + block = await this.blockStorage.getBlock(transactionsHash); + } else { + const blockHeight = + height ?? (await this.blockStorage.getCurrentBlockHeight()) - 1; + + block = await this.blockStorage.getBlockAt(blockHeight); + } if (block !== undefined) { return UnprovenBlockModel.fromServiceLayerModel(block); diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index d80ef685c..82ea8a4a9 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -64,7 +64,7 @@ model Block { model Batch { height Int @id - proof String + proof Json @db.Json blocks Block[] } diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index 4644d826d..589aa4a85 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -179,18 +179,12 @@ export class BlockProducerModule extends SequencerModule { this.productionInProgress = false; const computedBundles = unprovenBlocks.map((bundle) => - bundle.block.transactions.map((tx) => { - return { - tx: tx.tx, - status: tx.status.toBoolean(), - statusMessage: tx.statusMessage, - }; - }) + bundle.block.transactionsHash.toString() ); return { block: { - proof: block.proof, + proof: block.proof.toJSON(), bundles: computedBundles, }, diff --git a/packages/sequencer/src/storage/MockStorageDependencyFactory.ts b/packages/sequencer/src/storage/MockStorageDependencyFactory.ts index cb2b028e4..835133b89 100644 --- a/packages/sequencer/src/storage/MockStorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/MockStorageDependencyFactory.ts @@ -132,6 +132,10 @@ class MockUnprovenBlockStorage public async pushMetadata(metadata: UnprovenBlockMetadata): Promise { this.metadata.push(metadata); } + + public async getBlock(transactionsHash: string): Promise { + return this.blocks.find(block => block.transactionsHash.toString() === transactionsHash); + } } @dependencyFactory() diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index 537cff9c3..da373d4b2 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -1,8 +1,4 @@ -import { - BlockProverPublicInput, - BlockProverPublicOutput, -} from "@proto-kit/protocol"; -import { Proof } from "o1js"; +import { JsonProof } from "o1js"; import { PendingTransaction } from "../../mempool/PendingTransaction"; @@ -13,6 +9,6 @@ export interface ComputedBlockTransaction { } export interface ComputedBlock { - proof: Proof; - bundles: ComputedBlockTransaction[][]; + proof: JsonProof; + bundles: string[]; } diff --git a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts index 0f6c29b08..d19d9dd08 100644 --- a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts +++ b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts @@ -21,4 +21,5 @@ export interface UnprovenBlockStorage { export interface HistoricalUnprovenBlockStorage { getBlockAt: (height: number) => Promise; + getBlock: (transactionsHash: string) => Promise; } From 227300a6d90f8af35c8ae4b68244a154a41c08a1 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 28 Dec 2023 17:34:12 +0100 Subject: [PATCH 08/54] Implemented Prisma Batch storage --- packages/api/.eslintrc | 6 ++ .../graphql/modules/BlockStorageResolver.ts | 3 +- packages/persistance/.eslintrc | 7 ++ packages/persistance/src/index.ts | 3 + .../src/services/prisma/PrismaBatchStore.ts | 67 +++++++++++++++++++ ...venBlockQueue.ts => PrismaBlockStorage.ts} | 20 ++++-- .../services/prisma/mappers/BatchMapper.ts | 28 ++++++++ .../services/prisma/mappers/BlockMapper.ts | 9 +-- packages/sdk/test/graphql/graphql.ts | 4 +- 9 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 packages/api/.eslintrc create mode 100644 packages/persistance/.eslintrc create mode 100644 packages/persistance/src/services/prisma/PrismaBatchStore.ts rename packages/persistance/src/services/prisma/{PrismaUnprovenBlockQueue.ts => PrismaBlockStorage.ts} (92%) create mode 100644 packages/persistance/src/services/prisma/mappers/BatchMapper.ts diff --git a/packages/api/.eslintrc b/packages/api/.eslintrc new file mode 100644 index 000000000..19ab89724 --- /dev/null +++ b/packages/api/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": "../../.eslintrc", + "rules": { + "new-cap": "off" + } +} \ No newline at end of file diff --git a/packages/api/src/graphql/modules/BlockStorageResolver.ts b/packages/api/src/graphql/modules/BlockStorageResolver.ts index 4f1dd6041..3ca88d473 100644 --- a/packages/api/src/graphql/modules/BlockStorageResolver.ts +++ b/packages/api/src/graphql/modules/BlockStorageResolver.ts @@ -1,4 +1,3 @@ -/* eslint-disable new-cap */ import { inject, injectable } from "tsyringe"; import { Arg, Field, ObjectType, Query, Resolver } from "type-graphql"; import { IsBoolean } from "class-validator"; @@ -54,6 +53,7 @@ export class ComputedBlockTransactionModel { export class ComputedBlockModel { public static fromServiceLayerModel( { bundles, proof }: ComputedBlock, + // eslint-disable-next-line putout/putout blocks: (UnprovenBlockModel | undefined)[] ): ComputedBlockModel { return new ComputedBlockModel( @@ -100,6 +100,7 @@ export class BlockStorageResolver extends GraphqlModule { if (batch !== undefined) { const blocks = await Promise.all( batch.bundles.map((bundle) => + // TODO Find a graphql-native way of doing this relational 1-n mapping this.unprovenResolver.block(undefined, bundle) ) ); diff --git a/packages/persistance/.eslintrc b/packages/persistance/.eslintrc new file mode 100644 index 000000000..b0a0cfe31 --- /dev/null +++ b/packages/persistance/.eslintrc @@ -0,0 +1,7 @@ +{ + "extends": "../../.eslintrc", + "rules": { + "ext/lines-between-object-properties": "off", + "no-underscore-dangle": "off" + } +} \ No newline at end of file diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index fae6eff84..83279d80d 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -1,3 +1,6 @@ export * from "./PrismaDatabaseConnection"; export * from "./services/prisma/PrismaMerkleTreeStore"; export * from "./services/prisma/PrismaStateService"; +export * from "./services/prisma/PrismaBlockStorage"; +export * from "./services/prisma/PrismaBatchStore"; +export * from "./services/redis/RedisMerkleTreeStore"; diff --git a/packages/persistance/src/services/prisma/PrismaBatchStore.ts b/packages/persistance/src/services/prisma/PrismaBatchStore.ts new file mode 100644 index 000000000..2851e952a --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaBatchStore.ts @@ -0,0 +1,67 @@ +import { ProvenBatchStorage } from "@proto-kit/sequencer/dist/storage/repositories/ProvenBatchStorage"; +import { ComputedBlock, HistoricalBlockStorage } from "@proto-kit/sequencer"; +import { Prisma } from "@prisma/client"; + +import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; + +import { BatchMapper } from "./mappers/BatchMapper"; + +export class PrismaBatchStore + implements ProvenBatchStorage, HistoricalBlockStorage +{ + public constructor( + private readonly connection: PrismaDatabaseConnection, + private readonly batchMapper: BatchMapper + ) {} + + public async getBlockAt(height: number): Promise { + const batch = await this.connection.client.batch.findFirst({ + where: { + height, + }, + include: { + blocks: { + select: { + transactionsHash: true, + }, + }, + }, + }); + + if (batch === null) { + return undefined; + } + + const blocks = batch.blocks.map((block) => block.transactionsHash); + return this.batchMapper.mapIn([batch, blocks]); + } + + public async getCurrentHeight(): Promise { + const batch = await this.connection.client.batch.aggregate({ + _max: { + height: true, + }, + }); + return (batch?._max.height ?? -1) + 1; + } + + public async pushBlock(block: ComputedBlock): Promise { + const height = await this.getCurrentHeight(); + + const [entity] = this.batchMapper.mapOut(block); + await this.connection.client.batch.create({ + data: { + proof: entity.proof as Prisma.InputJsonValue, + height, + blocks: { + connect: block.bundles.map((transactionsHash) => ({ + transactionsHash, + })), + }, + }, + include: { + blocks: true, + }, + }); + } +} diff --git a/packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts similarity index 92% rename from packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts rename to packages/persistance/src/services/prisma/PrismaBlockStorage.ts index d65d4fd94..01456024c 100644 --- a/packages/persistance/src/services/prisma/PrismaUnprovenBlockQueue.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -16,7 +16,7 @@ import { import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; import { BlockMapper } from "./mappers/BlockMapper"; -export class PrismaUnprovenBlockQueue +export class PrismaBlockStorage implements UnprovenBlockQueue, UnprovenBlockStorage, @@ -29,11 +29,11 @@ export class PrismaUnprovenBlockQueue private readonly blockMapper: BlockMapper ) {} - public async getBlockAt(height: number): Promise { + private async getBlockByQuery( + where: { height: number } | { transactionsHash: string } + ): Promise { const result = await this.connection.client.block.findFirst({ - where: { - height, - }, + where, include: { transactions: { include: { @@ -56,6 +56,16 @@ export class PrismaUnprovenBlockQueue }; } + public async getBlockAt(height: number): Promise { + return await this.getBlockByQuery({ height }); + } + + public async getBlock( + transactionsHash: string + ): Promise { + return await this.getBlockByQuery({ transactionsHash }); + } + public async pushBlock(block: UnprovenBlock): Promise { const transactions = block.transactions.map( (tx) => { diff --git a/packages/persistance/src/services/prisma/mappers/BatchMapper.ts b/packages/persistance/src/services/prisma/mappers/BatchMapper.ts new file mode 100644 index 000000000..be8d02acf --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/BatchMapper.ts @@ -0,0 +1,28 @@ +import { injectable, singleton } from "tsyringe"; +import { ObjectMapper } from "../../../ObjectMapper"; +import { ComputedBlock, TransactionExecutionResult } from "@proto-kit/sequencer"; +import { Batch, Block } from "@prisma/client"; +import { BlockMapper } from "./BlockMapper"; +import { JsonProof, Proof } from "o1js"; + +@singleton() +export class BatchMapper implements ObjectMapper { + public constructor(private readonly blockMapper: BlockMapper) { + } + + public mapIn(input: [Batch, string[]]): ComputedBlock { + return { + bundles: input[1], + proof: input[0].proof as JsonProof + }; + } + + public mapOut(input: ComputedBlock): [Batch, string[]] { + const batch: Batch = { + proof: input.proof, + // TODO + height: 0 + } + return [batch, []]; + } +} \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts index 9fe4c9147..e4548191d 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -1,7 +1,7 @@ import { singleton } from "tsyringe"; import { UnprovenBlock } from "@proto-kit/sequencer"; import { Block } from "@prisma/client"; -import { DefaultProvableHashList, NetworkState } from "@proto-kit/protocol"; +import { NetworkState } from "@proto-kit/protocol"; import { Field } from "o1js"; import { ObjectMapper } from "../../../ObjectMapper"; @@ -21,15 +21,10 @@ export class BlockMapper implements ObjectMapper { } public mapOut(input: UnprovenBlock): Block { - const transactionsHash = new DefaultProvableHashList(Field); - input.transactions.forEach((tx) => { - transactionsHash.push(tx.tx.hash()); - }); - return { height: Number(input.networkState.block.height.toBigInt()), networkState: NetworkState.toJSON(input.networkState), - transactionsHash: transactionsHash.commitment.toString(), + transactionsHash: input.transactionsHash.toString(), batchHeight: null, }; } diff --git a/packages/sdk/test/graphql/graphql.ts b/packages/sdk/test/graphql/graphql.ts index 12dae2fbd..a6039f14a 100644 --- a/packages/sdk/test/graphql/graphql.ts +++ b/packages/sdk/test/graphql/graphql.ts @@ -94,7 +94,7 @@ export class Balances extends RuntimeModule { export async function startServer() { - // log.setLevel("DEBUG") + log.setLevel("DEBUG") const appChain = AppChain.from({ runtime: Runtime.from({ @@ -222,7 +222,7 @@ export async function startServer() { const tx2 = appChain.transaction(priv.toPublicKey(), () => { balances.addBalance(priv.toPublicKey(), UInt64.from(1000)) - }, {nonce: 0}) + }, {nonce: 1}) await tx2.sign(); await tx2.send(); From 00b1473443b5a9acb9d04406cf1b09326222607d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 30 Dec 2023 16:48:19 +0100 Subject: [PATCH 09/54] Merge changes fix --- .../inmemory/InMemoryAsyncMerkleTreeStore.ts | 32 ++++++++++--------- .../storage/inmemory/InMemoryBlockStorage.ts | 15 +++++---- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/sequencer/src/storage/inmemory/InMemoryAsyncMerkleTreeStore.ts b/packages/sequencer/src/storage/inmemory/InMemoryAsyncMerkleTreeStore.ts index e98fd8f33..c2466d564 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryAsyncMerkleTreeStore.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryAsyncMerkleTreeStore.ts @@ -1,30 +1,32 @@ -import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import { InMemoryMerkleTreeStorage } from "@proto-kit/protocol"; import { noop } from "@proto-kit/common"; +import { + AsyncMerkleTreeStore, + MerkleTreeNode, + MerkleTreeNodeQuery, +} from "../../state/async/AsyncMerkleTreeStore"; + export class InMemoryAsyncMerkleTreeStore implements AsyncMerkleTreeStore { private readonly store = new InMemoryMerkleTreeStorage(); - public commit(): void { - noop(); + public writeNodes(nodes: MerkleTreeNode[]): void { + nodes.forEach(({ key, level, value }) => + this.store.setNode(key, level, value) + ); } - public openTransaction(): void { + public async commit(): Promise { noop(); } - public async getNodeAsync( - key: bigint, - level: number - ): Promise { - return this.store.getNode(key, level); + public async openTransaction(): Promise { + noop(); } - public async setNodeAsync( - key: bigint, - level: number, - value: bigint - ): Promise { - this.store.setNode(key, level, value); + public async getNodesAsync( + nodes: MerkleTreeNodeQuery[] + ): Promise<(bigint | undefined)[]> { + return nodes.map(({ key, level }) => this.store.getNode(key, level)); } } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts index 154754c57..a92d65dae 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts @@ -33,9 +33,7 @@ export class InMemoryBlockStorage return await this.getBlockAt((await this.getCurrentBlockHeight()) - 1); } - public async popNewBlocks( - remove: boolean - ): Promise { + public async getNewBlocks(): Promise { const slice = this.blocks.slice(this.cursor); // eslint-disable-next-line putout/putout @@ -46,9 +44,10 @@ export class InMemoryBlockStorage metadata = [undefined, ...metadata]; } - if (remove) { - this.cursor = this.blocks.length; - } + // This assumes that getNewBlocks() is only called once per block prod cycle + // TODO query batch storage which the last proven block was instead + this.cursor = this.blocks.length; + return slice.map((block, index) => ({ block, lastBlockMetadata: metadata[index], @@ -66,4 +65,8 @@ export class InMemoryBlockStorage public async pushMetadata(metadata: UnprovenBlockMetadata): Promise { this.metadata.push(metadata); } + + public async getBlock(transactionsHash: string): Promise { + return this.blocks.find(block => block.transactionsHash.toString() === transactionsHash); + } } From 0245814c601c4ccb1d1457e7cf6978e5f5411ca8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 30 Dec 2023 23:37:02 +0100 Subject: [PATCH 10/54] Changed datatype in stateservice from bigint to decimal --- packages/persistance/prisma/schema.prisma | 7 ++- .../src/services/prisma/PrismaStateService.ts | 57 +++++++++++-------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 82ea8a4a9..229761136 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -11,8 +11,11 @@ datasource db { } model State { - path BigInt @id - values BigInt[] + path Decimal @db.Decimal(78, 0) + values Decimal[] @db.Decimal(78, 0) + mask String @db.VarChar(256) + + @@id([path, mask]) } model TreeElement { diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 7757df13d..926c25d7a 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -1,29 +1,39 @@ import { AsyncStateService } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { inject, injectable } from "tsyringe"; +import { Prisma } from "@prisma/client"; +import { noop } from "@proto-kit/common"; +@injectable() export class PrismaStateService implements AsyncStateService { private cache: [Field, Field[] | undefined][] = []; - public constructor(private readonly connection: PrismaDatabaseConnection) {} + /** + * @param connection + * @param mask A indicator to which masking level the values belong + */ + public constructor( + @inject("Database") private readonly connection: PrismaDatabaseConnection, + private readonly mask: string = "base" + ) {} public async commit(): Promise { const { client } = this.connection; const data = this.cache .filter((entry) => entry[1] !== undefined) - .map((entry) => { - return { - path: entry[0].toBigInt(), - values: entry[1]!.map((field) => field.toBigInt()), - }; - }); + .map((entry) => ({ + path: new Prisma.Decimal(entry[0].toString()), + values: entry[1]!.map((field) => new Prisma.Decimal(field.toString())), + mask: this.mask, + })); await client.$transaction([ client.state.deleteMany({ where: { path: { - in: this.cache.map((x) => x[0].toBigInt()), + in: this.cache.map((x) => new Prisma.Decimal(x[0].toString())), }, }, }), @@ -32,33 +42,30 @@ export class PrismaStateService implements AsyncStateService { }), ]); - // const removeKeys = this.cache - // .filter((x) => x[1] === undefined) - // .map((x) => x[0]); - // - // if (removeKeys.length > 0) { - // await this.client.client.state.deleteMany({ - // where: { - // path: { - // in: removeKeys.map((x) => x.toBigInt()), - // }, - // }, - // }); - // } - this.cache = []; } public async getAsync(key: Field): Promise { const record = await this.connection.client.state.findFirst({ where: { - path: key.toBigInt(), + AND: [ + { + path: new Prisma.Decimal(key.toString()), + }, + { + mask: this.mask, + }, + ], }, }); - return record?.values.map((x) => Field(x)) ?? undefined; + // x.toNumber() is safe, because we know that the actual DB-type + // is a decimal with 0 decimal places + return record?.values.map((x) => Field(x.toNumber())) ?? undefined; } - public async openTransaction(): Promise {} + public async openTransaction(): Promise { + noop(); + } public async setAsync(key: Field, value: Field[] | undefined): Promise { this.cache.push([key, value]); From 6fb23371c592d158e9ff4a23f9e8dbb256551548 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 30 Dec 2023 23:39:28 +0100 Subject: [PATCH 11/54] Dependency providers for prisma & redis databases --- .../src/PrismaDatabaseConnection.ts | 41 ++++++++++++++++- packages/persistance/src/RedisConnection.ts | 27 ++++++++++- .../src/services/prisma/PrismaBatchStore.ts | 21 +++++---- .../src/services/prisma/PrismaBlockStorage.ts | 12 +++-- .../services/redis/RedisMerkleTreeStore.ts | 45 +++++++++---------- .../unproven/TransactionExecutionService.ts | 43 +++++++++++------- .../unproven/UnprovenProducerModule.ts | 6 ++- .../src/storage/StorageDependencyFactory.ts | 6 +-- .../src/storage/inmemory/InMemoryDatabase.ts | 13 +++--- 9 files changed, 146 insertions(+), 68 deletions(-) diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index f407163f9..77ffd105d 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -1,5 +1,44 @@ import { PrismaClient } from "@prisma/client"; +import { + SequencerModule, + StorageDependencyMinimumDependencies, +} from "@proto-kit/sequencer"; +import { DependencyFactory, noop } from "@proto-kit/common"; -export class PrismaDatabaseConnection { +import { PrismaStateService } from "./services/prisma/PrismaStateService"; +import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; +import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; + +export class PrismaDatabaseConnection + extends SequencerModule + implements DependencyFactory +{ public readonly client = new PrismaClient(); + + public dependencies(): Omit< + StorageDependencyMinimumDependencies, + "asyncMerkleStore" | "unprovenMerkleStore" + > { + return { + asyncStateService: { + useClass: PrismaStateService, + }, + blockStorage: { + useClass: PrismaBatchStore, + }, + unprovenBlockQueue: { + useClass: PrismaBlockStorage, + }, + unprovenBlockStorage: { + useClass: PrismaBlockStorage, + }, + unprovenStateService: { + useFactory: () => new PrismaStateService(this, "block"), + }, + }; + } + + public async start(): Promise { + noop(); + } } diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index 55df61780..f2ea44d34 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -1,12 +1,21 @@ import { createClient, RedisClientType } from "redis"; -import { SequencerModule } from "@proto-kit/sequencer"; +import { + SequencerModule, + StorageDependencyMinimumDependencies, +} from "@proto-kit/sequencer"; +import { DependencyFactory } from "@proto-kit/common"; + +import { RedisMerkleTreeStore } from "./services/redis/RedisMerkleTreeStore"; export interface RedisConnectionConfig { url: string; password: string; } -export class RedisConnection extends SequencerModule { +export class RedisConnection + extends SequencerModule + implements DependencyFactory +{ private redisClient?: RedisClientType; public get client(): RedisClientType { @@ -18,6 +27,20 @@ export class RedisConnection extends SequencerModule { return this.redisClient; } + public dependencies(): Pick< + StorageDependencyMinimumDependencies, + "asyncMerkleStore" | "unprovenMerkleStore" + > { + return { + asyncMerkleStore: { + useClass: RedisMerkleTreeStore, + }, + unprovenMerkleStore: { + useFactory: () => new RedisMerkleTreeStore(this, "unproven"), + }, + }; + } + public async init() { this.redisClient = createClient(this.config); await this.redisClient.connect(); diff --git a/packages/persistance/src/services/prisma/PrismaBatchStore.ts b/packages/persistance/src/services/prisma/PrismaBatchStore.ts index 2851e952a..859f97a2c 100644 --- a/packages/persistance/src/services/prisma/PrismaBatchStore.ts +++ b/packages/persistance/src/services/prisma/PrismaBatchStore.ts @@ -1,16 +1,19 @@ -import { ProvenBatchStorage } from "@proto-kit/sequencer/dist/storage/repositories/ProvenBatchStorage"; -import { ComputedBlock, HistoricalBlockStorage } from "@proto-kit/sequencer"; +import { + ComputedBlock, + HistoricalBlockStorage, + BlockStorage, +} from "@proto-kit/sequencer"; import { Prisma } from "@prisma/client"; +import { inject, injectable } from "tsyringe"; -import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; import { BatchMapper } from "./mappers/BatchMapper"; -export class PrismaBatchStore - implements ProvenBatchStorage, HistoricalBlockStorage -{ +@injectable() +export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { public constructor( - private readonly connection: PrismaDatabaseConnection, + @inject("Database") private readonly connection: PrismaDatabaseConnection, private readonly batchMapper: BatchMapper ) {} @@ -36,7 +39,7 @@ export class PrismaBatchStore return this.batchMapper.mapIn([batch, blocks]); } - public async getCurrentHeight(): Promise { + public async getCurrentBlockHeight(): Promise { const batch = await this.connection.client.batch.aggregate({ _max: { height: true, @@ -46,7 +49,7 @@ export class PrismaBatchStore } public async pushBlock(block: ComputedBlock): Promise { - const height = await this.getCurrentHeight(); + const height = await this.getCurrentBlockHeight(); const [entity] = this.batchMapper.mapOut(block); await this.connection.client.batch.create({ diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 01456024c..a33b8d3d9 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -7,15 +7,19 @@ import { UnprovenBlockStorage, UnprovenBlockWithPreviousMetadata, } from "@proto-kit/sequencer"; -import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; -import { TransactionExecutionResultMapper } from "./mappers/TransactionMapper"; import { Prisma, TransactionExecutionResult as DBTransactionExecutionResult, } from "@prisma/client"; +import { inject, injectable } from "tsyringe"; + +import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; + +import { TransactionExecutionResultMapper } from "./mappers/TransactionMapper"; import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; import { BlockMapper } from "./mappers/BlockMapper"; +@injectable() export class PrismaBlockStorage implements UnprovenBlockQueue, @@ -23,7 +27,7 @@ export class PrismaBlockStorage HistoricalUnprovenBlockStorage { public constructor( - private readonly connection: PrismaDatabaseConnection, + @inject("Database") private readonly connection: PrismaDatabaseConnection, private readonly transactionResultMapper: TransactionExecutionResultMapper, private readonly blockMetadataMapper: UnprovenBlockMetadataMapper, private readonly blockMapper: BlockMapper @@ -125,7 +129,7 @@ export class PrismaBlockStorage height: true, }, }); - // TODO I have no idea what this should give in case no block are in the DB. Document properly + // TODO I have no idea what this should give in case no blocks are in the DB. Document properly return (result?._max.height ?? -1) + 1; } diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index 12c0d99d0..c9f025d7f 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -3,53 +3,52 @@ import { MerkleTreeNode, MerkleTreeNodeQuery, } from "@proto-kit/sequencer"; - import { noop } from "@proto-kit/common"; -import { Prisma } from "@prisma/client"; +import { inject, injectable } from "tsyringe"; + import { RedisConnection } from "../../RedisConnection"; +@injectable() export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { private cache: MerkleTreeNode[] = []; - public constructor(private readonly connection: RedisConnection) {} + public constructor( + @inject("Redis") private readonly connection: RedisConnection, + private readonly mask: string = "base" + ) {} public async openTransaction(): Promise { noop(); } + private getKey(node: MerkleTreeNodeQuery): string { + return `${this.mask}:${node.level}:${node.key.toString()}`; + } + public async commit(): Promise { // TODO Filter distinct const start = Date.now(); const array: [string, string][] = this.cache.map( - ({ key, level, value }) => { - const s = key.toString() - // const padded = Array(78 - s.length).fill("0").join("") + s; - return [ - level + ":" + s, - value.toString() - // Buffer.from(padded + ":" + level), - // Buffer.from(value.toString()) - ] - } + ({ key, level, value }) => [this.getKey({ key, level }), value.toString()] ); console.log(`Took ${Date.now() - start} ms`); const start2 = Date.now(); - if(array.length === 0){ + if (array.length === 0) { return; } try { - console.log(array.slice(0, 5).flat(1)) + console.log(array.slice(0, 5).flat(1)); await this.connection.client!.mSet(array.flat(1)); - }catch(e){ - if(e instanceof Error){ - console.log(e.name) - console.log(e.message) - console.log(e.stack) - }else{ + } catch (e) { + if (e instanceof Error) { + console.log(e.name); + console.log(e.message); + console.log(e.stack); + } else { console.log(e); } } @@ -63,11 +62,11 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { public async getNodesAsync( nodes: MerkleTreeNodeQuery[] ): Promise<(bigint | undefined)[]> { - const keys = nodes.map(node => node.level + ":" + node.key.toString()) + const keys = nodes.map((node) => this.getKey(node)); const result = await this.connection.client!.mGet(keys); - return result.map(x => x !== null ? BigInt(x) : undefined) + return result.map((x) => (x !== null ? BigInt(x) : undefined)); } public writeNodes(nodes: MerkleTreeNode[]): void { diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 6a275ae44..86da51c44 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -16,7 +16,8 @@ import { StateTransition, BlockTransactionPosition, BlockTransactionPositionType, - ProvableBlockHook, StateServiceProvider + ProvableBlockHook, + StateServiceProvider, } from "@proto-kit/protocol"; import { Bool, Field, Poseidon } from "o1js"; import { AreProofsEnabled, log } from "@proto-kit/common"; @@ -36,6 +37,7 @@ import type { StateRecord } from "../BlockProducerModule"; import { RuntimeMethodExecution } from "./RuntimeMethodExecution"; import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; const errors = { methodIdNotFound: (methodId: string) => @@ -199,7 +201,7 @@ export class TransactionExecutionService { * attached that is needed for tracing */ public async createUnprovenBlock( - stateService: CachedStateService, + stateService: AsyncStateService, transactions: PendingTransaction[], metadata: UnprovenBlockMetadata ): Promise { @@ -258,7 +260,7 @@ export class TransactionExecutionService { public async generateMetadataForNextBlock( block: UnprovenBlock, - merkleTreeStore: CachedMerkleTreeStore, + merkleTreeStore: AsyncMerkleTreeStore, modifyTreeStore = true ): Promise { // Flatten diff list into a single diff by applying them over each other @@ -269,10 +271,7 @@ export class TransactionExecutionService { return Object.assign(accumulator, diff); }, {}); - // If we modify the parent store, we use it, otherwise we abstract over it. - const inMemoryStore = modifyTreeStore - ? merkleTreeStore - : new CachedMerkleTreeStore(merkleTreeStore); + const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore); const tree = new RollupMerkleTree(inMemoryStore); for (const key of Object.keys(combinedDiff)) { @@ -302,6 +301,10 @@ export class TransactionExecutionService { block.networkState ); + if (modifyTreeStore) { + await inMemoryStore.mergeIntoParent(); + } + return { resultingNetworkState, resultingStateRoot: stateRoot.toBigInt(), @@ -382,11 +385,13 @@ export class TransactionExecutionService { // eslint-disable-next-line max-statements private async createExecutionTrace( - stateService: CachedStateService, + asyncStateService: AsyncStateService, tx: PendingTransaction, networkState: NetworkState, transactionPosition: BlockTransactionPositionType ): Promise { + const cachedStateService = new CachedStateService(asyncStateService); + const { method, args, module } = this.decodeTransaction(tx); // Disable proof generation for tracing @@ -414,17 +419,17 @@ export class TransactionExecutionService { args, runtimeContextInputs, blockContextInputs, - stateService + asyncStateService ); // Preload keys - await stateService.preloadKeys( + await cachedStateService.preloadKeys( runtimeKeys.concat(protocolKeys).filter(distinctByString) ); // Execute second time with preloaded state. The following steps // generate and apply the correct STs with the right values - this.stateServiceProvider.setCurrentStateService(stateService); + this.stateServiceProvider.setCurrentStateService(cachedStateService); const protocolResult = this.executeProtocolHooks( runtimeContextInputs, @@ -445,10 +450,13 @@ export class TransactionExecutionService { ); // Apply protocol STs - await this.applyTransitions(stateService, protocolResult.stateTransitions); + await this.applyTransitions( + cachedStateService, + protocolResult.stateTransitions + ); let stateDiff = this.collectStateDiff( - stateService, + cachedStateService, protocolResult.stateTransitions ); @@ -465,10 +473,13 @@ export class TransactionExecutionService { // Apply runtime STs (only if the tx succeeded) if (runtimeResult.status.toBoolean()) { - await this.applyTransitions(stateService, runtimeResult.stateTransitions); + await this.applyTransitions( + cachedStateService, + runtimeResult.stateTransitions + ); stateDiff = this.collectStateDiff( - stateService, + cachedStateService, protocolResult.stateTransitions.concat(runtimeResult.stateTransitions) ); } @@ -479,6 +490,8 @@ export class TransactionExecutionService { // Reset proofs enabled appChain.setProofsEnabled(previousProofsEnabled); + await cachedStateService.mergeIntoParent(); + return { tx, status: runtimeResult.status, diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index 70d944b6c..2a9a096f0 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -24,6 +24,8 @@ import { UnprovenBlock, UnprovenBlockMetadata, } from "./TransactionExecutionService"; +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; +import { AsyncStateService } from "../../../state/async/AsyncStateService"; const errors = { txRemovalFailed: () => new Error("Removal of txs from mempool failed"), @@ -45,9 +47,9 @@ export class UnprovenProducerModule public constructor( @inject("Mempool") private readonly mempool: Mempool, @inject("UnprovenStateService") - private readonly unprovenStateService: CachedStateService, + private readonly unprovenStateService: AsyncStateService, @inject("UnprovenMerkleStore") - private readonly unprovenMerkleStore: CachedMerkleTreeStore, + private readonly unprovenMerkleStore: AsyncMerkleTreeStore, @inject("UnprovenBlockQueue") private readonly unprovenBlockQueue: UnprovenBlockQueue, private readonly executionService: TransactionExecutionService diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index 1aec135a9..acd065b43 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -6,8 +6,6 @@ import { import { AsyncStateService } from "../state/async/AsyncStateService"; import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; -import { CachedStateService } from "../state/state/CachedStateService"; -import { CachedMerkleTreeStore } from "../state/merkle/CachedMerkleTreeStore"; import { BlockStorage } from "./repositories/BlockStorage"; import { @@ -21,8 +19,8 @@ export interface StorageDependencyMinimumDependencies extends DependencyRecord { blockStorage: DependencyDeclaration; unprovenBlockQueue: DependencyDeclaration; unprovenBlockStorage: DependencyDeclaration; - unprovenStateService: DependencyDeclaration; - unprovenMerkleStore: DependencyDeclaration; + unprovenStateService: DependencyDeclaration; + unprovenMerkleStore: DependencyDeclaration; } export interface StorageDependencyFactory extends DependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index 4c82f45f8..098081f27 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -15,25 +15,22 @@ import { import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; +import { InMemoryStateService } from "@proto-kit/module"; @sequencerModule() export class InMemoryDatabase extends SequencerModule implements StorageDependencyFactory { - private readonly asyncService = new CachedStateService(undefined); - - private readonly merkleStore = new InMemoryAsyncMerkleTreeStore(); - private readonly blockStorageQueue = new InMemoryBlockStorage(); public dependencies(): StorageDependencyMinimumDependencies { return { asyncMerkleStore: { - useValue: this.merkleStore, + useClass: InMemoryAsyncMerkleTreeStore, }, asyncStateService: { - useValue: this.asyncService, + useFactory: () => new CachedStateService(undefined), }, blockStorage: { useClass: InMemoryBatchStorage, @@ -45,10 +42,10 @@ export class InMemoryDatabase useValue: this.blockStorageQueue, }, unprovenStateService: { - useFactory: () => new CachedStateService(this.asyncService), + useFactory: () => new CachedStateService(undefined), }, unprovenMerkleStore: { - useFactory: () => new CachedMerkleTreeStore(this.merkleStore), + useClass: InMemoryAsyncMerkleTreeStore, }, }; } From 2ef56a352ea892c8ee2f71536c6a9bf3759f7638 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 1 Jan 2024 15:40:43 +0100 Subject: [PATCH 12/54] Fixed error for mock proofs --- .../services/prisma/mappers/BatchMapper.ts | 25 ++++++++++--------- .../production/BlockProducerModule.ts | 5 +++- packages/sequencer/src/storage/model/Block.ts | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/persistance/src/services/prisma/mappers/BatchMapper.ts b/packages/persistance/src/services/prisma/mappers/BatchMapper.ts index be8d02acf..ec5bed4af 100644 --- a/packages/persistance/src/services/prisma/mappers/BatchMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BatchMapper.ts @@ -1,19 +1,20 @@ -import { injectable, singleton } from "tsyringe"; +import { singleton } from "tsyringe"; +import { ComputedBlock } from "@proto-kit/sequencer"; +import { Batch } from "@prisma/client"; +import { JsonProof } from "o1js"; + import { ObjectMapper } from "../../../ObjectMapper"; -import { ComputedBlock, TransactionExecutionResult } from "@proto-kit/sequencer"; -import { Batch, Block } from "@prisma/client"; -import { BlockMapper } from "./BlockMapper"; -import { JsonProof, Proof } from "o1js"; @singleton() -export class BatchMapper implements ObjectMapper { - public constructor(private readonly blockMapper: BlockMapper) { - } +export class BatchMapper + implements ObjectMapper +{ + public constructor() {} public mapIn(input: [Batch, string[]]): ComputedBlock { return { bundles: input[1], - proof: input[0].proof as JsonProof + proof: input[0].proof as JsonProof, }; } @@ -21,8 +22,8 @@ export class BatchMapper implements ObjectMapper Date: Tue, 2 Jan 2024 11:54:19 +0100 Subject: [PATCH 13/54] Moved UnprovenBlock types to own file --- .../production/BlockProducerModule.ts | 8 ++--- .../production/TransactionTraceService.ts | 2 +- .../production/trigger/ManualBlockTrigger.ts | 2 +- .../unproven/TransactionExecutionService.ts | 29 ++++-------------- .../unproven/UnprovenProducerModule.ts | 9 ++---- .../storage/inmemory/InMemoryBlockStorage.ts | 12 +++++--- .../src/storage/model/UnprovenBlock.ts | 30 +++++++++++++++++++ .../repositories/UnprovenBlockStorage.ts | 9 ++---- 8 files changed, 54 insertions(+), 47 deletions(-) create mode 100644 packages/sequencer/src/storage/model/UnprovenBlock.ts diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index c982b5347..4e9679b75 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -21,16 +21,16 @@ import { CachedStateService } from "../../state/state/CachedStateService"; import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { AsyncStateService } from "../../state/async/AsyncStateService"; import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; +import { + UnprovenBlock, + UnprovenBlockMetadata, +} from "../../storage/model/UnprovenBlock"; import { BlockProverParameters } from "./tasks/BlockProvingTask"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; import { RuntimeProofParameters } from "./tasks/RuntimeTaskParameters"; import { TransactionTraceService } from "./TransactionTraceService"; import { BlockTaskFlowService } from "./BlockTaskFlowService"; -import { - UnprovenBlock, - UnprovenBlockMetadata, -} from "./unproven/TransactionExecutionService"; export interface StateRecord { [key: string]: Field[] | undefined; diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 911a8f904..c0724b776 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -19,9 +19,9 @@ import { distinctByString } from "../../helpers/utils"; import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { CachedStateService } from "../../state/state/CachedStateService"; import { SyncCachedMerkleTreeStore } from "../../state/merkle/SyncCachedMerkleTreeStore"; +import type { TransactionExecutionResult } from "../../storage/model/UnprovenBlock"; import type { TransactionTrace } from "./BlockProducerModule"; -import type { TransactionExecutionResult } from "./unproven/TransactionExecutionService"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; import { UntypedStateTransition } from "./helpers/UntypedStateTransition"; diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index 797da9a30..d855a5d18 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -5,8 +5,8 @@ import { SequencerModule } from "../../../sequencer/builder/SequencerModule"; import { ComputedBlock } from "../../../storage/model/Block"; import { BlockProducerModule } from "../BlockProducerModule"; import { UnprovenProducerModule } from "../unproven/UnprovenProducerModule"; +import { UnprovenBlock } from "../../../storage/model/UnprovenBlock"; import { UnprovenBlockQueue } from "../../../storage/repositories/UnprovenBlockStorage"; -import { UnprovenBlock } from "../unproven/TransactionExecutionService"; import { BlockTrigger } from "./BlockTrigger"; diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 429fa727d..f6007edc5 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -38,36 +38,17 @@ import type { StateRecord } from "../BlockProducerModule"; import { RuntimeMethodExecution } from "./RuntimeMethodExecution"; import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; +import { + TransactionExecutionResult, + UnprovenBlock, + UnprovenBlockMetadata, +} from "../../../storage/model/UnprovenBlock"; const errors = { methodIdNotFound: (methodId: string) => new Error(`Can't find runtime method with id ${methodId}`), }; -export interface TransactionExecutionResult { - tx: PendingTransaction; - stateTransitions: UntypedStateTransition[]; - protocolTransitions: UntypedStateTransition[]; - status: Bool; - statusMessage?: string; - /** - * TODO Remove - * @deprecated - */ - stateDiff: StateRecord; -} - -export interface UnprovenBlock { - networkState: NetworkState; - transactions: TransactionExecutionResult[]; - transactionsHash: Field; -} - -export interface UnprovenBlockMetadata { - resultingStateRoot: bigint; - resultingNetworkState: NetworkState; -} - @injectable() @scoped(Lifecycle.ContainerScoped) export class TransactionExecutionService { diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index 408b6239d..b24c0a50c 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -10,22 +10,19 @@ import { } from "@proto-kit/common"; import { Mempool } from "../../../mempool/Mempool"; -import { CachedStateService } from "../../../state/state/CachedStateService"; import { sequencerModule, SequencerModule, } from "../../../sequencer/builder/SequencerModule"; import { UnprovenBlockQueue } from "../../../storage/repositories/UnprovenBlockStorage"; import { PendingTransaction } from "../../../mempool/PendingTransaction"; -import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; +import { AsyncStateService } from "../../../state/async/AsyncStateService"; +import { UnprovenBlock, UnprovenBlockMetadata } from "../../../storage/model/UnprovenBlock"; import { TransactionExecutionService, - UnprovenBlock, - UnprovenBlockMetadata, } from "./TransactionExecutionService"; -import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; -import { AsyncStateService } from "../../../state/async/AsyncStateService"; const errors = { txRemovalFailed: () => new Error("Removal of txs from mempool failed"), diff --git a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts index a92d65dae..a594a1dce 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts @@ -3,10 +3,10 @@ import { UnprovenBlockQueue, UnprovenBlockStorage, } from "../repositories/UnprovenBlockStorage"; -import { +import type { UnprovenBlock, UnprovenBlockMetadata, -} from "../../protocol/production/unproven/TransactionExecutionService"; +} from "../model/UnprovenBlock"; import { UnprovenBlockWithPreviousMetadata } from "../../protocol/production/BlockProducerModule"; export class InMemoryBlockStorage @@ -66,7 +66,11 @@ export class InMemoryBlockStorage this.metadata.push(metadata); } - public async getBlock(transactionsHash: string): Promise { - return this.blocks.find(block => block.transactionsHash.toString() === transactionsHash); + public async getBlock( + transactionsHash: string + ): Promise { + return this.blocks.find( + (block) => block.transactionsHash.toString() === transactionsHash + ); } } diff --git a/packages/sequencer/src/storage/model/UnprovenBlock.ts b/packages/sequencer/src/storage/model/UnprovenBlock.ts new file mode 100644 index 000000000..5c6548ecd --- /dev/null +++ b/packages/sequencer/src/storage/model/UnprovenBlock.ts @@ -0,0 +1,30 @@ +import { Bool, Field } from "o1js"; +import { NetworkState } from "@proto-kit/protocol"; + +import { PendingTransaction } from "../../mempool/PendingTransaction"; +import { UntypedStateTransition } from "../../protocol/production/helpers/UntypedStateTransition"; +import { StateRecord } from "../../protocol/production/BlockProducerModule"; + +export interface TransactionExecutionResult { + tx: PendingTransaction; + stateTransitions: UntypedStateTransition[]; + protocolTransitions: UntypedStateTransition[]; + status: Bool; + statusMessage?: string; + /** + * TODO Remove + * @deprecated + */ + stateDiff: StateRecord; +} + +export interface UnprovenBlock { + networkState: NetworkState; + transactions: TransactionExecutionResult[]; + transactionsHash: Field; +} + +export interface UnprovenBlockMetadata { + resultingStateRoot: bigint; + resultingNetworkState: NetworkState; +} diff --git a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts index d19d9dd08..fb30360a8 100644 --- a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts +++ b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts @@ -1,10 +1,5 @@ -import { - UnprovenBlock, - UnprovenBlockMetadata -} from "../../protocol/production/unproven/TransactionExecutionService"; -import { - UnprovenBlockWithPreviousMetadata -} from "../../protocol/production/BlockProducerModule"; +import { UnprovenBlockWithPreviousMetadata } from "../../protocol/production/BlockProducerModule"; +import type { UnprovenBlock, UnprovenBlockMetadata } from "../model/UnprovenBlock"; export interface UnprovenBlockQueue { pushBlock: (block: UnprovenBlock) => Promise; From 6803953e4753181b9bf979c5f94645fd3ddb7bb7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 2 Jan 2024 12:09:35 +0100 Subject: [PATCH 14/54] Removed stateDiff from block model --- .../prisma/mappers/TransactionMapper.ts | 3 +- packages/sequencer/src/index.ts | 1 + .../unproven/TransactionExecutionService.ts | 45 ++++++++----------- .../src/storage/model/UnprovenBlock.ts | 6 --- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts index 3527f0368..63777ba1e 100644 --- a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts @@ -43,8 +43,7 @@ export class TransactionExecutionResultMapper status: Bool(executionResult.status), statusMessage: executionResult.statusMessage ?? undefined, stateTransitions: this.stArrayMapper.mapIn(executionResult.stateTransitions), - protocolTransitions: this.stArrayMapper.mapIn(executionResult.protocolTransitions), - stateDiff: {} + protocolTransitions: this.stArrayMapper.mapIn(executionResult.protocolTransitions) }; } diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index b56bd4306..d3dd98d2a 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -37,6 +37,7 @@ export * from "./protocol/production/unproven/RuntimeMethodExecution"; export * from "./protocol/production/unproven/TransactionExecutionService"; export * from "./protocol/production/unproven/UnprovenProducerModule"; export * from "./storage/model/Block"; +export * from "./storage/model/UnprovenBlock"; export * from "./storage/repositories/BlockStorage"; export * from "./storage/repositories/UnprovenBlockStorage"; export * from "./storage/inmemory/InMemoryDatabase"; diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index f6007edc5..8ea786a64 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -85,6 +85,18 @@ export class TransactionExecutionService { return stateTransitions.map((st) => st.path).filter(distinctByString); } + private collectStateDiff( + stateTransitions: UntypedStateTransition[] + ): StateRecord { + return stateTransitions.reduce>( + (state, st) => { + state[st.path.toString()] = st.toValue.value; + return state; + }, + {} + ); + } + private decodeTransaction(tx: PendingTransaction): { method: (...args: unknown[]) => unknown; args: unknown[]; @@ -246,7 +258,12 @@ export class TransactionExecutionService { ): Promise { // Flatten diff list into a single diff by applying them over each other const combinedDiff = block.transactions - .map((tx) => tx.stateDiff) + .map((tx) => { + const transitions = tx.protocolTransitions.concat( + tx.status.toBoolean() ? tx.stateTransitions : [] + ); + return this.collectStateDiff(transitions); + }) .reduce((accumulator, diff) => { // accumulator properties will be overwritten by diff's values return Object.assign(accumulator, diff); @@ -292,18 +309,6 @@ export class TransactionExecutionService { }; } - private collectStateDiff( - stateService: CachedStateService, - stateTransitions: StateTransition[] - ): StateRecord { - const keys = this.allKeys(stateTransitions); - - return keys.reduce>((state, key) => { - state[key.toString()] = stateService.get(key); - return state; - }, {}); - } - private async applyTransitions( stateService: CachedStateService, stateTransitions: StateTransition[] @@ -440,11 +445,6 @@ export class TransactionExecutionService { protocolResult.stateTransitions ); - let stateDiff = this.collectStateDiff( - cachedStateService, - protocolResult.stateTransitions - ); - const runtimeResult = this.executeRuntimeMethod( method, args, @@ -466,11 +466,6 @@ export class TransactionExecutionService { cachedStateService, runtimeResult.stateTransitions ); - - stateDiff = this.collectStateDiff( - cachedStateService, - protocolResult.stateTransitions.concat(runtimeResult.stateTransitions) - ); } // Reset global stateservice @@ -492,9 +487,7 @@ export class TransactionExecutionService { protocolTransitions: protocolResult.stateTransitions.map((st) => UntypedStateTransition.fromStateTransition(st) - ), - - stateDiff, + ) }; } } diff --git a/packages/sequencer/src/storage/model/UnprovenBlock.ts b/packages/sequencer/src/storage/model/UnprovenBlock.ts index 5c6548ecd..09dd56d9e 100644 --- a/packages/sequencer/src/storage/model/UnprovenBlock.ts +++ b/packages/sequencer/src/storage/model/UnprovenBlock.ts @@ -3,7 +3,6 @@ import { NetworkState } from "@proto-kit/protocol"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { UntypedStateTransition } from "../../protocol/production/helpers/UntypedStateTransition"; -import { StateRecord } from "../../protocol/production/BlockProducerModule"; export interface TransactionExecutionResult { tx: PendingTransaction; @@ -11,11 +10,6 @@ export interface TransactionExecutionResult { protocolTransitions: UntypedStateTransition[]; status: Bool; statusMessage?: string; - /** - * TODO Remove - * @deprecated - */ - stateDiff: StateRecord; } export interface UnprovenBlock { From 429135912bb90c803353e13bc02f547cf3d2bdf0 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 2 Jan 2024 17:56:06 +0100 Subject: [PATCH 15/54] Added stack package to execute hosted sequencer --- packages/stack/jest.config.cjs | 1 + packages/stack/package.json | 41 ++++ packages/stack/test/start/run-stack.test.ts | 9 + packages/stack/test/start/start.ts | 258 ++++++++++++++++++++ packages/stack/tsconfig.json | 10 + packages/stack/tsconfig.test.json | 12 + 6 files changed, 331 insertions(+) create mode 100644 packages/stack/jest.config.cjs create mode 100644 packages/stack/package.json create mode 100644 packages/stack/test/start/run-stack.test.ts create mode 100644 packages/stack/test/start/start.ts create mode 100644 packages/stack/tsconfig.json create mode 100644 packages/stack/tsconfig.test.json diff --git a/packages/stack/jest.config.cjs b/packages/stack/jest.config.cjs new file mode 100644 index 000000000..87e143bf4 --- /dev/null +++ b/packages/stack/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("../../jest.config.cjs"); diff --git a/packages/stack/package.json b/packages/stack/package.json new file mode 100644 index 000000000..669ab57ff --- /dev/null +++ b/packages/stack/package.json @@ -0,0 +1,41 @@ +{ + "name": "@proto-kit/stack", + "license": "MIT", + "private": false, + "type": "module", + "version": "0.1.1-develop.267+b252853", + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch", + "lint": "eslint ./src ./test", + "test:file": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads ../../node_modules/jest/bin/jest.js", + "test": "npm run test:file -- ./src/** ./test/**", + "test:watch": "npm run test:file -- ./src/** ./test/** --watch", + "start": "npm run test:file -- test/start/run-stack.test.ts" + }, + "main": "dist/index.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "lodash": "^4.17.21", + "loglevel": "^1.8.1" + }, + "peerDependencies": { + "@proto-kit/common": "*", + "@proto-kit/module": "*", + "@proto-kit/protocol": "*", + "@proto-kit/sequencer": "*", + "@proto-kit/sdk": "*", + "@proto-kit/api": "*", + "@proto-kit/persistance": "*", + "reflect-metadata": "^0.1.13", + "o1js": "0.13.1", + "tsyringe": "^4.7.0" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/lodash": "^4.14.194" + }, + "gitHead": "b2528538c73747d000cc3ea99ee26ee415d8248d" +} diff --git a/packages/stack/test/start/run-stack.test.ts b/packages/stack/test/start/run-stack.test.ts new file mode 100644 index 000000000..00ac8e976 --- /dev/null +++ b/packages/stack/test/start/run-stack.test.ts @@ -0,0 +1,9 @@ +import { startServer } from "./start"; +import { sleep } from "@proto-kit/common"; + +describe("run graphql", () => { + it("run", async () => { + const server = await startServer(); + await sleep(1000000000); + }, 1000000000); +}); diff --git a/packages/stack/test/start/start.ts b/packages/stack/test/start/start.ts new file mode 100644 index 000000000..5d3501f10 --- /dev/null +++ b/packages/stack/test/start/start.ts @@ -0,0 +1,258 @@ +import "reflect-metadata"; +import { Field, PrivateKey, PublicKey, UInt64 } from "o1js"; +import { + Runtime, + runtimeMethod, + RuntimeModule, + runtimeModule, + state, +} from "@proto-kit/module"; +import { + AccountStateModule, BlockHeightHook, + Option, + State, + StateMap, + VanillaProtocol +} from "@proto-kit/protocol"; +import { Presets, log, sleep } from "@proto-kit/common"; +import { + AsyncStateService, + BlockProducerModule, InMemoryDatabase, + LocalTaskQueue, + LocalTaskWorkerModule, + NoopBaseLayer, + PendingTransaction, + PrivateMempool, + Sequencer, + TimedBlockTrigger, UnprovenProducerModule, + UnsignedTransaction +} from "@proto-kit/sequencer"; +import { + BlockStorageResolver, + GraphqlSequencerModule, + GraphqlServer, + MempoolResolver, + NodeStatusResolver, + QueryGraphqlModule, UnprovenBlockResolver +} from "@proto-kit/api"; + +import { container } from "tsyringe"; +import { PrismaDatabaseConnection } from "@proto-kit/persistance"; +import { RedisConnection } from "@proto-kit/persistance/dist/RedisConnection"; +import { + AppChain, + BlockStorageNetworkStateModule, + InMemorySigner, + InMemoryTransactionSender, + StateServiceQueryModule +} from "@proto-kit/sdk"; + +log.setLevel(log.levels.INFO); + +function createNewTx() { + const pk = PrivateKey.random(); + + const tx = new UnsignedTransaction({ + nonce: UInt64.zero, + args: [Field(1)], + methodId: Field(1), + sender: pk.toPublicKey(), + }).sign(pk); + + console.log(tx.toJSON()); +} +createNewTx(); + +@runtimeModule() +export class Balances extends RuntimeModule { + /** + * We use `satisfies` here in order to be able to access + * presets by key in a type safe way. + */ + public static presets = {} satisfies Presets; + + @state() public balances = StateMap.from( + PublicKey, + UInt64 + ); + + @state() public totalSupply = State.from(UInt64); + + @runtimeMethod() + public getBalance(address: PublicKey): Option { + return this.balances.get(address); + } + + @runtimeMethod() + public addBalance(address: PublicKey, balance: UInt64) { + const totalSupply = this.totalSupply.get() + this.totalSupply.set(totalSupply.orElse(UInt64.zero).add(balance)); + + const previous = this.balances.get(address) + this.balances.set(address, previous.orElse(UInt64.zero).add(balance)); + } +} + +export async function startServer() { + + log.setLevel("INFO") + + const appChain = AppChain.from({ + runtime: Runtime.from({ + modules: { + Balances, + }, + + config: { + Balances: {}, + }, + }), + + protocol: VanillaProtocol.from( + { AccountStateModule, BlockHeightHook }, + { AccountStateModule: {}, StateTransitionProver: {}, BlockProver: {}, BlockHeightHook: {} } + ), + + sequencer: Sequencer.from({ + modules: { + Database: PrismaDatabaseConnection, + Redis: RedisConnection, + Mempool: PrivateMempool, + GraphqlServer, + LocalTaskWorkerModule, + BaseLayer: NoopBaseLayer, + BlockProducerModule, + UnprovenProducerModule, + BlockTrigger: TimedBlockTrigger, + TaskQueue: LocalTaskQueue, + + Graphql: GraphqlSequencerModule.from({ + modules: { + MempoolResolver, + QueryGraphqlModule, + BlockStorageResolver, + UnprovenBlockResolver, + NodeStatusResolver, + }, + + config: { + MempoolResolver: {}, + QueryGraphqlModule: {}, + BlockStorageResolver: {}, + NodeStatusResolver: {}, + UnprovenBlockResolver: {} + }, + }), + }, + }), + + modules: { + Signer: InMemorySigner, + TransactionSender: InMemoryTransactionSender, + QueryTransportModule: StateServiceQueryModule, + NetworkStateTransportModule: BlockStorageNetworkStateModule, + }, + }); + + appChain.configure({ + Runtime: { + Balances: {}, + }, + + Protocol: { + BlockProver: {}, + StateTransitionProver: {}, + AccountStateModule: {}, + BlockHeightHook: {}, + }, + + Sequencer: { + GraphqlServer: { + port: 8080, + host: "0.0.0.0", + graphiql: true + }, + + Graphql: { + QueryGraphqlModule: {}, + MempoolResolver: {}, + BlockStorageResolver: {}, + NodeStatusResolver: {}, + UnprovenBlockResolver: {} + }, + + Redis: { + url: "redis://localhost:6379/", + password: "password", + }, + + Database: {}, + Mempool: {}, + BlockProducerModule: {}, + LocalTaskWorkerModule: {}, + BaseLayer: {}, + TaskQueue: {}, + UnprovenProducerModule: {}, + + BlockTrigger: { + blockInterval: 15000, + settlementInterval: 30000, + }, + }, + + TransactionSender: {}, + QueryTransportModule: {}, + NetworkStateTransportModule: {}, + + Signer: { + signer: PrivateKey.random(), + }, + }); + + await appChain.start(container.createChildContainer()); + const pk = PublicKey.fromBase58( + "B62qmETai5Y8vvrmWSU8F4NX7pTyPqYLMhc1pgX3wD8dGc2wbCWUcqP" + ); + console.log(pk.toJSON()); + + const balances = appChain.runtime.resolve("Balances"); + + const priv = PrivateKey.fromBase58( + "EKFEMDTUV2VJwcGmCwNKde3iE1cbu7MHhzBqTmBtGAd6PdsLTifY" + ); + + const tx = appChain.transaction(priv.toPublicKey(), () => { + balances.addBalance(priv.toPublicKey(), UInt64.from(1000)) + }) + appChain.resolve("Signer").config.signer = priv + await tx.sign(); + await tx.send(); + // console.log((tx.transaction as PendingTransaction).toJSON()) + + const tx2 = appChain.transaction(priv.toPublicKey(), () => { + balances.addBalance(priv.toPublicKey(), UInt64.from(1000)) + }, {nonce: 1}) + await tx2.sign(); + await tx2.send(); + + const { txs } = appChain.sequencer.resolve("Mempool").getTxs() + console.log(txs.map(tx => tx.toJSON())) + console.log(txs.map(tx => tx.hash().toString())) + + console.log("Path:", balances.balances.getPath(pk).toString()); + + // const asyncState = + // appChain.sequencer.dependencyContainer.resolve( + // "AsyncStateService" + // ); + // await asyncState.setAsync(balances.balances.getPath(pk), [Field(100)]); + // await asyncState.setAsync(balances.totalSupply.path!, [Field(10_000)]); + + // appChain.query.runtime.Balances.totalSupply + + // await sleep(30000); + + return appChain +} + +// await startServer(); \ No newline at end of file diff --git a/packages/stack/tsconfig.json b/packages/stack/tsconfig.json new file mode 100644 index 000000000..70ad68aac --- /dev/null +++ b/packages/stack/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "./../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "moduleResolution": "node", + "lib": ["DOM"] + }, + "include": ["./src/index.ts"], + "exclude": ["./dist/**/*.ts"] +} diff --git a/packages/stack/tsconfig.test.json b/packages/stack/tsconfig.test.json new file mode 100644 index 000000000..2822c6172 --- /dev/null +++ b/packages/stack/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./../../tsconfig.json", + "compilerOptions": { + "lib": ["DOM"] + }, + "include": [ + "./src/**/*.test.ts", + "./test/**/*.ts", + "./test/*.ts", + "./src/**/*.ts" + ] +} From d067ba73c7672a6d079780c543f4aa16d2ce6458 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 2 Jan 2024 17:58:03 +0100 Subject: [PATCH 16/54] Link to parent block + metadata linkage by blockhash --- packages/persistance/.eslintrc | 3 +- packages/persistance/prisma/schema.prisma | 9 +- .../src/services/prisma/PrismaBlockStorage.ts | 103 +++++++++--------- .../services/prisma/mappers/BlockMapper.ts | 5 + .../mappers/UnprovenBlockMetadataMapper.ts | 5 +- .../production/BlockProducerModule.ts | 1 + .../unproven/TransactionExecutionService.ts | 7 ++ .../unproven/UnprovenProducerModule.ts | 1 + .../src/storage/model/UnprovenBlock.ts | 2 + 9 files changed, 83 insertions(+), 53 deletions(-) diff --git a/packages/persistance/.eslintrc b/packages/persistance/.eslintrc index b0a0cfe31..eda4c5f32 100644 --- a/packages/persistance/.eslintrc +++ b/packages/persistance/.eslintrc @@ -2,6 +2,7 @@ "extends": "../../.eslintrc", "rules": { "ext/lines-between-object-properties": "off", - "no-underscore-dangle": "off" + "no-underscore-dangle": "off", + "unicorn/no-null": "off" } } \ No newline at end of file diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 229761136..663c94470 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -58,7 +58,12 @@ model Block { networkState Json @db.Json height Int + parentTransactionsHash String? @unique + parent Block? @relation("Parent", fields: [parentTransactionsHash], references: [transactionsHash]) + successor Block? @relation("Parent") + transactions TransactionExecutionResult[] + metadata UnprovenBlockMetadata? batch Batch? @relation(fields: [batchHeight], references: [height]) batchHeight Int? @@ -73,7 +78,9 @@ model Batch { } model UnprovenBlockMetadata { - height Int @id + blockTransactionHash String @id @unique resultingStateRoot String resultingNetworkState Json @db.Json + + block Block? @relation(fields: [blockTransactionHash], references: [transactionsHash]) } \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index a33b8d3d9..53bd8f8e1 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -15,7 +15,10 @@ import { inject, injectable } from "tsyringe"; import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; -import { TransactionExecutionResultMapper } from "./mappers/TransactionMapper"; +import { + TransactionExecutionResultMapper, + TransactionMapper, +} from "./mappers/TransactionMapper"; import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; import { BlockMapper } from "./mappers/BlockMapper"; @@ -29,12 +32,13 @@ export class PrismaBlockStorage public constructor( @inject("Database") private readonly connection: PrismaDatabaseConnection, private readonly transactionResultMapper: TransactionExecutionResultMapper, + private readonly transactionMapper: TransactionMapper, private readonly blockMetadataMapper: UnprovenBlockMetadataMapper, private readonly blockMapper: BlockMapper ) {} private async getBlockByQuery( - where: { height: number } | { transactionsHash: string } + where: { height: number } | { parent: null } | { transactionsHash: string } ): Promise { const result = await this.connection.client.block.findFirst({ where, @@ -71,6 +75,8 @@ export class PrismaBlockStorage } public async pushBlock(block: UnprovenBlock): Promise { + console.log(`Pushing block`, block.transactions.map(x => x.tx.hash().toString())) + const transactions = block.transactions.map( (tx) => { const encoded = this.transactionResultMapper.mapOut(tx); @@ -83,33 +89,42 @@ export class PrismaBlockStorage const encodedBlock = this.blockMapper.mapOut(block); - const result = await this.connection.client.block.create({ - data: { - ...encodedBlock, - networkState: encodedBlock.networkState as Prisma.InputJsonValue, - - transactions: { - createMany: { - data: transactions.map((tx) => { - return { - ...tx, - stateTransitions: tx.stateTransitions as Prisma.InputJsonValue, - protocolTransitions: - tx.protocolTransitions as Prisma.InputJsonValue, - }; - }), + await this.connection.client.$transaction([ + this.connection.client.transaction.createMany({ + data: block.transactions.map((txr) => + this.transactionMapper.mapOut(txr.tx) + ), + }), + + this.connection.client.block.create({ + data: { + ...encodedBlock, + networkState: encodedBlock.networkState as Prisma.InputJsonObject, + + transactions: { + createMany: { + data: transactions.map((tx) => { + return { + status: tx.status, + statusMessage: tx.statusMessage, + txHash: tx.txHash, + + stateTransitions: + tx.stateTransitions as Prisma.InputJsonArray, + protocolTransitions: + tx.protocolTransitions as Prisma.InputJsonArray, + }; + }), + }, }, - }, - batchHeight: undefined, - }, - }); + batchHeight: undefined, + }, + }), + ]); } public async pushMetadata(metadata: UnprovenBlockMetadata): Promise { - // TODO Save this DB trip - const height = await this.getCurrentBlockHeight(); - const encoded = this.blockMetadataMapper.mapOut(metadata); await this.connection.client.unprovenBlockMetadata.create({ @@ -118,11 +133,12 @@ export class PrismaBlockStorage encoded.resultingNetworkState as Prisma.InputJsonValue, resultingStateRoot: encoded.resultingStateRoot, - height: height - 1, + blockTransactionHash: encoded.blockTransactionHash, }, }); } + // TODO Phase out and replace with getLatestBlock().network.height public async getCurrentBlockHeight(): Promise { const result = await this.connection.client.block.aggregate({ _max: { @@ -134,20 +150,20 @@ export class PrismaBlockStorage } public async getLatestBlock(): Promise { - const height = await this.getCurrentBlockHeight(); - if (height > 0) { - return await this.getBlockAt(height - 1); - } - return undefined; + return this.getBlockByQuery({ parent: null }); } public async getNewestMetadata(): Promise { - const height = await this.getCurrentBlockHeight(); + const latestBlock = await this.getLatestBlock(); + + if (latestBlock === undefined) { + return undefined; + } const result = await this.connection.client.unprovenBlockMetadata.findFirst( { where: { - height, + blockTransactionHash: latestBlock.transactionsHash.toString(), }, } ); @@ -162,35 +178,22 @@ export class PrismaBlockStorage public async getNewBlocks(): Promise { const blocks = await this.connection.client.block.findMany({ where: { - // eslint-disable-next-line unicorn/no-null batch: null, }, + include: { + metadata: true, + }, orderBy: { height: Prisma.SortOrder.asc, }, }); - const minUnbatchedBlock = blocks - .map((b) => b.height) - .reduce((a, b) => (a < b ? a : b)); - - const metadata = - await this.connection.client.unprovenBlockMetadata.findMany({ - where: { - height: { - gte: minUnbatchedBlock, - }, - }, - }); - return blocks.map((block, index) => { - const correspondingMetadata = metadata.find( - (entry) => entry.height == block.height - 1 - ); + const correspondingMetadata = block.metadata; return { block: this.blockMapper.mapIn(block), lastBlockMetadata: - correspondingMetadata !== undefined + correspondingMetadata !== null ? this.blockMetadataMapper.mapIn(correspondingMetadata) : undefined, }; diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts index e4548191d..09270724f 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -17,6 +17,10 @@ export class BlockMapper implements ObjectMapper { ), transactionsHash: Field(input.transactionsHash), + previousBlockTransactionsHash: + input.parentTransactionsHash !== null + ? Field(input.parentTransactionsHash) + : undefined, }; } @@ -25,6 +29,7 @@ export class BlockMapper implements ObjectMapper { height: Number(input.networkState.block.height.toBigInt()), networkState: NetworkState.toJSON(input.networkState), transactionsHash: input.transactionsHash.toString(), + parentTransactionsHash: input.previousBlockTransactionsHash?.toString() ?? null, batchHeight: null, }; } diff --git a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts index 06ab15dee..a52b3fbf3 100644 --- a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts @@ -1,9 +1,10 @@ import { singleton } from "tsyringe"; -import { ObjectMapper } from "../../../ObjectMapper"; import { UnprovenBlockMetadata } from "@proto-kit/sequencer"; import { UnprovenBlockMetadata as DBUnprovenBlockMetadata } from "@prisma/client"; import { NetworkState } from "@proto-kit/protocol"; +import { ObjectMapper } from "../../../ObjectMapper"; + @singleton() export class UnprovenBlockMetadataMapper implements @@ -20,6 +21,7 @@ export class UnprovenBlockMetadataMapper resultingNetworkState: new NetworkState( NetworkState.fromJSON(input.resultingNetworkState as any) ), + blockTransactionsHash: BigInt(input.blockTransactionHash), }; } @@ -28,6 +30,7 @@ export class UnprovenBlockMetadataMapper ): Omit { return { resultingStateRoot: input.resultingStateRoot.toString(), + blockTransactionHash: input.blockTransactionsHash.toString(), resultingNetworkState: NetworkState.toJSON(input.resultingNetworkState), }; diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index 4e9679b75..2b82bae28 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -95,6 +95,7 @@ export class BlockProducerModule extends SequencerModule { return { resultingNetworkState: NetworkState.empty(), resultingStateRoot: RollupMerkleTree.EMPTY_ROOT, + blockTransactionsHash: 0n, }; } diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 8ea786a64..5eac9198e 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -244,10 +244,16 @@ export class TransactionExecutionService { } } + const previousBlockTransactionsHash = + metadata.blockTransactionsHash === 0n + ? undefined + : Field(metadata.blockTransactionsHash); + return { transactions: executionResults, networkState, transactionsHash: transactionsHashList.commitment, + previousBlockTransactionsHash, }; } @@ -306,6 +312,7 @@ export class TransactionExecutionService { return { resultingNetworkState, resultingStateRoot: stateRoot.toBigInt(), + blockTransactionsHash: block.transactionsHash.toBigInt(), }; } diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index b24c0a50c..1604bcf69 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -58,6 +58,7 @@ export class UnprovenProducerModule return { resultingNetworkState: NetworkState.empty(), resultingStateRoot: RollupMerkleTree.EMPTY_ROOT, + blockTransactionsHash: 0n, }; } diff --git a/packages/sequencer/src/storage/model/UnprovenBlock.ts b/packages/sequencer/src/storage/model/UnprovenBlock.ts index 09dd56d9e..797de752b 100644 --- a/packages/sequencer/src/storage/model/UnprovenBlock.ts +++ b/packages/sequencer/src/storage/model/UnprovenBlock.ts @@ -16,9 +16,11 @@ export interface UnprovenBlock { networkState: NetworkState; transactions: TransactionExecutionResult[]; transactionsHash: Field; + previousBlockTransactionsHash: Field | undefined; } export interface UnprovenBlockMetadata { resultingStateRoot: bigint; resultingNetworkState: NetworkState; + blockTransactionsHash: bigint; } From 03f465fcf5f794d1f86b133551a0d9c99f0df808 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 2 Jan 2024 17:58:44 +0100 Subject: [PATCH 17/54] Fixed injection issue for services with masks --- packages/persistance/src/PrismaDatabaseConnection.ts | 6 ++++-- packages/persistance/src/RedisConnection.ts | 2 +- .../persistance/src/services/prisma/PrismaStateService.ts | 6 +++--- .../persistance/src/services/redis/RedisMerkleTreeStore.ts | 7 +++---- .../production/unproven/TransactionExecutionService.ts | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 77ffd105d..81ad6c3f4 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -1,7 +1,8 @@ import { PrismaClient } from "@prisma/client"; import { + sequencerModule, SequencerModule, - StorageDependencyMinimumDependencies, + StorageDependencyMinimumDependencies } from "@proto-kit/sequencer"; import { DependencyFactory, noop } from "@proto-kit/common"; @@ -9,6 +10,7 @@ import { PrismaStateService } from "./services/prisma/PrismaStateService"; import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; +@sequencerModule() export class PrismaDatabaseConnection extends SequencerModule implements DependencyFactory @@ -21,7 +23,7 @@ export class PrismaDatabaseConnection > { return { asyncStateService: { - useClass: PrismaStateService, + useFactory: () => new PrismaStateService(this), }, blockStorage: { useClass: PrismaBatchStore, diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index f2ea44d34..643513e4b 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -33,7 +33,7 @@ export class RedisConnection > { return { asyncMerkleStore: { - useClass: RedisMerkleTreeStore, + useFactory: () => new RedisMerkleTreeStore(this), }, unprovenMerkleStore: { useFactory: () => new RedisMerkleTreeStore(this, "unproven"), diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 926c25d7a..0eeb110e9 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -1,11 +1,11 @@ import { AsyncStateService } from "@proto-kit/sequencer"; import { Field } from "o1js"; -import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; import { inject, injectable } from "tsyringe"; import { Prisma } from "@prisma/client"; import { noop } from "@proto-kit/common"; -@injectable() +import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; + export class PrismaStateService implements AsyncStateService { private cache: [Field, Field[] | undefined][] = []; @@ -14,7 +14,7 @@ export class PrismaStateService implements AsyncStateService { * @param mask A indicator to which masking level the values belong */ public constructor( - @inject("Database") private readonly connection: PrismaDatabaseConnection, + private readonly connection: PrismaDatabaseConnection, private readonly mask: string = "base" ) {} diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index c9f025d7f..a27d3e95a 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -8,12 +8,11 @@ import { inject, injectable } from "tsyringe"; import { RedisConnection } from "../../RedisConnection"; -@injectable() export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { private cache: MerkleTreeNode[] = []; public constructor( - @inject("Redis") private readonly connection: RedisConnection, + private readonly connection: RedisConnection, private readonly mask: string = "base" ) {} @@ -32,7 +31,7 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { const array: [string, string][] = this.cache.map( ({ key, level, value }) => [this.getKey({ key, level }), value.toString()] ); - console.log(`Took ${Date.now() - start} ms`); + console.log(`Committing ${array.length} kv-pairs took ${Date.now() - start} ms`); const start2 = Date.now(); @@ -41,7 +40,7 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { } try { - console.log(array.slice(0, 5).flat(1)); + // console.log(array.slice(0, 5).flat(1)); await this.connection.client!.mSet(array.flat(1)); } catch (e) { if (e instanceof Error) { diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 5eac9198e..d24a6345f 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -494,7 +494,7 @@ export class TransactionExecutionService { protocolTransitions: protocolResult.stateTransitions.map((st) => UntypedStateTransition.fromStateTransition(st) - ) + ), }; } } From 7fe8fd2b0e67046ecf3fc40ff31315c16becebbc Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 2 Jan 2024 17:59:05 +0100 Subject: [PATCH 18/54] Fixed bugs --- .../prisma/mappers/TransactionMapper.ts | 5 +- packages/persistance/test/run.test.ts | 59 +++++++++++++++++-- packages/sdk/src/index.ts | 1 + .../production/BlockProducerModule.ts | 14 +++-- .../production/trigger/ManualBlockTrigger.ts | 13 +--- .../production/trigger/TimedBlockTrigger.ts | 5 +- .../unproven/UnprovenProducerModule.ts | 2 + 7 files changed, 72 insertions(+), 27 deletions(-) diff --git a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts index 63777ba1e..e3553f5a7 100644 --- a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts @@ -22,7 +22,10 @@ export class TransactionMapper public mapOut(input: PendingTransaction): DBTransaction { const json = input.toJSON(); return { - ...json, + methodId: json.methodId, + nonce: json.nonce, + sender: json.sender, + args: json.args, signature_r: json.signature.r, signature_s: json.signature.s, hash: input.hash().toString() diff --git a/packages/persistance/test/run.test.ts b/packages/persistance/test/run.test.ts index f151eceb7..cce4a247b 100644 --- a/packages/persistance/test/run.test.ts +++ b/packages/persistance/test/run.test.ts @@ -1,20 +1,69 @@ +import "reflect-metadata"; import { describe } from "@jest/globals"; import { PrismaDatabaseConnection } from "../src/PrismaDatabaseConnection"; import { PrismaStateService } from "../src/services/prisma/PrismaStateService"; -import { Field } from "o1js"; -import { PrismaMerkleTreeStore } from "../src"; -import { CachedMerkleTreeStore } from "@proto-kit/sequencer"; -import { RollupMerkleTree } from "@proto-kit/protocol"; +import { Bool, Field, PrivateKey, PublicKey, Signature } from "o1js"; +import { PrismaBlockStorage, PrismaMerkleTreeStore } from "../src"; +import { + CachedMerkleTreeStore, + PendingTransaction, + UnprovenBlock, + UntypedStateTransition, +} from "@proto-kit/sequencer"; +import { + NetworkState, + Option, + RollupMerkleTree, + StateTransition, +} from "@proto-kit/protocol"; import { RedisConnection } from "../src/RedisConnection"; import { RedisMerkleTreeStore } from "../src/services/redis/RedisMerkleTreeStore"; +import { container } from "tsyringe"; describe("prisma", () => { + it.only("block", async () => { + const db = new PrismaDatabaseConnection(); + const c = container.createChildContainer(); + c.register("Database", { useValue: db }); + + const storage = c.resolve(PrismaBlockStorage); + const block: UnprovenBlock = { + transactionsHash: Field("123"), + transactions: [ + { + status: Bool(true), + stateTransitions: [ + UntypedStateTransition.fromStateTransition( + StateTransition.fromTo( + Field(555), + Option.fromValue(Field(5), Field), + Option.fromValue(Field(10), Field) + ) + ), + ], + protocolTransitions: [], + tx: PendingTransaction.fromJSON({ + nonce: "1", + args: ["535"], + methodId: "0", + sender: PrivateKey.random().toPublicKey().toBase58(), + signature: Signature.create(PrivateKey.random(), [ + Field(0), + ]).toJSON(), + }), + }, + ], + networkState: NetworkState.empty(), + }; + await storage.pushBlock(block); + }); + it("merkle store", async () => { const db = new RedisConnection(); db.config = { url: "redis://localhost:6379/", password: "password", - } + }; await db.start(); const store = new RedisMerkleTreeStore(db); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index f4b9b9372..9073fe007 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -3,6 +3,7 @@ export * from "./appChain/AppChainModule"; export * from "./appChain/TestingAppChain"; export * from "./appChain/AreProofsEnabledFactory"; export * from "./query/StateServiceQueryModule"; +export * from "./query/BlockStorageNetworkStateModule"; export * from "./transaction/AppChainTransaction"; export * from "./transaction/InMemorySigner"; export * from "./transaction/InMemoryTransactionSender"; diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index 2b82bae28..72940a6d3 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -142,8 +142,15 @@ export class BlockProducerModule extends SequencerModule { ): Promise { if (!this.productionInProgress) { try { - return await this.produceBlock(unprovenBlocks); + this.productionInProgress = true; + + const block = await this.produceBlock(unprovenBlocks); + + this.productionInProgress = false; + + return block; } catch (error: unknown) { + this.productionInProgress = false; if (error instanceof Error) { if ( !error.message.includes( @@ -153,7 +160,6 @@ export class BlockProducerModule extends SequencerModule { log.error(error); } - this.productionInProgress = false; throw error; } else { log.error(error); @@ -170,15 +176,11 @@ export class BlockProducerModule extends SequencerModule { private async produceBlock( unprovenBlocks: UnprovenBlockWithPreviousMetadata[] ): Promise { - this.productionInProgress = true; - const blockId = unprovenBlocks[0].block.networkState.block.height.toBigInt(); const block = await this.computeBlock(unprovenBlocks, Number(blockId)); - this.productionInProgress = false; - const computedBundles = unprovenBlocks.map((bundle) => bundle.block.transactionsHash.toString() ); diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index d855a5d18..e1c260a8b 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -42,17 +42,8 @@ export class ManualBlockTrigger return undefined; } - public async produceUnproven( - enqueueInSettlementQueue = true - ): Promise { - const unprovenBlock = - await this.unprovenProducerModule.tryProduceUnprovenBlock(); - - if (unprovenBlock && enqueueInSettlementQueue) { - await this.unprovenBlockQueue.pushBlock(unprovenBlock); - } - - return unprovenBlock; + public async produceUnproven(): Promise { + return await this.unprovenProducerModule.tryProduceUnprovenBlock(); } public async start(): Promise { diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index 111944c6d..38f4d42e0 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -79,10 +79,7 @@ export class TimedBlockTrigger this.mempool.getTxs().txs.length > 0 || (this.config.produceEmptyBlocks ?? true) ) { - const block = await this.unprovenProducerModule.tryProduceUnprovenBlock(); - if (block !== undefined) { - await this.unprovenBlockQueue.pushBlock(block); - } + await this.unprovenProducerModule.tryProduceUnprovenBlock(); } } diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index 1604bcf69..b97ccd562 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -72,6 +72,8 @@ export class UnprovenProducerModule return undefined; } + await this.unprovenBlockQueue.pushBlock(block); + log.info(`Produced unproven block (${block.transactions.length} txs)`); this.events.emit("unprovenBlockProduced", [block]); From b1e7280e70da5935248ecd8031a011a5a86ab5f6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 3 Jan 2024 15:25:54 +0100 Subject: [PATCH 19/54] Fixed wrong linkage of metadata in PrismaBlockStorage --- packages/common/src/utils.ts | 10 +++++ .../src/services/prisma/PrismaBlockStorage.ts | 41 ++++++++++++++++--- .../protocol/src/prover/block/BlockProver.ts | 19 +++++---- .../production/tasks/BlockProvingTask.ts | 2 +- .../src/state/async/AsyncStateService.ts | 1 + .../src/state/state/CachedStateService.ts | 2 + 6 files changed, 62 insertions(+), 13 deletions(-) diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index f3e2f18cf..d0e8ceda7 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -49,3 +49,13 @@ export async function sleep(ms: number) { // eslint-disable-next-line promise/avoid-new,no-promise-executor-return await new Promise((resolve) => setTimeout(resolve, ms)); } + +export function filterNonNull(value: Type | null): value is Type { + return value !== null; +} + +export function filterNonUndefined( + value: Type | undefined +): value is Type { + return value !== null; +} diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 53bd8f8e1..84a2d213a 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -21,6 +21,7 @@ import { } from "./mappers/TransactionMapper"; import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; import { BlockMapper } from "./mappers/BlockMapper"; +import { filterNonNull } from "@proto-kit/common"; @injectable() export class PrismaBlockStorage @@ -75,7 +76,10 @@ export class PrismaBlockStorage } public async pushBlock(block: UnprovenBlock): Promise { - console.log(`Pushing block`, block.transactions.map(x => x.tx.hash().toString())) + console.log( + `Pushing block`, + block.transactions.map((x) => x.tx.hash().toString()) + ); const transactions = block.transactions.map( (tx) => { @@ -181,19 +185,46 @@ export class PrismaBlockStorage batch: null, }, include: { - metadata: true, + transactions: { + include: { + tx: true, + }, + }, }, orderBy: { height: Prisma.SortOrder.asc, }, }); + const blockHashes = blocks + .map((block) => block.parentTransactionsHash) + .filter(filterNonNull); + const metadata = + await this.connection.client.unprovenBlockMetadata.findMany({ + where: { + blockTransactionHash: { + in: blockHashes, + }, + }, + }); + return blocks.map((block, index) => { - const correspondingMetadata = block.metadata; + const transactions = block.transactions.map( + (txresult) => { + return this.transactionResultMapper.mapIn([txresult, txresult.tx]); + } + ); + const decodedBlock = this.blockMapper.mapIn(block); + decodedBlock.transactions = transactions; + + const correspondingMetadata = metadata.find( + (candidate) => + candidate.blockTransactionHash === block.parentTransactionsHash + ); return { - block: this.blockMapper.mapIn(block), + block: decodedBlock, lastBlockMetadata: - correspondingMetadata !== null + correspondingMetadata !== undefined ? this.blockMetadataMapper.mapIn(correspondingMetadata) : undefined, }; diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index f11d99e4e..a6587f639 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -53,7 +53,10 @@ const errors = { stateRootNotMatching: (step: string) => `StateRoots not matching ${step}`, transactionsHashNotMatching: (step: string) => - `transactions hash not matching ${step}`, + `Transactions hash not matching ${step}`, + + networkStateHashNotMatching: (step: string) => + `Network satte hash not matching ${step}`, }; export interface BlockProverState { @@ -324,6 +327,11 @@ export class BlockProverProgrammable extends ZkProgrammable< networkStateHash: publicInput.networkStateHash, }; + state.networkStateHash.assertEquals( + executionData.networkState.hash(), + "ExecutionData Networkstate doesn't equal public input hash" + ); + const bundleInclusionResult = this.addTransactionToBundle( state, stateProof, @@ -399,11 +407,11 @@ export class BlockProverProgrammable extends ZkProgrammable< // Check networkhash publicInput.networkStateHash.assertEquals( proof1.publicInput.networkStateHash, - errors.transactionsHashNotMatching("publicInput.from -> proof1.from") + errors.networkStateHashNotMatching("publicInput.from -> proof1.from") ); proof1.publicOutput.networkStateHash.assertEquals( proof2.publicInput.networkStateHash, - errors.transactionsHashNotMatching("proof1.to -> proof2.from") + errors.networkStateHashNotMatching("proof1.to -> proof2.from") ); return new BlockProverPublicOutput({ @@ -495,10 +503,7 @@ 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 { public zkProgrammable: BlockProverProgrammable; public constructor( diff --git a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts index e172a8ce3..7c656a880 100644 --- a/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts +++ b/packages/sequencer/src/protocol/production/tasks/BlockProvingTask.ts @@ -9,7 +9,7 @@ import { ReturnType, StateServiceProvider, StateTransitionProof, - StateTransitionProvable, + StateTransitionProvable } from "@proto-kit/protocol"; import { Field, Proof } from "o1js"; import { Runtime } from "@proto-kit/module"; diff --git a/packages/sequencer/src/state/async/AsyncStateService.ts b/packages/sequencer/src/state/async/AsyncStateService.ts index 6bc6304c1..6ee9ba445 100644 --- a/packages/sequencer/src/state/async/AsyncStateService.ts +++ b/packages/sequencer/src/state/async/AsyncStateService.ts @@ -10,6 +10,7 @@ export interface AsyncStateService { commit: () => Promise; + // TODO Make sync, we have openTx and commit() for the actual writing operations setAsync: (key: Field, value: Field[] | undefined) => Promise; getAsync: (key: Field) => Promise; diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index 530dfe2d4..6ff5486b6 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -77,10 +77,12 @@ export class CachedStateService this.assertParentNotNull(parent); // Set all cached values on parent + await parent.openTransaction(); const promises = Object.entries(values).map(async (value) => { await parent.setAsync(Field(value[0]), value[1]); }); await Promise.all(promises); + await parent.commit(); // Clear cache this.values = {}; } From 1ba995c5080710c0e262bdc61f99c27fd0a5b52f Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 3 Jan 2024 16:32:57 +0100 Subject: [PATCH 20/54] Fixed log.trace --- packages/common/src/log.ts | 8 ++++++-- .../sequencer/src/state/merkle/CachedMerkleTreeStore.ts | 2 +- packages/sequencer/src/state/state/CachedStateService.ts | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/common/src/log.ts b/packages/common/src/log.ts index 1ea541e20..8d0c6faa7 100644 --- a/packages/common/src/log.ts +++ b/packages/common/src/log.ts @@ -39,7 +39,7 @@ export const log = { }, trace: (...args: unknown[]) => { - logProvable(loglevel.trace, ...args); + logProvable(log.trace, ...args); }, warn: (...args: unknown[]) => { @@ -60,7 +60,11 @@ export const log = { }, trace: (...args: unknown[]) => { - loglevel.trace(...args); + // Loglevel prints the stack trace by default. To still be able to use trace + // inside out application, we use the level, but call debug() under the hood + if (loglevel.getLevel() >= loglevel.levels.TRACE) { + loglevel.debug(...args); + } }, warn: (...args: unknown[]) => { diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index bb41e0ce1..87ae5728f 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -77,7 +77,7 @@ export class CachedMerkleTreeStore level, }); if (level === 0) { - log.debug(`Queued preloading of ${key} @ ${level}`); + log.trace(`Queued preloading of ${key} @ ${level}`); } } diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index 6ff5486b6..ddfdd6f9e 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -40,7 +40,7 @@ export class CachedStateService // Only preload it if it hasn't been preloaded previously if (this.parent !== undefined && this.get(key) === undefined) { const value = await this.parent.getAsync(key); - log.debug( + log.trace( `Preloading ${key.toString()}: ${ // eslint-disable-next-line max-len // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -78,8 +78,8 @@ export class CachedStateService // Set all cached values on parent await parent.openTransaction(); - const promises = Object.entries(values).map(async (value) => { - await parent.setAsync(Field(value[0]), value[1]); + const promises = Object.entries(values).map(async ([key, value]) => { + await parent.setAsync(Field(key), value); }); await Promise.all(promises); await parent.commit(); From 158fe9f8378339d8a2f4457de0b10d5b7c8b4ab1 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 3 Jan 2024 16:34:24 +0100 Subject: [PATCH 21/54] Fixed pipeline to correctly retrieve latest blocks --- .../src/PrismaDatabaseConnection.ts | 2 +- .../src/services/prisma/PrismaBlockStorage.ts | 16 ++- .../src/services/prisma/PrismaStateService.ts | 3 +- .../services/redis/RedisMerkleTreeStore.ts | 11 +- packages/persistance/test/run.test.ts | 1 + .../production/BlockProducerModule.ts | 6 +- .../unproven/TransactionExecutionService.ts | 11 +- packages/stack/test/start/start.ts | 119 ++++++++++++------ 8 files changed, 121 insertions(+), 48 deletions(-) diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 81ad6c3f4..4f509082f 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -23,7 +23,7 @@ export class PrismaDatabaseConnection > { return { asyncStateService: { - useFactory: () => new PrismaStateService(this), + useFactory: () => new PrismaStateService(this, "batch"), }, blockStorage: { useClass: PrismaBatchStore, diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 84a2d213a..47185c5a9 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -39,7 +39,7 @@ export class PrismaBlockStorage ) {} private async getBlockByQuery( - where: { height: number } | { parent: null } | { transactionsHash: string } + where: { height: number } | { transactionsHash: string } ): Promise { const result = await this.connection.client.block.findFirst({ where, @@ -154,7 +154,19 @@ export class PrismaBlockStorage } public async getLatestBlock(): Promise { - return this.getBlockByQuery({ parent: null }); + const latestBlock = await this.connection.client.$queryRaw< + { transactionsHash: string }[] + >`SELECT b1."transactionsHash" FROM "Block" b1 + LEFT JOIN "Block" child ON child."parentTransactionsHash" = b1."transactionsHash" + WHERE child IS NULL LIMIT 1`; + + if (latestBlock.length === 0) { + return undefined; + } + + return await this.getBlockByQuery({ + transactionsHash: latestBlock[0].transactionsHash, + }); } public async getNewestMetadata(): Promise { diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 0eeb110e9..bfdeae018 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -15,7 +15,7 @@ export class PrismaStateService implements AsyncStateService { */ public constructor( private readonly connection: PrismaDatabaseConnection, - private readonly mask: string = "base" + private readonly mask: string ) {} public async commit(): Promise { @@ -35,6 +35,7 @@ export class PrismaStateService implements AsyncStateService { path: { in: this.cache.map((x) => new Prisma.Decimal(x[0].toString())), }, + mask: this.mask, }, }), client.state.createMany({ diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index a27d3e95a..d469346f3 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -3,7 +3,7 @@ import { MerkleTreeNode, MerkleTreeNodeQuery, } from "@proto-kit/sequencer"; -import { noop } from "@proto-kit/common"; +import { log, noop } from "@proto-kit/common"; import { inject, injectable } from "tsyringe"; import { RedisConnection } from "../../RedisConnection"; @@ -31,7 +31,6 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { const array: [string, string][] = this.cache.map( ({ key, level, value }) => [this.getKey({ key, level }), value.toString()] ); - console.log(`Committing ${array.length} kv-pairs took ${Date.now() - start} ms`); const start2 = Date.now(); @@ -51,9 +50,11 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { console.log(e); } } - - console.log(`Took ${Date.now() - start2} ms`); - // console.log(`Rows: ${rows}`); + log.debug( + `Committing ${array.length} kv-pairs took ${ + Date.now() - start + } ms (preparing the input was ${start2 - start} ms)` + ); this.cache = []; } diff --git a/packages/persistance/test/run.test.ts b/packages/persistance/test/run.test.ts index cce4a247b..3cee25363 100644 --- a/packages/persistance/test/run.test.ts +++ b/packages/persistance/test/run.test.ts @@ -54,6 +54,7 @@ describe("prisma", () => { }, ], networkState: NetworkState.empty(), + previousBlockTransactionsHash: undefined }; await storage.pushBlock(block); }); diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index 72940a6d3..db61edaf0 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -175,7 +175,7 @@ export class BlockProducerModule extends SequencerModule { private async produceBlock( unprovenBlocks: UnprovenBlockWithPreviousMetadata[] - ): Promise { + ): Promise { const blockId = unprovenBlocks[0].block.networkState.block.height.toBigInt(); @@ -259,6 +259,10 @@ export class BlockProducerModule extends SequencerModule { result.blockProver.executionData.networkState = previousMetadata.resultingNetworkState; + result.blockProver.publicInput.networkStateHash = + previousMetadata.resultingNetworkState.hash(); + // We don't set it to runtimeProver.networkState here, because the + // runtime consumes the networkstate after the beforeBlock() hook } traces.push(result); diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index d24a6345f..71d447042 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -197,7 +197,7 @@ export class TransactionExecutionService { stateService: AsyncStateService, transactions: PendingTransaction[], metadata: UnprovenBlockMetadata - ): Promise { + ): Promise { const executionResults: TransactionExecutionResult[] = []; const transactionsHashList = new DefaultProvableHashList(Field); @@ -239,11 +239,18 @@ export class TransactionExecutionService { transactionsHashList.push(tx.hash()); } catch (error) { if (error instanceof Error) { - log.error("Error in inclusion of tx, skipping", error); + log.info("Error in inclusion of tx, skipping", error); } } } + if (executionResults.length === 0) { + log.info( + "After sequencing, block has no sequencable transactions left, skipping block" + ); + return undefined; + } + const previousBlockTransactionsHash = metadata.blockTransactionsHash === 0n ? undefined diff --git a/packages/stack/test/start/start.ts b/packages/stack/test/start/start.ts index 5d3501f10..65bc8e8b3 100644 --- a/packages/stack/test/start/start.ts +++ b/packages/stack/test/start/start.ts @@ -8,24 +8,28 @@ import { state, } from "@proto-kit/module"; import { - AccountStateModule, BlockHeightHook, + AccountStateModule, + BlockHeightHook, Option, State, StateMap, - VanillaProtocol + VanillaProtocol, } from "@proto-kit/protocol"; import { Presets, log, sleep } from "@proto-kit/common"; import { AsyncStateService, - BlockProducerModule, InMemoryDatabase, + BlockProducerModule, + InMemoryDatabase, LocalTaskQueue, LocalTaskWorkerModule, + ManualBlockTrigger, NoopBaseLayer, PendingTransaction, PrivateMempool, Sequencer, - TimedBlockTrigger, UnprovenProducerModule, - UnsignedTransaction + TimedBlockTrigger, + UnprovenProducerModule, + UnsignedTransaction, } from "@proto-kit/sequencer"; import { BlockStorageResolver, @@ -33,18 +37,22 @@ import { GraphqlServer, MempoolResolver, NodeStatusResolver, - QueryGraphqlModule, UnprovenBlockResolver + QueryGraphqlModule, + UnprovenBlockResolver, } from "@proto-kit/api"; import { container } from "tsyringe"; -import { PrismaDatabaseConnection } from "@proto-kit/persistance"; +import { + PrismaBlockStorage, + PrismaDatabaseConnection, +} from "@proto-kit/persistance"; import { RedisConnection } from "@proto-kit/persistance/dist/RedisConnection"; import { AppChain, BlockStorageNetworkStateModule, InMemorySigner, InMemoryTransactionSender, - StateServiceQueryModule + StateServiceQueryModule, } from "@proto-kit/sdk"; log.setLevel(log.levels.INFO); @@ -85,17 +93,16 @@ export class Balances extends RuntimeModule { @runtimeMethod() public addBalance(address: PublicKey, balance: UInt64) { - const totalSupply = this.totalSupply.get() + const totalSupply = this.totalSupply.get(); this.totalSupply.set(totalSupply.orElse(UInt64.zero).add(balance)); - const previous = this.balances.get(address) + const previous = this.balances.get(address); this.balances.set(address, previous.orElse(UInt64.zero).add(balance)); } } export async function startServer() { - - log.setLevel("INFO") + log.setLevel("DEBUG"); const appChain = AppChain.from({ runtime: Runtime.from({ @@ -110,20 +117,27 @@ export async function startServer() { protocol: VanillaProtocol.from( { AccountStateModule, BlockHeightHook }, - { AccountStateModule: {}, StateTransitionProver: {}, BlockProver: {}, BlockHeightHook: {} } + { + AccountStateModule: {}, + StateTransitionProver: {}, + BlockProver: {}, + BlockHeightHook: {}, + } ), sequencer: Sequencer.from({ modules: { - Database: PrismaDatabaseConnection, - Redis: RedisConnection, + // Database: PrismaDatabaseConnection, + // Redis: RedisConnection, + Database: InMemoryDatabase, Mempool: PrivateMempool, GraphqlServer, LocalTaskWorkerModule, BaseLayer: NoopBaseLayer, BlockProducerModule, UnprovenProducerModule, - BlockTrigger: TimedBlockTrigger, + // BlockTrigger: TimedBlockTrigger, + BlockTrigger: ManualBlockTrigger, TaskQueue: LocalTaskQueue, Graphql: GraphqlSequencerModule.from({ @@ -140,7 +154,7 @@ export async function startServer() { QueryGraphqlModule: {}, BlockStorageResolver: {}, NodeStatusResolver: {}, - UnprovenBlockResolver: {} + UnprovenBlockResolver: {}, }, }), }, @@ -170,7 +184,7 @@ export async function startServer() { GraphqlServer: { port: 8080, host: "0.0.0.0", - graphiql: true + graphiql: true, }, Graphql: { @@ -178,13 +192,13 @@ export async function startServer() { MempoolResolver: {}, BlockStorageResolver: {}, NodeStatusResolver: {}, - UnprovenBlockResolver: {} + UnprovenBlockResolver: {}, }, - Redis: { - url: "redis://localhost:6379/", - password: "password", - }, + // Redis: { + // url: "redis://localhost:6379/", + // password: "password", + // }, Database: {}, Mempool: {}, @@ -195,8 +209,8 @@ export async function startServer() { UnprovenProducerModule: {}, BlockTrigger: { - blockInterval: 15000, - settlementInterval: 30000, + // blockInterval: 15000, + // settlementInterval: 30000, }, }, @@ -210,11 +224,18 @@ export async function startServer() { }); await appChain.start(container.createChildContainer()); + const pk = PublicKey.fromBase58( "B62qmETai5Y8vvrmWSU8F4NX7pTyPqYLMhc1pgX3wD8dGc2wbCWUcqP" ); console.log(pk.toJSON()); + // const storage = appChain.sequencer.dependencyContainer.resolve("UnprovenBlockQueue"); + // const b = await storage.getNewBlocks() + // console.log() + + // await appChain.sequencer.resolve("BlockTrigger").produceProven(); + const balances = appChain.runtime.resolve("Balances"); const priv = PrivateKey.fromBase58( @@ -222,25 +243,51 @@ export async function startServer() { ); const tx = appChain.transaction(priv.toPublicKey(), () => { - balances.addBalance(priv.toPublicKey(), UInt64.from(1000)) - }) - appChain.resolve("Signer").config.signer = priv + balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); + }); + appChain.resolve("Signer").config.signer = priv; await tx.sign(); await tx.send(); // console.log((tx.transaction as PendingTransaction).toJSON()) - const tx2 = appChain.transaction(priv.toPublicKey(), () => { - balances.addBalance(priv.toPublicKey(), UInt64.from(1000)) - }, {nonce: 1}) + const tx2 = appChain.transaction( + priv.toPublicKey(), + () => { + balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); + }, + { nonce: 1 } + ); await tx2.sign(); await tx2.send(); - const { txs } = appChain.sequencer.resolve("Mempool").getTxs() - console.log(txs.map(tx => tx.toJSON())) - console.log(txs.map(tx => tx.hash().toString())) + const { txs } = appChain.sequencer.resolve("Mempool").getTxs(); + console.log(txs.map((tx) => tx.toJSON())); + console.log(txs.map((tx) => tx.hash().toString())); console.log("Path:", balances.balances.getPath(pk).toString()); + const trigger = appChain.sequencer.resolve("BlockTrigger"); + await trigger.produceBlock(); + + for (let i = 0; i < 10; i++) { + const tx3 = appChain.transaction( + priv.toPublicKey(), + () => { + balances.addBalance(PrivateKey.random().toPublicKey(), UInt64.from((i + 1) * 1000)); + }, + { nonce: i + 2 } + ); + await tx3.sign(); + await tx3.send(); + + await trigger.produceUnproven(); + + // await trigger.produceProven(); + if (i % 2 === 1) { + await trigger.produceProven(); + } + } + // const asyncState = // appChain.sequencer.dependencyContainer.resolve( // "AsyncStateService" @@ -252,7 +299,7 @@ export async function startServer() { // await sleep(30000); - return appChain + return appChain; } -// await startServer(); \ No newline at end of file +// await startServer(); From aa6be5660e642e7f386f950469c560e68eeddd7e Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 4 Jan 2024 12:32:11 +0100 Subject: [PATCH 22/54] Added test for multi-block batches --- .../production/BlockTaskFlowService.ts | 2 +- .../test/integration/BlockProduction.test.ts | 91 ++++++++++++++++--- 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts b/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts index 6dab8ef7a..115e62813 100644 --- a/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts +++ b/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts @@ -145,7 +145,7 @@ export class BlockTaskFlowService { ) ) .and( - a.publicInput.networkStateHash.equals( + a.publicOutput.networkStateHash.equals( b.publicInput.networkStateHash ) ) diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 6aac6f221..2dceee15a 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -8,14 +8,15 @@ import { AppChain } from "@proto-kit/sdk"; import { Fieldable, Runtime, MethodIdResolver } from "@proto-kit/module"; import { AccountState, - AccountStateModule, BlockHeightHook, + AccountStateModule, + BlockHeightHook, BlockProver, Option, Path, Protocol, StateTransition, StateTransitionProver, - VanillaProtocol + VanillaProtocol, } from "@proto-kit/protocol"; import { Bool, Field, PrivateKey, PublicKey, UInt64 } from "o1js"; import { log, range } from "@proto-kit/common"; @@ -26,8 +27,9 @@ import { UnsignedTransaction } from "../../src/mempool/PendingTransaction"; import { Sequencer } from "../../src/sequencer/executor/Sequencer"; import { AsyncStateService, - BlockProducerModule, InMemoryDatabase, - ManualBlockTrigger + BlockProducerModule, + InMemoryDatabase, + ManualBlockTrigger, } from "../../src"; import { LocalTaskWorkerModule } from "../../src/worker/worker/LocalTaskWorkerModule"; @@ -90,7 +92,12 @@ describe("block production", () => { const protocolClass = VanillaProtocol.from( { AccountStateModule, BlockHeightHook }, - { StateTransitionProver: {}, BlockProver: {}, AccountStateModule: {}, BlockHeightHook: {} } + { + StateTransitionProver: {}, + BlockProver: {}, + AccountStateModule: {}, + BlockHeightHook: {}, + } ); const app = AppChain.from({ @@ -118,7 +125,7 @@ describe("block production", () => { AccountStateModule: {}, BlockProver: {}, StateTransitionProver: {}, - BlockHeightHook: {} + BlockHeightHook: {}, }, }); @@ -176,7 +183,7 @@ describe("block production", () => { expect(block!.transactions[0].status.toBoolean()).toBe(true); expect(block!.transactions[0].statusMessage).toBeUndefined(); - let batch = await blockTrigger.produceProven() + let batch = await blockTrigger.produceProven(); expect(batch).toBeDefined(); @@ -188,7 +195,10 @@ describe("block production", () => { "AsyncStateService" ); - const unprovenStateService = sequencer.dependencyContainer.resolve("UnprovenStateService") + const unprovenStateService = + sequencer.dependencyContainer.resolve( + "UnprovenStateService" + ); const balanceModule = runtime.resolve("Balance"); const balancesPath = Path.fromKey( @@ -202,7 +212,9 @@ describe("block production", () => { expect(newState).toBeDefined(); expect(newUnprovenState).toBeDefined(); expect(UInt64.fromFields(newState!)).toStrictEqual(UInt64.from(100)); - expect(UInt64.fromFields(newUnprovenState!)).toStrictEqual(UInt64.from(100)); + expect(UInt64.fromFields(newUnprovenState!)).toStrictEqual( + UInt64.from(100) + ); // Check that nonce has been set const accountModule = protocol.resolve("AccountStateModule"); @@ -320,11 +332,15 @@ describe("block production", () => { expect(block!.transactions[index].status.toBoolean()).toBe(true); expect(block!.transactions[index].statusMessage).toBe(undefined); - const transitions = block!.transactions[index].stateTransitions + const transitions = block!.transactions[index].stateTransitions; const fromBalance = increment * index; - expect(transitions[0].fromValue.value[0].toBigInt()).toStrictEqual(BigInt(fromBalance)) - expect(transitions[1].toValue.value[0].toBigInt()).toStrictEqual(BigInt(fromBalance + increment)) + expect(transitions[0].fromValue.value[0].toBigInt()).toStrictEqual( + BigInt(fromBalance) + ); + expect(transitions[1].toValue.value[0].toBigInt()).toStrictEqual( + BigInt(fromBalance + increment) + ); }); const batch = await blockTrigger.produceProven(); @@ -350,6 +366,57 @@ describe("block production", () => { ); }, 160_000); + it("should produce multiple blocks with multiple batches with multiple transactions", async () => { + const batches = 2; + const blocksPerBatch = 2; + const txsPerBlock = 2; + + // expect.assertions((2 * batches) + (3 * batches * blocksPerBatch)); + expect.assertions(16); + + const sender = PrivateKey.random(); + + const keys = range(0, batches * blocksPerBatch * txsPerBlock).map(() => + PrivateKey.random() + ); + + const increment = 100; + + let iterationIndex = 0; + + for (let i = 0; i < batches; i++) { + for (let j = 0; j < blocksPerBatch; j++) { + for (let k = 0; k < txsPerBlock; k++) { + mempool.add( + createTransaction({ + method: ["Balance", "addBalance"], + privateKey: sender, + args: [ + keys[iterationIndex].toPublicKey(), + UInt64.from(increment * iterationIndex), + ], + nonce: iterationIndex, + }) + ); + + iterationIndex += 1; + } + + // Produce block + const block = await blockTrigger.produceUnproven(); + + expect(block).toBeDefined(); + expect(block!.transactions).toHaveLength(txsPerBlock); + expect(block!.transactions[0].status.toBoolean()).toBe(true); + } + + const batch = await blockTrigger.produceProven(); + + expect(batch).toBeDefined(); + expect(batch!.bundles).toHaveLength(blocksPerBatch); + } + }, 500_000); + it("should produce block with a tx with a lot of STs", async () => { expect.assertions(11); From 9e670619c156503239ee8bee628eef6f3eacc005 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 4 Jan 2024 16:17:17 +0100 Subject: [PATCH 23/54] Added configurability for Prisma Connector --- .../src/PrismaDatabaseConnection.ts | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 4f509082f..c2128e884 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -2,7 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { sequencerModule, SequencerModule, - StorageDependencyMinimumDependencies + StorageDependencyMinimumDependencies, } from "@proto-kit/sequencer"; import { DependencyFactory, noop } from "@proto-kit/common"; @@ -10,12 +10,32 @@ import { PrismaStateService } from "./services/prisma/PrismaStateService"; import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; +export interface PrismaDatabaseConfig { + connection?: { + username: string; + password: string; + host: string; + port?: number; + db?: { + name: string; + schema?: string; + }; + }; +} + @sequencerModule() export class PrismaDatabaseConnection - extends SequencerModule + extends SequencerModule implements DependencyFactory { - public readonly client = new PrismaClient(); + private initializedClient: PrismaClient | undefined = undefined; + + public get client(): PrismaClient { + if (this.initializedClient === undefined) { + throw new Error("Client not initialized yet, wait for after the startup"); + } + return this.initializedClient; + } public dependencies(): Omit< StorageDependencyMinimumDependencies, @@ -41,6 +61,29 @@ export class PrismaDatabaseConnection } public async start(): Promise { - noop(); + const { connection } = this.config; + if (connection !== undefined) { + const { host, port, username, password, db } = connection; + + const dbString = + db !== undefined + ? `${db.name}?schema=${db.schema ?? "public"}` + : "protokit?schema=public"; + + const url = `postgresql://${username}:${password}@${host}:${ + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + port ?? 5432 + }/${dbString}`; + + this.initializedClient = new PrismaClient({ + datasources: { + db: { + url, + }, + }, + }); + } else { + this.initializedClient = new PrismaClient(); + } } } From 4e72fffe639b895740601cbcf7d7ee66fa2f0436 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 4 Jan 2024 16:19:26 +0100 Subject: [PATCH 24/54] Fixed a few minor issues --- packages/common/src/log.ts | 2 +- .../src/services/prisma/PrismaBlockStorage.ts | 6 +- .../services/redis/RedisMerkleTreeStore.ts | 19 +++- packages/sdk/src/appChain/TestingAppChain.ts | 2 +- .../test/integration/BlockProduction.test.ts | 92 ++++++++++--------- 5 files changed, 69 insertions(+), 52 deletions(-) diff --git a/packages/common/src/log.ts b/packages/common/src/log.ts index 8d0c6faa7..1eeb5c1f9 100644 --- a/packages/common/src/log.ts +++ b/packages/common/src/log.ts @@ -62,7 +62,7 @@ export const log = { trace: (...args: unknown[]) => { // Loglevel prints the stack trace by default. To still be able to use trace // inside out application, we use the level, but call debug() under the hood - if (loglevel.getLevel() >= loglevel.levels.TRACE) { + if (loglevel.getLevel() <= loglevel.levels.TRACE) { loglevel.debug(...args); } }, diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 47185c5a9..9b16fec81 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -21,7 +21,7 @@ import { } from "./mappers/TransactionMapper"; import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; import { BlockMapper } from "./mappers/BlockMapper"; -import { filterNonNull } from "@proto-kit/common"; +import { filterNonNull, log } from "@proto-kit/common"; @injectable() export class PrismaBlockStorage @@ -76,8 +76,8 @@ export class PrismaBlockStorage } public async pushBlock(block: UnprovenBlock): Promise { - console.log( - `Pushing block`, + log.trace( + `Pushing block to DB. Txs:`, block.transactions.map((x) => x.tx.hash().toString()) ); diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index d469346f3..ce8d259e9 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -25,8 +25,6 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { } public async commit(): Promise { - // TODO Filter distinct - const start = Date.now(); const array: [string, string][] = this.cache.map( ({ key, level, value }) => [this.getKey({ key, level }), value.toString()] @@ -71,5 +69,22 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { public writeNodes(nodes: MerkleTreeNode[]): void { this.cache = this.cache.concat(nodes); + // TODO Filter distinct + // We might not even need this, since the distinctness filter might already + // be implicitely done by the layer above (i.e. cachedmtstore) + + // const concat = this.cache.concat(nodes); + // const reversed = concat.slice().reverse(); + // this.cache = concat.filter((node, index) => { + // const reversedIndex = concat.length - 1 - index; + // // We find the last item with that particular (key + value) id. + // // This is the one we want to use. + // const foundIndex = reversed.findIndex( + // ({ key, value }) => key === node.key && value === node.value + // ); + // // Now we only take this item is the found item + // return foundIndex === reversedIndex; + // }); + // console.log(`Reduced ${concat.length} to ${this.cache.length} items to write`) } } diff --git a/packages/sdk/src/appChain/TestingAppChain.ts b/packages/sdk/src/appChain/TestingAppChain.ts index 0e3bd67fa..cb4520496 100644 --- a/packages/sdk/src/appChain/TestingAppChain.ts +++ b/packages/sdk/src/appChain/TestingAppChain.ts @@ -156,6 +156,6 @@ export class TestingAppChain< ManualBlockTrigger ); - return await blockTrigger.produceUnproven(true); + return await blockTrigger.produceUnproven(); } } diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 2dceee15a..a64969303 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -366,56 +366,58 @@ describe("block production", () => { ); }, 160_000); - it("should produce multiple blocks with multiple batches with multiple transactions", async () => { - const batches = 2; - const blocksPerBatch = 2; - const txsPerBlock = 2; - - // expect.assertions((2 * batches) + (3 * batches * blocksPerBatch)); - expect.assertions(16); - - const sender = PrivateKey.random(); - - const keys = range(0, batches * blocksPerBatch * txsPerBlock).map(() => - PrivateKey.random() - ); - - const increment = 100; + it.each([ + [2, 2, 1], + [2, 2, 2], + ])( + "should produce multiple blocks with multiple batches with multiple transactions", + async (batches, blocksPerBatch, txsPerBlock) => { + expect.assertions(2 * batches + 3 * batches * blocksPerBatch); + + const sender = PrivateKey.random(); + + const keys = range(0, batches * blocksPerBatch * txsPerBlock).map(() => + PrivateKey.random() + ); - let iterationIndex = 0; - - for (let i = 0; i < batches; i++) { - for (let j = 0; j < blocksPerBatch; j++) { - for (let k = 0; k < txsPerBlock; k++) { - mempool.add( - createTransaction({ - method: ["Balance", "addBalance"], - privateKey: sender, - args: [ - keys[iterationIndex].toPublicKey(), - UInt64.from(increment * iterationIndex), - ], - nonce: iterationIndex, - }) - ); - - iterationIndex += 1; + const increment = 100; + + let iterationIndex = 0; + + for (let i = 0; i < batches; i++) { + for (let j = 0; j < blocksPerBatch; j++) { + for (let k = 0; k < txsPerBlock; k++) { + mempool.add( + createTransaction({ + method: ["Balance", "addBalance"], + privateKey: sender, + args: [ + keys[iterationIndex].toPublicKey(), + UInt64.from(increment * iterationIndex), + ], + nonce: iterationIndex, + }) + ); + + iterationIndex += 1; + } + + // Produce block + const block = await blockTrigger.produceUnproven(); + + expect(block).toBeDefined(); + expect(block!.transactions).toHaveLength(txsPerBlock); + expect(block!.transactions[0].status.toBoolean()).toBe(true); } - // Produce block - const block = await blockTrigger.produceUnproven(); + const batch = await blockTrigger.produceProven(); - expect(block).toBeDefined(); - expect(block!.transactions).toHaveLength(txsPerBlock); - expect(block!.transactions[0].status.toBoolean()).toBe(true); + expect(batch).toBeDefined(); + expect(batch!.bundles).toHaveLength(blocksPerBatch); } - - const batch = await blockTrigger.produceProven(); - - expect(batch).toBeDefined(); - expect(batch!.bundles).toHaveLength(blocksPerBatch); - } - }, 500_000); + }, + 500_000 + ); it("should produce block with a tx with a lot of STs", async () => { expect.assertions(11); From bd59fdfee332bd3557b9dfbb2030cb434062d115 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 4 Jan 2024 17:07:54 +0100 Subject: [PATCH 25/54] Committed migrations --- .../20231221185008_init/migration.sql | 7 + .../migration.sql | 83 ++++++++++++ .../20240102161834_parent_block/migration.sql | 30 +++++ .../prisma/migrations/migration_lock.toml | 3 + packages/stack/test/start/start.ts | 126 +++++++++++------- 5 files changed, 202 insertions(+), 47 deletions(-) create mode 100644 packages/persistance/prisma/migrations/20231221185008_init/migration.sql create mode 100644 packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql create mode 100644 packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql create mode 100644 packages/persistance/prisma/migrations/migration_lock.toml diff --git a/packages/persistance/prisma/migrations/20231221185008_init/migration.sql b/packages/persistance/prisma/migrations/20231221185008_init/migration.sql new file mode 100644 index 000000000..7e328dfec --- /dev/null +++ b/packages/persistance/prisma/migrations/20231221185008_init/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "State" ( + "path" BIGINT NOT NULL, + "values" BIGINT[], + + CONSTRAINT "State_pkey" PRIMARY KEY ("path") +); diff --git a/packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql b/packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql new file mode 100644 index 000000000..bc6de8b85 --- /dev/null +++ b/packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql @@ -0,0 +1,83 @@ +/* + Warnings: + + - The primary key for the `State` table will be changed. If it partially fails, the table could be left without primary key constraint. + - Added the required column `mask` to the `State` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "State" DROP CONSTRAINT "State_pkey", +ADD COLUMN "mask" VARCHAR(256) NOT NULL, +ALTER COLUMN "path" SET DATA TYPE DECIMAL(78,0), +ALTER COLUMN "values" SET DATA TYPE DECIMAL(78,0)[], +ADD CONSTRAINT "State_pkey" PRIMARY KEY ("path", "mask"); + +-- CreateTable +CREATE TABLE "TreeElement" ( + "key" DECIMAL(78,0) NOT NULL, + "level" SMALLINT NOT NULL, + "value" DECIMAL(78,0) NOT NULL, + + CONSTRAINT "TreeElement_pkey" PRIMARY KEY ("key","level") +); + +-- CreateTable +CREATE TABLE "Transaction" ( + "hash" TEXT NOT NULL, + "methodId" TEXT NOT NULL, + "sender" TEXT NOT NULL, + "nonce" TEXT NOT NULL, + "args" TEXT[], + "signature_r" TEXT NOT NULL, + "signature_s" TEXT NOT NULL, + + CONSTRAINT "Transaction_pkey" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "TransactionExecutionResult" ( + "stateTransitions" JSON NOT NULL, + "protocolTransitions" JSON NOT NULL, + "status" BOOLEAN NOT NULL, + "statusMessage" TEXT, + "txHash" TEXT NOT NULL, + "blockHash" TEXT NOT NULL, + + CONSTRAINT "TransactionExecutionResult_pkey" PRIMARY KEY ("txHash") +); + +-- CreateTable +CREATE TABLE "Block" ( + "transactionsHash" TEXT NOT NULL, + "networkState" JSON NOT NULL, + "height" INTEGER NOT NULL, + "batchHeight" INTEGER, + + CONSTRAINT "Block_pkey" PRIMARY KEY ("transactionsHash") +); + +-- CreateTable +CREATE TABLE "Batch" ( + "height" INTEGER NOT NULL, + "proof" JSON NOT NULL, + + CONSTRAINT "Batch_pkey" PRIMARY KEY ("height") +); + +-- CreateTable +CREATE TABLE "UnprovenBlockMetadata" ( + "height" INTEGER NOT NULL, + "resultingStateRoot" TEXT NOT NULL, + "resultingNetworkState" JSON NOT NULL, + + CONSTRAINT "UnprovenBlockMetadata_pkey" PRIMARY KEY ("height") +); + +-- AddForeignKey +ALTER TABLE "TransactionExecutionResult" ADD CONSTRAINT "TransactionExecutionResult_txHash_fkey" FOREIGN KEY ("txHash") REFERENCES "Transaction"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TransactionExecutionResult" ADD CONSTRAINT "TransactionExecutionResult_blockHash_fkey" FOREIGN KEY ("blockHash") REFERENCES "Block"("transactionsHash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Block" ADD CONSTRAINT "Block_batchHeight_fkey" FOREIGN KEY ("batchHeight") REFERENCES "Batch"("height") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql b/packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql new file mode 100644 index 000000000..694a021dc --- /dev/null +++ b/packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql @@ -0,0 +1,30 @@ +/* + Warnings: + + - The primary key for the `UnprovenBlockMetadata` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `height` on the `UnprovenBlockMetadata` table. All the data in the column will be lost. + - A unique constraint covering the columns `[parentTransactionsHash]` on the table `Block` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[blockTransactionHash]` on the table `UnprovenBlockMetadata` will be added. If there are existing duplicate values, this will fail. + - Added the required column `blockTransactionHash` to the `UnprovenBlockMetadata` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Block" ADD COLUMN "parentTransactionsHash" TEXT; + +-- AlterTable +ALTER TABLE "UnprovenBlockMetadata" DROP CONSTRAINT "UnprovenBlockMetadata_pkey", +DROP COLUMN "height", +ADD COLUMN "blockTransactionHash" TEXT NOT NULL, +ADD CONSTRAINT "UnprovenBlockMetadata_pkey" PRIMARY KEY ("blockTransactionHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "Block_parentTransactionsHash_key" ON "Block"("parentTransactionsHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "UnprovenBlockMetadata_blockTransactionHash_key" ON "UnprovenBlockMetadata"("blockTransactionHash"); + +-- AddForeignKey +ALTER TABLE "Block" ADD CONSTRAINT "Block_parentTransactionsHash_fkey" FOREIGN KEY ("parentTransactionsHash") REFERENCES "Block"("transactionsHash") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnprovenBlockMetadata" ADD CONSTRAINT "UnprovenBlockMetadata_blockTransactionHash_fkey" FOREIGN KEY ("blockTransactionHash") REFERENCES "Block"("transactionsHash") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/migrations/migration_lock.toml b/packages/persistance/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000..fbffa92c2 --- /dev/null +++ b/packages/persistance/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/packages/stack/test/start/start.ts b/packages/stack/test/start/start.ts index 65bc8e8b3..c6962a68b 100644 --- a/packages/stack/test/start/start.ts +++ b/packages/stack/test/start/start.ts @@ -127,17 +127,17 @@ export async function startServer() { sequencer: Sequencer.from({ modules: { - // Database: PrismaDatabaseConnection, - // Redis: RedisConnection, - Database: InMemoryDatabase, + Database: PrismaDatabaseConnection, + Redis: RedisConnection, + // Database: InMemoryDatabase, Mempool: PrivateMempool, GraphqlServer, LocalTaskWorkerModule, BaseLayer: NoopBaseLayer, BlockProducerModule, UnprovenProducerModule, - // BlockTrigger: TimedBlockTrigger, - BlockTrigger: ManualBlockTrigger, + BlockTrigger: TimedBlockTrigger, + // BlockTrigger: ManualBlockTrigger, TaskQueue: LocalTaskQueue, Graphql: GraphqlSequencerModule.from({ @@ -195,10 +195,10 @@ export async function startServer() { UnprovenBlockResolver: {}, }, - // Redis: { - // url: "redis://localhost:6379/", - // password: "password", - // }, + Redis: { + url: "redis://localhost:6379/", + password: "password", + }, Database: {}, Mempool: {}, @@ -209,8 +209,8 @@ export async function startServer() { UnprovenProducerModule: {}, BlockTrigger: { - // blockInterval: 15000, - // settlementInterval: 30000, + blockInterval: 15000, + settlementInterval: 30000, }, }, @@ -242,50 +242,82 @@ export async function startServer() { "EKFEMDTUV2VJwcGmCwNKde3iE1cbu7MHhzBqTmBtGAd6PdsLTifY" ); - const tx = appChain.transaction(priv.toPublicKey(), () => { - balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - }); - appChain.resolve("Signer").config.signer = priv; - await tx.sign(); - await tx.send(); - // console.log((tx.transaction as PendingTransaction).toJSON()) - - const tx2 = appChain.transaction( - priv.toPublicKey(), - () => { + // const tx = appChain.transaction(priv.toPublicKey(), () => { + // balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); + // }); + // appChain.resolve("Signer").config.signer = priv; + // await tx.sign(); + // await tx.send(); + // // console.log((tx.transaction as PendingTransaction).toJSON()) + // + // const tx2 = appChain.transaction( + // priv.toPublicKey(), + // () => { + // balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); + // }, + // { nonce: 1 } + // ); + // await tx2.sign(); + // await tx2.send(); + // + // const { txs } = appChain.sequencer.resolve("Mempool").getTxs(); + // console.log(txs.map((tx) => tx.toJSON())); + // console.log(txs.map((tx) => tx.hash().toString())); + // + // console.log("Path:", balances.balances.getPath(pk).toString()); + + // const trigger = appChain.sequencer.resolve("BlockTrigger"); + // await trigger.produceBlock(); + // + // for (let i = 0; i < 12; i++) { + // const tx3 = appChain.transaction( + // priv.toPublicKey(), + // () => { + // balances.addBalance(PrivateKey.random().toPublicKey(), UInt64.from((i + 1) * 1000)); + // }, + // { nonce: i + 2 } + // ); + // await tx3.sign(); + // await tx3.send(); + // + // + // // await trigger.produceProven(); + // if (i % 2 === 1) { + // await trigger.produceUnproven(); + // if (i % 4 == 3) { + // await trigger.produceProven(); + // } + // } + // } + + const as = await appChain.query.protocol.AccountStateModule.accountState.get(priv.toPublicKey()); + let nonce = Number(as!.nonce.toBigInt()) + console.log(nonce) + + while(true){ + const tx = appChain.transaction(priv.toPublicKey(), () => { balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - }, - { nonce: 1 } - ); - await tx2.sign(); - await tx2.send(); + }, { nonce: nonce }); + appChain.resolve("Signer").config.signer = priv; + await tx.sign(); + await tx.send(); + // console.log((tx.transaction as PendingTransaction).toJSON()) - const { txs } = appChain.sequencer.resolve("Mempool").getTxs(); - console.log(txs.map((tx) => tx.toJSON())); - console.log(txs.map((tx) => tx.hash().toString())); - - console.log("Path:", balances.balances.getPath(pk).toString()); - - const trigger = appChain.sequencer.resolve("BlockTrigger"); - await trigger.produceBlock(); - - for (let i = 0; i < 10; i++) { - const tx3 = appChain.transaction( + const tx2 = appChain.transaction( priv.toPublicKey(), () => { - balances.addBalance(PrivateKey.random().toPublicKey(), UInt64.from((i + 1) * 1000)); + balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); }, - { nonce: i + 2 } + { nonce: nonce + 1 } ); - await tx3.sign(); - await tx3.send(); + await tx2.sign(); + await tx2.send(); + + nonce += 2; - await trigger.produceUnproven(); + console.log("Added 2 txs to mempool") - // await trigger.produceProven(); - if (i % 2 === 1) { - await trigger.produceProven(); - } + await sleep(15000); } // const asyncState = From fcc9406cd743f76876035cbde82b20a93d04705c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Fri, 5 Jan 2024 16:59:22 +0100 Subject: [PATCH 26/54] Optimized preloading behaviour for sequencing --- .../unproven/RuntimeMethodExecution.ts | 40 +++++++++++-------- .../test/integration/BlockProduction.test.ts | 2 +- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/sequencer/src/protocol/production/unproven/RuntimeMethodExecution.ts b/packages/sequencer/src/protocol/production/unproven/RuntimeMethodExecution.ts index 0ed42e998..65ebc9dbc 100644 --- a/packages/sequencer/src/protocol/production/unproven/RuntimeMethodExecution.ts +++ b/packages/sequencer/src/protocol/production/unproven/RuntimeMethodExecution.ts @@ -11,6 +11,7 @@ import { Field } from "o1js"; import { AsyncStateService } from "../../../state/async/AsyncStateService"; import { CachedStateService } from "../../../state/state/CachedStateService"; import { distinctByString } from "../../../helpers/utils"; +import { SyncCachedStateService } from "../../../state/state/SyncCachedStateService"; export class RuntimeMethodExecution { public constructor( @@ -19,26 +20,20 @@ export class RuntimeMethodExecution { private readonly executionContext: RuntimeMethodExecutionContext ) {} - private async executeMethodWithKeys( - touchedKeys: string[], + private executeMethodWithKeys( method: () => void, contextInputs: RuntimeMethodExecutionData, - parentStateService: AsyncStateService - ): Promise[]> { + parentStateService: CachedStateService + ): StateTransition[] { const { executionContext, runtime, protocol } = this; executionContext.setup(contextInputs); executionContext.setSimulated(true); - const stateService = new CachedStateService(parentStateService); + const stateService = new SyncCachedStateService(parentStateService); runtime.stateServiceProvider.setCurrentStateService(stateService); protocol.stateServiceProvider.setCurrentStateService(stateService); - // Preload previously determined keys - await stateService.preloadKeys( - touchedKeys.map((fieldString) => Field(fieldString)) - ); - // Execute method method(); @@ -73,13 +68,14 @@ export class RuntimeMethodExecution { let lastRuntimeResult: StateTransition[]; + const preloadingStateService = new CachedStateService(parentStateService); + do { // eslint-disable-next-line no-await-in-loop - const stateTransitions = await this.executeMethodWithKeys( - touchedKeys, + const stateTransitions = this.executeMethodWithKeys( method, contextInputs, - parentStateService + preloadingStateService ); if (numberMethodSTs === undefined) { @@ -93,11 +89,16 @@ export class RuntimeMethodExecution { const keys = stateTransitions .map((st) => st.path.toString()) .filter(distinctByString); - const stateTransitionsFullRun = await this.executeMethodWithKeys( - keys, + const optimisticRunStateService = new CachedStateService( + parentStateService + ); + await optimisticRunStateService.preloadKeys( + keys.map((fieldString) => Field(fieldString)) + ); + const stateTransitionsFullRun = this.executeMethodWithKeys( method, contextInputs, - parentStateService + optimisticRunStateService ); const firstDiffIndex = _.zip( @@ -117,7 +118,13 @@ export class RuntimeMethodExecution { .slice(0, firstDiffIndex) .map((st) => st.path.toString()) .filter(distinctByString); + + // Preload eligible keys touchedKeys.push(...additionalKeys); + await preloadingStateService.preloadKeys( + additionalKeys.map((key) => Field(key)) + ); + collectedSTs = firstDiffIndex - 1; lastRuntimeResult = stateTransitions; continue; @@ -131,6 +138,7 @@ export class RuntimeMethodExecution { !touchedKeys.includes(latestST.path.toString()) ) { touchedKeys.push(latestST.path.toString()); + await preloadingStateService.preloadKey(latestST.path); } collectedSTs += 1; diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index a64969303..d4598d0e5 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -301,7 +301,7 @@ describe("block production", () => { expect(newState).toBeUndefined(); }, 30_000); - const numberTxs = 2; + const numberTxs = 3; it("should produce block with multiple transaction", async () => { // eslint-disable-next-line jest/prefer-expect-assertions From bf01482588f98797ebbd7d86a8f8fb1c9c71b041 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sat, 13 Jan 2024 15:39:28 +0100 Subject: [PATCH 27/54] Fixed merge problems --- .../production/TransactionTraceService.ts | 10 +-- .../unproven/TransactionExecutionService.ts | 81 ++----------------- .../unproven/UnprovenProducerModule.ts | 9 +-- .../src/state/merkle/CachedMerkleTreeStore.ts | 24 +++--- .../src/storage/model/UnprovenBlock.ts | 55 ++++++++++++- .../repositories/UnprovenBlockStorage.ts | 7 +- .../test/integration/BlockProduction.test.ts | 6 +- 7 files changed, 83 insertions(+), 109 deletions(-) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 8e7207f29..933b43785 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -18,17 +18,15 @@ import { distinctByString } from "../../helpers/utils"; import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { CachedStateService } from "../../state/state/CachedStateService"; import { SyncCachedMerkleTreeStore } from "../../state/merkle/SyncCachedMerkleTreeStore"; -import type { TransactionExecutionResult } from "../../storage/model/UnprovenBlock"; -import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; - -import type { TransactionTrace } from "./BlockProducerModule"; import type { TransactionExecutionResult, UnprovenBlockWithMetadata, -} from "./unproven/TransactionExecutionService"; +} from "../../storage/model/UnprovenBlock"; +import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; + +import type { TransactionTrace, BlockTrace } from "./BlockProducerModule"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; import { UntypedStateTransition } from "./helpers/UntypedStateTransition"; -import { BlockTrace } from "./BlockProducerModule"; @injectable() @scoped(Lifecycle.ContainerScoped) diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index f09348bc2..720a0387d 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -15,7 +15,6 @@ import { StateTransition, ProvableBlockHook, BlockHashMerkleTree, - BlockHashMerkleTreeWitness, StateServiceProvider, BlockHashTreeEntry, } from "@proto-kit/protocol"; @@ -34,86 +33,22 @@ import { distinctByString } from "../../../helpers/utils"; import { AsyncStateService } from "../../../state/async/AsyncStateService"; import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; -import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; -import type { StateRecord } from "../BlockProducerModule"; - -import { RuntimeMethodExecution } from "./RuntimeMethodExecution"; -import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { TransactionExecutionResult, UnprovenBlock, UnprovenBlockMetadata, + UnprovenBlockWithMetadata, } from "../../../storage/model/UnprovenBlock"; +import { UntypedStateTransition } from "../helpers/UntypedStateTransition"; +import type { StateRecord } from "../BlockProducerModule"; + +import { RuntimeMethodExecution } from "./RuntimeMethodExecution"; const errors = { methodIdNotFound: (methodId: string) => new Error(`Can't find runtime method with id ${methodId}`), }; -export interface TransactionExecutionResult { - tx: PendingTransaction; - stateTransitions: UntypedStateTransition[]; - protocolTransitions: UntypedStateTransition[]; - status: Bool; - statusMessage?: string; - /** - * TODO Remove - * @deprecated - */ - stateDiff: StateRecord; -} - -export interface UnprovenBlock { - height: Field; - networkState: { - before: NetworkState; - during: NetworkState; - }; - transactions: TransactionExecutionResult[]; - transactionsHash: Field; - toEternalTransactionsHash: Field; - fromEternalTransactionsHash: Field; - fromBlockHashRoot: Field; -} - -export interface UnprovenBlockMetadata { - stateRoot: bigint; - blockHashRoot: bigint; - afterNetworkState: NetworkState; - blockStateTransitions: UntypedStateTransition[]; - blockHashWitness: BlockHashMerkleTreeWitness; -} - -export interface UnprovenBlockWithMetadata { - block: UnprovenBlock; - metadata: UnprovenBlockMetadata; -} - -export const UnprovenBlockWithMetadata = { - createEmpty: () => - ({ - block: { - height: Field(0), - transactionsHash: Field(0), - fromEternalTransactionsHash: Field(0), - toEternalTransactionsHash: Field(0), - transactions: [], - networkState: { - before: NetworkState.empty(), - during: NetworkState.empty(), - }, - fromBlockHashRoot: Field(BlockHashMerkleTree.EMPTY_ROOT), - }, - metadata: { - afterNetworkState: NetworkState.empty(), - stateRoot: RollupMerkleTree.EMPTY_ROOT, - blockHashRoot: BlockHashMerkleTree.EMPTY_ROOT, - blockStateTransitions: [], - blockHashWitness: BlockHashMerkleTree.WITNESS.dummy(), - }, - } satisfies UnprovenBlockWithMetadata), -}; - @injectable() @scoped(Lifecycle.ContainerScoped) export class TransactionExecutionService { @@ -316,9 +251,9 @@ export class TransactionExecutionService { } const previousBlockTransactionsHash = - metadata.blockTransactionsHash === 0n + lastMetadata.blockTransactionsHash === 0n ? undefined - : Field(metadata.blockTransactionsHash); + : Field(lastMetadata.blockTransactionsHash); return { transactions: executionResults, @@ -327,12 +262,12 @@ export class TransactionExecutionService { toEternalTransactionsHash: eternalTransactionsHashList.commitment, height: lastBlock.height.add(1), fromBlockHashRoot: Field(lastMetadata.blockHashRoot), + previousBlockTransactionsHash, networkState: { before: new NetworkState(lastMetadata.afterNetworkState), during: networkState, }, - previousBlockTransactionsHash, }; } diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index 429f737ec..c55dc80ba 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -15,17 +15,14 @@ import { } from "../../../sequencer/builder/SequencerModule"; import { UnprovenBlockQueue } from "../../../storage/repositories/UnprovenBlockStorage"; import { PendingTransaction } from "../../../mempool/PendingTransaction"; -import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; -import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; import { AsyncStateService } from "../../../state/async/AsyncStateService"; -import { UnprovenBlock, UnprovenBlockMetadata } from "../../../storage/model/UnprovenBlock"; - import { - TransactionExecutionService, UnprovenBlock, UnprovenBlockWithMetadata, -} from "./TransactionExecutionService"; +} from "../../../storage/model/UnprovenBlock"; + +import { TransactionExecutionService } from "./TransactionExecutionService"; const errors = { txRemovalFailed: () => new Error("Removal of txs from mempool failed"), diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index ade6653a3..76b19bb3a 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -3,8 +3,7 @@ import { noop, InMemoryMerkleTreeStorage, RollupMerkleTree, -} from "@proto-kit/protocol"; -import { log, noop } from "@proto-kit/common"; +} from "@proto-kit/common"; import { AsyncMerkleTreeStore, MerkleTreeNode, @@ -108,18 +107,19 @@ export class CachedMerkleTreeStore } await this.parent.openTransaction(); - const { height } = RollupMerkleTree; const nodes = this.getWrittenNodes(); const writes = Object.keys(nodes).flatMap((levelString) => { const level = Number(levelString); - return Object.entries(nodes[level]).map(([key, value]) => { - return { - key: BigInt(key), - level, - value, - }; - }) + return Object.entries(nodes[level]).map( + ([key, value]) => { + return { + key: BigInt(key), + level, + value, + }; + } + ); }); this.parent.writeNodes(writes); @@ -139,9 +139,7 @@ export class CachedMerkleTreeStore public async getNodesAsync( nodes: MerkleTreeNodeQuery[] ): Promise<(bigint | undefined)[]> { - const results = Array( - nodes.length - ).fill(undefined); + const results = Array(nodes.length).fill(undefined); const toFetch: MerkleTreeNodeQuery[] = []; diff --git a/packages/sequencer/src/storage/model/UnprovenBlock.ts b/packages/sequencer/src/storage/model/UnprovenBlock.ts index 797de752b..29917a1cc 100644 --- a/packages/sequencer/src/storage/model/UnprovenBlock.ts +++ b/packages/sequencer/src/storage/model/UnprovenBlock.ts @@ -1,8 +1,13 @@ import { Bool, Field } from "o1js"; -import { NetworkState } from "@proto-kit/protocol"; +import { + BlockHashMerkleTree, + BlockHashMerkleTreeWitness, + NetworkState, +} from "@proto-kit/protocol"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { UntypedStateTransition } from "../../protocol/production/helpers/UntypedStateTransition"; +import { RollupMerkleTree } from "@proto-kit/common"; export interface TransactionExecutionResult { tx: PendingTransaction; @@ -13,14 +18,56 @@ export interface TransactionExecutionResult { } export interface UnprovenBlock { - networkState: NetworkState; + height: Field; + networkState: { + before: NetworkState; + during: NetworkState; + }; transactions: TransactionExecutionResult[]; transactionsHash: Field; + toEternalTransactionsHash: Field; + fromEternalTransactionsHash: Field; + fromBlockHashRoot: Field; previousBlockTransactionsHash: Field | undefined; } export interface UnprovenBlockMetadata { - resultingStateRoot: bigint; - resultingNetworkState: NetworkState; + stateRoot: bigint; + blockHashRoot: bigint; + afterNetworkState: NetworkState; + blockStateTransitions: UntypedStateTransition[]; + blockHashWitness: BlockHashMerkleTreeWitness; blockTransactionsHash: bigint; } + +export interface UnprovenBlockWithMetadata { + block: UnprovenBlock; + metadata: UnprovenBlockMetadata; +} + +export const UnprovenBlockWithMetadata = { + createEmpty: () => + ({ + block: { + height: Field(0), + transactionsHash: Field(0), + fromEternalTransactionsHash: Field(0), + toEternalTransactionsHash: Field(0), + transactions: [], + networkState: { + before: NetworkState.empty(), + during: NetworkState.empty(), + }, + fromBlockHashRoot: Field(BlockHashMerkleTree.EMPTY_ROOT), + previousBlockTransactionsHash: undefined, + }, + metadata: { + afterNetworkState: NetworkState.empty(), + stateRoot: RollupMerkleTree.EMPTY_ROOT, + blockHashRoot: BlockHashMerkleTree.EMPTY_ROOT, + blockStateTransitions: [], + blockHashWitness: BlockHashMerkleTree.WITNESS.dummy(), + blockTransactionsHash: 0n, + }, + } satisfies UnprovenBlockWithMetadata), +}; diff --git a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts index 946d12ef2..64c597fc4 100644 --- a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts +++ b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts @@ -1,10 +1,9 @@ -import { +import { UnprovenBlockWithPreviousMetadata } from "../../protocol/production/BlockProducerModule"; +import type { UnprovenBlock, UnprovenBlockMetadata, UnprovenBlockWithMetadata, -} from "../../protocol/production/unproven/TransactionExecutionService"; -import { UnprovenBlockWithPreviousMetadata } from "../../protocol/production/BlockProducerModule"; -import type { UnprovenBlock, UnprovenBlockMetadata } from "../model/UnprovenBlock"; +} from "../model/UnprovenBlock"; export interface UnprovenBlockQueue { pushBlock: (block: UnprovenBlock) => Promise; diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 2d0ce916f..45a28dba7 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -396,7 +396,7 @@ describe("block production", () => { expect(block).toBeDefined(); expect(batch!.bundles).toHaveLength(1); - expect(batch!.bundles[0]).toHaveLength(2); + expect(block!.transactions).toHaveLength(2); const stateService = sequencer.dependencyContainer.resolve( @@ -464,8 +464,8 @@ describe("block production", () => { ); it.each([ - [2, 2, 1], - [2, 2, 2], + [1, 2, 1], + // [2, 2, 2], ])( "should produce multiple blocks with multiple batches with multiple transactions", async (batches, blocksPerBatch, txsPerBlock) => { From 7ba9dc398d780e0d8ba0bc0a0d2466711666f9b6 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 15 Jan 2024 15:23:46 +0100 Subject: [PATCH 28/54] Adapted prisma mappers to new block schema --- packages/persistance/prisma/schema.prisma | 14 ++- .../src/services/prisma/PrismaBlockStorage.ts | 89 +++++++++++-------- .../services/prisma/mappers/BlockMapper.ts | 25 ++++-- .../mappers/UnprovenBlockMetadataMapper.ts | 34 +++++-- 4 files changed, 107 insertions(+), 55 deletions(-) diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 663c94470..e506b486a 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -55,8 +55,12 @@ model TransactionExecutionResult { model Block { transactionsHash String @id - networkState Json @db.Json + beforeNetworkState Json @db.Json + duringNetworkState Json @db.Json height Int + fromEternalTransactionsHash String + toEternalTransactionsHash String + fromBlockHashRoot String parentTransactionsHash String? @unique parent Block? @relation("Parent", fields: [parentTransactionsHash], references: [transactionsHash]) @@ -79,8 +83,12 @@ model Batch { model UnprovenBlockMetadata { blockTransactionHash String @id @unique - resultingStateRoot String - resultingNetworkState Json @db.Json + + stateRoot String + blockHashRoot String + afterNetworkState Json @db.Json + blockStateTransitions Json @db.Json + blockHashWitness Json @db.Json block Block? @relation(fields: [blockTransactionHash], references: [transactionsHash]) } \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 9b16fec81..7c7466515 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -1,12 +1,15 @@ import { + distinctByString, HistoricalUnprovenBlockStorage, TransactionExecutionResult, UnprovenBlock, UnprovenBlockMetadata, UnprovenBlockQueue, UnprovenBlockStorage, + UnprovenBlockWithMetadata, UnprovenBlockWithPreviousMetadata, } from "@proto-kit/sequencer"; +import { filterNonNull, log } from "@proto-kit/common"; import { Prisma, TransactionExecutionResult as DBTransactionExecutionResult, @@ -21,7 +24,6 @@ import { } from "./mappers/TransactionMapper"; import { UnprovenBlockMetadataMapper } from "./mappers/UnprovenBlockMetadataMapper"; import { BlockMapper } from "./mappers/BlockMapper"; -import { filterNonNull, log } from "@proto-kit/common"; @injectable() export class PrismaBlockStorage @@ -40,7 +42,7 @@ export class PrismaBlockStorage private async getBlockByQuery( where: { height: number } | { transactionsHash: string } - ): Promise { + ): Promise { const result = await this.connection.client.block.findFirst({ where, include: { @@ -49,6 +51,7 @@ export class PrismaBlockStorage tx: true, }, }, + metadata: true, }, }); if (result === null) { @@ -59,20 +62,27 @@ export class PrismaBlockStorage return this.transactionResultMapper.mapIn([txresult, txresult.tx]); } ); + if (result.metadata === undefined || result.metadata === null) { + throw new Error(`No Metadata has been set for block ${where} yet`); + } + return { - ...this.blockMapper.mapIn(result), - transactions, + block: { + ...this.blockMapper.mapIn(result), + transactions, + }, + metadata: this.blockMetadataMapper.mapIn(result.metadata), }; } public async getBlockAt(height: number): Promise { - return await this.getBlockByQuery({ height }); + return (await this.getBlockByQuery({ height }))?.block; } public async getBlock( transactionsHash: string ): Promise { - return await this.getBlockByQuery({ transactionsHash }); + return (await this.getBlockByQuery({ transactionsHash }))?.block; } public async pushBlock(block: UnprovenBlock): Promise { @@ -103,7 +113,10 @@ export class PrismaBlockStorage this.connection.client.block.create({ data: { ...encodedBlock, - networkState: encodedBlock.networkState as Prisma.InputJsonObject, + beforeNetworkState: + encodedBlock.beforeNetworkState as Prisma.InputJsonObject, + duringNetworkState: + encodedBlock.duringNetworkState as Prisma.InputJsonObject, transactions: { createMany: { @@ -133,11 +146,14 @@ export class PrismaBlockStorage await this.connection.client.unprovenBlockMetadata.create({ data: { - resultingNetworkState: - encoded.resultingNetworkState as Prisma.InputJsonValue, + afterNetworkState: encoded.afterNetworkState as Prisma.InputJsonValue, + blockHashWitness: encoded.blockHashWitness as Prisma.InputJsonValue, + blockStateTransitions: + encoded.blockStateTransitions as Prisma.InputJsonValue, - resultingStateRoot: encoded.resultingStateRoot, + stateRoot: encoded.stateRoot, blockTransactionHash: encoded.blockTransactionHash, + blockHashRoot: encoded.blockHashRoot, }, }); } @@ -153,7 +169,9 @@ export class PrismaBlockStorage return (result?._max.height ?? -1) + 1; } - public async getLatestBlock(): Promise { + public async getLatestBlock(): Promise< + UnprovenBlockWithMetadata | undefined + > { const latestBlock = await this.connection.client.$queryRaw< { transactionsHash: string }[] >`SELECT b1."transactionsHash" FROM "Block" b1 @@ -169,28 +187,6 @@ export class PrismaBlockStorage }); } - public async getNewestMetadata(): Promise { - const latestBlock = await this.getLatestBlock(); - - if (latestBlock === undefined) { - return undefined; - } - - const result = await this.connection.client.unprovenBlockMetadata.findFirst( - { - where: { - blockTransactionHash: latestBlock.transactionsHash.toString(), - }, - } - ); - - if (result === null) { - return undefined; - } - - return this.blockMetadataMapper.mapIn(result); - } - public async getNewBlocks(): Promise { const blocks = await this.connection.client.block.findMany({ where: { @@ -209,8 +205,12 @@ export class PrismaBlockStorage }); const blockHashes = blocks - .map((block) => block.parentTransactionsHash) - .filter(filterNonNull); + .flatMap((block) => [ + block.parentTransactionsHash, + block.transactionsHash, + ]) + .filter(filterNonNull) + .filter(distinctByString); const metadata = await this.connection.client.unprovenBlockMetadata.findMany({ where: { @@ -230,14 +230,27 @@ export class PrismaBlockStorage decodedBlock.transactions = transactions; const correspondingMetadata = metadata.find( + (candidate) => candidate.blockTransactionHash === block.transactionsHash + ); + + if (correspondingMetadata === undefined) { + throw new Error( + `No Metadata has been set for block ${block.transactionsHash} yet` + ); + } + + const parentMetadata = metadata.find( (candidate) => candidate.blockTransactionHash === block.parentTransactionsHash ); return { - block: decodedBlock, + block: { + block: decodedBlock, + metadata: this.blockMetadataMapper.mapIn(correspondingMetadata), + }, lastBlockMetadata: - correspondingMetadata !== undefined - ? this.blockMetadataMapper.mapIn(correspondingMetadata) + parentMetadata !== undefined + ? this.blockMetadataMapper.mapIn(parentMetadata) : undefined, }; }); diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts index 09270724f..bb05b9356 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -12,9 +12,19 @@ export class BlockMapper implements ObjectMapper { return { transactions: [], - networkState: new NetworkState( - NetworkState.fromJSON(input.networkState as any) - ), + networkState: { + before: new NetworkState( + NetworkState.fromJSON(input.beforeNetworkState as any) + ), + during: new NetworkState( + NetworkState.fromJSON(input.duringNetworkState as any) + ) + }, + + height: Field(input.height), + fromEternalTransactionsHash: Field(input.fromEternalTransactionsHash), + toEternalTransactionsHash: Field(input.toEternalTransactionsHash), + fromBlockHashRoot: Field(input.fromBlockHashRoot), transactionsHash: Field(input.transactionsHash), previousBlockTransactionsHash: @@ -26,8 +36,13 @@ export class BlockMapper implements ObjectMapper { public mapOut(input: UnprovenBlock): Block { return { - height: Number(input.networkState.block.height.toBigInt()), - networkState: NetworkState.toJSON(input.networkState), + height: Number(input.height.toBigInt()), + beforeNetworkState: NetworkState.toJSON(input.networkState.before), + duringNetworkState: NetworkState.toJSON(input.networkState.during), + fromEternalTransactionsHash: input.fromEternalTransactionsHash.toString(), + toEternalTransactionsHash: input.toEternalTransactionsHash.toString(), + fromBlockHashRoot: input.fromBlockHashRoot.toString(), + transactionsHash: input.transactionsHash.toString(), parentTransactionsHash: input.previousBlockTransactionsHash?.toString() ?? null, batchHeight: null, diff --git a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts index a52b3fbf3..55b707710 100644 --- a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts @@ -1,25 +1,38 @@ import { singleton } from "tsyringe"; import { UnprovenBlockMetadata } from "@proto-kit/sequencer"; import { UnprovenBlockMetadata as DBUnprovenBlockMetadata } from "@prisma/client"; -import { NetworkState } from "@proto-kit/protocol"; +import { BlockHashMerkleTreeWitness, NetworkState } from "@proto-kit/protocol"; import { ObjectMapper } from "../../../ObjectMapper"; +import { StateTransitionArrayMapper } from "./StateTransitionMapper"; @singleton() export class UnprovenBlockMetadataMapper implements ObjectMapper< UnprovenBlockMetadata, - Omit + DBUnprovenBlockMetadata > { + public constructor( + private readonly stArrayMapper: StateTransitionArrayMapper + ) {} + public mapIn( - input: Omit + input: DBUnprovenBlockMetadata ): UnprovenBlockMetadata { return { - resultingStateRoot: BigInt(input.resultingStateRoot), - resultingNetworkState: new NetworkState( - NetworkState.fromJSON(input.resultingNetworkState as any) + afterNetworkState: new NetworkState( + NetworkState.fromJSON(input.afterNetworkState as any) + ), + + stateRoot: BigInt(input.stateRoot), + blockHashRoot: BigInt(input.blockHashRoot), + blockHashWitness: new BlockHashMerkleTreeWitness( + BlockHashMerkleTreeWitness.fromJSON(input.blockHashWitness as any) + ), + blockStateTransitions: this.stArrayMapper.mapIn( + input.blockStateTransitions ), blockTransactionsHash: BigInt(input.blockTransactionHash), }; @@ -27,12 +40,15 @@ export class UnprovenBlockMetadataMapper public mapOut( input: UnprovenBlockMetadata - ): Omit { + ): DBUnprovenBlockMetadata { return { - resultingStateRoot: input.resultingStateRoot.toString(), + stateRoot: input.stateRoot.toString(), blockTransactionHash: input.blockTransactionsHash.toString(), + blockHashRoot: input.blockHashRoot.toString(), - resultingNetworkState: NetworkState.toJSON(input.resultingNetworkState), + blockHashWitness: BlockHashMerkleTreeWitness.toJSON(input.blockHashWitness), + blockStateTransitions: this.stArrayMapper.mapOut(input.blockStateTransitions), + afterNetworkState: NetworkState.toJSON(input.afterNetworkState), }; } } From d8b2911838443944b8b64951f9564523504ce8d4 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 15 Jan 2024 16:38:30 +0100 Subject: [PATCH 29/54] Few fixes --- packages/sdk/src/query/BlockStorageNetworkStateModule.ts | 8 +++++--- .../src/protocol/production/BlockProducerModule.ts | 4 +--- .../production/unproven/TransactionExecutionService.ts | 7 ------- .../production/unproven/UnprovenProducerModule.ts | 1 + 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/sdk/src/query/BlockStorageNetworkStateModule.ts b/packages/sdk/src/query/BlockStorageNetworkStateModule.ts index 95d71c972..4354a4e41 100644 --- a/packages/sdk/src/query/BlockStorageNetworkStateModule.ts +++ b/packages/sdk/src/query/BlockStorageNetworkStateModule.ts @@ -3,6 +3,7 @@ import { BlockStorage, HistoricalBlockStorage, HistoricalUnprovenBlockStorage, + NetworkStateTransportModule, Sequencer, SequencerModulesRecord, UnprovenBlockQueue, @@ -13,9 +14,10 @@ import { NetworkState } from "@proto-kit/protocol"; import { AppChainModule } from "../appChain/AppChainModule"; @injectable() -export class BlockStorageNetworkStateModule extends AppChainModule< - Record -> { +export class BlockStorageNetworkStateModule + extends AppChainModule> + implements NetworkStateTransportModule +{ public constructor( @inject("Sequencer") private readonly sequencer: Sequencer diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index 554e0b738..5c8fa0422 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -114,9 +114,7 @@ export class BlockProducerModule extends SequencerModule { if (blockMetadata !== undefined) { log.debug( - `Batch produced (${blockMetadata.block.bundles.length} bundles, ${ - blockMetadata.block.bundles.flat(1).length - } txs)` + `Batch produced (${blockMetadata.block.bundles.length} bundles)` ); // Apply state changes to current StateService await this.applyStateChanges( diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index d44745b88..bcf92bc8b 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -244,13 +244,6 @@ export class TransactionExecutionService { } } - if (executionResults.length === 0) { - log.info( - "After sequencing, block has no sequencable transactions left, skipping block" - ); - return undefined; - } - const previousBlockTransactionsHash = lastMetadata.blockTransactionsHash === 0n ? undefined diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index f94172ad2..1bdf75b2d 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -21,6 +21,7 @@ import { UnprovenBlock, UnprovenBlockWithMetadata, } from "../../../storage/model/UnprovenBlock"; +import { CachedStateService } from "../../../state/state/CachedStateService"; import { TransactionExecutionService } from "./TransactionExecutionService"; From 98bc025656ae091a122bf5f0bfd11079c3d290b0 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 15 Jan 2024 20:02:42 +0100 Subject: [PATCH 30/54] Added hash to block --- .../graphql/modules/BlockStorageResolver.ts | 3 +- .../graphql/modules/UnprovenBlockResolver.ts | 24 +++++++++++---- packages/persistance/prisma/schema.prisma | 13 ++++---- .../src/services/prisma/PrismaBatchStore.ts | 8 ++--- .../src/services/prisma/PrismaBlockStorage.ts | 30 +++++++++---------- .../services/prisma/mappers/BlockMapper.ts | 10 ++++--- .../mappers/UnprovenBlockMetadataMapper.ts | 4 +-- .../protocol/src/prover/block/BlockProver.ts | 8 +++-- .../accummulators/BlockHashMerkleTree.ts | 4 +-- .../unproven/TransactionExecutionService.ts | 21 ++++++++----- .../storage/inmemory/InMemoryBlockStorage.ts | 4 +-- .../src/storage/model/UnprovenBlock.ts | 27 +++++++++++++---- .../repositories/UnprovenBlockStorage.ts | 2 +- 13 files changed, 98 insertions(+), 60 deletions(-) diff --git a/packages/api/src/graphql/modules/BlockStorageResolver.ts b/packages/api/src/graphql/modules/BlockStorageResolver.ts index 3ca88d473..a5deb81c0 100644 --- a/packages/api/src/graphql/modules/BlockStorageResolver.ts +++ b/packages/api/src/graphql/modules/BlockStorageResolver.ts @@ -53,12 +53,11 @@ export class ComputedBlockTransactionModel { export class ComputedBlockModel { public static fromServiceLayerModel( { bundles, proof }: ComputedBlock, - // eslint-disable-next-line putout/putout blocks: (UnprovenBlockModel | undefined)[] ): ComputedBlockModel { return new ComputedBlockModel( bundles.map( - (bundle) => blocks.find((block) => block?.transactionsHash === bundle)! + (bundle) => blocks.find((block) => block?.hash === bundle)! ), proof.proof === MOCK_PROOF ? "mock-proof" : JSON.stringify(proof) ); diff --git a/packages/api/src/graphql/modules/UnprovenBlockResolver.ts b/packages/api/src/graphql/modules/UnprovenBlockResolver.ts index 4807482f5..ec7971385 100644 --- a/packages/api/src/graphql/modules/UnprovenBlockResolver.ts +++ b/packages/api/src/graphql/modules/UnprovenBlockResolver.ts @@ -22,10 +22,18 @@ export class UnprovenBlockModel { statusMessage: tx.statusMessage, }) ), - unprovenBlock.transactionsHash.toString() + unprovenBlock.transactionsHash.toString(), + unprovenBlock.hash.toString(), + unprovenBlock.previousBlockHash?.toString() ); } + @Field() + hash: string; + + @Field({ nullable: true }) + previousBlockHash: string | undefined; + @Field() height: number; @@ -38,11 +46,15 @@ export class UnprovenBlockModel { private constructor( height: number, txs: ComputedBlockTransactionModel[], - transactionsHash: string + transactionsHash: string, + hash: string, + previousBlockHash: string | undefined ) { this.height = height; this.txs = txs; this.transactionsHash = transactionsHash; + this.hash = hash; + this.previousBlockHash = previousBlockHash; } } @@ -60,13 +72,13 @@ export class UnprovenBlockResolver extends GraphqlModule { public async block( @Arg("height", () => Number, { nullable: true }) height: number | undefined, - @Arg("transactionsHash", () => String, { nullable: true }) - transactionsHash: string | undefined + @Arg("hash", () => String, { nullable: true }) + hash: string | undefined ) { let block: UnprovenBlock | undefined; - if (transactionsHash !== undefined) { - block = await this.blockStorage.getBlock(transactionsHash); + if (hash !== undefined) { + block = await this.blockStorage.getBlock(hash); } else { const blockHeight = height ?? (await this.blockStorage.getCurrentBlockHeight()) - 1; diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index e506b486a..f0e139237 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -48,13 +48,14 @@ model TransactionExecutionResult { tx Transaction @relation(fields: [txHash], references: [hash]) txHash String @id - block Block @relation(fields: [blockHash], references: [transactionsHash]) + block Block @relation(fields: [blockHash], references: [hash]) blockHash String } model Block { - transactionsHash String @id + hash String @id + transactionsHash String beforeNetworkState Json @db.Json duringNetworkState Json @db.Json height Int @@ -62,8 +63,8 @@ model Block { toEternalTransactionsHash String fromBlockHashRoot String - parentTransactionsHash String? @unique - parent Block? @relation("Parent", fields: [parentTransactionsHash], references: [transactionsHash]) + parentHash String? @unique + parent Block? @relation("Parent", fields: [parentHash], references: [hash]) successor Block? @relation("Parent") transactions TransactionExecutionResult[] @@ -82,7 +83,7 @@ model Batch { } model UnprovenBlockMetadata { - blockTransactionHash String @id @unique + blockHash String @id @unique stateRoot String blockHashRoot String @@ -90,5 +91,5 @@ model UnprovenBlockMetadata { blockStateTransitions Json @db.Json blockHashWitness Json @db.Json - block Block? @relation(fields: [blockTransactionHash], references: [transactionsHash]) + block Block? @relation(fields: [blockHash], references: [hash]) } \ No newline at end of file diff --git a/packages/persistance/src/services/prisma/PrismaBatchStore.ts b/packages/persistance/src/services/prisma/PrismaBatchStore.ts index 859f97a2c..f1797d16c 100644 --- a/packages/persistance/src/services/prisma/PrismaBatchStore.ts +++ b/packages/persistance/src/services/prisma/PrismaBatchStore.ts @@ -25,7 +25,7 @@ export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { include: { blocks: { select: { - transactionsHash: true, + hash: true, }, }, }, @@ -35,7 +35,7 @@ export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { return undefined; } - const blocks = batch.blocks.map((block) => block.transactionsHash); + const blocks = batch.blocks.map((block) => block.hash); return this.batchMapper.mapIn([batch, blocks]); } @@ -57,8 +57,8 @@ export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { proof: entity.proof as Prisma.InputJsonValue, height, blocks: { - connect: block.bundles.map((transactionsHash) => ({ - transactionsHash, + connect: block.bundles.map((hash) => ({ + hash, })), }, }, diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 7c7466515..7ab89f73e 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -41,7 +41,7 @@ export class PrismaBlockStorage ) {} private async getBlockByQuery( - where: { height: number } | { transactionsHash: string } + where: { height: number } | { hash: string } ): Promise { const result = await this.connection.client.block.findFirst({ where, @@ -80,9 +80,9 @@ export class PrismaBlockStorage } public async getBlock( - transactionsHash: string + hash: string ): Promise { - return (await this.getBlockByQuery({ transactionsHash }))?.block; + return (await this.getBlockByQuery({ hash }))?.block; } public async pushBlock(block: UnprovenBlock): Promise { @@ -96,7 +96,7 @@ export class PrismaBlockStorage const encoded = this.transactionResultMapper.mapOut(tx); return { ...encoded[0], - blockHash: block.transactionsHash.toString(), + blockHash: block.hash.toString(), }; } ); @@ -152,7 +152,7 @@ export class PrismaBlockStorage encoded.blockStateTransitions as Prisma.InputJsonValue, stateRoot: encoded.stateRoot, - blockTransactionHash: encoded.blockTransactionHash, + blockHash: encoded.blockHash, blockHashRoot: encoded.blockHashRoot, }, }); @@ -173,9 +173,9 @@ export class PrismaBlockStorage UnprovenBlockWithMetadata | undefined > { const latestBlock = await this.connection.client.$queryRaw< - { transactionsHash: string }[] - >`SELECT b1."transactionsHash" FROM "Block" b1 - LEFT JOIN "Block" child ON child."parentTransactionsHash" = b1."transactionsHash" + { hash: string }[] + >`SELECT b1."hash" FROM "Block" b1 + LEFT JOIN "Block" child ON child."parentHash" = b1."hash" WHERE child IS NULL LIMIT 1`; if (latestBlock.length === 0) { @@ -183,7 +183,7 @@ export class PrismaBlockStorage } return await this.getBlockByQuery({ - transactionsHash: latestBlock[0].transactionsHash, + hash: latestBlock[0].hash, }); } @@ -206,15 +206,15 @@ export class PrismaBlockStorage const blockHashes = blocks .flatMap((block) => [ - block.parentTransactionsHash, - block.transactionsHash, + block.parentHash, + block.hash, ]) .filter(filterNonNull) .filter(distinctByString); const metadata = await this.connection.client.unprovenBlockMetadata.findMany({ where: { - blockTransactionHash: { + blockHash: { in: blockHashes, }, }, @@ -230,18 +230,18 @@ export class PrismaBlockStorage decodedBlock.transactions = transactions; const correspondingMetadata = metadata.find( - (candidate) => candidate.blockTransactionHash === block.transactionsHash + (candidate) => candidate.blockHash === block.hash ); if (correspondingMetadata === undefined) { throw new Error( - `No Metadata has been set for block ${block.transactionsHash} yet` + `No Metadata has been set for block ${block.hash} yet` ); } const parentMetadata = metadata.find( (candidate) => - candidate.blockTransactionHash === block.parentTransactionsHash + candidate.blockHash === block.parentHash ); return { block: { diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts index bb05b9356..e5ca4d2e0 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -21,15 +21,16 @@ export class BlockMapper implements ObjectMapper { ) }, + hash: Field(input.hash), height: Field(input.height), fromEternalTransactionsHash: Field(input.fromEternalTransactionsHash), toEternalTransactionsHash: Field(input.toEternalTransactionsHash), fromBlockHashRoot: Field(input.fromBlockHashRoot), transactionsHash: Field(input.transactionsHash), - previousBlockTransactionsHash: - input.parentTransactionsHash !== null - ? Field(input.parentTransactionsHash) + previousBlockHash: + input.parentHash !== null + ? Field(input.parentHash) : undefined, }; } @@ -43,8 +44,9 @@ export class BlockMapper implements ObjectMapper { toEternalTransactionsHash: input.toEternalTransactionsHash.toString(), fromBlockHashRoot: input.fromBlockHashRoot.toString(), + hash: input.hash.toString(), transactionsHash: input.transactionsHash.toString(), - parentTransactionsHash: input.previousBlockTransactionsHash?.toString() ?? null, + parentHash: input.previousBlockHash?.toString() ?? null, batchHeight: null, }; } diff --git a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts index 55b707710..16afda925 100644 --- a/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/UnprovenBlockMetadataMapper.ts @@ -34,7 +34,7 @@ export class UnprovenBlockMetadataMapper blockStateTransitions: this.stArrayMapper.mapIn( input.blockStateTransitions ), - blockTransactionsHash: BigInt(input.blockTransactionHash), + blockHash: BigInt(input.blockHash), }; } @@ -43,7 +43,7 @@ export class UnprovenBlockMetadataMapper ): DBUnprovenBlockMetadata { return { stateRoot: input.stateRoot.toString(), - blockTransactionHash: input.blockTransactionsHash.toString(), + blockHash: input.blockHash.toString(), blockHashRoot: input.blockHashRoot.toString(), blockHashWitness: BlockHashMerkleTreeWitness.toJSON(input.blockHashWitness), diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 9993f7b2a..aa27b8923 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -2,10 +2,10 @@ import { Bool, Experimental, - Field, + Field, Poseidon, type Proof, Provable, - SelfProof, + SelfProof } from "o1js"; import { container, inject, injectable, injectAll } from "tsyringe"; import { @@ -45,6 +45,7 @@ import { BlockHashMerkleTreeWitness, BlockHashTreeEntry, } from "./accummulators/BlockHashMerkleTree"; +import { UnprovenBlock } from "@proto-kit/sequencer"; const errors = { stateProofNotStartingAtZero: () => @@ -532,7 +533,8 @@ export class BlockProverProgrammable extends ZkProgrammable< state.blockHashRoot = blockWitness.calculateRoot( new BlockHashTreeEntry({ - transactionsHash: state.transactionsHash, + // Mirroring UnprovenBlock.hash() + blockHash: Poseidon.hash([blockIndex, state.transactionsHash]), closed: Bool(true), }).hash() ); diff --git a/packages/protocol/src/prover/block/accummulators/BlockHashMerkleTree.ts b/packages/protocol/src/prover/block/accummulators/BlockHashMerkleTree.ts index ff7bbefa7..0eaf25239 100644 --- a/packages/protocol/src/prover/block/accummulators/BlockHashMerkleTree.ts +++ b/packages/protocol/src/prover/block/accummulators/BlockHashMerkleTree.ts @@ -5,12 +5,12 @@ export class BlockHashMerkleTree extends createMerkleTree(8) {} export class BlockHashMerkleTreeWitness extends BlockHashMerkleTree.WITNESS {} export class BlockHashTreeEntry extends Struct({ - transactionsHash: Field, + blockHash: Field, closed: Bool, // TODO We could add startingEternalTransactionsHash here to offer // a more trivial connection to the sequence state }) { public hash(): Field { - return Poseidon.hash([this.transactionsHash, ...this.closed.toFields()]); + return Poseidon.hash([this.blockHash, ...this.closed.toFields()]); } } diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index bcf92bc8b..297929140 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -244,10 +244,10 @@ export class TransactionExecutionService { } } - const previousBlockTransactionsHash = - lastMetadata.blockTransactionsHash === 0n + const previousBlockHash = + lastMetadata.blockHash === 0n ? undefined - : Field(lastMetadata.blockTransactionsHash); + : Field(lastMetadata.blockHash); if (executionResults.length === 0 && !allowEmptyBlocks) { log.info( @@ -256,20 +256,27 @@ export class TransactionExecutionService { return undefined; } - return { + const block: Omit = { transactions: executionResults, transactionsHash: transactionsHashList.commitment, fromEternalTransactionsHash: lastBlock.toEternalTransactionsHash, toEternalTransactionsHash: eternalTransactionsHashList.commitment, height: lastBlock.height.add(1), fromBlockHashRoot: Field(lastMetadata.blockHashRoot), - previousBlockTransactionsHash, + previousBlockHash, networkState: { before: new NetworkState(lastMetadata.afterNetworkState), during: networkState, }, }; + + const hash = UnprovenBlock.hash(block); + + return { + ...block, + hash, + }; } public async generateMetadataForNextBlock( @@ -304,7 +311,7 @@ export class TransactionExecutionService { } // In case the diff is empty, we preload key 0 in order to // retrieve the root, which we need later - if(Object.keys(combinedDiff).length === 0){ + if (Object.keys(combinedDiff).length === 0) { await inMemoryStore.preloadKey(0n); } @@ -366,7 +373,7 @@ export class TransactionExecutionService { blockStateTransitions: stateTransitions.map((st) => UntypedStateTransition.fromStateTransition(st) ), - blockTransactionsHash: block.transactionsHash.toBigInt(), + blockHash: block.hash.toBigInt(), }; } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts index d00120097..7a904abf2 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts @@ -81,10 +81,10 @@ export class InMemoryBlockStorage } public async getBlock( - transactionsHash: string + hash: string ): Promise { return this.blocks.find( - (block) => block.transactionsHash.toString() === transactionsHash + (block) => block.hash.toString() === hash ); } } diff --git a/packages/sequencer/src/storage/model/UnprovenBlock.ts b/packages/sequencer/src/storage/model/UnprovenBlock.ts index 29917a1cc..d0ed96aaf 100644 --- a/packages/sequencer/src/storage/model/UnprovenBlock.ts +++ b/packages/sequencer/src/storage/model/UnprovenBlock.ts @@ -1,13 +1,13 @@ -import { Bool, Field } from "o1js"; +import { Bool, Field, Poseidon } from "o1js"; import { BlockHashMerkleTree, BlockHashMerkleTreeWitness, NetworkState, } from "@proto-kit/protocol"; +import { RollupMerkleTree } from "@proto-kit/common"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { UntypedStateTransition } from "../../protocol/production/helpers/UntypedStateTransition"; -import { RollupMerkleTree } from "@proto-kit/common"; export interface TransactionExecutionResult { tx: PendingTransaction; @@ -18,6 +18,8 @@ export interface TransactionExecutionResult { } export interface UnprovenBlock { + hash: Field; + height: Field; networkState: { before: NetworkState; @@ -28,16 +30,27 @@ export interface UnprovenBlock { toEternalTransactionsHash: Field; fromEternalTransactionsHash: Field; fromBlockHashRoot: Field; - previousBlockTransactionsHash: Field | undefined; + previousBlockHash: Field | undefined; } +export const UnprovenBlock = { + calculateHash(height: Field, transactionsHash: Field): Field { + return Poseidon.hash([height, transactionsHash]); + }, + + hash(block: Omit): Field { + return UnprovenBlock.calculateHash(block.height, block.transactionsHash); + }, +}; + export interface UnprovenBlockMetadata { + blockHash: bigint; + stateRoot: bigint; blockHashRoot: bigint; afterNetworkState: NetworkState; blockStateTransitions: UntypedStateTransition[]; blockHashWitness: BlockHashMerkleTreeWitness; - blockTransactionsHash: bigint; } export interface UnprovenBlockWithMetadata { @@ -49,6 +62,8 @@ export const UnprovenBlockWithMetadata = { createEmpty: () => ({ block: { + hash: Field(0), + height: Field(0), transactionsHash: Field(0), fromEternalTransactionsHash: Field(0), @@ -59,7 +74,7 @@ export const UnprovenBlockWithMetadata = { during: NetworkState.empty(), }, fromBlockHashRoot: Field(BlockHashMerkleTree.EMPTY_ROOT), - previousBlockTransactionsHash: undefined, + previousBlockHash: undefined, }, metadata: { afterNetworkState: NetworkState.empty(), @@ -67,7 +82,7 @@ export const UnprovenBlockWithMetadata = { blockHashRoot: BlockHashMerkleTree.EMPTY_ROOT, blockStateTransitions: [], blockHashWitness: BlockHashMerkleTree.WITNESS.dummy(), - blockTransactionsHash: 0n, + blockHash: 0n, }, } satisfies UnprovenBlockWithMetadata), }; diff --git a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts index 64c597fc4..b60650816 100644 --- a/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts +++ b/packages/sequencer/src/storage/repositories/UnprovenBlockStorage.ts @@ -20,5 +20,5 @@ export interface UnprovenBlockStorage { export interface HistoricalUnprovenBlockStorage { getBlockAt: (height: number) => Promise; - getBlock: (transactionsHash: string) => Promise; + getBlock: (hash: string) => Promise; } From e53b8b7e23cf06093f6d9a01330d6a6f96780d31 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 15 Jan 2024 20:46:44 +0100 Subject: [PATCH 31/54] Fixes --- .../graphql/modules/UnprovenBlockResolver.ts | 2 +- .../20240102161834_parent_block/migration.sql | 30 ---------------- .../migration.sql | 35 +++++++++++++++---- .../src/PrismaDatabaseConnection.ts | 4 +-- packages/persistance/src/RedisConnection.ts | 5 ++- packages/sdk/test/graphql/graphql.ts | 12 ++++++- packages/sequencer/src/index.ts | 1 + 7 files changed, 47 insertions(+), 42 deletions(-) delete mode 100644 packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql rename packages/persistance/prisma/migrations/{20240102111208_sequencer_model => 20240115202458_blockhash}/migration.sql (64%) diff --git a/packages/api/src/graphql/modules/UnprovenBlockResolver.ts b/packages/api/src/graphql/modules/UnprovenBlockResolver.ts index ec7971385..2d9a288e1 100644 --- a/packages/api/src/graphql/modules/UnprovenBlockResolver.ts +++ b/packages/api/src/graphql/modules/UnprovenBlockResolver.ts @@ -31,7 +31,7 @@ export class UnprovenBlockModel { @Field() hash: string; - @Field({ nullable: true }) + @Field(() => String, { nullable: true }) previousBlockHash: string | undefined; @Field() diff --git a/packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql b/packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql deleted file mode 100644 index 694a021dc..000000000 --- a/packages/persistance/prisma/migrations/20240102161834_parent_block/migration.sql +++ /dev/null @@ -1,30 +0,0 @@ -/* - Warnings: - - - The primary key for the `UnprovenBlockMetadata` table will be changed. If it partially fails, the table could be left without primary key constraint. - - You are about to drop the column `height` on the `UnprovenBlockMetadata` table. All the data in the column will be lost. - - A unique constraint covering the columns `[parentTransactionsHash]` on the table `Block` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[blockTransactionHash]` on the table `UnprovenBlockMetadata` will be added. If there are existing duplicate values, this will fail. - - Added the required column `blockTransactionHash` to the `UnprovenBlockMetadata` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Block" ADD COLUMN "parentTransactionsHash" TEXT; - --- AlterTable -ALTER TABLE "UnprovenBlockMetadata" DROP CONSTRAINT "UnprovenBlockMetadata_pkey", -DROP COLUMN "height", -ADD COLUMN "blockTransactionHash" TEXT NOT NULL, -ADD CONSTRAINT "UnprovenBlockMetadata_pkey" PRIMARY KEY ("blockTransactionHash"); - --- CreateIndex -CREATE UNIQUE INDEX "Block_parentTransactionsHash_key" ON "Block"("parentTransactionsHash"); - --- CreateIndex -CREATE UNIQUE INDEX "UnprovenBlockMetadata_blockTransactionHash_key" ON "UnprovenBlockMetadata"("blockTransactionHash"); - --- AddForeignKey -ALTER TABLE "Block" ADD CONSTRAINT "Block_parentTransactionsHash_fkey" FOREIGN KEY ("parentTransactionsHash") REFERENCES "Block"("transactionsHash") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "UnprovenBlockMetadata" ADD CONSTRAINT "UnprovenBlockMetadata_blockTransactionHash_fkey" FOREIGN KEY ("blockTransactionHash") REFERENCES "Block"("transactionsHash") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql b/packages/persistance/prisma/migrations/20240115202458_blockhash/migration.sql similarity index 64% rename from packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql rename to packages/persistance/prisma/migrations/20240115202458_blockhash/migration.sql index bc6de8b85..6d4bb6c75 100644 --- a/packages/persistance/prisma/migrations/20240102111208_sequencer_model/migration.sql +++ b/packages/persistance/prisma/migrations/20240115202458_blockhash/migration.sql @@ -48,12 +48,18 @@ CREATE TABLE "TransactionExecutionResult" ( -- CreateTable CREATE TABLE "Block" ( + "hash" TEXT NOT NULL, "transactionsHash" TEXT NOT NULL, - "networkState" JSON NOT NULL, + "beforeNetworkState" JSON NOT NULL, + "duringNetworkState" JSON NOT NULL, "height" INTEGER NOT NULL, + "fromEternalTransactionsHash" TEXT NOT NULL, + "toEternalTransactionsHash" TEXT NOT NULL, + "fromBlockHashRoot" TEXT NOT NULL, + "parentHash" TEXT, "batchHeight" INTEGER, - CONSTRAINT "Block_pkey" PRIMARY KEY ("transactionsHash") + CONSTRAINT "Block_pkey" PRIMARY KEY ("hash") ); -- CreateTable @@ -66,18 +72,33 @@ CREATE TABLE "Batch" ( -- CreateTable CREATE TABLE "UnprovenBlockMetadata" ( - "height" INTEGER NOT NULL, - "resultingStateRoot" TEXT NOT NULL, - "resultingNetworkState" JSON NOT NULL, + "blockHash" TEXT NOT NULL, + "stateRoot" TEXT NOT NULL, + "blockHashRoot" TEXT NOT NULL, + "afterNetworkState" JSON NOT NULL, + "blockStateTransitions" JSON NOT NULL, + "blockHashWitness" JSON NOT NULL, - CONSTRAINT "UnprovenBlockMetadata_pkey" PRIMARY KEY ("height") + CONSTRAINT "UnprovenBlockMetadata_pkey" PRIMARY KEY ("blockHash") ); +-- CreateIndex +CREATE UNIQUE INDEX "Block_parentHash_key" ON "Block"("parentHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "UnprovenBlockMetadata_blockHash_key" ON "UnprovenBlockMetadata"("blockHash"); + -- AddForeignKey ALTER TABLE "TransactionExecutionResult" ADD CONSTRAINT "TransactionExecutionResult_txHash_fkey" FOREIGN KEY ("txHash") REFERENCES "Transaction"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "TransactionExecutionResult" ADD CONSTRAINT "TransactionExecutionResult_blockHash_fkey" FOREIGN KEY ("blockHash") REFERENCES "Block"("transactionsHash") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "TransactionExecutionResult" ADD CONSTRAINT "TransactionExecutionResult_blockHash_fkey" FOREIGN KEY ("blockHash") REFERENCES "Block"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Block" ADD CONSTRAINT "Block_parentHash_fkey" FOREIGN KEY ("parentHash") REFERENCES "Block"("hash") ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Block" ADD CONSTRAINT "Block_batchHeight_fkey" FOREIGN KEY ("batchHeight") REFERENCES "Batch"("height") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnprovenBlockMetadata" ADD CONSTRAINT "UnprovenBlockMetadata_blockHash_fkey" FOREIGN KEY ("blockHash") REFERENCES "Block"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index c2128e884..86bd32deb 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -39,7 +39,7 @@ export class PrismaDatabaseConnection public dependencies(): Omit< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "unprovenMerkleStore" + "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" > { return { asyncStateService: { @@ -56,7 +56,7 @@ export class PrismaDatabaseConnection }, unprovenStateService: { useFactory: () => new PrismaStateService(this, "block"), - }, + } }; } diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index 643513e4b..d4ee55e6e 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -29,7 +29,7 @@ export class RedisConnection public dependencies(): Pick< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "unprovenMerkleStore" + "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" > { return { asyncMerkleStore: { @@ -38,6 +38,9 @@ export class RedisConnection unprovenMerkleStore: { useFactory: () => new RedisMerkleTreeStore(this, "unproven"), }, + blockTreeStore: { + useFactory: () => new RedisMerkleTreeStore(this, "blockHash"), + }, }; } diff --git a/packages/sdk/test/graphql/graphql.ts b/packages/sdk/test/graphql/graphql.ts index 81d7a04fe..0f3024279 100644 --- a/packages/sdk/test/graphql/graphql.ts +++ b/packages/sdk/test/graphql/graphql.ts @@ -47,6 +47,8 @@ import { container } from "tsyringe"; import { UnprovenProducerModule } from "@proto-kit/sequencer/dist/protocol/production/unproven/UnprovenProducerModule"; import { BlockStorageNetworkStateModule } from "../../src/query/BlockStorageNetworkStateModule"; import { MessageBoard, Post } from "./Post"; +import { PrismaDatabaseConnection } from "@proto-kit/persistance"; +import { RedisConnection } from "@proto-kit/persistance/dist/RedisConnection"; log.setLevel(log.levels.INFO); @@ -122,7 +124,10 @@ export async function startServer() { sequencer: Sequencer.from({ modules: { - Database: InMemoryDatabase, + // Database: InMemoryDatabase, + Database: PrismaDatabaseConnection, + Redis: RedisConnection, + Mempool: PrivateMempool, GraphqlServer, LocalTaskWorkerModule, @@ -189,6 +194,11 @@ export async function startServer() { }, Database: {}, + Redis: { + url: "redis://localhost:6379", + password: "password", + }, + Mempool: {}, BlockProducerModule: {}, LocalTaskWorkerModule: {}, diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index d3dd98d2a..78012d8f4 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -40,6 +40,7 @@ export * from "./storage/model/Block"; export * from "./storage/model/UnprovenBlock"; export * from "./storage/repositories/BlockStorage"; export * from "./storage/repositories/UnprovenBlockStorage"; +export * from "./storage/repositories/TransactionRepository"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; From f8d8d0bab52c0239b7a126fd23a611c5cf20aee2 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 24 Jan 2024 14:55:54 +0100 Subject: [PATCH 32/54] Fixed errors --- packages/protocol/src/prover/block/BlockProver.ts | 1 - packages/sequencer/src/index.ts | 1 - .../protocol/production/unproven/TransactionExecutionService.ts | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index aa27b8923..d199e6647 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -45,7 +45,6 @@ import { BlockHashMerkleTreeWitness, BlockHashTreeEntry, } from "./accummulators/BlockHashMerkleTree"; -import { UnprovenBlock } from "@proto-kit/sequencer"; const errors = { stateProofNotStartingAtZero: () => diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 78012d8f4..d3dd98d2a 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -40,7 +40,6 @@ export * from "./storage/model/Block"; export * from "./storage/model/UnprovenBlock"; export * from "./storage/repositories/BlockStorage"; export * from "./storage/repositories/UnprovenBlockStorage"; -export * from "./storage/repositories/TransactionRepository"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 297929140..f806a86d6 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -352,7 +352,7 @@ export class TransactionExecutionService { blockHashTree.setLeaf( block.height.toBigInt(), new BlockHashTreeEntry({ - transactionsHash: block.transactionsHash, + blockHash: block.transactionsHash, closed: Bool(true), }).hash() ); From 6534623c2b07e0d16497d8d30eda03f389ae3a06 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 30 Jan 2024 20:17:57 +0100 Subject: [PATCH 33/54] Optimized loading of merkletree nodes --- .../protocol/production/TransactionTraceService.ts | 6 +----- .../unproven/TransactionExecutionService.ts | 10 +++------- .../src/state/merkle/CachedMerkleTreeStore.ts | 14 ++++++++++++-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index 933b43785..1a91ccb90 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -247,11 +247,7 @@ export class TransactionTraceService { merkleStore ); - await Promise.all( - keys.map(async (key) => { - await merkleStore.preloadKey(key.toBigInt()); - }) - ); + await merkleStore.preloadKeys(keys.map(key => key.toBigInt())) const tree = new RollupMerkleTree(merkleStore); const runtimeTree = new RollupMerkleTree(runtimeSimulationMerkleStore); diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index f806a86d6..4055ede3c 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -245,9 +245,7 @@ export class TransactionExecutionService { } const previousBlockHash = - lastMetadata.blockHash === 0n - ? undefined - : Field(lastMetadata.blockHash); + lastMetadata.blockHash === 0n ? undefined : Field(lastMetadata.blockHash); if (executionResults.length === 0 && !allowEmptyBlocks) { log.info( @@ -305,10 +303,8 @@ export class TransactionExecutionService { ); const blockHashTree = new BlockHashMerkleTree(blockHashInMemoryStore); - for (const key of Object.keys(combinedDiff)) { - // eslint-disable-next-line no-await-in-loop - await inMemoryStore.preloadKey(BigInt(key)); - } + await inMemoryStore.preloadKeys(Object.keys(combinedDiff).map(BigInt)); + // In case the diff is empty, we preload key 0 in order to // retrieve the root, which we need later if (Object.keys(combinedDiff).length === 0) { diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 7b82c3967..f8ecd5546 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -54,8 +54,7 @@ export class CachedMerkleTreeStore this.writeCache = {}; } - // eslint-disable-next-line sonarjs/cognitive-complexity - public async preloadKey(index: bigint): Promise { + private collectNodesToFetch(index: bigint) { // Algo from RollupMerkleTree.getWitness() const { leafCount, HEIGHT } = RollupMerkleTree; @@ -91,6 +90,13 @@ export class CachedMerkleTreeStore } index /= 2n; } + return nodesToRetrieve; + } + + public async preloadKeys(keys: bigint[]) { + const nodesToRetrieve = keys.flatMap((key) => + this.collectNodesToFetch(key) + ); const results = await this.parent.getNodesAsync(nodesToRetrieve); nodesToRetrieve.forEach(({ key, level }, index) => { @@ -101,6 +107,10 @@ export class CachedMerkleTreeStore }); } + public async preloadKey(index: bigint): Promise { + await this.preloadKeys([index]); + } + public async mergeIntoParent(): Promise { // In case no state got set we can skip this step if (Object.keys(this.writeCache).length === 0) { From b4fb02a43ed9dccdf044998b671eaa74e1986793 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 31 Jan 2024 10:57:03 +0100 Subject: [PATCH 34/54] Added batch storage assertion to prod test --- .../test/integration/BlockProduction.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index ef9f0d85f..f971304ec 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -28,9 +28,9 @@ import { UnsignedTransaction } from "../../src/mempool/PendingTransaction"; import { Sequencer } from "../../src/sequencer/executor/Sequencer"; import { AsyncStateService, - BlockProducerModule, + BlockProducerModule, BlockStorage, HistoricalBlockStorage, InMemoryDatabase, - ManualBlockTrigger, + ManualBlockTrigger } from "../../src"; import { LocalTaskWorkerModule } from "../../src/worker/worker/LocalTaskWorkerModule"; @@ -162,7 +162,7 @@ describe("block production", () => { // eslint-disable-next-line max-statements it("should produce a dummy block proof", async () => { - expect.assertions(21); + expect.assertions(22); const privateKey = PrivateKey.random(); const publicKey = privateKey.toPublicKey(); @@ -192,6 +192,11 @@ describe("block production", () => { expect(batch!.bundles).toHaveLength(1); expect(batch!.proof.proof).toBe("mock-proof"); + // Check if the batchstorage has received the block + const batchStorage = sequencer.resolve("BlockStorage") as BlockStorage & HistoricalBlockStorage; + const retrievedBatch = await batchStorage.getBlockAt(0) + expect(retrievedBatch).toBeDefined(); + const stateService = sequencer.dependencyContainer.resolve( "AsyncStateService" From ae6080aac6fa6cb87c82d60b6e35ff7a0f0d9002 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 31 Jan 2024 12:02:07 +0100 Subject: [PATCH 35/54] Improven error message for redis connection failure --- packages/persistance/src/RedisConnection.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index d4ee55e6e..361de5070 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -46,7 +46,14 @@ export class RedisConnection public async init() { this.redisClient = createClient(this.config); - await this.redisClient.connect(); + try { + await this.redisClient.connect(); + } catch (e: unknown) { + if (e instanceof Error) { + throw new Error(`Connection to Redis failed: ${e.message}`); + } + throw e; + } } public async start(): Promise { From 3a437d9ac84060eeb29ae7458ebd671f8ee92459 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 4 Feb 2024 12:42:50 +0100 Subject: [PATCH 36/54] Fixed merge errors --- .../src/protocol/production/TransactionTraceService.ts | 1 - .../src/protocol/production/trigger/BlockTrigger.ts | 4 ++-- .../sequencer/src/settlement/messages/WithdrawalQueue.ts | 7 +++---- packages/sequencer/src/storage/model/UnprovenBlock.ts | 9 ++++++++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/sequencer/src/protocol/production/TransactionTraceService.ts b/packages/sequencer/src/protocol/production/TransactionTraceService.ts index ff2df57e6..08d5f415f 100644 --- a/packages/sequencer/src/protocol/production/TransactionTraceService.ts +++ b/packages/sequencer/src/protocol/production/TransactionTraceService.ts @@ -27,7 +27,6 @@ import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import type { TransactionTrace, BlockTrace } from "./BlockProducerModule"; import { StateTransitionProofParameters } from "./tasks/StateTransitionTaskParameters"; import { UntypedStateTransition } from "./helpers/UntypedStateTransition"; -import type { BlockTrace } from "./BlockProducerModule"; @injectable() @scoped(Lifecycle.ContainerScoped) diff --git a/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts index d2704a260..a7136a58e 100644 --- a/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts @@ -1,12 +1,12 @@ import { NoConfig, noop } from "@proto-kit/common"; import { ComputedBlock } from "../../../storage/model/Block"; -import { UnprovenBlock } from "../unproven/TransactionExecutionService"; import { BlockProducerModule } from "../BlockProducerModule"; import { UnprovenProducerModule } from "../unproven/UnprovenProducerModule"; import { UnprovenBlockQueue } from "../../../storage/repositories/UnprovenBlockStorage"; import { SequencerModule } from "../../../sequencer/builder/SequencerModule"; import { SettlementModule } from "../../../settlement/SettlementModule"; +import { UnprovenBlock } from "../../../storage/model/UnprovenBlock"; /** * A BlockTrigger is the primary method to start the production of a block and @@ -29,7 +29,7 @@ export class BlockTriggerBase } protected async produceProven(): Promise { - const blocks = await this.unprovenBlockQueue.popNewBlocks(true); + const blocks = await this.unprovenBlockQueue.getNewBlocks(); if (blocks.length > 0) { return await this.blockProducerModule.createBlock(blocks); } diff --git a/packages/sequencer/src/settlement/messages/WithdrawalQueue.ts b/packages/sequencer/src/settlement/messages/WithdrawalQueue.ts index 0f17f267d..f6a7b5e7d 100644 --- a/packages/sequencer/src/settlement/messages/WithdrawalQueue.ts +++ b/packages/sequencer/src/settlement/messages/WithdrawalQueue.ts @@ -1,21 +1,20 @@ -import { delay, inject, injectable } from "tsyringe"; +import { inject, injectable } from "tsyringe"; import { MethodIdResolver, Runtime, RuntimeModulesRecord, } from "@proto-kit/module"; import { Path, Withdrawal } from "@proto-kit/protocol"; -import { Field, PublicKey, UInt64 } from "o1js"; +import { Field } from "o1js"; import { UnprovenProducerModule } from "../../protocol/production/unproven/UnprovenProducerModule"; import { SequencerModule } from "../../sequencer/builder/SequencerModule"; -import { UnprovenBlock } from "../../protocol/production/unproven/TransactionExecutionService"; import type { SettlementModule } from "../SettlementModule"; import { Sequencer, SequencerModulesRecord, } from "../../sequencer/executor/Sequencer"; -import { ChildContainerProvider } from "@proto-kit/common"; +import { UnprovenBlock } from "../../storage/model/UnprovenBlock"; export interface OutgoingMessage { index: number; diff --git a/packages/sequencer/src/storage/model/UnprovenBlock.ts b/packages/sequencer/src/storage/model/UnprovenBlock.ts index d0ed96aaf..396ec9e91 100644 --- a/packages/sequencer/src/storage/model/UnprovenBlock.ts +++ b/packages/sequencer/src/storage/model/UnprovenBlock.ts @@ -1,8 +1,9 @@ import { Bool, Field, Poseidon } from "o1js"; import { + ACTIONS_EMPTY_HASH, BlockHashMerkleTree, BlockHashMerkleTreeWitness, - NetworkState, + NetworkState } from "@proto-kit/protocol"; import { RollupMerkleTree } from "@proto-kit/common"; @@ -30,6 +31,9 @@ export interface UnprovenBlock { toEternalTransactionsHash: Field; fromEternalTransactionsHash: Field; fromBlockHashRoot: Field; + fromMessagesHash: Field; + toMessagesHash: Field; + previousBlockHash: Field | undefined; } @@ -74,6 +78,9 @@ export const UnprovenBlockWithMetadata = { during: NetworkState.empty(), }, fromBlockHashRoot: Field(BlockHashMerkleTree.EMPTY_ROOT), + fromMessagesHash: Field(0), + toMessagesHash: ACTIONS_EMPTY_HASH, + previousBlockHash: undefined, }, metadata: { From a91047624d483fb2629166dd48a45b4464f27509 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Sun, 4 Feb 2024 14:27:54 +0100 Subject: [PATCH 37/54] Adapted schema to integrate messages --- packages/persistance/prisma/schema.prisma | 7 +++- .../services/prisma/mappers/BlockMapper.ts | 4 +++ .../prisma/mappers/TransactionMapper.ts | 4 ++- .../production/BlockProducerModule.ts | 13 +++++--- .../helpers/BlockProofSerializer.ts | 32 +++++++++++++++++++ .../src/settlement/SettlementModule.ts | 10 ++++-- packages/sequencer/src/storage/model/Block.ts | 3 +- 7 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 packages/sequencer/src/protocol/production/helpers/BlockProofSerializer.ts diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index f0e139237..28f69b5bf 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -32,10 +32,13 @@ model Transaction { methodId String sender String nonce String - args String[] + argsFields String[] + argsJSON String[] signature_r String signature_s String + isMessage Boolean + executionResult TransactionExecutionResult? } @@ -62,6 +65,8 @@ model Block { fromEternalTransactionsHash String toEternalTransactionsHash String fromBlockHashRoot String + fromMessagesHash String + toMessagesHash String parentHash String? @unique parent Block? @relation("Parent", fields: [parentHash], references: [hash]) diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts index e5ca4d2e0..5ca706861 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -26,6 +26,8 @@ export class BlockMapper implements ObjectMapper { fromEternalTransactionsHash: Field(input.fromEternalTransactionsHash), toEternalTransactionsHash: Field(input.toEternalTransactionsHash), fromBlockHashRoot: Field(input.fromBlockHashRoot), + fromMessagesHash: Field(input.fromMessagesHash), + toMessagesHash: Field(input.toMessagesHash), transactionsHash: Field(input.transactionsHash), previousBlockHash: @@ -43,6 +45,8 @@ export class BlockMapper implements ObjectMapper { fromEternalTransactionsHash: input.fromEternalTransactionsHash.toString(), toEternalTransactionsHash: input.toEternalTransactionsHash.toString(), fromBlockHashRoot: input.fromBlockHashRoot.toString(), + fromMessagesHash: input.fromMessagesHash.toString(), + toMessagesHash: input.toMessagesHash.toString(), hash: input.hash.toString(), transactionsHash: input.transactionsHash.toString(), diff --git a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts index e3553f5a7..2ab294eb6 100644 --- a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts @@ -25,7 +25,9 @@ export class TransactionMapper methodId: json.methodId, nonce: json.nonce, sender: json.sender, - args: json.args, + argsFields: json.argsFields, + argsJSON: json.argsJSON, + isMessage: json.isMessage, signature_r: json.signature.r, signature_s: json.signature.s, hash: input.hash().toString() diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index ae25207fa..e0d6eb7cd 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -31,6 +31,7 @@ import { RuntimeProofParameters } from "./tasks/RuntimeTaskParameters"; import { TransactionTraceService } from "./TransactionTraceService"; import { BlockTaskFlowService } from "./BlockTaskFlowService"; import { NewBlockProverParameters } from "./tasks/NewBlockTask"; +import { BlockProofSerializer } from "./helpers/BlockProofSerializer"; export interface StateRecord { [key: string]: Field[] | undefined; @@ -86,7 +87,8 @@ export class BlockProducerModule extends SequencerModule { @inject("BlockTreeStore") private readonly blockTreeStore: AsyncMerkleTreeStore, private readonly traceService: TransactionTraceService, - private readonly blockFlowService: BlockTaskFlowService + private readonly blockFlowService: BlockTaskFlowService, + private readonly blockProofSerializer: BlockProofSerializer ) { super(); } @@ -184,12 +186,13 @@ export class BlockProducerModule extends SequencerModule { bundle.block.block.transactionsHash.toString() ); + const jsonProof = this.blockProofSerializer + .getBlockProofSerializer() + .toJSONProof(block.proof); + return { block: { - proof: - block.proof.proof === "mock-proof" - ? { proof: "mock-proof" } - : block.proof.toJSON(), + proof: jsonProof, bundles: computedBundles, }, diff --git a/packages/sequencer/src/protocol/production/helpers/BlockProofSerializer.ts b/packages/sequencer/src/protocol/production/helpers/BlockProofSerializer.ts new file mode 100644 index 000000000..4b9d92113 --- /dev/null +++ b/packages/sequencer/src/protocol/production/helpers/BlockProofSerializer.ts @@ -0,0 +1,32 @@ +import { inject, injectable, Lifecycle, scoped } from "tsyringe"; +import { + BlockProverPublicInput, + BlockProverPublicOutput, + Protocol, + ProtocolModulesRecord, +} from "@proto-kit/protocol"; + +import { ProofTaskSerializer } from "../../../helpers/utils"; + +@injectable() +@scoped(Lifecycle.ContainerScoped) +export class BlockProofSerializer { + private serializer?: ProofTaskSerializer< + BlockProverPublicInput, + BlockProverPublicOutput + >; + + public constructor( + @inject("Protocol") + private readonly protocol: Protocol + ) {} + + public getBlockProofSerializer() { + if (this.serializer === undefined) { + const blockProver = this.protocol.resolve("BlockProver"); + const proofType = blockProver.zkProgrammable.zkProgram.Proof; + this.serializer = new ProofTaskSerializer(proofType); + } + return this.serializer; + } +} diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 3860d72d8..e89173467 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -53,6 +53,7 @@ import { CachedMerkleTreeStore } from "../state/merkle/CachedMerkleTreeStore"; import { IncomingMessageAdapter } from "./messages/IncomingMessageAdapter"; import { OutgoingMessageQueue } from "./messages/WithdrawalQueue"; +import { BlockProofSerializer } from "../protocol/production/helpers/BlockProofSerializer"; export interface SettlementModuleConfig { feepayer: PrivateKey; @@ -93,7 +94,8 @@ export class SettlementModule @inject("OutgoingMessageQueue") private readonly outgoingMessageQueue: OutgoingMessageQueue, @inject("AsyncMerkleStore") - private readonly merkleTreeStore: AsyncMerkleTreeStore + private readonly merkleTreeStore: AsyncMerkleTreeStore, + private readonly blockProofSerializer: BlockProofSerializer ) { super(); } @@ -276,6 +278,10 @@ export class SettlementModule actions.messages ); + const blockProof = this.blockProofSerializer + .getBlockProofSerializer() + .fromJSONProof(batch.proof); + const tx = await Mina.transaction( { sender: feepayer.toPublicKey(), @@ -285,7 +291,7 @@ export class SettlementModule }, () => { contract.settle( - batch.proof, + blockProof, signature, feepayer.toPublicKey(), networkStateFrom, diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index 4e5f913ef..311c7ae10 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -9,6 +9,7 @@ export interface ComputedBlockTransaction { } export interface ComputedBlock { - proof: JsonProof | { proof: "mock-proof" }; + proof: JsonProof; bundles: string[]; + // TODO Height } From 365aca3daac2f9fd06d1d2f3502531b7e331fd11 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 5 Feb 2024 11:07:20 +0100 Subject: [PATCH 38/54] Added Prisma stores for Messages and Settlements --- packages/persistance/prisma/schema.prisma | 32 +++++++++ .../src/PrismaDatabaseConnection.ts | 10 ++- .../src/services/prisma/PrismaBlockStorage.ts | 1 + .../services/prisma/PrismaMessageStorage.ts | 68 +++++++++++++++++++ .../prisma/PrismaSettlementStorage.ts | 32 +++++++++ .../services/prisma/mappers/BatchMapper.ts | 7 +- .../prisma/mappers/SettlementMapper.ts | 28 ++++++++ packages/sequencer/src/index.ts | 3 + .../production/BlockProducerModule.ts | 13 ++-- .../inmemory/InMemoryMessageStorage.ts | 18 +++-- packages/sequencer/src/storage/model/Block.ts | 2 +- .../sequencer/src/storage/model/Settlement.ts | 6 +- .../storage/repositories/MessageStorage.ts | 6 +- .../test/settlement/Settlement.test.ts | 8 ++- 14 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 packages/persistance/src/services/prisma/PrismaMessageStorage.ts create mode 100644 packages/persistance/src/services/prisma/PrismaSettlementStorage.ts create mode 100644 packages/persistance/src/services/prisma/mappers/SettlementMapper.ts diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 28f69b5bf..4dc45b239 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -40,6 +40,8 @@ model Transaction { isMessage Boolean executionResult TransactionExecutionResult? + + IncomingMessageBatchTransaction IncomingMessageBatchTransaction[] } model TransactionExecutionResult { @@ -85,6 +87,9 @@ model Batch { proof Json @db.Json blocks Block[] + + settlementTransactionHash String? + settlement Settlement? @relation(fields: [settlementTransactionHash], references: [transactionHash]) } model UnprovenBlockMetadata { @@ -97,4 +102,31 @@ model UnprovenBlockMetadata { blockHashWitness Json @db.Json block Block? @relation(fields: [blockHash], references: [hash]) +} + +model Settlement { + // transaction String + transactionHash String @id + promisedMessagesHash String + + batches Batch[] +} + +model IncomingMessageBatchTransaction { + transactionHash String + transaction Transaction @relation(fields: [transactionHash], references: [hash]) + + batchId Int + batch IncomingMessageBatch @relation(fields: [batchId], references: [id]) + + @@id([transactionHash, batchId]) +} + +model IncomingMessageBatch { + id Int @id @default(autoincrement()) + + fromMessageHash String + toMessageHash String + + messages IncomingMessageBatchTransaction[] } \ No newline at end of file diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 86bd32deb..fd075368d 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -9,6 +9,8 @@ import { DependencyFactory, noop } from "@proto-kit/common"; import { PrismaStateService } from "./services/prisma/PrismaStateService"; import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; +import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStorage"; +import { PrismaMessageStorage } from "./services/prisma/PrismaMessageStorage"; export interface PrismaDatabaseConfig { connection?: { @@ -56,7 +58,13 @@ export class PrismaDatabaseConnection }, unprovenStateService: { useFactory: () => new PrismaStateService(this, "block"), - } + }, + settlementStorage: { + useClass: PrismaSettlementStorage, + }, + messageStorage: { + useClass: PrismaMessageStorage, + }, }; } diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 7ab89f73e..97d21e620 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -108,6 +108,7 @@ export class PrismaBlockStorage data: block.transactions.map((txr) => this.transactionMapper.mapOut(txr.tx) ), + skipDuplicates: true }), this.connection.client.block.create({ diff --git a/packages/persistance/src/services/prisma/PrismaMessageStorage.ts b/packages/persistance/src/services/prisma/PrismaMessageStorage.ts new file mode 100644 index 000000000..f0f65b858 --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaMessageStorage.ts @@ -0,0 +1,68 @@ +import { MessageStorage, PendingTransaction } from "@proto-kit/sequencer"; +import { inject } from "tsyringe"; +import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { TransactionMapper } from "./mappers/TransactionMapper"; + +export class PrismaMessageStorage implements MessageStorage { + public constructor( + @inject("Database") private readonly connection: PrismaDatabaseConnection, + private readonly transactionMapper: TransactionMapper + ) {} + + public async getMessages( + fromMessageHash: string + ): Promise { + const { client } = this.connection; + + const batch = await client.incomingMessageBatch.findFirst({ + where: { + fromMessageHash, + }, + include: { + messages: { + include: { + transaction: true, + }, + }, + }, + }); + + const dbTransactions = batch!.messages.map((message) => { + return message.transaction; + }); + + return dbTransactions.map((dbTx) => this.transactionMapper.mapIn(dbTx)); + } + + public async pushMessages( + fromMessageHash: string, + toMessageHash: string, + messages: PendingTransaction[] + ): Promise { + const transactions = messages.map((message) => + this.transactionMapper.mapOut(message) + ); + + const { client } = this.connection; + await client.$transaction([ + client.transaction.createMany({ + data: transactions, + skipDuplicates: true, + }), + + client.incomingMessageBatch.create({ + data: { + fromMessageHash, + toMessageHash, + messages: { + createMany: { + data: transactions.map((transaction) => ({ + transactionHash: transaction.hash, + })), + }, + }, + }, + }), + ]); + } +} diff --git a/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts b/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts new file mode 100644 index 000000000..5b473eba8 --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts @@ -0,0 +1,32 @@ +import { Settlement, SettlementStorage } from "@proto-kit/sequencer"; +import { inject } from "tsyringe"; +import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { TransactionMapper } from "./mappers/TransactionMapper"; +import { SettlementMapper } from "./mappers/SettlementMapper"; + +export class PrismaSettlementStorage implements SettlementStorage { + public constructor( + @inject("Database") private readonly connection: PrismaDatabaseConnection, + private readonly settlementMapper: SettlementMapper + ) {} + + public async pushSettlement(settlement: Settlement): Promise { + const { client } = this.connection; + + const dbSettlement = this.settlementMapper.mapOut(settlement); + + await client.settlement.create({ + data: { + ...dbSettlement[0], + batches: { + connect: dbSettlement[1].map((batchHeight) => ({ + height: batchHeight, + })), + }, + }, + include: { + batches: true, + }, + }); + } +} diff --git a/packages/persistance/src/services/prisma/mappers/BatchMapper.ts b/packages/persistance/src/services/prisma/mappers/BatchMapper.ts index ec5bed4af..7dfd1dcbf 100644 --- a/packages/persistance/src/services/prisma/mappers/BatchMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BatchMapper.ts @@ -9,20 +9,19 @@ import { ObjectMapper } from "../../../ObjectMapper"; export class BatchMapper implements ObjectMapper { - public constructor() {} - public mapIn(input: [Batch, string[]]): ComputedBlock { return { bundles: input[1], proof: input[0].proof as JsonProof, + height: input[0].height, }; } public mapOut(input: ComputedBlock): [Batch, string[]] { const batch: Batch = { proof: input.proof, - // TODO - height: 0, + height: input.height, + settlementTransactionHash: null, }; return [batch, []]; } diff --git a/packages/persistance/src/services/prisma/mappers/SettlementMapper.ts b/packages/persistance/src/services/prisma/mappers/SettlementMapper.ts new file mode 100644 index 000000000..d7d430310 --- /dev/null +++ b/packages/persistance/src/services/prisma/mappers/SettlementMapper.ts @@ -0,0 +1,28 @@ +import { injectable } from "tsyringe"; +import { ObjectMapper } from "../../../ObjectMapper"; +import { Settlement } from "@proto-kit/sequencer"; +import { Settlement as DBSettlement } from "@prisma/client"; + +@injectable() +export class SettlementMapper + implements ObjectMapper +{ + public mapIn(input: [DBSettlement, number[]]): Settlement { + const [settlement, batches] = input; + return { + batches, + transactionHash: settlement.transactionHash, + promisedMessagesHash: settlement.promisedMessagesHash, + }; + } + + public mapOut(input: Settlement): [DBSettlement, number[]] { + return [ + { + promisedMessagesHash: input.promisedMessagesHash, + transactionHash: input.transactionHash, + }, + input.batches, + ]; + } +} diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index d3dd98d2a..1d20fd868 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -38,8 +38,11 @@ export * from "./protocol/production/unproven/TransactionExecutionService"; export * from "./protocol/production/unproven/UnprovenProducerModule"; export * from "./storage/model/Block"; export * from "./storage/model/UnprovenBlock"; +export * from "./storage/model/Settlement"; export * from "./storage/repositories/BlockStorage"; export * from "./storage/repositories/UnprovenBlockStorage"; +export * from "./storage/repositories/SettlementStorage"; +export * from "./storage/repositories/MessageStorage"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index e0d6eb7cd..1ac6765b1 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -111,7 +111,9 @@ export class BlockProducerModule extends SequencerModule { ): Promise { log.info("Producing batch..."); - const blockMetadata = await this.tryProduceBlock(unprovenBlocks); + const height = await this.blockStorage.getCurrentBlockHeight(); + + const blockMetadata = await this.tryProduceBlock(unprovenBlocks, height); if (blockMetadata !== undefined) { log.info( @@ -140,13 +142,14 @@ export class BlockProducerModule extends SequencerModule { } private async tryProduceBlock( - unprovenBlocks: UnprovenBlockWithPreviousMetadata[] + unprovenBlocks: UnprovenBlockWithPreviousMetadata[], + height: number ): Promise { if (!this.productionInProgress) { try { this.productionInProgress = true; - const block = await this.produceBlock(unprovenBlocks); + const block = await this.produceBlock(unprovenBlocks, height); this.productionInProgress = false; @@ -176,7 +179,8 @@ export class BlockProducerModule extends SequencerModule { } private async produceBlock( - unprovenBlocks: UnprovenBlockWithPreviousMetadata[] + unprovenBlocks: UnprovenBlockWithPreviousMetadata[], + height: number ): Promise { const blockId = unprovenBlocks[0].block.block.height.toBigInt(); @@ -194,6 +198,7 @@ export class BlockProducerModule extends SequencerModule { block: { proof: jsonProof, bundles: computedBundles, + height, }, stateService: block.stateService, diff --git a/packages/sequencer/src/storage/inmemory/InMemoryMessageStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryMessageStorage.ts index 358f4dbdf..7b486a100 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryMessageStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryMessageStorage.ts @@ -5,13 +5,19 @@ import { MessageStorage } from "../repositories/MessageStorage"; @injectable() export class InMemoryMessageStorage implements MessageStorage { - private messages: { [key: string]: PendingTransaction[] } = {} + private messages: { [key: string]: PendingTransaction[] } = {}; - public async getMessages(from: string): Promise { - return this.messages[from] ?? []; + public async getMessages( + fromMessagesHash: string + ): Promise { + return this.messages[fromMessagesHash] ?? []; } - public async pushMessages(from: string, to: string, messages: PendingTransaction[]): Promise { - this.messages[from] = messages; + public async pushMessages( + fromMessagesHash: string, + toMessagesHash: string, + messages: PendingTransaction[] + ): Promise { + this.messages[fromMessagesHash] = messages; } -} \ No newline at end of file +} diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index 311c7ae10..b508da1dc 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -11,5 +11,5 @@ export interface ComputedBlockTransaction { export interface ComputedBlock { proof: JsonProof; bundles: string[]; - // TODO Height + height: number; } diff --git a/packages/sequencer/src/storage/model/Settlement.ts b/packages/sequencer/src/storage/model/Settlement.ts index 50cfc02fe..441d29e85 100644 --- a/packages/sequencer/src/storage/model/Settlement.ts +++ b/packages/sequencer/src/storage/model/Settlement.ts @@ -1,6 +1,6 @@ export interface Settlement { - transaction: string; - tranasactionHash: string; + // transaction: string; + transactionHash: string; promisedMessagesHash: string; - // TODO Link with settled batch + batches: number[]; } diff --git a/packages/sequencer/src/storage/repositories/MessageStorage.ts b/packages/sequencer/src/storage/repositories/MessageStorage.ts index 55bbc5b41..cf9b83008 100644 --- a/packages/sequencer/src/storage/repositories/MessageStorage.ts +++ b/packages/sequencer/src/storage/repositories/MessageStorage.ts @@ -5,9 +5,9 @@ import { PendingTransaction } from "../../mempool/PendingTransaction"; */ export interface MessageStorage { pushMessages: ( - from: string, - to: string, + fromMessagesHash: string, + toMessagesHash: string, messages: PendingTransaction[] ) => Promise; - getMessages: (from: string) => Promise; + getMessages: (fromMessagesHash: string) => Promise; } diff --git a/packages/sequencer/test/settlement/Settlement.test.ts b/packages/sequencer/test/settlement/Settlement.test.ts index 7fbf05cf1..fd8ceef2e 100644 --- a/packages/sequencer/test/settlement/Settlement.test.ts +++ b/packages/sequencer/test/settlement/Settlement.test.ts @@ -68,6 +68,7 @@ import { Actions } from "o1js/dist/node/lib/account_update"; import { expect } from "@jest/globals"; import { Withdrawals } from "../integration/mocks/Withdrawals"; import { WithdrawalQueue } from "../../src/settlement/messages/WithdrawalQueue"; +import { BlockProofSerializer } from "../../src/protocol/production/helpers/BlockProofSerializer"; log.setLevel("DEBUG"); @@ -81,6 +82,8 @@ describe("settlement contracts", () => { let settlementModule: SettlementModule; let blockQueue: UnprovenBlockQueue; + let blockSerializer: BlockProofSerializer; + function setupAppChain() { const runtime = Runtime.from({ modules: { @@ -226,8 +229,9 @@ describe("settlement contracts", () => { console.log( `block ${block?.height.toString()} ${block?.fromMessagesHash.toString()} -> ${block?.toMessagesHash.toString()}` ); + const proof = blockSerializer.getBlockProofSerializer().fromJSONProof(batch!.proof) console.log( - `block ${batch?.proof.publicInput.incomingMessagesHash} -> ${batch?.proof.publicOutput.incomingMessagesHash}` + `block ${proof.publicInput.incomingMessagesHash} -> ${proof.publicOutput.incomingMessagesHash}` ); return result; @@ -254,6 +258,8 @@ describe("settlement contracts", () => { const baseLayer = appChain.sequencer.resolve("BaseLayer") as MinaBaseLayer; + blockSerializer = appChain.sequencer.dependencyContainer.resolve(BlockProofSerializer); + const localChain = baseLayer.network as ReturnType< typeof Mina.LocalBlockchain >; From dcb02930cd4c2aba9e85ae69cff99ea6379761d5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 5 Feb 2024 12:08:23 +0100 Subject: [PATCH 39/54] Fixed correct relaying of errors in sub-tasks --- .../production/BlockTaskFlowService.ts | 5 ++++ .../production/flow/ReductionTaskFlow.ts | 30 +++++++++++++++++-- .../production/trigger/BlockTrigger.ts | 8 +++-- packages/sequencer/src/worker/flow/Flow.ts | 8 +++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts b/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts index 381e63d87..29a5eec69 100644 --- a/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts +++ b/packages/sequencer/src/protocol/production/BlockTaskFlowService.ts @@ -204,6 +204,7 @@ export class BlockTaskFlowService { log.debug(`Block generation finished, with proof ${result.proof}`); // TODO Remove result logging flow.resolve(result); }); + blockMergingFlow.deferErrorsTo(flow); return await flow.withFlow(async () => { await flow.forEach(blockTraces, async (blockTrace, blockNumber) => { @@ -236,6 +237,7 @@ export class BlockTaskFlowService { flow.state.blockPairings[blockNumber].blockProof = blockProof; await this.pushBlockPairing(flow, blockMergingFlow, blockNumber); }); + transactionMergingFlow.deferErrorsTo(flow); // Execute if the block is empty // eslint-disable-next-line unicorn/no-array-method-this-argument @@ -273,6 +275,8 @@ export class BlockTaskFlowService { transactionIndex ); }); + stReductionFlow.deferErrorsTo(flow); + await flow.forEach(trace.stateTransitionProver, async (stp) => { await stReductionFlow.pushInput(stp); }); @@ -336,6 +340,7 @@ export class BlockTaskFlowService { flow.state.blockPairings[blockNumber].stProof = result; await this.pushBlockPairing(flow, blockMergingFlow, blockNumber); }); + blockSTFlow.deferErrorsTo(flow); await flow.forEach(blockTrace.stateTransitionProver, async (stp) => { await blockSTFlow.pushInput(stp); diff --git a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts index c1be18aae..16cb53012 100644 --- a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts @@ -22,6 +22,8 @@ export class ReductionTaskFlow { private started = false; + private parentFlow?: Flow; + public constructor( private readonly options: { name: string; @@ -120,8 +122,21 @@ export class ReductionTaskFlow { throw new Error("Flow already started, use pushInput() to add inputs"); } this.started = true; - const result = await this.flow.withFlow(async () => {}); - await callback(result); + try { + const result = await this.flow.withFlow(async () => {}); + + await callback(result); + } catch (e: unknown) { + if (e instanceof Error) { + if (this.parentFlow !== undefined) { + this.parentFlow.reject(e); + } else { + this.flow.reject(e); + } + } else { + throw new Error(`Non-Error caught: ${e}`); + } + } } /** @@ -134,6 +149,17 @@ export class ReductionTaskFlow { void this.initCompletionCallback(callback); } + /** + * To be used in conjunction with onCompletion + * It allows errors from this flow to be "defered" to another parent + * flow which might be properly awaited and therefore will throw the + * error up to the user + * @param flow + */ + public deferErrorsTo(flow: Flow) { + this.parentFlow = flow; + } + /** * Execute the flow using the returned Promise that resolved when * the flow is finished diff --git a/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts index a7136a58e..a4d00c6e7 100644 --- a/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts @@ -23,7 +23,7 @@ export class BlockTriggerBase protected readonly blockProducerModule: BlockProducerModule, protected readonly unprovenProducerModule: UnprovenProducerModule, protected readonly unprovenBlockQueue: UnprovenBlockQueue, - protected readonly settlementModule: SettlementModule + protected readonly settlementModule?: SettlementModule ) { super(); } @@ -43,13 +43,15 @@ export class BlockTriggerBase await this.unprovenProducerModule.tryProduceUnprovenBlock(); if (unprovenBlock && enqueueInSettlementQueue) { - await this.unprovenBlockQueue.pushBlock(unprovenBlock); + await this.unprovenBlockQueue.pushBlock(unprovenBlock.block); + await this.unprovenBlockQueue.pushMetadata(unprovenBlock.metadata); } - return unprovenBlock; + return unprovenBlock?.block; } protected async settle(batch: ComputedBlock) { + // TODO After Persistance PR because we need batch.blocks for that } diff --git a/packages/sequencer/src/worker/flow/Flow.ts b/packages/sequencer/src/worker/flow/Flow.ts index 2fa51260a..ae112a187 100644 --- a/packages/sequencer/src/worker/flow/Flow.ts +++ b/packages/sequencer/src/worker/flow/Flow.ts @@ -140,14 +140,18 @@ export class Flow implements Closeable { this.resolveFunction(result); } + public reject(error: Error) { + this.erroredOut = true; + this.errorFunction?.(error); + } + private async resolveResponse(response: TaskPayload) { if (response.taskId !== undefined) { const resolveFunction = this.resultsPending[response.taskId]; if (!this.erroredOut) { if (response.status === "error") { - this.erroredOut = true; - this.errorFunction?.( + this.reject( new Error( `Error in worker: ${response.payload}, task: ${response.flowId}:${response.taskId}` ) From aca1646a8ac1cce464aa460a4893f3166dea7b0a Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 7 Feb 2024 00:19:30 +0100 Subject: [PATCH 40/54] Created module to hold both a redis and prisma connection --- packages/common/src/types.ts | 9 ++- .../src/PrismaDatabaseConnection.ts | 12 ++-- .../persistance/src/PrismaRedisDatabase.ts | 70 +++++++++++++++++++ packages/persistance/src/RedisConnection.ts | 18 +++-- .../src/services/prisma/PrismaBatchStore.ts | 10 +-- .../src/services/prisma/PrismaBlockStorage.ts | 24 ++++--- .../services/prisma/PrismaMerkleTreeStore.ts | 6 +- .../services/prisma/PrismaMessageStorage.ts | 12 ++-- .../prisma/PrismaSettlementStorage.ts | 4 +- .../src/services/prisma/PrismaStateService.ts | 10 +-- .../services/redis/RedisMerkleTreeStore.ts | 4 +- 11 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 packages/persistance/src/PrismaRedisDatabase.ts diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 17ff21af2..979d7b124 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -32,9 +32,16 @@ export type UnionToIntersection = ( export type MergeObjects> = UnionToIntersection; +export type OmitKeys = { + [Key in keyof Record as Key extends Keys ? never : Key]: Record[Key]; +}; + // Because Publickey.empty() is not usable in combination with real // cryptographic operations because it's group evaluation isn't defined in Fp, // we use some other arbitrary point which we treat as "empty" in our circuits // other arbitrary point export const EMPTY_PUBLICKEY_X = Field(4600); -export const EMPTY_PUBLICKEY = PublicKey.fromObject({ x: EMPTY_PUBLICKEY_X, isOdd: Bool(true) }); +export const EMPTY_PUBLICKEY = PublicKey.fromObject({ + x: EMPTY_PUBLICKEY_X, + isOdd: Bool(true), +}); diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index fd075368d..4f8dd64ca 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -4,7 +4,7 @@ import { SequencerModule, StorageDependencyMinimumDependencies, } from "@proto-kit/sequencer"; -import { DependencyFactory, noop } from "@proto-kit/common"; +import { DependencyFactory, OmitKeys } from "@proto-kit/common"; import { PrismaStateService } from "./services/prisma/PrismaStateService"; import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; @@ -25,21 +25,25 @@ export interface PrismaDatabaseConfig { }; } +export interface PrismaConnection { + get prismaClient(): PrismaClient; +} + @sequencerModule() export class PrismaDatabaseConnection extends SequencerModule - implements DependencyFactory + implements DependencyFactory, PrismaConnection { private initializedClient: PrismaClient | undefined = undefined; - public get client(): PrismaClient { + public get prismaClient(): PrismaClient { if (this.initializedClient === undefined) { throw new Error("Client not initialized yet, wait for after the startup"); } return this.initializedClient; } - public dependencies(): Omit< + public dependencies(): OmitKeys< StorageDependencyMinimumDependencies, "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" > { diff --git a/packages/persistance/src/PrismaRedisDatabase.ts b/packages/persistance/src/PrismaRedisDatabase.ts new file mode 100644 index 000000000..d710f4133 --- /dev/null +++ b/packages/persistance/src/PrismaRedisDatabase.ts @@ -0,0 +1,70 @@ +import { + sequencerModule, + SequencerModule, + StorageDependencyMinimumDependencies, +} from "@proto-kit/sequencer"; +import { ChildContainerProvider, DependencyFactory } from "@proto-kit/common"; + +import { + PrismaConnection, + PrismaDatabaseConfig, + PrismaDatabaseConnection, +} from "./PrismaDatabaseConnection"; +import { + RedisConnection, + RedisConnectionConfig, + RedisConnectionModule, +} from "./RedisConnection"; +import { PrismaClient, Prisma } from "@prisma/client"; +import { DefaultArgs } from "@prisma/client/runtime/library"; +import { RedisClientType } from "redis"; + +export interface PrismaRedisCombinedConfig { + prisma: PrismaDatabaseConfig; + redis: RedisConnectionConfig; +} + +@sequencerModule() +export class PrismaRedisDatabase + extends SequencerModule + implements DependencyFactory, PrismaConnection, RedisConnection +{ + public prisma: PrismaDatabaseConnection; + + public redis: RedisConnectionModule; + + public constructor() { + super(); + this.prisma = new PrismaDatabaseConnection(); + this.redis = new RedisConnectionModule(); + } + + public get prismaClient(): PrismaClient { + return this.prisma.prismaClient; + } + + public get redisClient(): RedisClientType { + return this.redis.redisClient; + } + + public create(childContainerProvider: ChildContainerProvider) { + super.create(childContainerProvider); + this.prisma.create(childContainerProvider); + this.redis.create(childContainerProvider); + } + + public dependencies(): StorageDependencyMinimumDependencies { + return { + ...this.prisma.dependencies(), + ...this.redis.dependencies(), + }; + } + + public async start(): Promise { + this.prisma.config = this.config.prisma; + await this.prisma.start(); + + this.redis.config = this.config.redis; + await this.redis.start(); + } +} diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index 361de5070..61af33c68 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -12,19 +12,23 @@ export interface RedisConnectionConfig { password: string; } -export class RedisConnection +export interface RedisConnection { + get redisClient(): RedisClientType; +} + +export class RedisConnectionModule extends SequencerModule - implements DependencyFactory + implements DependencyFactory, RedisConnection { - private redisClient?: RedisClientType; + private client?: RedisClientType; - public get client(): RedisClientType { - if (this.redisClient === undefined) { + public get redisClient(): RedisClientType { + if (this.client === undefined) { throw new Error( "Redis client not initialized yet, wait for .start() to be called" ); } - return this.redisClient; + return this.client; } public dependencies(): Pick< @@ -45,7 +49,7 @@ export class RedisConnection } public async init() { - this.redisClient = createClient(this.config); + this.client = createClient(this.config); try { await this.redisClient.connect(); } catch (e: unknown) { diff --git a/packages/persistance/src/services/prisma/PrismaBatchStore.ts b/packages/persistance/src/services/prisma/PrismaBatchStore.ts index f1797d16c..faceda200 100644 --- a/packages/persistance/src/services/prisma/PrismaBatchStore.ts +++ b/packages/persistance/src/services/prisma/PrismaBatchStore.ts @@ -6,19 +6,19 @@ import { import { Prisma } from "@prisma/client"; import { inject, injectable } from "tsyringe"; -import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; import { BatchMapper } from "./mappers/BatchMapper"; @injectable() export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { public constructor( - @inject("Database") private readonly connection: PrismaDatabaseConnection, + @inject("Database") private readonly connection: PrismaConnection, private readonly batchMapper: BatchMapper ) {} public async getBlockAt(height: number): Promise { - const batch = await this.connection.client.batch.findFirst({ + const batch = await this.connection.prismaClient.batch.findFirst({ where: { height, }, @@ -40,7 +40,7 @@ export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { } public async getCurrentBlockHeight(): Promise { - const batch = await this.connection.client.batch.aggregate({ + const batch = await this.connection.prismaClient.batch.aggregate({ _max: { height: true, }, @@ -52,7 +52,7 @@ export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { const height = await this.getCurrentBlockHeight(); const [entity] = this.batchMapper.mapOut(block); - await this.connection.client.batch.create({ + await this.connection.prismaClient.batch.create({ data: { proof: entity.proof as Prisma.InputJsonValue, height, diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 97d21e620..98c7d74eb 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -16,7 +16,7 @@ import { } from "@prisma/client"; import { inject, injectable } from "tsyringe"; -import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; import { TransactionExecutionResultMapper, @@ -33,7 +33,7 @@ export class PrismaBlockStorage HistoricalUnprovenBlockStorage { public constructor( - @inject("Database") private readonly connection: PrismaDatabaseConnection, + @inject("Database") private readonly connection: PrismaConnection, private readonly transactionResultMapper: TransactionExecutionResultMapper, private readonly transactionMapper: TransactionMapper, private readonly blockMetadataMapper: UnprovenBlockMetadataMapper, @@ -43,7 +43,7 @@ export class PrismaBlockStorage private async getBlockByQuery( where: { height: number } | { hash: string } ): Promise { - const result = await this.connection.client.block.findFirst({ + const result = await this.connection.prismaClient.block.findFirst({ where, include: { transactions: { @@ -103,15 +103,17 @@ export class PrismaBlockStorage const encodedBlock = this.blockMapper.mapOut(block); - await this.connection.client.$transaction([ - this.connection.client.transaction.createMany({ + const { prismaClient } = this.connection; + + await prismaClient.$transaction([ + prismaClient.transaction.createMany({ data: block.transactions.map((txr) => this.transactionMapper.mapOut(txr.tx) ), skipDuplicates: true }), - this.connection.client.block.create({ + prismaClient.block.create({ data: { ...encodedBlock, beforeNetworkState: @@ -145,7 +147,7 @@ export class PrismaBlockStorage public async pushMetadata(metadata: UnprovenBlockMetadata): Promise { const encoded = this.blockMetadataMapper.mapOut(metadata); - await this.connection.client.unprovenBlockMetadata.create({ + await this.connection.prismaClient.unprovenBlockMetadata.create({ data: { afterNetworkState: encoded.afterNetworkState as Prisma.InputJsonValue, blockHashWitness: encoded.blockHashWitness as Prisma.InputJsonValue, @@ -161,7 +163,7 @@ export class PrismaBlockStorage // TODO Phase out and replace with getLatestBlock().network.height public async getCurrentBlockHeight(): Promise { - const result = await this.connection.client.block.aggregate({ + const result = await this.connection.prismaClient.block.aggregate({ _max: { height: true, }, @@ -173,7 +175,7 @@ export class PrismaBlockStorage public async getLatestBlock(): Promise< UnprovenBlockWithMetadata | undefined > { - const latestBlock = await this.connection.client.$queryRaw< + const latestBlock = await this.connection.prismaClient.$queryRaw< { hash: string }[] >`SELECT b1."hash" FROM "Block" b1 LEFT JOIN "Block" child ON child."parentHash" = b1."hash" @@ -189,7 +191,7 @@ export class PrismaBlockStorage } public async getNewBlocks(): Promise { - const blocks = await this.connection.client.block.findMany({ + const blocks = await this.connection.prismaClient.block.findMany({ where: { batch: null, }, @@ -213,7 +215,7 @@ export class PrismaBlockStorage .filter(filterNonNull) .filter(distinctByString); const metadata = - await this.connection.client.unprovenBlockMetadata.findMany({ + await this.connection.prismaClient.unprovenBlockMetadata.findMany({ where: { blockHash: { in: blockHashes, diff --git a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts index f03c15faf..9f69bc7fa 100644 --- a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts +++ b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts @@ -4,7 +4,7 @@ import { MerkleTreeNodeQuery, } from "@proto-kit/sequencer"; -import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { PrismaConnection } from "../../PrismaDatabaseConnection"; import { noop } from "@proto-kit/common"; import { Prisma } from "@prisma/client"; @@ -14,7 +14,7 @@ import { Prisma } from "@prisma/client"; export class PrismaMerkleTreeStore implements AsyncMerkleTreeStore { private cache: MerkleTreeNode[] = []; - public constructor(private readonly connection: PrismaDatabaseConnection) {} + public constructor(private readonly connection: PrismaConnection) {} public async openTransaction(): Promise { noop(); @@ -34,7 +34,7 @@ export class PrismaMerkleTreeStore implements AsyncMerkleTreeStore { console.log(`Took ${Date.now() - start} ms`) const start2 = Date.now(); - const rows = await this.connection.client.$executeRaw( + const rows = await this.connection.prismaClient.$executeRaw( Prisma.sql`INSERT INTO "TreeElement" (key, level, value) VALUES ${Prisma.join( array.map((entry) => Prisma.sql`(${Prisma.join(entry)})`) )} ON CONFLICT ON CONSTRAINT "TreeElement_pkey" DO UPDATE SET value = EXCLUDED.value;` diff --git a/packages/persistance/src/services/prisma/PrismaMessageStorage.ts b/packages/persistance/src/services/prisma/PrismaMessageStorage.ts index f0f65b858..1abbf3c34 100644 --- a/packages/persistance/src/services/prisma/PrismaMessageStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaMessageStorage.ts @@ -12,9 +12,9 @@ export class PrismaMessageStorage implements MessageStorage { public async getMessages( fromMessageHash: string ): Promise { - const { client } = this.connection; + const { prismaClient } = this.connection; - const batch = await client.incomingMessageBatch.findFirst({ + const batch = await prismaClient.incomingMessageBatch.findFirst({ where: { fromMessageHash, }, @@ -43,14 +43,14 @@ export class PrismaMessageStorage implements MessageStorage { this.transactionMapper.mapOut(message) ); - const { client } = this.connection; - await client.$transaction([ - client.transaction.createMany({ + const { prismaClient } = this.connection; + await prismaClient.$transaction([ + prismaClient.transaction.createMany({ data: transactions, skipDuplicates: true, }), - client.incomingMessageBatch.create({ + prismaClient.incomingMessageBatch.create({ data: { fromMessageHash, toMessageHash, diff --git a/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts b/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts index 5b473eba8..8f51a4ab4 100644 --- a/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts @@ -11,11 +11,11 @@ export class PrismaSettlementStorage implements SettlementStorage { ) {} public async pushSettlement(settlement: Settlement): Promise { - const { client } = this.connection; + const { prismaClient } = this.connection; const dbSettlement = this.settlementMapper.mapOut(settlement); - await client.settlement.create({ + await prismaClient.settlement.create({ data: { ...dbSettlement[0], batches: { diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index bfdeae018..4b97f2d88 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -19,7 +19,7 @@ export class PrismaStateService implements AsyncStateService { ) {} public async commit(): Promise { - const { client } = this.connection; + const { prismaClient } = this.connection; const data = this.cache .filter((entry) => entry[1] !== undefined) @@ -29,8 +29,8 @@ export class PrismaStateService implements AsyncStateService { mask: this.mask, })); - await client.$transaction([ - client.state.deleteMany({ + await prismaClient.$transaction([ + prismaClient.state.deleteMany({ where: { path: { in: this.cache.map((x) => new Prisma.Decimal(x[0].toString())), @@ -38,7 +38,7 @@ export class PrismaStateService implements AsyncStateService { mask: this.mask, }, }), - client.state.createMany({ + prismaClient.state.createMany({ data, }), ]); @@ -47,7 +47,7 @@ export class PrismaStateService implements AsyncStateService { } public async getAsync(key: Field): Promise { - const record = await this.connection.client.state.findFirst({ + const record = await this.connection.prismaClient.state.findFirst({ where: { AND: [ { diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index ce8d259e9..9350b1b75 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -38,7 +38,7 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { try { // console.log(array.slice(0, 5).flat(1)); - await this.connection.client!.mSet(array.flat(1)); + await this.connection.redisClient!.mSet(array.flat(1)); } catch (e) { if (e instanceof Error) { console.log(e.name); @@ -62,7 +62,7 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { ): Promise<(bigint | undefined)[]> { const keys = nodes.map((node) => this.getKey(node)); - const result = await this.connection.client!.mGet(keys); + const result = await this.connection.redisClient!.mGet(keys); return result.map((x) => (x !== null ? BigInt(x) : undefined)); } From 3d4d140ff0a906edc366a9fe60bddfcba9e35fe0 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 7 Feb 2024 00:20:37 +0100 Subject: [PATCH 41/54] Fixed DependencyFactory module name typing --- packages/common/src/dependencyFactory/DependencyFactory.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/common/src/dependencyFactory/DependencyFactory.ts b/packages/common/src/dependencyFactory/DependencyFactory.ts index 16d1051c9..416a0b0aa 100644 --- a/packages/common/src/dependencyFactory/DependencyFactory.ts +++ b/packages/common/src/dependencyFactory/DependencyFactory.ts @@ -37,8 +37,10 @@ export type TypeFromDependencyDeclaration< Declaration extends DependencyDeclaration > = Declaration extends DependencyDeclaration ? Dependency : never; +export type CapitalizeAny = Key extends string ? Capitalize : Key + export type MapDependencyRecordToTypes = { - [Key in keyof Record]: TypedClass>; + [Key in keyof Record as CapitalizeAny]: TypedClass>; }; export type InferDependencies = From 1c8091a0ba827668cecfa3a8edbd2fd460f74c35 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 7 Feb 2024 14:13:57 +0100 Subject: [PATCH 42/54] Changed AsyncStateService to properly batch reads and writes --- .../unproven/TransactionExecutionService.ts | 24 +++-- .../src/state/async/AsyncStateService.ts | 12 ++- .../src/state/state/CachedStateService.ts | 76 ++++++++++------ .../test/integration/BlockProduction.test.ts | 88 ++++++------------- 4 files changed, 98 insertions(+), 102 deletions(-) diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 22ee53c70..82bba2062 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -22,11 +22,7 @@ import { MinaActionsHashList, } from "@proto-kit/protocol"; import { Bool, Field, Poseidon } from "o1js"; -import { - AreProofsEnabled, - log, - RollupMerkleTree, -} from "@proto-kit/common"; +import { AreProofsEnabled, log, RollupMerkleTree } from "@proto-kit/common"; import { MethodParameterEncoder, Runtime, @@ -400,15 +396,15 @@ export class TransactionExecutionService { stateService: CachedStateService, stateTransitions: StateTransition[] ): Promise { - await Promise.all( - // Use updated stateTransitions since only they will have the - // right values - stateTransitions - .filter((st) => st.to.isSome.toBoolean()) - .map(async (st) => { - await stateService.setAsync(st.path, st.to.toFields()); - }) - ); + const writes = stateTransitions + .filter((st) => st.to.isSome.toBoolean()) + .map((st) => + // Use updated stateTransitions since only they will have the + // right values + ({ key: st.path, value: st.to.toFields() }) + ); + // Maybe replace with stateService.set() because its cached anyways? + stateService.writeStates(writes); } // eslint-disable-next-line no-warning-comments diff --git a/packages/sequencer/src/state/async/AsyncStateService.ts b/packages/sequencer/src/state/async/AsyncStateService.ts index 6ee9ba445..d239a02e7 100644 --- a/packages/sequencer/src/state/async/AsyncStateService.ts +++ b/packages/sequencer/src/state/async/AsyncStateService.ts @@ -1,5 +1,10 @@ import { Field } from "o1js"; +export interface StateEntry { + key: Field; + value: Field[] | undefined; +} + /** * This Interface should be implemented for services that store the state * in an external storage (like a DB). This can be used in conjunction with @@ -10,8 +15,9 @@ export interface AsyncStateService { commit: () => Promise; - // TODO Make sync, we have openTx and commit() for the actual writing operations - setAsync: (key: Field, value: Field[] | undefined) => Promise; + writeStates: (entries: StateEntry[]) => void; + + getAsync: (keys: Field[]) => Promise; - getAsync: (key: Field) => Promise; + getSingleAsync: (key: Field) => Promise; } diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index ddfdd6f9e..0447a3b50 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -2,7 +2,7 @@ import { Field } from "o1js"; import { log, noop } from "@proto-kit/common"; import { InMemoryStateService } from "@proto-kit/module"; -import { AsyncStateService } from "../async/AsyncStateService"; +import { AsyncStateService, StateEntry } from "../async/AsyncStateService"; const errors = { parentIsUndefined: () => new Error("Parent StateService is undefined"), @@ -28,6 +28,12 @@ export class CachedStateService } } + public writeStates(entries: StateEntry[]): void { + entries.forEach(({ key, value }) => { + this.set(key, value); + }); + } + public async commit(): Promise { noop(); } @@ -37,34 +43,51 @@ export class CachedStateService } public async preloadKey(key: Field) { - // Only preload it if it hasn't been preloaded previously - if (this.parent !== undefined && this.get(key) === undefined) { - const value = await this.parent.getAsync(key); + await this.preloadKeys([key]); + } + + public async preloadKeys(keys: Field[]): Promise { + if (this.parent !== undefined) { + // Only preload it if it hasn't been preloaded previously + // TODO Not safe for deletes + const keysToBeLoaded = keys.filter((key) => this.get(key) === undefined); + const loaded = await this.parent.getAsync(keysToBeLoaded); + log.trace( - `Preloading ${key.toString()}: ${ - // eslint-disable-next-line max-len - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - value?.map((element) => element.toString()) ?? [] - }` + `Preloaded: ${loaded.map( + ({ key, value }) => `${key}: ${value?.map((x) => x.toString()) ?? []}` + )}` ); - this.set(key, value); + + loaded.forEach(({ key, value }) => { + this.set(key, value); + }); } } - public async preloadKeys(keys: Field[]): Promise { - await Promise.all( - keys.map(async (key) => { - await this.preloadKey(key); - }) - ); - } + public async getAsync(keys: Field[]): Promise { + const remoteKeys: Field[] = []; + + const local: StateEntry[] = []; + + keys.forEach((key) => { + const localValue = this.get(key); + // TODO Not safe for deletes + if (localValue !== undefined) { + local.push({ key, value: localValue }); + } else { + remoteKeys.push(key); + } + }); - public async getAsync(key: Field): Promise { - return this.get(key) ?? this.parent?.getAsync(key); + const remote = await this.parent?.getAsync(remoteKeys); + + return local.concat(remote ?? []); } - public async setAsync(key: Field, value: Field[] | undefined): Promise { - this.set(key, value); + public async getSingleAsync(key: Field): Promise { + const entries = await this.getAsync([key]); + return entries.at(0)?.value; } /** @@ -78,10 +101,13 @@ export class CachedStateService // Set all cached values on parent await parent.openTransaction(); - const promises = Object.entries(values).map(async ([key, value]) => { - await parent.setAsync(Field(key), value); - }); - await Promise.all(promises); + + const writes = Object.entries(values).map(([key, value]) => ({ + key: Field(key), + value, + })); + parent.writeStates(writes); + await parent.commit(); // Clear cache this.values = {}; diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index c0ee78493..787cae78f 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -38,19 +38,12 @@ import { Balance } from "./mocks/Balance"; import { NoopBaseLayer } from "../../src/protocol/baselayer/NoopBaseLayer"; import { UnprovenProducerModule } from "../../src/protocol/production/unproven/UnprovenProducerModule"; import { container } from "tsyringe"; +import { DefaultTestingSequencerModules, testingSequencerFromModules } from "../TestingSequencer"; +import { createTransaction } from "./utils"; describe("block production", () => { let runtime: Runtime<{ Balance: typeof Balance }>; - let sequencer: Sequencer<{ - Database: typeof InMemoryDatabase; - Mempool: typeof PrivateMempool; - LocalTaskWorkerModule: typeof LocalTaskWorkerModule; - BaseLayer: typeof NoopBaseLayer; - BlockProducerModule: typeof BlockProducerModule; - UnprovenProducerModule: typeof UnprovenProducerModule; - BlockTrigger: typeof ManualBlockTrigger; - TaskQueue: typeof LocalTaskQueue; - }>; + let sequencer: Sequencer; let protocol: InstanceType> @@ -74,27 +67,16 @@ describe("block production", () => { }, }); - const sequencerClass = Sequencer.from({ - modules: { - Database: InMemoryDatabase, - Mempool: PrivateMempool, - LocalTaskWorkerModule, - BaseLayer: NoopBaseLayer, - BlockProducerModule, - UnprovenProducerModule, - BlockTrigger: ManualBlockTrigger, - TaskQueue: LocalTaskQueue, - }, - }); + const sequencerClass = testingSequencerFromModules({}); const protocolClass = VanillaProtocol.from( { } ); const app = AppChain.from({ - runtime: runtimeClass, - sequencer: sequencerClass, - protocol: protocolClass, + Runtime: runtimeClass, + Sequencer: sequencerClass, + Protocol: protocolClass, modules: {}, }); @@ -132,29 +114,6 @@ describe("block production", () => { mempool = sequencer.resolve("Mempool"); }); - function createTransaction(spec: { - privateKey: PrivateKey; - method: [string, string]; - args: ArgumentTypes; - nonce: number; - }) { - const methodId = runtime.dependencyContainer - .resolve("MethodIdResolver") - .getMethodId(spec.method[0], spec.method[1]); - - const decoder = MethodParameterEncoder.fromMethod(runtime.resolve(spec.method[0] as "Balance"), spec.method[1]); - const { argsFields, argsJSON } = decoder.encode(spec.args) - - return new UnsignedTransaction({ - methodId: Field(methodId), - argsFields, - argsJSON, - sender: spec.privateKey.toPublicKey(), - nonce: UInt64.from(spec.nonce), - isMessage: false - }).sign(spec.privateKey); - } - // eslint-disable-next-line max-statements it("should produce a dummy block proof", async () => { expect.assertions(22); @@ -164,6 +123,7 @@ describe("block production", () => { mempool.add( createTransaction({ + runtime, method: ["Balance", "setBalanceIf"], privateKey, args: [publicKey, UInt64.from(100), Bool(true)], @@ -208,8 +168,8 @@ describe("block production", () => { balanceModule.balances.keyType, publicKey ); - const newState = await stateService.getAsync(balancesPath); - const newUnprovenState = await unprovenStateService.getAsync(balancesPath); + const newState = await stateService.getSingleAsync(balancesPath); + const newUnprovenState = await unprovenStateService.getSingleAsync(balancesPath); expect(newState).toBeDefined(); expect(newUnprovenState).toBeDefined(); @@ -225,7 +185,7 @@ describe("block production", () => { accountModule.accountState.keyType, publicKey ); - const newAccountState = await stateService.getAsync(accountStatePath); + const newAccountState = await stateService.getSingleAsync(accountStatePath); expect(newAccountState).toBeDefined(); expect(AccountState.fromFields(newAccountState!).nonce.toBigInt()).toBe(1n); @@ -233,6 +193,7 @@ describe("block production", () => { // Second tx mempool.add( createTransaction({ + runtime, method: ["Balance", "addBalanceToSelf"], privateKey, args: [UInt64.from(100), UInt64.from(1)], @@ -255,13 +216,12 @@ describe("block production", () => { expect(batch!.bundles).toHaveLength(1); expect(batch!.proof.proof).toBe("mock-proof"); - const state2 = await stateService.getAsync(balancesPath); + const state2 = await stateService.getSingleAsync(balancesPath); expect(state2).toBeDefined(); expect(UInt64.fromFields(state2!)).toStrictEqual(UInt64.from(200)); }, 60_000); - // TODO Fix the error that we get when execution this after the first test it("should reject tx and not apply the state", async () => { expect.assertions(5); @@ -271,6 +231,7 @@ describe("block production", () => { mempool.add( createTransaction({ + runtime, method: ["Balance", "setBalanceIf"], privateKey, args: [PrivateKey.random().toPublicKey(), UInt64.from(100), Bool(false)], @@ -298,8 +259,8 @@ describe("block production", () => { balanceModule.balances.keyType, PublicKey.empty() ); - const unprovenState = await unprovenStateService.getAsync(balancesPath); - const newState = await stateService.getAsync(balancesPath); + const unprovenState = await unprovenStateService.getSingleAsync(balancesPath); + const newState = await stateService.getSingleAsync(balancesPath); // Assert that state is not set expect(unprovenState).toBeUndefined(); @@ -320,6 +281,7 @@ describe("block production", () => { range(0, numberTxs).forEach((index) => { mempool.add( createTransaction({ + runtime, method: ["Balance", "addBalanceToSelf"], privateKey, args: [UInt64.from(increment), UInt64.from(0)], @@ -363,7 +325,7 @@ describe("block production", () => { balanceModule.balances.keyType, publicKey ); - const newState = await stateService.getAsync(balancesPath); + const newState = await stateService.getSingleAsync(balancesPath); expect(newState).toBeDefined(); expect(UInt64.fromFields(newState!)).toStrictEqual( @@ -379,6 +341,7 @@ describe("block production", () => { mempool.add( createTransaction({ + runtime, method: ["Balance", "setBalanceIf"], privateKey: pk1, args: [pk1.toPublicKey(), UInt64.from(100), Bool(false)], @@ -387,6 +350,7 @@ describe("block production", () => { ); mempool.add( createTransaction({ + runtime, method: ["Balance", "setBalanceIf"], privateKey: pk2, args: [pk2.toPublicKey(), UInt64.from(100), Bool(true)], @@ -414,7 +378,7 @@ describe("block production", () => { balanceModule.balances.keyType, pk1.toPublicKey() ); - const newState1 = await stateService.getAsync(balancesPath1); + const newState1 = await stateService.getSingleAsync(balancesPath1); expect(newState1).toBeUndefined(); @@ -423,7 +387,7 @@ describe("block production", () => { balanceModule.balances.keyType, pk2.toPublicKey() ); - const newState2 = await stateService.getAsync(balancesPath2); + const newState2 = await stateService.getSingleAsync(balancesPath2); expect(newState2).toBeDefined(); expect(UInt64.fromFields(newState2!)).toStrictEqual(UInt64.from(100)); @@ -452,6 +416,7 @@ describe("block production", () => { mempool.add( createTransaction({ + runtime, method: ["Balance", "setBalanceIf"], privateKey: pk1, args: [pk1.toPublicKey(), UInt64.from(100), Bool(true)], @@ -463,6 +428,7 @@ describe("block production", () => { mempool.add( createTransaction({ + runtime, method: ["Balance", "setBalanceIf"], privateKey: pk2, args: [pk2.toPublicKey(), UInt64.from(200), Bool(true)], @@ -502,6 +468,7 @@ describe("block production", () => { for (let k = 0; k < txsPerBlock; k++) { mempool.add( createTransaction({ + runtime, method: ["Balance", "addBalance"], privateKey: sender, args: [ @@ -541,6 +508,7 @@ describe("block production", () => { mempool.add( createTransaction({ + runtime, method: ["Balance", "lotOfSTs"], privateKey, args: [field], @@ -566,7 +534,7 @@ describe("block production", () => { "AsyncStateService" ); const supplyPath = Path.fromProperty("Balance", "totalSupply"); - const newState = await stateService.getAsync(supplyPath); + const newState = await stateService.getSingleAsync(supplyPath); expect(newState).toBeDefined(); expect(UInt64.fromFields(newState!)).toStrictEqual( @@ -582,7 +550,7 @@ describe("block production", () => { pk2 ); - const newBalance = await stateService.getAsync(balancesPath); + const newBalance = await stateService.getSingleAsync(balancesPath); expect(newBalance).toBeDefined(); expect(UInt64.fromFields(newBalance!)).toStrictEqual(UInt64.from(200)); From 29855cdce0fb777a384d403b8070d00bcf961541 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 7 Feb 2024 16:48:54 +0100 Subject: [PATCH 43/54] Fixed issue where block prod flow would hang up because of mismatching block tree values --- .../production/flow/ReductionTaskFlow.ts | 22 +++++++++++++++++++ .../unproven/TransactionExecutionService.ts | 2 +- packages/sequencer/src/worker/flow/Flow.ts | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts index 16cb53012..cae404725 100644 --- a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts @@ -3,6 +3,7 @@ import { log } from "@proto-kit/common"; import { Flow, FlowCreator } from "../../../worker/flow/Flow"; import { Task } from "../../../worker/flow/Task"; import { PairTuple } from "../../../helpers/utils"; +import { BlockProverPublicInput, BlockProverPublicOutput } from "@proto-kit/protocol"; interface ReductionState { numMergesCompleted: 0; @@ -73,6 +74,27 @@ export class ReductionTaskFlow { } } + // Print error if the flow is stuck if + // 1. no tasks are in progress still + // 2. and not every queue element has been resolved + // 3. and all inputs have been pushed to the task already + const queueSize = this.flow.state.queue.length; + if ( + this.flow.tasksInProgress === 0 && + res.length * 2 < queueSize && + this.flow.state.numMergesCompleted + res.length * 2 + queueSize === + this.options.inputLength + ) { + log.error( + `Flow ${this.flow.flowId} seems to have halted with ${this.flow.state.queue.length} elements left in the queue` + ); + const json0 = BlockProverPublicInput.toJSON((this.flow.state.queue[0] as any)["publicInput"] as any); + const json1 = BlockProverPublicOutput.toJSON((this.flow.state.queue[0] as any)["publicOutput"] as any); + const json2 = BlockProverPublicInput.toJSON((this.flow.state.queue[1] as any)["publicInput"] as any); + const json3 = BlockProverPublicOutput.toJSON((this.flow.state.queue[1] as any)["publicOutput"] as any); + console.log(); + } + return { availableReductions: res, touchedIndizes }; } diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 82bba2062..9805662b8 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -367,7 +367,7 @@ export class TransactionExecutionService { blockHashTree.setLeaf( block.height.toBigInt(), new BlockHashTreeEntry({ - blockHash: block.transactionsHash, + blockHash: Poseidon.hash([block.height, state.transactionsHash]), closed: Bool(true), }).hash() ); diff --git a/packages/sequencer/src/worker/flow/Flow.ts b/packages/sequencer/src/worker/flow/Flow.ts index ae112a187..90ddede26 100644 --- a/packages/sequencer/src/worker/flow/Flow.ts +++ b/packages/sequencer/src/worker/flow/Flow.ts @@ -109,7 +109,7 @@ export class Flow implements Closeable { public constructor( private readonly connectionHolder: ConnectionHolder, - private readonly flowId: string, + public readonly flowId: string, public state: State ) {} From 26274cf2b525a84775cfeb6f3efa00d384c7ac70 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 7 Feb 2024 17:00:25 +0100 Subject: [PATCH 44/54] Fixed a few smaller issues --- .../production/BlockProducerModule.ts | 4 +- .../production/trigger/ManualBlockTrigger.ts | 6 +- .../production/trigger/TimedBlockTrigger.ts | 6 +- .../unproven/UnprovenProducerModule.ts | 12 +- .../src/settlement/SettlementModule.ts | 5 +- packages/sequencer/test/TestingSequencer.ts | 46 +++++ .../integration/StorageIntegration.test.ts | 159 ++++++++++++++++++ packages/sequencer/test/integration/utils.ts | 47 ++++++ 8 files changed, 267 insertions(+), 18 deletions(-) create mode 100644 packages/sequencer/test/TestingSequencer.ts create mode 100644 packages/sequencer/test/integration/StorageIntegration.test.ts create mode 100644 packages/sequencer/test/integration/utils.ts diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index 1ac6765b1..f90aa1a05 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -182,9 +182,7 @@ export class BlockProducerModule extends SequencerModule { unprovenBlocks: UnprovenBlockWithPreviousMetadata[], height: number ): Promise { - const blockId = unprovenBlocks[0].block.block.height.toBigInt(); - - const block = await this.computeBlock(unprovenBlocks, Number(blockId)); + const block = await this.computeBlock(unprovenBlocks, height); const computedBundles = unprovenBlocks.map((bundle) => bundle.block.block.transactionsHash.toString() diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index 5b2552335..9b30c0b3a 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -22,14 +22,14 @@ export class ManualBlockTrigger unprovenProducerModule: UnprovenProducerModule, @inject("UnprovenBlockQueue") unprovenBlockQueue: UnprovenBlockQueue, - @inject("SettlementModule") - settlementModule: SettlementModule + // @inject("SettlementModule") + // settlementModule: SettlementModule ) { super( blockProducerModule, unprovenProducerModule, unprovenBlockQueue, - settlementModule + undefined ); } diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index 2492649bc..1192f29ef 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -33,8 +33,8 @@ export class TimedBlockTrigger unprovenProducerModule: UnprovenProducerModule, @inject("UnprovenBlockQueue") unprovenBlockQueue: UnprovenBlockQueue, - @inject("SettlementModule") - settlementModule: SettlementModule, + // @inject("SettlementModule") + // settlementModule: SettlementModule, @inject("Mempool") private readonly mempool: Mempool ) { @@ -42,7 +42,7 @@ export class TimedBlockTrigger blockProducerModule, unprovenProducerModule, unprovenBlockQueue, - settlementModule + undefined ); } diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index 20396d33f..b671645ad 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -68,7 +68,9 @@ export class UnprovenProducerModule return this.config.allowEmptyBlock ?? true; } - public async tryProduceUnprovenBlock(): Promise { + public async tryProduceUnprovenBlock(): Promise< + UnprovenBlockWithMetadata | undefined + > { if (!this.productionInProgress) { try { const block = await this.produceUnprovenBlock(); @@ -82,8 +84,6 @@ export class UnprovenProducerModule return undefined; } - await this.unprovenBlockQueue.pushBlock(block); - log.info(`Produced unproven block (${block.transactions.length} txs)`); this.events.emit("unprovenBlockProduced", block); @@ -97,9 +97,11 @@ export class UnprovenProducerModule this.blockTreeStore, true ); - await this.unprovenBlockQueue.pushMetadata(metadata); - return block; + return { + block, + metadata, + }; } catch (error: unknown) { if (error instanceof Error) { throw error; diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index e89173467..2d6a6198d 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -197,10 +197,7 @@ export class SettlementModule Path.fromKey(basePath, Field, Field(x.index)) ); // Preload keys - // TODO Use preloadKeys() after persistance PR - await Promise.all( - keys.map((key) => cachedStore.preloadKey(key.toBigInt())) - ); + await cachedStore.preloadKeys(keys.map(key => key.toBigInt())); const transactionParamaters = batch.map((message, index) => { const witness = tree.getWitness(keys[index].toBigInt()); diff --git a/packages/sequencer/test/TestingSequencer.ts b/packages/sequencer/test/TestingSequencer.ts new file mode 100644 index 000000000..0a8e17763 --- /dev/null +++ b/packages/sequencer/test/TestingSequencer.ts @@ -0,0 +1,46 @@ +import { + BlockProducerModule, + InMemoryDatabase, + LocalTaskQueue, + LocalTaskWorkerModule, + ManualBlockTrigger, + NoopBaseLayer, + PrivateMempool, + Sequencer, + SequencerModulesRecord, + UnprovenProducerModule, +} from "../src"; +import { TypedClass } from "@proto-kit/common"; + +export interface DefaultTestingSequencerModules extends SequencerModulesRecord { + Database: typeof InMemoryDatabase; + Mempool: typeof PrivateMempool; + LocalTaskWorkerModule: typeof LocalTaskWorkerModule; + BaseLayer: typeof NoopBaseLayer; + BlockProducerModule: typeof BlockProducerModule; + UnprovenProducerModule: typeof UnprovenProducerModule; + BlockTrigger: typeof ManualBlockTrigger; + TaskQueue: typeof LocalTaskQueue; +} + +export function testingSequencerFromModules( + modules: AdditionalModules +): TypedClass> { + const defaultModules: DefaultTestingSequencerModules = { + Database: InMemoryDatabase, + Mempool: PrivateMempool, + LocalTaskWorkerModule, + BaseLayer: NoopBaseLayer, + BlockProducerModule, + UnprovenProducerModule, + BlockTrigger: ManualBlockTrigger, + TaskQueue: LocalTaskQueue, + }; + + return Sequencer.from({ + modules: { + ...defaultModules, + ...modules, + }, + }); +} diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts new file mode 100644 index 000000000..7692d0b88 --- /dev/null +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -0,0 +1,159 @@ +import { InMemoryDatabase } from "@proto-kit/sequencer"; +import { beforeEach } from "@jest/globals"; +import { + DefaultTestingSequencerModules, + testingSequencerFromModules, +} from "../TestingSequencer"; +import { Runtime } from "@proto-kit/module"; +import { Balance } from "./mocks/Balance"; +import { + ProtocolCustomModulesRecord, + VanillaProtocol, +} from "@proto-kit/protocol"; +import { AppChain } from "@proto-kit/sdk"; +import { + AsyncMerkleTreeStore, + AsyncStateService, + Sequencer, + StateEntry, + StateRecord, +} from "../../src"; +import { collectStateDiff, createTransaction, expectDefined } from "./utils"; +import { Bool, Field, PrivateKey, UInt64 } from "o1js"; + +function checkStateDiffEquality(stateDiff: StateRecord, state: StateEntry[]) { + return Object.entries(stateDiff) + .map(([key, value]) => { + const entry = state.find((s) => s.key.toString() === key); + if (entry !== undefined) { + if (entry.value === undefined) { + return value === undefined; + } else if (value !== undefined) { + return entry.value.find((v, i) => v !== value[i]) === undefined; + } + } + return false; + }) + .reduce((acc, v) => acc && v, true); +} + +describe.each([["InMemory", InMemoryDatabase]])( + "Storage Adapter Test %s", + () => { + let appChain: AppChain< + { Balance: typeof Balance }, + ProtocolCustomModulesRecord, + DefaultTestingSequencerModules, + {} + >; + let sequencer: Sequencer; + let runtime: Runtime<{ Balance: typeof Balance }>; + + let unprovenState: AsyncStateService; + let provenState: AsyncStateService; + + let unprovenTreeStore: AsyncMerkleTreeStore; + let provenTreeStore: AsyncMerkleTreeStore; + + const sk = PrivateKey.random(); + const pk = sk.toPublicKey(); + + beforeEach(async () => { + const sequencerClass = testingSequencerFromModules({ + // Database2: InMemoryDatabase, + }); + + const runtimeClass = Runtime.from({ + modules: { + Balance, + }, + }); + + const protocolClass = VanillaProtocol.create(); + + appChain = AppChain.from({ + Sequencer: sequencerClass, + Runtime: runtimeClass, + Protocol: protocolClass, + modules: {}, + }); + + appChain.configure({ + Runtime: { + Balance: {}, + }, + Sequencer: { + Database: {}, + BlockTrigger: {}, + Mempool: {}, + BlockProducerModule: {}, + UnprovenProducerModule: {}, + LocalTaskWorkerModule: {}, + BaseLayer: {}, + TaskQueue: {}, + }, + Protocol: { + AccountState: {}, + BlockProver: {}, + StateTransitionProver: {}, + BlockHeight: {}, + LastStateRoot: {}, + }, + }); + + await appChain.start(); + + runtime = appChain.runtime; + sequencer = appChain.sequencer; + + unprovenState = sequencer.resolve("UnprovenStateService"); + provenState = sequencer.resolve("AsyncStateService"); + + unprovenTreeStore = sequencer.resolve("UnprovenMerkleStore"); + provenTreeStore = sequencer.resolve("AsyncMerkleStore"); + }); + + it("test unproven block prod", async () => { + appChain.sequencer.resolve("Mempool").add( + createTransaction({ + runtime, + method: ["Balance", "setBalanceIf"], + privateKey: sk, + args: [pk, UInt64.from(100), Bool(true)], + nonce: 0, + }) + ); + + const generatedBlock = await sequencer + .resolve("BlockTrigger") + .produceUnproven(true); + + expectDefined(generatedBlock); + + const blocks = await sequencer + .resolve("UnprovenBlockQueue") + .getNewBlocks(); + + expect(blocks).toHaveLength(1); + + const { lastBlockMetadata, block } = blocks[0]; + + expect(lastBlockMetadata).toBeUndefined(); + expect(block.block.hash.toBigInt()).toStrictEqual( + generatedBlock.hash.toBigInt() + ); + + const stateDiff = collectStateDiff( + block.block.transactions.flatMap((tx) => + tx.stateTransitions.concat(tx.protocolTransitions) + ) + ); + + const state = await unprovenState.getAsync( + Object.keys(stateDiff).map(Field) + ); + + expect(checkStateDiffEquality(stateDiff, state)).toBe(true); + }); + } +); diff --git a/packages/sequencer/test/integration/utils.ts b/packages/sequencer/test/integration/utils.ts new file mode 100644 index 000000000..ce15802d4 --- /dev/null +++ b/packages/sequencer/test/integration/utils.ts @@ -0,0 +1,47 @@ +import { Field, PrivateKey, UInt64 } from "o1js"; +import { ArgumentTypes } from "@proto-kit/common"; +import { MethodIdResolver, MethodParameterEncoder, Runtime } from "@proto-kit/module"; +import { StateRecord, UnsignedTransaction, UntypedStateTransition } from "../../src"; + +export function createTransaction(spec: { + runtime: Runtime, + privateKey: PrivateKey; + method: [string, string]; + args: ArgumentTypes; + nonce: number; +}) { + const methodId = spec.runtime.dependencyContainer + .resolve("MethodIdResolver") + .getMethodId(spec.method[0], spec.method[1]); + + const decoder = MethodParameterEncoder.fromMethod( + spec.runtime.resolve(spec.method[0]), + spec.method[1] + ); + const { argsFields, argsJSON } = decoder.encode(spec.args); + + return new UnsignedTransaction({ + methodId: Field(methodId), + argsFields, + argsJSON, + sender: spec.privateKey.toPublicKey(), + nonce: UInt64.from(spec.nonce), + isMessage: false, + }).sign(spec.privateKey); +} + +export function expectDefined(value: T | undefined): asserts value is T { + expect(value).toBeDefined(); +} + +export function collectStateDiff( + stateTransitions: UntypedStateTransition[] +): StateRecord { + return stateTransitions.reduce>( + (state, st) => { + state[st.path.toString()] = st.toValue.value; + return state; + }, + {} + ); +} \ No newline at end of file From 520536f8e45b2a532ed2035cfaa3bd9350b626ad Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 8 Feb 2024 13:49:44 +0100 Subject: [PATCH 45/54] Exported new persistance classes --- packages/persistance/src/index.ts | 4 ++++ packages/sdk/src/query/StateServiceQueryModule.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 83279d80d..0c979dd0f 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -1,6 +1,10 @@ export * from "./PrismaDatabaseConnection"; +export * from "./RedisConnection"; +export * from "./PrismaRedisDatabase"; export * from "./services/prisma/PrismaMerkleTreeStore"; export * from "./services/prisma/PrismaStateService"; export * from "./services/prisma/PrismaBlockStorage"; export * from "./services/prisma/PrismaBatchStore"; +export * from "./services/prisma/PrismaSettlementStorage"; +export * from "./services/prisma/PrismaMessageStorage"; export * from "./services/redis/RedisMerkleTreeStore"; diff --git a/packages/sdk/src/query/StateServiceQueryModule.ts b/packages/sdk/src/query/StateServiceQueryModule.ts index 91efbdf3a..8226c3848 100644 --- a/packages/sdk/src/query/StateServiceQueryModule.ts +++ b/packages/sdk/src/query/StateServiceQueryModule.ts @@ -34,7 +34,7 @@ export class StateServiceQueryModule } public async get(key: Field) { - return await this.asyncStateService.getAsync(key); + return await this.asyncStateService.getSingleAsync(key); } public async merkleWitness( From dd2920db98118747f4863ab030ed94314e830767 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Feb 2024 14:51:20 +0100 Subject: [PATCH 46/54] Refactored batch storage --- packages/common/src/config/ModuleContainer.ts | 6 ++- .../dependencyFactory/DependencyFactory.ts | 13 +++-- .../src/services/prisma/PrismaBatchStore.ts | 23 ++++++++ .../src/services/prisma/PrismaStateService.ts | 39 ++++++++------ packages/sdk/test/graphql/Post.ts | 2 +- packages/sdk/test/graphql/run-graphql.test.ts | 9 ++++ packages/sdk/test/graphql/server.ts | 54 ++++++++++++++----- .../production/BlockProducerModule.ts | 2 +- .../storage/inmemory/InMemoryBatchStorage.ts | 5 ++ .../storage/inmemory/InMemoryBlockStorage.ts | 39 ++++++++------ .../src/storage/inmemory/InMemoryDatabase.ts | 6 +-- .../src/storage/repositories/BlockStorage.ts | 2 + .../integration/StorageIntegration.test.ts | 52 ++++++++++++++---- 13 files changed, 188 insertions(+), 64 deletions(-) create mode 100644 packages/sdk/test/graphql/run-graphql.test.ts diff --git a/packages/common/src/config/ModuleContainer.ts b/packages/common/src/config/ModuleContainer.ts index 543849d4c..64d2be621 100644 --- a/packages/common/src/config/ModuleContainer.ts +++ b/packages/common/src/config/ModuleContainer.ts @@ -4,11 +4,11 @@ import "reflect-metadata"; import { DependencyContainer, Frequency, - injectable, InjectionToken, instancePerContainerCachingFactory, isClassProvider, isFactoryProvider, + isTokenProvider, isValueProvider, Lifecycle, } from "tsyringe"; @@ -408,6 +408,10 @@ export class ModuleContainer< this.container.register(key, declaration, { lifecycle: Lifecycle.Singleton, }); + } else if (isTokenProvider(declaration)) { + this.container.register(key, declaration, { + lifecycle: Lifecycle.Singleton, + }); } else { // Can never be reached throw new Error("Above if-statement is exhaustive"); diff --git a/packages/common/src/dependencyFactory/DependencyFactory.ts b/packages/common/src/dependencyFactory/DependencyFactory.ts index 416a0b0aa..f854729cd 100644 --- a/packages/common/src/dependencyFactory/DependencyFactory.ts +++ b/packages/common/src/dependencyFactory/DependencyFactory.ts @@ -1,6 +1,7 @@ import { ClassProvider, FactoryProvider, + TokenProvider, ValueProvider, } from "tsyringe"; @@ -10,6 +11,7 @@ import type { BaseModuleInstanceType } from "../config/ModuleContainer"; export type DependencyDeclaration = | ClassProvider | FactoryProvider + | TokenProvider | ValueProvider; export type DependencyRecord = Record< @@ -35,12 +37,17 @@ export interface DependencyFactory { export type TypeFromDependencyDeclaration< Declaration extends DependencyDeclaration -> = Declaration extends DependencyDeclaration ? Dependency : never; +> = Declaration extends DependencyDeclaration + ? Dependency + : never; -export type CapitalizeAny = Key extends string ? Capitalize : Key +export type CapitalizeAny = + Key extends string ? Capitalize : Key; export type MapDependencyRecordToTypes = { - [Key in keyof Record as CapitalizeAny]: TypedClass>; + [Key in keyof Record as CapitalizeAny]: TypedClass< + TypeFromDependencyDeclaration + >; }; export type InferDependencies = diff --git a/packages/persistance/src/services/prisma/PrismaBatchStore.ts b/packages/persistance/src/services/prisma/PrismaBatchStore.ts index faceda200..fc5eb04f8 100644 --- a/packages/persistance/src/services/prisma/PrismaBatchStore.ts +++ b/packages/persistance/src/services/prisma/PrismaBatchStore.ts @@ -67,4 +67,27 @@ export class PrismaBatchStore implements BlockStorage, HistoricalBlockStorage { }, }); } + + public async getLatestBlock(): Promise { + const batch = await this.connection.prismaClient.batch.findFirst({ + orderBy: { + height: Prisma.SortOrder.desc, + }, + include: { + blocks: { + select: { + hash: true, + }, + }, + }, + take: 1, + }); + if (batch === null) { + return undefined; + } + return this.batchMapper.mapIn([ + batch, + batch.blocks.map((block) => block.hash), + ]); + } } diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 4b97f2d88..3d67e4662 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -1,13 +1,12 @@ -import { AsyncStateService } from "@proto-kit/sequencer"; +import { AsyncStateService, StateEntry } from "@proto-kit/sequencer"; import { Field } from "o1js"; -import { inject, injectable } from "tsyringe"; import { Prisma } from "@prisma/client"; import { noop } from "@proto-kit/common"; import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; export class PrismaStateService implements AsyncStateService { - private cache: [Field, Field[] | undefined][] = []; + private cache: StateEntry[] = []; /** * @param connection @@ -22,10 +21,12 @@ export class PrismaStateService implements AsyncStateService { const { prismaClient } = this.connection; const data = this.cache - .filter((entry) => entry[1] !== undefined) + .filter((entry) => entry.value !== undefined) .map((entry) => ({ - path: new Prisma.Decimal(entry[0].toString()), - values: entry[1]!.map((field) => new Prisma.Decimal(field.toString())), + path: new Prisma.Decimal(entry.key.toString()), + values: entry.value!.map( + (field) => new Prisma.Decimal(field.toString()) + ), mask: this.mask, })); @@ -33,7 +34,7 @@ export class PrismaStateService implements AsyncStateService { prismaClient.state.deleteMany({ where: { path: { - in: this.cache.map((x) => new Prisma.Decimal(x[0].toString())), + in: this.cache.map((x) => new Prisma.Decimal(x.key.toString())), }, mask: this.mask, }, @@ -46,12 +47,14 @@ export class PrismaStateService implements AsyncStateService { this.cache = []; } - public async getAsync(key: Field): Promise { - const record = await this.connection.prismaClient.state.findFirst({ + public async getAsync(keys: Field[]): Promise { + const records = await this.connection.prismaClient.state.findMany({ where: { AND: [ { - path: new Prisma.Decimal(key.toString()), + path: { + in: keys.map((key) => new Prisma.Decimal(key.toString())), + }, }, { mask: this.mask, @@ -59,16 +62,22 @@ export class PrismaStateService implements AsyncStateService { ], }, }); - // x.toNumber() is safe, because we know that the actual DB-type - // is a decimal with 0 decimal places - return record?.values.map((x) => Field(x.toNumber())) ?? undefined; + return records.map((record) => ({ + key: Field(record.path.toNumber()), + value: record.values.map((x) => Field(x.toString())), + })); } public async openTransaction(): Promise { noop(); } - public async setAsync(key: Field, value: Field[] | undefined): Promise { - this.cache.push([key, value]); + public async getSingleAsync(key: Field): Promise { + const state = await this.getAsync([key]); + return state.at(-1)?.value; + } + + public writeStates(entries: StateEntry[]): void { + this.cache.push(...entries); } } diff --git a/packages/sdk/test/graphql/Post.ts b/packages/sdk/test/graphql/Post.ts index f7208dd49..e924fa612 100644 --- a/packages/sdk/test/graphql/Post.ts +++ b/packages/sdk/test/graphql/Post.ts @@ -25,7 +25,7 @@ export class MessageBoard extends RuntimeModule> { public post(message: CircuitString) { const post = new Post({ message, - author: this.transaction.sender, + author: this.transaction.sender.value, createdAt: this.network.block.height, }); diff --git a/packages/sdk/test/graphql/run-graphql.test.ts b/packages/sdk/test/graphql/run-graphql.test.ts new file mode 100644 index 000000000..4b4648244 --- /dev/null +++ b/packages/sdk/test/graphql/run-graphql.test.ts @@ -0,0 +1,9 @@ +import { startServer } from "./server"; +import { sleep } from "@proto-kit/common"; + +describe("run graphql", () => { + it("run", async () => { + const server = await startServer(); + await sleep(1000000000); + }, 1000000000); +}); \ No newline at end of file diff --git a/packages/sdk/test/graphql/server.ts b/packages/sdk/test/graphql/server.ts index 7fc0139d6..e457807b7 100644 --- a/packages/sdk/test/graphql/server.ts +++ b/packages/sdk/test/graphql/server.ts @@ -15,7 +15,7 @@ import { StateMap, VanillaProtocol, } from "@proto-kit/protocol"; -import { Presets, log, sleep } from "@proto-kit/common"; +import { Presets, log, sleep, range } from "@proto-kit/common"; import { BlockProducerModule, InMemoryDatabase, @@ -25,7 +25,7 @@ import { PrivateMempool, Sequencer, TimedBlockTrigger, - UnprovenProducerModule + UnprovenProducerModule, } from "@proto-kit/sequencer"; import { BlockStorageResolver, @@ -45,8 +45,7 @@ import { InMemoryTransactionSender } from "../../src/transaction/InMemoryTransac import { container } from "tsyringe"; import { BlockStorageNetworkStateModule } from "../../src/query/BlockStorageNetworkStateModule"; import { MessageBoard, Post } from "./Post"; -import { PrismaDatabaseConnection } from "@proto-kit/persistance"; -import { RedisConnection } from "@proto-kit/persistance/dist/RedisConnection"; +import { PrismaRedisDatabase } from "@proto-kit/persistance"; @runtimeModule() export class Balances extends RuntimeModule { @@ -94,13 +93,12 @@ export async function startServer() { }, }), - protocol: VanillaProtocol.from({ }), + protocol: VanillaProtocol.from({}), sequencer: Sequencer.from({ modules: { - // Database: InMemoryDatabase, - Database: PrismaDatabaseConnection, - Redis: RedisConnection, + Database: InMemoryDatabase, + // Database: PrismaRedisDatabase, Mempool: PrivateMempool, GraphqlServer, @@ -152,7 +150,7 @@ export async function startServer() { StateTransitionProver: {}, AccountState: {}, BlockHeight: {}, - LastStateRoot: {} + LastStateRoot: {}, }, Sequencer: { @@ -172,10 +170,10 @@ export async function startServer() { }, Database: {}, - Redis: { - url: "redis://localhost:6379", - password: "password", - }, + // Redis: { + // url: "redis://localhost:6379", + // password: "password", + // }, Mempool: {}, BlockProducerModule: {}, @@ -189,7 +187,7 @@ export async function startServer() { BlockTrigger: { blockInterval: 15000, - settlementInterval: 30000, + settlementInterval: 10000000, }, }, @@ -230,5 +228,33 @@ export async function startServer() { await tx2.sign(); await tx2.send(); + let i = 2; + + setInterval(async () => { + try { + const p = range(0, 15).map(async () => { + const tx2 = await appChain.transaction( + priv.toPublicKey(), + () => { + balances.addBalance( + PrivateKey.random().toPublicKey(), + UInt64.from(1000) + ); + }, + { nonce: i++ } + ); + await tx2.sign(); + await tx2.send(); + }); + + await Promise.all(p) + + console.log(process.memoryUsage().heapUsed / 1024 / 1024 + "MB"); + } catch (e) { + console.log(e); + } + console.log("Sent new tx"); + }, 8000); + return appChain; } diff --git a/packages/sequencer/src/protocol/production/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/BlockProducerModule.ts index f90aa1a05..c45726d74 100644 --- a/packages/sequencer/src/protocol/production/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BlockProducerModule.ts @@ -185,7 +185,7 @@ export class BlockProducerModule extends SequencerModule { const block = await this.computeBlock(unprovenBlocks, height); const computedBundles = unprovenBlocks.map((bundle) => - bundle.block.block.transactionsHash.toString() + bundle.block.block.hash.toString() ); const jsonProof = this.blockProofSerializer diff --git a/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts index 11e87e8ea..2820d8ba6 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts @@ -18,6 +18,11 @@ export class InMemoryBatchStorage } public async pushBlock(block: ComputedBlock): Promise { + console.log("Pushed Batch") this.blocks.push(block); } + + public async getLatestBlock(): Promise { + return this.blocks.at(-1); + } } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts index 7a904abf2..a38e0b1a3 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts @@ -1,3 +1,5 @@ +import { inject, injectable } from "tsyringe"; + import { HistoricalUnprovenBlockStorage, UnprovenBlockQueue, @@ -9,19 +11,23 @@ import type { UnprovenBlockWithMetadata, } from "../model/UnprovenBlock"; import { UnprovenBlockWithPreviousMetadata } from "../../protocol/production/BlockProducerModule"; +import { BlockStorage } from "../repositories/BlockStorage"; +@injectable() export class InMemoryBlockStorage implements UnprovenBlockStorage, HistoricalUnprovenBlockStorage, UnprovenBlockQueue { + public constructor( + @inject("BlockStorage") private readonly batchStorage: BlockStorage + ) {} + private readonly blocks: UnprovenBlock[] = []; private readonly metadata: UnprovenBlockMetadata[] = []; - private cursor = 0; - public async getBlockAt(height: number): Promise { return this.blocks.at(height); } @@ -46,19 +52,26 @@ export class InMemoryBlockStorage } public async getNewBlocks(): Promise { - const slice = this.blocks.slice(this.cursor); + const latestBatch = await this.batchStorage.getLatestBlock(); + + let cursor = 0; + if (latestBatch !== undefined) { + cursor = this.blocks.reduce( + (c, block, index) => + latestBatch.bundles.includes(block.hash.toString()) ? index + 1 : c, + 0 + ); + } + + const slice = this.blocks.slice(cursor); let metadata: (UnprovenBlockMetadata | undefined)[] = this.metadata.slice( - Math.max(this.cursor - 1, 0) + Math.max(cursor - 1, 0) ); - if (this.cursor === 0) { + if (cursor === 0) { metadata = [undefined, ...metadata]; } - // This assumes that getNewBlocks() is only called once per block prod cycle - // TODO query batch storage which the last proven block was instead - this.cursor = this.blocks.length; - return slice.map((block, index) => ({ block: { block, @@ -80,11 +93,7 @@ export class InMemoryBlockStorage this.metadata.push(metadata); } - public async getBlock( - hash: string - ): Promise { - return this.blocks.find( - (block) => block.hash.toString() === hash - ); + public async getBlock(hash: string): Promise { + return this.blocks.find((block) => block.hash.toString() === hash); } } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index ff99f0582..29df561e8 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -24,8 +24,6 @@ export class InMemoryDatabase extends SequencerModule implements StorageDependencyFactory { - private readonly blockStorageQueue = new InMemoryBlockStorage(); - public dependencies(): StorageDependencyMinimumDependencies { return { asyncMerkleStore: { @@ -38,10 +36,10 @@ export class InMemoryDatabase useClass: InMemoryBatchStorage, }, unprovenBlockQueue: { - useValue: this.blockStorageQueue, + useClass: InMemoryBlockStorage, }, unprovenBlockStorage: { - useValue: this.blockStorageQueue, + useToken: "UnprovenBlockQueue", }, unprovenStateService: { useFactory: () => new CachedStateService(undefined), diff --git a/packages/sequencer/src/storage/repositories/BlockStorage.ts b/packages/sequencer/src/storage/repositories/BlockStorage.ts index 92535624b..e6a4c889c 100644 --- a/packages/sequencer/src/storage/repositories/BlockStorage.ts +++ b/packages/sequencer/src/storage/repositories/BlockStorage.ts @@ -1,7 +1,9 @@ import { ComputedBlock } from "../model/Block"; export interface BlockStorage { + // TODO Rename to getCurrentChainLength(), blockheight seems misleading here getCurrentBlockHeight: () => Promise; + getLatestBlock: () => Promise; pushBlock: (block: ComputedBlock) => Promise; } diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index 7692d0b88..bd149493a 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -1,5 +1,4 @@ -import { InMemoryDatabase } from "@proto-kit/sequencer"; -import { beforeEach } from "@jest/globals"; +import { beforeEach, expect } from "@jest/globals"; import { DefaultTestingSequencerModules, testingSequencerFromModules, @@ -14,6 +13,9 @@ import { AppChain } from "@proto-kit/sdk"; import { AsyncMerkleTreeStore, AsyncStateService, + BlockStorage, + HistoricalBlockStorage, + InMemoryDatabase, Sequencer, StateEntry, StateRecord, @@ -39,14 +41,16 @@ function checkStateDiffEquality(stateDiff: StateRecord, state: StateEntry[]) { describe.each([["InMemory", InMemoryDatabase]])( "Storage Adapter Test %s", - () => { + (testName, Database) => { let appChain: AppChain< { Balance: typeof Balance }, ProtocolCustomModulesRecord, - DefaultTestingSequencerModules, + DefaultTestingSequencerModules & { Database: typeof Database }, {} >; - let sequencer: Sequencer; + let sequencer: Sequencer< + DefaultTestingSequencerModules & { Database: typeof Database } + >; let runtime: Runtime<{ Balance: typeof Balance }>; let unprovenState: AsyncStateService; @@ -58,9 +62,9 @@ describe.each([["InMemory", InMemoryDatabase]])( const sk = PrivateKey.random(); const pk = sk.toPublicKey(); - beforeEach(async () => { + beforeAll(async () => { const sequencerClass = testingSequencerFromModules({ - // Database2: InMemoryDatabase, + Database, }); const runtimeClass = Runtime.from({ @@ -72,9 +76,9 @@ describe.each([["InMemory", InMemoryDatabase]])( const protocolClass = VanillaProtocol.create(); appChain = AppChain.from({ - Sequencer: sequencerClass, - Runtime: runtimeClass, - Protocol: protocolClass, + sequencer: sequencerClass, + runtime: runtimeClass, + protocol: protocolClass, modules: {}, }); @@ -154,6 +158,34 @@ describe.each([["InMemory", InMemoryDatabase]])( ); expect(checkStateDiffEquality(stateDiff, state)).toBe(true); + + await expect( + provenState.getSingleAsync(state[0].key) + ).resolves.toBeUndefined(); + }); + + it("test proven block prod", async () => { + const generatedBatch = await sequencer + .resolve("BlockTrigger") + .produceProven(); + + expectDefined(generatedBatch); + + const blocks = await sequencer + .resolve("UnprovenBlockQueue") + .getNewBlocks(); + expect(blocks).toHaveLength(0); + + const batchStorage = sequencer.resolve( + "BlockStorage" + ) as HistoricalBlockStorage & BlockStorage; + const batch = await batchStorage.getBlockAt(0); + + expectDefined(batch); + expect(batch.height).toStrictEqual(generatedBatch?.height); + await expect(batchStorage.getCurrentBlockHeight()).resolves.toStrictEqual( + 1 + ); }); } ); From a9d8339d686656489fdbae76c68a17dcb70d4783 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Feb 2024 17:12:06 +0100 Subject: [PATCH 47/54] Fixed proven network state query --- .../query/BlockStorageNetworkStateModule.ts | 18 ++++--- packages/sdk/test/graphql/server.ts | 52 +++++++++---------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/packages/sdk/src/query/BlockStorageNetworkStateModule.ts b/packages/sdk/src/query/BlockStorageNetworkStateModule.ts index 72d2f1869..642ecf6d5 100644 --- a/packages/sdk/src/query/BlockStorageNetworkStateModule.ts +++ b/packages/sdk/src/query/BlockStorageNetworkStateModule.ts @@ -59,8 +59,7 @@ export class BlockStorageNetworkStateModule } public async getProvenNetworkState() { - const batchHeight = await this.provenStorage.getCurrentBlockHeight(); - const batch = await this.provenStorage.getBlockAt(batchHeight - 1); + const batch = await this.provenStorage.getLatestBlock(); if (batch !== undefined) { const lastBlock = batch.bundles.at(-1); @@ -70,13 +69,16 @@ export class BlockStorageNetworkStateModule ); } - // const block = await this.unprovenStorage.getBlock(lastBlock); - // - // if (block !== undefined) { - // return block.networkState.during; // TODO Probably metadata.after? - // } + const block = await this.unprovenStorage.getBlock(lastBlock); + + if (block === undefined) { + throw new Error( + `Highest block of latest batch not found in blockStorage (hash ${lastBlock})` + ); + } + return block.networkState.during; // TODO Probably metadata.after? } - // We currently do not carry networkstate data with proven blocks + // TODO Replace by NetworkState.empty() across the whole application return undefined; } } diff --git a/packages/sdk/test/graphql/server.ts b/packages/sdk/test/graphql/server.ts index e457807b7..a9b5faa5c 100644 --- a/packages/sdk/test/graphql/server.ts +++ b/packages/sdk/test/graphql/server.ts @@ -187,7 +187,7 @@ export async function startServer() { BlockTrigger: { blockInterval: 15000, - settlementInterval: 10000000, + settlementInterval: 30000, }, }, @@ -230,31 +230,31 @@ export async function startServer() { let i = 2; - setInterval(async () => { - try { - const p = range(0, 15).map(async () => { - const tx2 = await appChain.transaction( - priv.toPublicKey(), - () => { - balances.addBalance( - PrivateKey.random().toPublicKey(), - UInt64.from(1000) - ); - }, - { nonce: i++ } - ); - await tx2.sign(); - await tx2.send(); - }); - - await Promise.all(p) - - console.log(process.memoryUsage().heapUsed / 1024 / 1024 + "MB"); - } catch (e) { - console.log(e); - } - console.log("Sent new tx"); - }, 8000); + // setInterval(async () => { + // try { + // const p = range(0, 1).map(async () => { + // const tx2 = await appChain.transaction( + // priv.toPublicKey(), + // () => { + // balances.addBalance( + // PrivateKey.random().toPublicKey(), + // UInt64.from(1000) + // ); + // }, + // { nonce: i++ } + // ); + // await tx2.sign(); + // await tx2.send(); + // }); + // + // await Promise.all(p) + // + // console.log(process.memoryUsage().heapUsed / 1024 / 1024 + "MB"); + // } catch (e) { + // console.log(e); + // } + // console.log("Sent new tx"); + // }, 8000); return appChain; } From 31fc72166286915610eafd32d2d6802ea40e4b0c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 13 Feb 2024 19:13:40 +0100 Subject: [PATCH 48/54] Added TransactionStorage, refactoring --- .../src/graphql/modules/MempoolResolver.ts | 24 +++++++---- packages/persistance/prisma/schema.prisma | 1 + .../src/PrismaDatabaseConnection.ts | 4 ++ .../persistance/src/PrismaRedisDatabase.ts | 5 +-- .../src/services/prisma/PrismaBlockStorage.ts | 1 + .../services/prisma/PrismaMerkleTreeStore.ts | 4 +- .../services/prisma/PrismaMessageStorage.ts | 15 +++++-- .../prisma/PrismaSettlementStorage.ts | 10 +++-- .../src/services/prisma/PrismaStateService.ts | 6 ++- .../prisma/PrismaTransactionStorage.ts | 41 ++++++++++++++++++ .../services/redis/RedisMerkleTreeStore.ts | 4 ++ packages/sdk/test/graphql/server.ts | 15 +++++-- packages/sequencer/src/index.ts | 4 ++ packages/sequencer/src/mempool/Mempool.ts | 7 +--- .../src/mempool/private/PrivateMempool.ts | 42 +++++-------------- .../production/trigger/TimedBlockTrigger.ts | 12 +++--- .../unproven/UnprovenProducerModule.ts | 11 +---- .../src/storage/StorageDependencyFactory.ts | 2 + .../src/storage/inmemory/InMemoryDatabase.ts | 4 ++ .../inmemory/InMemoryTransactionStorage.ts | 40 ++++++++++++++++++ .../repositories/TransactionStorage.ts | 6 +++ .../test/integration/BlockProduction.test.ts | 29 ++++++------- .../integration/StorageIntegration.test.ts | 37 ++++++++++++++-- 23 files changed, 228 insertions(+), 96 deletions(-) create mode 100644 packages/persistance/src/services/prisma/PrismaTransactionStorage.ts create mode 100644 packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts create mode 100644 packages/sequencer/src/storage/repositories/TransactionStorage.ts diff --git a/packages/api/src/graphql/modules/MempoolResolver.ts b/packages/api/src/graphql/modules/MempoolResolver.ts index a310c1487..949528e41 100644 --- a/packages/api/src/graphql/modules/MempoolResolver.ts +++ b/packages/api/src/graphql/modules/MempoolResolver.ts @@ -35,8 +35,15 @@ export class Signature { @InputType("TransactionObjectInput") export class TransactionObject { public static fromServiceLayerModel(pt: PendingTransaction) { - const { methodId, sender, nonce, signature, argsFields, argsJSON, isMessage } = - pt.toJSON(); + const { + methodId, + sender, + nonce, + signature, + argsFields, + argsJSON, + isMessage, + } = pt.toJSON(); return new TransactionObject( methodId, sender, @@ -105,10 +112,9 @@ export class MempoolResolver extends GraphqlModule { } @Query(() => String) - public transactionState(@Arg("hash") hash: string) { - const tx = this.mempool - .getTxs() - .txs.find((x) => x.hash().toString() === hash); + public async transactionState(@Arg("hash") hash: string) { + const txs = await this.mempool.getTxs(); + const tx = txs.find((x) => x.hash().toString() === hash); if (tx) { return "pending"; @@ -118,9 +124,9 @@ export class MempoolResolver extends GraphqlModule { } @Query(() => [String]) - public transactions() { - const tx = this.mempool.getTxs().txs; - return tx.map((x) => x.hash().toString()); + public async transactions() { + const txs = await this.mempool.getTxs(); + return txs.map((x) => x.hash().toString()); } // @Query(returns => [TransactionObject]) diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 4dc45b239..c2897f777 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -3,6 +3,7 @@ generator client { provider = "prisma-client-js" + // previewFeatures = ["relationJoins"] } datasource db { diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 4f8dd64ca..a0fa93e15 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -11,6 +11,7 @@ import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStorage"; import { PrismaMessageStorage } from "./services/prisma/PrismaMessageStorage"; +import { PrismaTransactionStorage } from "./services/prisma/PrismaTransactionStorage"; export interface PrismaDatabaseConfig { connection?: { @@ -69,6 +70,9 @@ export class PrismaDatabaseConnection messageStorage: { useClass: PrismaMessageStorage, }, + transactionStorage: { + useClass: PrismaTransactionStorage, + }, }; } diff --git a/packages/persistance/src/PrismaRedisDatabase.ts b/packages/persistance/src/PrismaRedisDatabase.ts index d710f4133..c422c8abb 100644 --- a/packages/persistance/src/PrismaRedisDatabase.ts +++ b/packages/persistance/src/PrismaRedisDatabase.ts @@ -4,6 +4,8 @@ import { StorageDependencyMinimumDependencies, } from "@proto-kit/sequencer"; import { ChildContainerProvider, DependencyFactory } from "@proto-kit/common"; +import { PrismaClient } from "@prisma/client"; +import { RedisClientType } from "redis"; import { PrismaConnection, @@ -15,9 +17,6 @@ import { RedisConnectionConfig, RedisConnectionModule, } from "./RedisConnection"; -import { PrismaClient, Prisma } from "@prisma/client"; -import { DefaultArgs } from "@prisma/client/runtime/library"; -import { RedisClientType } from "redis"; export interface PrismaRedisCombinedConfig { prisma: PrismaDatabaseConfig; diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 98c7d74eb..e921a0580 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -135,6 +135,7 @@ export class PrismaBlockStorage tx.protocolTransitions as Prisma.InputJsonArray, }; }), + skipDuplicates: true, }, }, diff --git a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts index 9f69bc7fa..85ed864e4 100644 --- a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts +++ b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts @@ -3,11 +3,11 @@ import { MerkleTreeNode, MerkleTreeNodeQuery, } from "@proto-kit/sequencer"; - -import { PrismaConnection } from "../../PrismaDatabaseConnection"; import { noop } from "@proto-kit/common"; import { Prisma } from "@prisma/client"; +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; + /** * @deprecated */ diff --git a/packages/persistance/src/services/prisma/PrismaMessageStorage.ts b/packages/persistance/src/services/prisma/PrismaMessageStorage.ts index 1abbf3c34..996eaf470 100644 --- a/packages/persistance/src/services/prisma/PrismaMessageStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaMessageStorage.ts @@ -1,11 +1,14 @@ import { MessageStorage, PendingTransaction } from "@proto-kit/sequencer"; -import { inject } from "tsyringe"; -import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import { inject, injectable } from "tsyringe"; + +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; + import { TransactionMapper } from "./mappers/TransactionMapper"; +@injectable() export class PrismaMessageStorage implements MessageStorage { public constructor( - @inject("Database") private readonly connection: PrismaDatabaseConnection, + @inject("Database") private readonly connection: PrismaConnection, private readonly transactionMapper: TransactionMapper ) {} @@ -27,7 +30,11 @@ export class PrismaMessageStorage implements MessageStorage { }, }); - const dbTransactions = batch!.messages.map((message) => { + if (batch === null) { + return []; + } + + const dbTransactions = batch.messages.map((message) => { return message.transaction; }); diff --git a/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts b/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts index 8f51a4ab4..0748e932a 100644 --- a/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaSettlementStorage.ts @@ -1,12 +1,14 @@ import { Settlement, SettlementStorage } from "@proto-kit/sequencer"; -import { inject } from "tsyringe"; -import { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; -import { TransactionMapper } from "./mappers/TransactionMapper"; +import { inject, injectable } from "tsyringe"; + +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; + import { SettlementMapper } from "./mappers/SettlementMapper"; +@injectable() export class PrismaSettlementStorage implements SettlementStorage { public constructor( - @inject("Database") private readonly connection: PrismaDatabaseConnection, + @inject("Database") private readonly connection: PrismaConnection, private readonly settlementMapper: SettlementMapper ) {} diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 3d67e4662..d290e638f 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -3,8 +3,10 @@ import { Field } from "o1js"; import { Prisma } from "@prisma/client"; import { noop } from "@proto-kit/common"; -import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; +import { injectable } from "tsyringe"; +@injectable() export class PrismaStateService implements AsyncStateService { private cache: StateEntry[] = []; @@ -13,7 +15,7 @@ export class PrismaStateService implements AsyncStateService { * @param mask A indicator to which masking level the values belong */ public constructor( - private readonly connection: PrismaDatabaseConnection, + private readonly connection: PrismaConnection, private readonly mask: string ) {} diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts new file mode 100644 index 000000000..4bd3a9b50 --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -0,0 +1,41 @@ +import { inject, injectable } from "tsyringe"; +import { PendingTransaction, TransactionStorage } from "@proto-kit/sequencer"; + +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; + +import { TransactionMapper } from "./mappers/TransactionMapper"; + +@injectable() +export class PrismaTransactionStorage implements TransactionStorage { + public constructor( + @inject("Database") private readonly connection: PrismaConnection, + private readonly transactionMapper: TransactionMapper + ) {} + + public async getPendingUserTransactions(): Promise { + const { prismaClient } = this.connection; + + const txs = await prismaClient.transaction.findMany({ + where: { + executionResult: { + is: null + }, + isMessage: { + equals: false + } + }, + }) + return txs.map(tx => this.transactionMapper.mapIn(tx)); + } + + public async pushUserTransaction(tx: PendingTransaction): Promise { + const { prismaClient } = this.connection; + + const result = await prismaClient.transaction.createMany({ + data: [this.transactionMapper.mapOut(tx)], + skipDuplicates: true, + }); + + return result.count === 1; + } +} diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index 9350b1b75..b45d154b9 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -60,6 +60,10 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { public async getNodesAsync( nodes: MerkleTreeNodeQuery[] ): Promise<(bigint | undefined)[]> { + if(nodes.length === 0){ + return []; + } + const keys = nodes.map((node) => this.getKey(node)); const result = await this.connection.redisClient!.mGet(keys); diff --git a/packages/sdk/test/graphql/server.ts b/packages/sdk/test/graphql/server.ts index a9b5faa5c..2cf8e3074 100644 --- a/packages/sdk/test/graphql/server.ts +++ b/packages/sdk/test/graphql/server.ts @@ -97,8 +97,8 @@ export async function startServer() { sequencer: Sequencer.from({ modules: { - Database: InMemoryDatabase, - // Database: PrismaRedisDatabase, + // Database: InMemoryDatabase, + Database: PrismaRedisDatabase, Mempool: PrivateMempool, GraphqlServer, @@ -169,7 +169,16 @@ export async function startServer() { MerkleWitnessResolver: {}, }, - Database: {}, + // Database: {}, + Database: { + redis: { + url: "redis://localhost:6379", + password: "password", + }, + prisma: { + + } + }, // Redis: { // url: "redis://localhost:6379", // password: "password", diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 1d20fd868..1d5685b0c 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -43,10 +43,14 @@ export * from "./storage/repositories/BlockStorage"; export * from "./storage/repositories/UnprovenBlockStorage"; export * from "./storage/repositories/SettlementStorage"; export * from "./storage/repositories/MessageStorage"; +export * from "./storage/repositories/TransactionStorage"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; export * from "./storage/inmemory/InMemoryBatchStorage"; +export * from "./storage/inmemory/InMemorySettlementStorage"; +export * from "./storage/inmemory/InMemoryMessageStorage"; +export * from "./storage/inmemory/InMemoryTransactionStorage"; export * from "./storage/StorageDependencyFactory"; export * from "./helpers/query/QueryTransportModule"; export * from "./helpers/query/QueryBuilderFactory"; diff --git a/packages/sequencer/src/mempool/Mempool.ts b/packages/sequencer/src/mempool/Mempool.ts index 44ee133a9..6d8bb1a67 100644 --- a/packages/sequencer/src/mempool/Mempool.ts +++ b/packages/sequencer/src/mempool/Mempool.ts @@ -11,13 +11,10 @@ export interface Mempool { * Add a transaction to the mempool * @returns The new commitment to the mempool */ - add: (tx: PendingTransaction) => MempoolCommitment; + add: (tx: PendingTransaction) => Promise; /** * Retrieve all transactions that are currently in the mempool */ - getTxs: () => { txs: PendingTransaction[]; commitment: MempoolCommitment }; - - // Add stuff for witness generation - removeTxs: (txs: PendingTransaction[]) => boolean; + getTxs: () => Promise; } diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 531adae69..90ca53a6e 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -1,57 +1,35 @@ -import { Field, Poseidon } from "o1js"; import { noop } from "@proto-kit/common"; +import { inject } from "tsyringe"; -import type { Mempool, MempoolCommitment } from "../Mempool.js"; +import type { Mempool } from "../Mempool.js"; import type { PendingTransaction } from "../PendingTransaction.js"; import { sequencerModule, SequencerModule, } from "../../sequencer/builder/SequencerModule"; +import { TransactionStorage } from "../../storage/repositories/TransactionStorage"; import { TransactionValidator } from "../verification/TransactionValidator"; -import { injectable } from "tsyringe"; @sequencerModule() export class PrivateMempool extends SequencerModule implements Mempool { - public commitment: Field; - - private queue: PendingTransaction[] = []; - public constructor( - private readonly transactionValidator: TransactionValidator + private readonly transactionValidator: TransactionValidator, + @inject("TransactionStorage") + private readonly transactionStorage: TransactionStorage ) { super(); - this.commitment = Field(0); } - public add(tx: PendingTransaction): MempoolCommitment { + public async add(tx: PendingTransaction): Promise { const [txValid, error] = this.transactionValidator.validateTx(tx); if (txValid) { - this.queue.push(tx); - - // Figure out how to generalize this - this.commitment = Poseidon.hash([this.commitment, tx.hash()]); - - return { transactionsHash: this.commitment }; + return await this.transactionStorage.pushUserTransaction(tx); } throw new Error(`Valdiation of tx failed: ${error ?? "unknown error"}`); } - public getTxs(): { - txs: PendingTransaction[]; - commitment: MempoolCommitment; - } { - return { - commitment: { transactionsHash: this.commitment }, - txs: this.queue, - }; - } - - public removeTxs(txs: PendingTransaction[]): boolean { - const { length } = this.queue; - this.queue = this.queue.filter((tx) => !txs.includes(tx)); - // Check that all elements have been removed and were in the mempool prior - // eslint-disable-next-line unicorn/consistent-destructuring - return length === this.queue.length + txs.length; + public async getTxs(): Promise { + return await this.transactionStorage.getPendingUserTransactions(); } public async start(): Promise { diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index 1192f29ef..0f89422a9 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -74,7 +74,11 @@ export class TimedBlockTrigger await this.produceProven(); } } catch (error) { - log.error(error); + if (error instanceof Error) { + log.error(error.message + "\n" + error.stack); + } else { + log.error(error); + } } }, timerInterval); @@ -82,12 +86,10 @@ export class TimedBlockTrigger } private async produceUnprovenBlock() { + const mempoolTxs = await this.mempool.getTxs(); // Produce a block if either produceEmptyBlocks is true or we have more // than 1 tx in mempool - if ( - this.mempool.getTxs().txs.length > 0 || - (this.config.produceEmptyBlocks ?? true) - ) { + if (mempoolTxs.length > 0 || (this.config.produceEmptyBlocks ?? true)) { await this.produceUnproven(true); } } diff --git a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts index b671645ad..5b0f633c7 100644 --- a/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts +++ b/packages/sequencer/src/protocol/production/unproven/UnprovenProducerModule.ts @@ -27,10 +27,6 @@ import { TransactionExecutionService } from "./TransactionExecutionService"; import { MessageStorage } from "../../../storage/repositories/MessageStorage"; import { ACTIONS_EMPTY_HASH } from "@proto-kit/protocol"; -const errors = { - txRemovalFailed: () => new Error("Removal of txs from mempool failed"), -}; - interface UnprovenProducerEvents extends EventsRecord { unprovenBlockProduced: [UnprovenBlock]; } @@ -119,7 +115,7 @@ export class UnprovenProducerModule txs: PendingTransaction[]; metadata: UnprovenBlockWithMetadata; }> { - const { txs } = this.mempool.getTxs(); + const txs = await this.mempool.getTxs(); const parentBlock = await this.unprovenBlockQueue.getLatestBlock(); @@ -170,11 +166,6 @@ export class UnprovenProducerModule this.productionInProgress = false; - requireTrue( - this.mempool.removeTxs(txs.filter((tx) => !tx.isMessage)), - errors.txRemovalFailed - ); - return block; } diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index 24bbd3de1..108d957d2 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -14,6 +14,7 @@ import { } from "./repositories/UnprovenBlockStorage"; import { MessageStorage } from "./repositories/MessageStorage"; import { SettlementStorage } from "./repositories/SettlementStorage"; +import { TransactionStorage } from "./repositories/TransactionStorage"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { asyncStateService: DependencyDeclaration; @@ -26,6 +27,7 @@ export interface StorageDependencyMinimumDependencies extends DependencyRecord { blockTreeStore: DependencyDeclaration; messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; + transactionStorage: DependencyDeclaration; } export interface StorageDependencyFactory extends DependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index 29df561e8..ee2abcefa 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -18,6 +18,7 @@ import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; import { InMemoryStateService } from "@proto-kit/module"; +import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; @sequencerModule() export class InMemoryDatabase @@ -56,6 +57,9 @@ export class InMemoryDatabase settlementStorage: { useClass: InMemorySettlementStorage, }, + transactionStorage: { + useClass: InMemoryTransactionStorage, + }, }; } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts new file mode 100644 index 000000000..127ccbb24 --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -0,0 +1,40 @@ +import { inject, injectable } from "tsyringe"; +import { TransactionStorage } from "../repositories/TransactionStorage"; +import { PendingTransaction } from "../../mempool/PendingTransaction"; +import { + HistoricalUnprovenBlockStorage, + UnprovenBlockStorage +} from "../repositories/UnprovenBlockStorage"; + +@injectable() +export class InMemoryTransactionStorage implements TransactionStorage { + private queue: PendingTransaction[] = []; + + private latestScannedBlock = -1; + + public constructor( + @inject("UnprovenBlockStorage") private readonly blockStorage: UnprovenBlockStorage & HistoricalUnprovenBlockStorage + ) {} + + public async getPendingUserTransactions(): Promise { + const nextHeight = await this.blockStorage.getCurrentBlockHeight() + for(let height = this.latestScannedBlock + 1 ; height < nextHeight ; height++) { + const block = await this.blockStorage.getBlockAt(height); + if (block !== undefined){ + const hashes = block.transactions.map(tx => tx.tx.hash().toString()) + this.queue = this.queue.filter(tx => !hashes.includes(tx.hash().toString())) + } + } + this.latestScannedBlock = nextHeight - 1; + + return this.queue.slice(); + } + + public async pushUserTransaction(tx: PendingTransaction): Promise { + const notInQueue = this.queue.find(tx2 => tx2.hash().toString() === tx.hash().toString()) === undefined + if(notInQueue){ + this.queue.push(tx); + } + return notInQueue; + } +} diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts new file mode 100644 index 000000000..2e88f7608 --- /dev/null +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -0,0 +1,6 @@ +import { PendingTransaction } from "../../mempool/PendingTransaction"; + +export interface TransactionStorage { + pushUserTransaction: (tx: PendingTransaction) => Promise; + getPendingUserTransactions: () => Promise; +} diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 787cae78f..dfbe340b7 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -74,9 +74,9 @@ describe("block production", () => { ); const app = AppChain.from({ - Runtime: runtimeClass, - Sequencer: sequencerClass, - Protocol: protocolClass, + runtime: runtimeClass, + sequencer: sequencerClass, + protocol: protocolClass, modules: {}, }); @@ -121,7 +121,7 @@ describe("block production", () => { const privateKey = PrivateKey.random(); const publicKey = privateKey.toPublicKey(); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], @@ -191,7 +191,7 @@ describe("block production", () => { expect(AccountState.fromFields(newAccountState!).nonce.toBigInt()).toBe(1n); // Second tx - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "addBalanceToSelf"], @@ -229,7 +229,7 @@ describe("block production", () => { const privateKey = PrivateKey.random(); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], @@ -278,8 +278,8 @@ describe("block production", () => { const increment = 100; - range(0, numberTxs).forEach((index) => { - mempool.add( + const p = range(0, numberTxs).map(async (index) => { + await mempool.add( createTransaction({ runtime, method: ["Balance", "addBalanceToSelf"], @@ -289,6 +289,7 @@ describe("block production", () => { }) ); }); + await Promise.all(p); const block = await blockTrigger.produceUnproven(); @@ -339,7 +340,7 @@ describe("block production", () => { const pk1 = PrivateKey.random(); const pk2 = PrivateKey.random(); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], @@ -348,7 +349,7 @@ describe("block production", () => { nonce: 0, }) ); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], @@ -414,7 +415,7 @@ describe("block production", () => { const pk1 = PrivateKey.fromBase58(pk1string); const pk2 = PrivateKey.fromBase58(pk2string); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], @@ -426,7 +427,7 @@ describe("block production", () => { await blockTrigger.produceBlock(); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], @@ -466,7 +467,7 @@ describe("block production", () => { for (let i = 0; i < batches; i++) { for (let j = 0; j < blocksPerBatch; j++) { for (let k = 0; k < txsPerBlock; k++) { - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "addBalance"], @@ -506,7 +507,7 @@ describe("block production", () => { const field = Field(100); - mempool.add( + await mempool.add( createTransaction({ runtime, method: ["Balance", "lotOfSTs"], diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index bd149493a..a619cf9b2 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -17,11 +17,15 @@ import { HistoricalBlockStorage, InMemoryDatabase, Sequencer, + SequencerModule, StateEntry, StateRecord, + StorageDependencyFactory, + TransactionStorage, } from "../../src"; import { collectStateDiff, createTransaction, expectDefined } from "./utils"; import { Bool, Field, PrivateKey, UInt64 } from "o1js"; +import { DependencyFactory, TypedClass } from "@proto-kit/common"; function checkStateDiffEquality(stateDiff: StateRecord, state: StateEntry[]) { return Object.entries(stateDiff) @@ -41,7 +45,10 @@ function checkStateDiffEquality(stateDiff: StateRecord, state: StateEntry[]) { describe.each([["InMemory", InMemoryDatabase]])( "Storage Adapter Test %s", - (testName, Database) => { + ( + testName, + Database: TypedClass + ) => { let appChain: AppChain< { Balance: typeof Balance }, ProtocolCustomModulesRecord, @@ -61,6 +68,7 @@ describe.each([["InMemory", InMemoryDatabase]])( const sk = PrivateKey.random(); const pk = sk.toPublicKey(); + let pkNonce = 0; beforeAll(async () => { const sequencerClass = testingSequencerFromModules({ @@ -118,13 +126,13 @@ describe.each([["InMemory", InMemoryDatabase]])( }); it("test unproven block prod", async () => { - appChain.sequencer.resolve("Mempool").add( + await appChain.sequencer.resolve("Mempool").add( createTransaction({ runtime, method: ["Balance", "setBalanceIf"], privateKey: sk, args: [pk, UInt64.from(100), Bool(true)], - nonce: 0, + nonce: pkNonce++, }) ); @@ -187,5 +195,28 @@ describe.each([["InMemory", InMemoryDatabase]])( 1 ); }); + + it("mempool + transaction storage", async () => { + const mempool = sequencer.resolve("Mempool"); + const txStorage = sequencer.resolve("TransactionStorage"); + + const tx = createTransaction({ + runtime, + method: ["Balance", "setBalanceIf"], + privateKey: sk, + args: [pk, UInt64.from(100), Bool(true)], + nonce: pkNonce++, + }); + await mempool.add(tx); + + const txs = await txStorage.getPendingUserTransactions(); + + expect(txs).toHaveLength(1); + expect(txs[0].hash().toString()).toStrictEqual(tx.hash().toString()); + + await sequencer.resolve("BlockTrigger").produceUnproven(); + + expect(txStorage.getPendingUserTransactions()).resolves.toHaveLength(0); + }); } ); From 7dccd0c1c4e2cfe05de80ff45d5c479d6f554a65 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 14 Feb 2024 14:51:33 +0100 Subject: [PATCH 49/54] Fixed wrong starting point for block heights --- .../src/services/prisma/PrismaBlockStorage.ts | 22 +++++----------- packages/sdk/test/graphql/server.ts | 25 +++++++++++++------ .../unproven/TransactionExecutionService.ts | 3 ++- .../integration/StorageIntegration.test.ts | 12 +++++++-- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index e921a0580..60605b454 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -58,9 +58,7 @@ export class PrismaBlockStorage return undefined; } const transactions = result.transactions.map( - (txresult) => { - return this.transactionResultMapper.mapIn([txresult, txresult.tx]); - } + (txresult) => this.transactionResultMapper.mapIn([txresult, txresult.tx]) ); if (result.metadata === undefined || result.metadata === null) { throw new Error(`No Metadata has been set for block ${where} yet`); @@ -79,9 +77,7 @@ export class PrismaBlockStorage return (await this.getBlockByQuery({ height }))?.block; } - public async getBlock( - hash: string - ): Promise { + public async getBlock(hash: string): Promise { return (await this.getBlockByQuery({ hash }))?.block; } @@ -110,7 +106,7 @@ export class PrismaBlockStorage data: block.transactions.map((txr) => this.transactionMapper.mapOut(txr.tx) ), - skipDuplicates: true + skipDuplicates: true, }), prismaClient.block.create({ @@ -209,10 +205,7 @@ export class PrismaBlockStorage }); const blockHashes = blocks - .flatMap((block) => [ - block.parentHash, - block.hash, - ]) + .flatMap((block) => [block.parentHash, block.hash]) .filter(filterNonNull) .filter(distinctByString); const metadata = @@ -238,14 +231,11 @@ export class PrismaBlockStorage ); if (correspondingMetadata === undefined) { - throw new Error( - `No Metadata has been set for block ${block.hash} yet` - ); + throw new Error(`No Metadata has been set for block ${block.hash} yet`); } const parentMetadata = metadata.find( - (candidate) => - candidate.blockHash === block.parentHash + (candidate) => candidate.blockHash === block.parentHash ); return { block: { diff --git a/packages/sdk/test/graphql/server.ts b/packages/sdk/test/graphql/server.ts index 2cf8e3074..4343c0850 100644 --- a/packages/sdk/test/graphql/server.ts +++ b/packages/sdk/test/graphql/server.ts @@ -175,9 +175,7 @@ export async function startServer() { url: "redis://localhost:6379", password: "password", }, - prisma: { - - } + prisma: {}, }, // Redis: { // url: "redis://localhost:6379", @@ -220,9 +218,20 @@ export async function startServer() { "EKFEMDTUV2VJwcGmCwNKde3iE1cbu7MHhzBqTmBtGAd6PdsLTifY" ); - const tx = await appChain.transaction(priv.toPublicKey(), () => { - balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - }); + const as = await appChain.query.protocol.AccountState.accountState.get( + priv.toPublicKey() + ); + const nonce = Number(as?.nonce.toString() ?? "0"); + + const tx = await appChain.transaction( + priv.toPublicKey(), + () => { + balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); + }, + { + nonce, + } + ); appChain.resolve("Signer").config.signer = priv; await tx.sign(); await tx.send(); @@ -232,12 +241,12 @@ export async function startServer() { () => { balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); }, - { nonce: 1 } + { nonce: nonce + 1 } ); await tx2.sign(); await tx2.send(); - let i = 2; + let i = nonce + 2; // setInterval(async () => { // try { diff --git a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts index 9805662b8..8f57cede4 100644 --- a/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/unproven/TransactionExecutionService.ts @@ -275,7 +275,8 @@ export class TransactionExecutionService { transactionsHash: transactionsHashList.commitment, fromEternalTransactionsHash: lastBlock.toEternalTransactionsHash, toEternalTransactionsHash: eternalTransactionsHashList.commitment, - height: lastBlock.height.add(1), + height: + lastBlock.hash.toBigInt() !== 0n ? lastBlock.height.add(1) : Field(0), fromBlockHashRoot: Field(lastMetadata.blockHashRoot), fromMessagesHash: lastBlock.toMessagesHash, toMessagesHash: incomingMessagesList.commitment, diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index a619cf9b2..418116f32 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -14,14 +14,14 @@ import { AsyncMerkleTreeStore, AsyncStateService, BlockStorage, - HistoricalBlockStorage, + HistoricalBlockStorage, HistoricalUnprovenBlockStorage, InMemoryDatabase, Sequencer, SequencerModule, StateEntry, StateRecord, StorageDependencyFactory, - TransactionStorage, + TransactionStorage, UnprovenBlockStorage } from "../../src"; import { collectStateDiff, createTransaction, expectDefined } from "./utils"; import { Bool, Field, PrivateKey, UInt64 } from "o1js"; @@ -155,6 +155,14 @@ describe.each([["InMemory", InMemoryDatabase]])( generatedBlock.hash.toBigInt() ); + const blockStorage = sequencer.resolve( + "UnprovenBlockStorage" + ) as HistoricalUnprovenBlockStorage & UnprovenBlockStorage; + const block2 = await blockStorage.getBlockAt(Number(blocks[0].block.block.height.toString())); + + expectDefined(block2); + expect(block2.hash.toBigInt()).toStrictEqual(generatedBlock.hash.toBigInt()) + const stateDiff = collectStateDiff( block.block.transactions.flatMap((tx) => tx.stateTransitions.concat(tx.protocolTransitions) From 586ac1683c8725a847d5588af7140e2aff8fd839 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 30 Jan 2024 19:06:24 +0100 Subject: [PATCH 50/54] Implemented transaction inclusion gql endpoint properly --- .../src/graphql/modules/MempoolResolver.ts | 48 ++++++++++++-- .../src/PrismaDatabaseConnection.ts | 4 ++ .../prisma/PrismaTransactionRepository.ts | 57 +++++++++++++++++ packages/sequencer/src/index.ts | 2 + .../src/storage/StorageDependencyFactory.ts | 2 + .../src/storage/inmemory/InMemoryDatabase.ts | 4 ++ .../inmemory/InMemoryTransactionRepository.ts | 62 +++++++++++++++++++ .../repositories/TransactionRepository.ts | 12 ++++ 8 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 packages/persistance/src/services/prisma/PrismaTransactionRepository.ts create mode 100644 packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts create mode 100644 packages/sequencer/src/storage/repositories/TransactionRepository.ts diff --git a/packages/api/src/graphql/modules/MempoolResolver.ts b/packages/api/src/graphql/modules/MempoolResolver.ts index 949528e41..4d934b0db 100644 --- a/packages/api/src/graphql/modules/MempoolResolver.ts +++ b/packages/api/src/graphql/modules/MempoolResolver.ts @@ -6,11 +6,16 @@ import { Mutation, ObjectType, Query, + registerEnumType, Resolver, } from "type-graphql"; import { inject, injectable } from "tsyringe"; import { IsNumberString } from "class-validator"; -import { Mempool, PendingTransaction } from "@proto-kit/sequencer"; +import { + Mempool, + PendingTransaction, + TransactionRepository, +} from "@proto-kit/sequencer"; import { graphqlModule, GraphqlModule } from "../GraphqlModule.js"; @@ -45,6 +50,7 @@ export class TransactionObject { isMessage, } = pt.toJSON(); return new TransactionObject( + pt.hash().toString(), methodId, sender, nonce, @@ -55,6 +61,9 @@ export class TransactionObject { ); } + @Field() + public hash: string; + @Field() @IsNumberString() public methodId: string; @@ -79,6 +88,7 @@ export class TransactionObject { public isMessage: boolean; public constructor( + hash: string, methodId: string, sender: string, nonce: string, @@ -87,6 +97,7 @@ export class TransactionObject { argsJSON: string[], isMessage: boolean ) { + this.hash = hash; this.methodId = methodId; this.sender = sender; this.nonce = nonce; @@ -97,9 +108,24 @@ export class TransactionObject { } } +enum InclusionStatus { + UNKNOWN = "unknown", + PENDING = "pending", + INCLUDED = "included", + SETTLED = "settled", +} + +registerEnumType(InclusionStatus, { + name: "InclusionStatus", +}); + @graphqlModule() export class MempoolResolver extends GraphqlModule { - public constructor(@inject("Mempool") private readonly mempool: Mempool) { + public constructor( + @inject("Mempool") private readonly mempool: Mempool, + @inject("TransactionRepository") + private readonly transactionRepository: TransactionRepository + ) { super(); } @@ -111,16 +137,26 @@ export class MempoolResolver extends GraphqlModule { return decoded.hash().toString(); } - @Query(() => String) - public async transactionState(@Arg("hash") hash: string) { + @Query(() => InclusionStatus) + public async transactionState(@Arg("hash") hash: string): Promise { const txs = await this.mempool.getTxs(); const tx = txs.find((x) => x.hash().toString() === hash); if (tx) { - return "pending"; + return InclusionStatus.PENDING; + } + + const dbTx = await this.transactionRepository.findTransaction(hash); + + if (dbTx !== undefined) { + if (dbTx.batch !== undefined) { + return InclusionStatus.SETTLED; + } else if (dbTx.block !== undefined) { + return InclusionStatus.INCLUDED; + } } - return "unknown"; + return InclusionStatus.UNKNOWN; } @Query(() => [String]) diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index a0fa93e15..6efbc1d65 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -12,6 +12,7 @@ import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStorage"; import { PrismaMessageStorage } from "./services/prisma/PrismaMessageStorage"; import { PrismaTransactionStorage } from "./services/prisma/PrismaTransactionStorage"; +import { PrismaTransactionRepository } from "./services/prisma/PrismaTransactionRepository"; export interface PrismaDatabaseConfig { connection?: { @@ -73,6 +74,9 @@ export class PrismaDatabaseConnection transactionStorage: { useClass: PrismaTransactionStorage, }, + transactionRepository: { + useClass: PrismaTransactionRepository, + }, }; } diff --git a/packages/persistance/src/services/prisma/PrismaTransactionRepository.ts b/packages/persistance/src/services/prisma/PrismaTransactionRepository.ts new file mode 100644 index 000000000..3553e85da --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaTransactionRepository.ts @@ -0,0 +1,57 @@ +import { inject, injectable } from "tsyringe"; +import { + PendingTransaction, + TransactionRepository, +} from "@proto-kit/sequencer"; + +import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; + +import { TransactionMapper } from "./mappers/TransactionMapper"; + +@injectable() +export class PrismaTransactionRepository implements TransactionRepository { + public constructor( + @inject("Database") private readonly database: PrismaDatabaseConnection, + private readonly transactionMapper: TransactionMapper + ) {} + + public async findTransaction(hash: string): Promise< + | { + transaction: PendingTransaction; + block?: string; + batch?: number; + } + | undefined + > { + const tx = await this.database.client.transaction.findFirst({ + where: { + hash, + }, + include: { + executionResult: { + include: { + block: { + include: { + batch: true, + }, + }, + }, + }, + }, + }); + + if (tx === null) { + return undefined; + } + + const transaction = this.transactionMapper.mapIn(tx); + const block = tx.executionResult?.block?.hash; + const batch = tx.executionResult?.block?.batch?.height; + + return { + transaction, + block, + batch, + }; + } +} diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 1d5685b0c..5dc170317 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -44,6 +44,7 @@ export * from "./storage/repositories/UnprovenBlockStorage"; export * from "./storage/repositories/SettlementStorage"; export * from "./storage/repositories/MessageStorage"; export * from "./storage/repositories/TransactionStorage"; +export * from "./storage/repositories/TransactionRepository"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; @@ -51,6 +52,7 @@ export * from "./storage/inmemory/InMemoryBatchStorage"; export * from "./storage/inmemory/InMemorySettlementStorage"; export * from "./storage/inmemory/InMemoryMessageStorage"; export * from "./storage/inmemory/InMemoryTransactionStorage"; +export * from "./storage/inmemory/InMemoryTransactionRepository"; export * from "./storage/StorageDependencyFactory"; export * from "./helpers/query/QueryTransportModule"; export * from "./helpers/query/QueryBuilderFactory"; diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index 108d957d2..a84907efd 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -15,6 +15,7 @@ import { import { MessageStorage } from "./repositories/MessageStorage"; import { SettlementStorage } from "./repositories/SettlementStorage"; import { TransactionStorage } from "./repositories/TransactionStorage"; +import { TransactionRepository } from "./repositories/TransactionRepository"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { asyncStateService: DependencyDeclaration; @@ -28,6 +29,7 @@ export interface StorageDependencyMinimumDependencies extends DependencyRecord { messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; transactionStorage: DependencyDeclaration; + transactionRepository: DependencyDeclaration; } export interface StorageDependencyFactory extends DependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index ee2abcefa..d7556b8d9 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -19,6 +19,7 @@ import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; import { InMemoryStateService } from "@proto-kit/module"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; +import { InMemoryTransactionRepository } from "./InMemoryTransactionRepository"; @sequencerModule() export class InMemoryDatabase @@ -60,6 +61,9 @@ export class InMemoryDatabase transactionStorage: { useClass: InMemoryTransactionStorage, }, + transactionRepository: { + useClass: InMemoryTransactionRepository + } }; } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts new file mode 100644 index 000000000..b3f35da83 --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts @@ -0,0 +1,62 @@ +import { inject, injectable } from "tsyringe"; +import { Field } from "o1js"; + +import { PendingTransaction } from "../../mempool/PendingTransaction"; +import { TransactionRepository } from "../repositories/TransactionRepository"; + +import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; +import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; + +@injectable() +export class InMemoryTransactionRepository implements TransactionRepository { + public constructor( + @inject("UnprovenBlockStorage") + private readonly blockStorage: InMemoryBlockStorage, + @inject("BlockStorage") private readonly batchStorage: InMemoryBatchStorage + ) {} + + private async findBatch(block: string): Promise { + const tipHeight = await this.batchStorage.getCurrentBlockHeight(); + + for (let height = tipHeight - 1; height >= 0; height--) { + const batch = await this.batchStorage.getBlockAt(height); + if (batch === undefined) { + return undefined; + } + if (batch.bundles.includes(block)) { + return height; + } + } + } + + public async findTransaction(hash: string): Promise< + | { + transaction: PendingTransaction; + block?: string; + batch?: number; + } + | undefined + > { + const tipHeight = await this.blockStorage.getCurrentBlockHeight(); + const hashField = Field(hash); + + for (let height = tipHeight - 1; height >= 0; height--) { + const block = await this.blockStorage.getBlockAt(height); + if (block === undefined) { + return undefined; + } + const tx = block.transactions.find((tx) => + tx.tx.hash().equals(hashField).toBoolean() + ); + if (tx !== undefined) { + const batch = await this.findBatch(block.hash.toString()); + return { + transaction: tx.tx, + block: block.transactionsHash.toString(), + batch, + }; + } + } + return undefined; + } +} diff --git a/packages/sequencer/src/storage/repositories/TransactionRepository.ts b/packages/sequencer/src/storage/repositories/TransactionRepository.ts new file mode 100644 index 000000000..2df7afa7e --- /dev/null +++ b/packages/sequencer/src/storage/repositories/TransactionRepository.ts @@ -0,0 +1,12 @@ +import { PendingTransaction } from "../../mempool/PendingTransaction"; + +export interface TransactionRepository { + findTransaction: (hash: string) => Promise< + | { + transaction: PendingTransaction; + block?: string; + batch?: number; + } + | undefined + >; +} From d552d16753cbc52fb224893f4039a0eb89a23328 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 14 Feb 2024 17:39:36 +0100 Subject: [PATCH 51/54] Merged TransactionRepository and TransactionStorage --- .../src/graphql/modules/MempoolResolver.ts | 9 +-- .../src/PrismaDatabaseConnection.ts | 4 - packages/persistance/src/index.ts | 1 + .../prisma/PrismaTransactionRepository.ts | 57 -------------- .../prisma/PrismaTransactionStorage.ts | 52 +++++++++++-- packages/sequencer/src/index.ts | 2 - .../src/storage/StorageDependencyFactory.ts | 2 - .../src/storage/inmemory/InMemoryDatabase.ts | 6 -- .../inmemory/InMemoryTransactionRepository.ts | 62 --------------- .../inmemory/InMemoryTransactionStorage.ts | 77 ++++++++++++++++--- .../repositories/TransactionRepository.ts | 12 --- .../repositories/TransactionStorage.ts | 10 +++ 12 files changed, 130 insertions(+), 164 deletions(-) delete mode 100644 packages/persistance/src/services/prisma/PrismaTransactionRepository.ts delete mode 100644 packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts delete mode 100644 packages/sequencer/src/storage/repositories/TransactionRepository.ts diff --git a/packages/api/src/graphql/modules/MempoolResolver.ts b/packages/api/src/graphql/modules/MempoolResolver.ts index 4d934b0db..6adf15b6c 100644 --- a/packages/api/src/graphql/modules/MempoolResolver.ts +++ b/packages/api/src/graphql/modules/MempoolResolver.ts @@ -13,8 +13,7 @@ import { inject, injectable } from "tsyringe"; import { IsNumberString } from "class-validator"; import { Mempool, - PendingTransaction, - TransactionRepository, + PendingTransaction, TransactionStorage } from "@proto-kit/sequencer"; import { graphqlModule, GraphqlModule } from "../GraphqlModule.js"; @@ -123,8 +122,8 @@ registerEnumType(InclusionStatus, { export class MempoolResolver extends GraphqlModule { public constructor( @inject("Mempool") private readonly mempool: Mempool, - @inject("TransactionRepository") - private readonly transactionRepository: TransactionRepository + @inject("TransactionStorage") + private readonly transactionStorage: TransactionStorage ) { super(); } @@ -146,7 +145,7 @@ export class MempoolResolver extends GraphqlModule { return InclusionStatus.PENDING; } - const dbTx = await this.transactionRepository.findTransaction(hash); + const dbTx = await this.transactionStorage.findTransaction(hash); if (dbTx !== undefined) { if (dbTx.batch !== undefined) { diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 6efbc1d65..a0fa93e15 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -12,7 +12,6 @@ import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStorage"; import { PrismaMessageStorage } from "./services/prisma/PrismaMessageStorage"; import { PrismaTransactionStorage } from "./services/prisma/PrismaTransactionStorage"; -import { PrismaTransactionRepository } from "./services/prisma/PrismaTransactionRepository"; export interface PrismaDatabaseConfig { connection?: { @@ -74,9 +73,6 @@ export class PrismaDatabaseConnection transactionStorage: { useClass: PrismaTransactionStorage, }, - transactionRepository: { - useClass: PrismaTransactionRepository, - }, }; } diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 0c979dd0f..a43740697 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -7,4 +7,5 @@ export * from "./services/prisma/PrismaBlockStorage"; export * from "./services/prisma/PrismaBatchStore"; export * from "./services/prisma/PrismaSettlementStorage"; export * from "./services/prisma/PrismaMessageStorage"; +export * from "./services/prisma/PrismaTransactionStorage"; export * from "./services/redis/RedisMerkleTreeStore"; diff --git a/packages/persistance/src/services/prisma/PrismaTransactionRepository.ts b/packages/persistance/src/services/prisma/PrismaTransactionRepository.ts deleted file mode 100644 index 3553e85da..000000000 --- a/packages/persistance/src/services/prisma/PrismaTransactionRepository.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { inject, injectable } from "tsyringe"; -import { - PendingTransaction, - TransactionRepository, -} from "@proto-kit/sequencer"; - -import type { PrismaDatabaseConnection } from "../../PrismaDatabaseConnection"; - -import { TransactionMapper } from "./mappers/TransactionMapper"; - -@injectable() -export class PrismaTransactionRepository implements TransactionRepository { - public constructor( - @inject("Database") private readonly database: PrismaDatabaseConnection, - private readonly transactionMapper: TransactionMapper - ) {} - - public async findTransaction(hash: string): Promise< - | { - transaction: PendingTransaction; - block?: string; - batch?: number; - } - | undefined - > { - const tx = await this.database.client.transaction.findFirst({ - where: { - hash, - }, - include: { - executionResult: { - include: { - block: { - include: { - batch: true, - }, - }, - }, - }, - }, - }); - - if (tx === null) { - return undefined; - } - - const transaction = this.transactionMapper.mapIn(tx); - const block = tx.executionResult?.block?.hash; - const batch = tx.executionResult?.block?.batch?.height; - - return { - transaction, - block, - batch, - }; - } -} diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts index 4bd3a9b50..8592ea71e 100644 --- a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -18,14 +18,14 @@ export class PrismaTransactionStorage implements TransactionStorage { const txs = await prismaClient.transaction.findMany({ where: { executionResult: { - is: null + is: null, }, isMessage: { - equals: false - } + equals: false, + }, }, - }) - return txs.map(tx => this.transactionMapper.mapIn(tx)); + }); + return txs.map((tx) => this.transactionMapper.mapIn(tx)); } public async pushUserTransaction(tx: PendingTransaction): Promise { @@ -38,4 +38,46 @@ export class PrismaTransactionStorage implements TransactionStorage { return result.count === 1; } + + public async findTransaction(hash: string): Promise< + | { + transaction: PendingTransaction; + block?: string; + batch?: number; + } + | undefined + > { + const { prismaClient } = this.connection; + + const tx = await prismaClient.transaction.findFirst({ + where: { + hash, + }, + include: { + executionResult: { + include: { + block: { + include: { + batch: true, + }, + }, + }, + }, + }, + }); + + if (tx === null) { + return undefined; + } + + const transaction = this.transactionMapper.mapIn(tx); + const block = tx.executionResult?.block?.hash; + const batch = tx.executionResult?.block?.batch?.height; + + return { + transaction, + block, + batch, + }; + } } diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 5dc170317..1d5685b0c 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -44,7 +44,6 @@ export * from "./storage/repositories/UnprovenBlockStorage"; export * from "./storage/repositories/SettlementStorage"; export * from "./storage/repositories/MessageStorage"; export * from "./storage/repositories/TransactionStorage"; -export * from "./storage/repositories/TransactionRepository"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; @@ -52,7 +51,6 @@ export * from "./storage/inmemory/InMemoryBatchStorage"; export * from "./storage/inmemory/InMemorySettlementStorage"; export * from "./storage/inmemory/InMemoryMessageStorage"; export * from "./storage/inmemory/InMemoryTransactionStorage"; -export * from "./storage/inmemory/InMemoryTransactionRepository"; export * from "./storage/StorageDependencyFactory"; export * from "./helpers/query/QueryTransportModule"; export * from "./helpers/query/QueryBuilderFactory"; diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index a84907efd..108d957d2 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -15,7 +15,6 @@ import { import { MessageStorage } from "./repositories/MessageStorage"; import { SettlementStorage } from "./repositories/SettlementStorage"; import { TransactionStorage } from "./repositories/TransactionStorage"; -import { TransactionRepository } from "./repositories/TransactionRepository"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { asyncStateService: DependencyDeclaration; @@ -29,7 +28,6 @@ export interface StorageDependencyMinimumDependencies extends DependencyRecord { messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; transactionStorage: DependencyDeclaration; - transactionRepository: DependencyDeclaration; } export interface StorageDependencyFactory extends DependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index d7556b8d9..cd1e9a67e 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -2,7 +2,6 @@ import { noop } from "@proto-kit/common"; import { CachedStateService } from "../../state/state/CachedStateService"; -import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { sequencerModule, SequencerModule, @@ -17,9 +16,7 @@ import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; -import { InMemoryStateService } from "@proto-kit/module"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; -import { InMemoryTransactionRepository } from "./InMemoryTransactionRepository"; @sequencerModule() export class InMemoryDatabase @@ -61,9 +58,6 @@ export class InMemoryDatabase transactionStorage: { useClass: InMemoryTransactionStorage, }, - transactionRepository: { - useClass: InMemoryTransactionRepository - } }; } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts deleted file mode 100644 index b3f35da83..000000000 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionRepository.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { inject, injectable } from "tsyringe"; -import { Field } from "o1js"; - -import { PendingTransaction } from "../../mempool/PendingTransaction"; -import { TransactionRepository } from "../repositories/TransactionRepository"; - -import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; -import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; - -@injectable() -export class InMemoryTransactionRepository implements TransactionRepository { - public constructor( - @inject("UnprovenBlockStorage") - private readonly blockStorage: InMemoryBlockStorage, - @inject("BlockStorage") private readonly batchStorage: InMemoryBatchStorage - ) {} - - private async findBatch(block: string): Promise { - const tipHeight = await this.batchStorage.getCurrentBlockHeight(); - - for (let height = tipHeight - 1; height >= 0; height--) { - const batch = await this.batchStorage.getBlockAt(height); - if (batch === undefined) { - return undefined; - } - if (batch.bundles.includes(block)) { - return height; - } - } - } - - public async findTransaction(hash: string): Promise< - | { - transaction: PendingTransaction; - block?: string; - batch?: number; - } - | undefined - > { - const tipHeight = await this.blockStorage.getCurrentBlockHeight(); - const hashField = Field(hash); - - for (let height = tipHeight - 1; height >= 0; height--) { - const block = await this.blockStorage.getBlockAt(height); - if (block === undefined) { - return undefined; - } - const tx = block.transactions.find((tx) => - tx.tx.hash().equals(hashField).toBoolean() - ); - if (tx !== undefined) { - const batch = await this.findBatch(block.hash.toString()); - return { - transaction: tx.tx, - block: block.transactionsHash.toString(), - batch, - }; - } - } - return undefined; - } -} diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts index 127ccbb24..19b00e010 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -3,8 +3,10 @@ import { TransactionStorage } from "../repositories/TransactionStorage"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { HistoricalUnprovenBlockStorage, - UnprovenBlockStorage + UnprovenBlockStorage, } from "../repositories/UnprovenBlockStorage"; +import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; +import { Field } from "o1js"; @injectable() export class InMemoryTransactionStorage implements TransactionStorage { @@ -13,16 +15,25 @@ export class InMemoryTransactionStorage implements TransactionStorage { private latestScannedBlock = -1; public constructor( - @inject("UnprovenBlockStorage") private readonly blockStorage: UnprovenBlockStorage & HistoricalUnprovenBlockStorage + @inject("UnprovenBlockStorage") + private readonly blockStorage: UnprovenBlockStorage & + HistoricalUnprovenBlockStorage, + @inject("BlockStorage") private readonly batchStorage: InMemoryBatchStorage ) {} public async getPendingUserTransactions(): Promise { - const nextHeight = await this.blockStorage.getCurrentBlockHeight() - for(let height = this.latestScannedBlock + 1 ; height < nextHeight ; height++) { + const nextHeight = await this.blockStorage.getCurrentBlockHeight(); + for ( + let height = this.latestScannedBlock + 1; + height < nextHeight; + height++ + ) { const block = await this.blockStorage.getBlockAt(height); - if (block !== undefined){ - const hashes = block.transactions.map(tx => tx.tx.hash().toString()) - this.queue = this.queue.filter(tx => !hashes.includes(tx.hash().toString())) + if (block !== undefined) { + const hashes = block.transactions.map((tx) => tx.tx.hash().toString()); + this.queue = this.queue.filter( + (tx) => !hashes.includes(tx.hash().toString()) + ); } } this.latestScannedBlock = nextHeight - 1; @@ -31,10 +42,58 @@ export class InMemoryTransactionStorage implements TransactionStorage { } public async pushUserTransaction(tx: PendingTransaction): Promise { - const notInQueue = this.queue.find(tx2 => tx2.hash().toString() === tx.hash().toString()) === undefined - if(notInQueue){ + const notInQueue = + this.queue.find( + (tx2) => tx2.hash().toString() === tx.hash().toString() + ) === undefined; + if (notInQueue) { this.queue.push(tx); } return notInQueue; } + + private async findBatch(block: string): Promise { + const tipHeight = await this.batchStorage.getCurrentBlockHeight(); + + for (let height = tipHeight - 1; height >= 0; height--) { + const batch = await this.batchStorage.getBlockAt(height); + if (batch === undefined) { + return undefined; + } + if (batch.bundles.includes(block)) { + return height; + } + } + } + + public async findTransaction(hash: string): Promise< + | { + transaction: PendingTransaction; + block?: string; + batch?: number; + } + | undefined + > { + const tipHeight = await this.blockStorage.getCurrentBlockHeight(); + const hashField = Field(hash); + + for (let height = tipHeight - 1; height >= 0; height--) { + const block = await this.blockStorage.getBlockAt(height); + if (block === undefined) { + return undefined; + } + const tx = block.transactions.find((tx) => + tx.tx.hash().equals(hashField).toBoolean() + ); + if (tx !== undefined) { + const batch = await this.findBatch(block.hash.toString()); + return { + transaction: tx.tx, + block: block.transactionsHash.toString(), + batch, + }; + } + } + return undefined; + } } diff --git a/packages/sequencer/src/storage/repositories/TransactionRepository.ts b/packages/sequencer/src/storage/repositories/TransactionRepository.ts deleted file mode 100644 index 2df7afa7e..000000000 --- a/packages/sequencer/src/storage/repositories/TransactionRepository.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PendingTransaction } from "../../mempool/PendingTransaction"; - -export interface TransactionRepository { - findTransaction: (hash: string) => Promise< - | { - transaction: PendingTransaction; - block?: string; - batch?: number; - } - | undefined - >; -} diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts index 2e88f7608..858d82ad6 100644 --- a/packages/sequencer/src/storage/repositories/TransactionStorage.ts +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -2,5 +2,15 @@ import { PendingTransaction } from "../../mempool/PendingTransaction"; export interface TransactionStorage { pushUserTransaction: (tx: PendingTransaction) => Promise; + getPendingUserTransactions: () => Promise; + + findTransaction: (hash: string) => Promise< + | { + transaction: PendingTransaction; + block?: string; + batch?: number; + } + | undefined + >; } From 0e545e0845f96e704e6bc21a61a9fe7976cf6da8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 14 Feb 2024 20:47:57 +0100 Subject: [PATCH 52/54] Refactoring --- .../src/graphql/modules/MempoolResolver.ts | 16 - packages/persistance/prisma/schema.prisma | 9 +- packages/persistance/src/index.ts | 1 - .../services/prisma/PrismaMerkleTreeStore.ts | 70 ---- .../services/redis/RedisMerkleTreeStore.ts | 37 +- packages/persistance/test/connection.test.ts | 93 +++++ packages/persistance/test/run.test.ts | 139 -------- .../src/storage/inmemory/InMemoryDatabase.ts | 2 - packages/stack/jest.config.cjs | 1 - packages/stack/package.json | 41 --- packages/stack/test/start/run-stack.test.ts | 9 - packages/stack/test/start/start.ts | 337 ------------------ packages/stack/tsconfig.json | 10 - packages/stack/tsconfig.test.json | 12 - 14 files changed, 107 insertions(+), 670 deletions(-) delete mode 100644 packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts create mode 100644 packages/persistance/test/connection.test.ts delete mode 100644 packages/persistance/test/run.test.ts delete mode 100644 packages/stack/jest.config.cjs delete mode 100644 packages/stack/package.json delete mode 100644 packages/stack/test/start/run-stack.test.ts delete mode 100644 packages/stack/test/start/start.ts delete mode 100644 packages/stack/tsconfig.json delete mode 100644 packages/stack/tsconfig.test.json diff --git a/packages/api/src/graphql/modules/MempoolResolver.ts b/packages/api/src/graphql/modules/MempoolResolver.ts index 949528e41..8c0cec223 100644 --- a/packages/api/src/graphql/modules/MempoolResolver.ts +++ b/packages/api/src/graphql/modules/MempoolResolver.ts @@ -128,20 +128,4 @@ export class MempoolResolver extends GraphqlModule { const txs = await this.mempool.getTxs(); return txs.map((x) => x.hash().toString()); } - - // @Query(returns => [TransactionObject]) - // transaction( - // @Arg("hash") hash: string - // ){ - // - // eslint-disable-next-line max-len - // let tx = this.mempool.getTxs().txs.find(x => x.hash().toString() === hash) //TODO Not very performant - // - // if(tx){ - // let parsed = tx.toJSON() - // return [parsed] - // }else{ - // return [] - // } - // } } diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index c2897f777..2419b0fb1 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -3,6 +3,7 @@ generator client { provider = "prisma-client-js" + // Enable after upgrade to 5.9.0 // previewFeatures = ["relationJoins"] } @@ -19,14 +20,6 @@ model State { @@id([path, mask]) } -model TreeElement { - key Decimal @db.Decimal(78, 0) - level Int @db.SmallInt - value Decimal @db.Decimal(78, 0) - - @@id([key, level]) -} - model Transaction { hash String @id diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 0c979dd0f..588a32cc4 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -1,7 +1,6 @@ export * from "./PrismaDatabaseConnection"; export * from "./RedisConnection"; export * from "./PrismaRedisDatabase"; -export * from "./services/prisma/PrismaMerkleTreeStore"; export * from "./services/prisma/PrismaStateService"; export * from "./services/prisma/PrismaBlockStorage"; export * from "./services/prisma/PrismaBatchStore"; diff --git a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts b/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts deleted file mode 100644 index 85ed864e4..000000000 --- a/packages/persistance/src/services/prisma/PrismaMerkleTreeStore.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - AsyncMerkleTreeStore, - MerkleTreeNode, - MerkleTreeNodeQuery, -} from "@proto-kit/sequencer"; -import { noop } from "@proto-kit/common"; -import { Prisma } from "@prisma/client"; - -import type { PrismaConnection } from "../../PrismaDatabaseConnection"; - -/** - * @deprecated - */ -export class PrismaMerkleTreeStore implements AsyncMerkleTreeStore { - private cache: MerkleTreeNode[] = []; - - public constructor(private readonly connection: PrismaConnection) {} - - public async openTransaction(): Promise { - noop(); - } - - public async commit(): Promise { - // TODO Filter distinct - - const start = Date.now(); - const array: [Prisma.Decimal, number, Prisma.Decimal][] = this.cache.map( - ({ key, level, value }) => [ - new Prisma.Decimal(key.toString()), - level, - new Prisma.Decimal(value.toString()), - ] - ); - console.log(`Took ${Date.now() - start} ms`) - - const start2 = Date.now(); - const rows = await this.connection.prismaClient.$executeRaw( - Prisma.sql`INSERT INTO "TreeElement" (key, level, value) VALUES ${Prisma.join( - array.map((entry) => Prisma.sql`(${Prisma.join(entry)})`) - )} ON CONFLICT ON CONSTRAINT "TreeElement_pkey" DO UPDATE SET value = EXCLUDED.value;` - ); - console.log(`Took ${Date.now() - start2} ms`) - console.log(`Rows: ${rows}`) - - this.cache = []; - } - - public async getNodesAsync( - nodes: MerkleTreeNodeQuery[] - ): Promise<(bigint | undefined)[]> { - // this.connection.client.treeElement.create({ - // data: { - // key: new Prisma.Decimal(123) - // } - // }) - // const result = await this.connection.client.treeElement.findMany({ - // where: { - // - // key: { - // in: nodes.map(x => x.key) - // } - // } - // }) - return Array(nodes.length).fill(undefined); - } - - public writeNodes(nodes: MerkleTreeNode[]): void { - this.cache = this.cache.concat(nodes); - } -} diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index b45d154b9..5907b44ee 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -4,9 +4,8 @@ import { MerkleTreeNodeQuery, } from "@proto-kit/sequencer"; import { log, noop } from "@proto-kit/common"; -import { inject, injectable } from "tsyringe"; -import { RedisConnection } from "../../RedisConnection"; +import type { RedisConnection } from "../../RedisConnection"; export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { private cache: MerkleTreeNode[] = []; @@ -16,42 +15,31 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { private readonly mask: string = "base" ) {} - public async openTransaction(): Promise { - noop(); - } - private getKey(node: MerkleTreeNodeQuery): string { return `${this.mask}:${node.level}:${node.key.toString()}`; } + public async openTransaction(): Promise { + noop(); + } + public async commit(): Promise { const start = Date.now(); const array: [string, string][] = this.cache.map( ({ key, level, value }) => [this.getKey({ key, level }), value.toString()] ); - const start2 = Date.now(); - if (array.length === 0) { return; } try { - // console.log(array.slice(0, 5).flat(1)); - await this.connection.redisClient!.mSet(array.flat(1)); - } catch (e) { - if (e instanceof Error) { - console.log(e.name); - console.log(e.message); - console.log(e.stack); - } else { - console.log(e); - } + await this.connection.redisClient.mSet(array.flat(1)); + } catch (error) { + log.error(error); } - log.debug( - `Committing ${array.length} kv-pairs took ${ - Date.now() - start - } ms (preparing the input was ${start2 - start} ms)` + log.trace( + `Committing ${array.length} kv-pairs took ${Date.now() - start} ms` ); this.cache = []; @@ -60,13 +48,13 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { public async getNodesAsync( nodes: MerkleTreeNodeQuery[] ): Promise<(bigint | undefined)[]> { - if(nodes.length === 0){ + if (nodes.length === 0) { return []; } const keys = nodes.map((node) => this.getKey(node)); - const result = await this.connection.redisClient!.mGet(keys); + const result = await this.connection.redisClient.mGet(keys); return result.map((x) => (x !== null ? BigInt(x) : undefined)); } @@ -77,6 +65,7 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { // We might not even need this, since the distinctness filter might already // be implicitely done by the layer above (i.e. cachedmtstore) + // Leaving this for now until I get to implementing it // const concat = this.cache.concat(nodes); // const reversed = concat.slice().reverse(); // this.cache = concat.filter((node, index) => { diff --git a/packages/persistance/test/connection.test.ts b/packages/persistance/test/connection.test.ts new file mode 100644 index 000000000..543ff25d3 --- /dev/null +++ b/packages/persistance/test/connection.test.ts @@ -0,0 +1,93 @@ +import "reflect-metadata"; +import { describe } from "@jest/globals"; +import { PrismaDatabaseConnection } from "../src/PrismaDatabaseConnection"; +import { PrismaStateService } from "../src/services/prisma/PrismaStateService"; +import { Bool, Field, PrivateKey, PublicKey, Signature } from "o1js"; +import { RedisConnectionModule } from "../src"; +import { + CachedMerkleTreeStore, +} from "@proto-kit/sequencer"; +import { RedisMerkleTreeStore } from "../src/services/redis/RedisMerkleTreeStore"; +import { expectDefined } from "@proto-kit/sequencer/test/integration/utils"; +import { RollupMerkleTree } from "@proto-kit/common"; + +// TODO Pull apart and test properly +describe("prisma", () => { + it("merkle store", async () => { + const db = new RedisConnectionModule(); + db.config = { + url: "redis://localhost:6379/", + password: "password", + }; + await db.start(); + const store = new RedisMerkleTreeStore(db); + + const cached = new CachedMerkleTreeStore(store); + const tree = new RollupMerkleTree(cached); + + await cached.preloadKey(100n); + await cached.preloadKey(500n); + + tree.setLeaf(100n, Field(6)); + await cached.mergeIntoParent(); + + tree.setLeaf(500n, Field(100)); + await cached.mergeIntoParent(); + + console.log(`Root ${tree.getRoot().toBigInt()}`); + + const store2 = new RedisMerkleTreeStore(db); + + const cached2 = new CachedMerkleTreeStore(store2); + const tree2 = new RollupMerkleTree(cached2); + + await cached2.preloadKey(103n); + + expect(tree2.getRoot().toBigInt()).toStrictEqual(tree.getRoot().toBigInt()); + + await cached2.preloadKey(100n); + const witness = tree2.getWitness(100n); + + const witnessRoot = witness.calculateRoot(Field(6)); + expect(witnessRoot.toBigInt()).toStrictEqual(tree.getRoot().toBigInt()); + + await db.redisClient.disconnect(); + }); + + it("fill and get", async () => { + const db = new PrismaDatabaseConnection(); + db.config = {} + await db.start(); + const service = new PrismaStateService(db, "testMask"); + + await service.openTransaction(); + service.writeStates([ + { + key: Field(5), + value: [Field(100)], + }, + ]); + await service.commit(); + + const result = await service.getSingleAsync(Field(5)); + console.log(`Received ${result?.map((x) => x.toString())}`); + + expectDefined(result); + + expect(result[0].toBigInt()).toBe(100n); + + await service.openTransaction(); + service.writeStates([ + { + key: Field(5), + value: undefined, + }, + ]); + await service.commit(); + + const result2 = await service.getSingleAsync(Field(5)); + expect(result2).toBeUndefined(); + + await db.prismaClient.$disconnect(); + }); +}); diff --git a/packages/persistance/test/run.test.ts b/packages/persistance/test/run.test.ts deleted file mode 100644 index 3cee25363..000000000 --- a/packages/persistance/test/run.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import "reflect-metadata"; -import { describe } from "@jest/globals"; -import { PrismaDatabaseConnection } from "../src/PrismaDatabaseConnection"; -import { PrismaStateService } from "../src/services/prisma/PrismaStateService"; -import { Bool, Field, PrivateKey, PublicKey, Signature } from "o1js"; -import { PrismaBlockStorage, PrismaMerkleTreeStore } from "../src"; -import { - CachedMerkleTreeStore, - PendingTransaction, - UnprovenBlock, - UntypedStateTransition, -} from "@proto-kit/sequencer"; -import { - NetworkState, - Option, - RollupMerkleTree, - StateTransition, -} from "@proto-kit/protocol"; -import { RedisConnection } from "../src/RedisConnection"; -import { RedisMerkleTreeStore } from "../src/services/redis/RedisMerkleTreeStore"; -import { container } from "tsyringe"; - -describe("prisma", () => { - it.only("block", async () => { - const db = new PrismaDatabaseConnection(); - const c = container.createChildContainer(); - c.register("Database", { useValue: db }); - - const storage = c.resolve(PrismaBlockStorage); - const block: UnprovenBlock = { - transactionsHash: Field("123"), - transactions: [ - { - status: Bool(true), - stateTransitions: [ - UntypedStateTransition.fromStateTransition( - StateTransition.fromTo( - Field(555), - Option.fromValue(Field(5), Field), - Option.fromValue(Field(10), Field) - ) - ), - ], - protocolTransitions: [], - tx: PendingTransaction.fromJSON({ - nonce: "1", - args: ["535"], - methodId: "0", - sender: PrivateKey.random().toPublicKey().toBase58(), - signature: Signature.create(PrivateKey.random(), [ - Field(0), - ]).toJSON(), - }), - }, - ], - networkState: NetworkState.empty(), - previousBlockTransactionsHash: undefined - }; - await storage.pushBlock(block); - }); - - it("merkle store", async () => { - const db = new RedisConnection(); - db.config = { - url: "redis://localhost:6379/", - password: "password", - }; - await db.start(); - const store = new RedisMerkleTreeStore(db); - - const cached = new CachedMerkleTreeStore(store); - const tree = new RollupMerkleTree(cached); - - await cached.preloadKey(100n); - await cached.preloadKey(500n); - - tree.setLeaf(100n, Field(6)); - await cached.mergeIntoParent(); - - tree.setLeaf(500n, Field(100)); - await cached.mergeIntoParent(); - - console.log(`Root ${tree.getRoot().toBigInt()}`); - - const store2 = new RedisMerkleTreeStore(db); - - const cached2 = new CachedMerkleTreeStore(store2); - const tree2 = new RollupMerkleTree(cached2); - - await cached2.preloadKey(103n); - - expect(tree2.getRoot().toBigInt()).toStrictEqual(tree.getRoot().toBigInt()); - - await cached2.preloadKey(100n); - const witness = tree2.getWitness(100n); - - const witnessRoot = witness.calculateRoot(Field(6)); - expect(witnessRoot.toBigInt()).toStrictEqual(tree.getRoot().toBigInt()); - - await db.client!.disconnect(); - }); - - it.skip("merkle store", async () => { - const db = new PrismaDatabaseConnection(); - const store = new PrismaMerkleTreeStore(db); - - const cached = new CachedMerkleTreeStore(store); - const tree = new RollupMerkleTree(cached); - - tree.setLeaf(10n, Field(6)); - await cached.mergeIntoParent(); - - // store.writeNodes([{ key: 1n, level: 0, value: 15n }]); - // store.writeNodes([{ key: 2n, level: 0, value: 2n ** 244n }]); - // await store.commit(); - }); - - it.skip("fill and get", async () => { - const db = new PrismaDatabaseConnection(); - const service = new PrismaStateService(db); - - await service.setAsync(Field(5), [Field(100)]); - await service.commit(); - - const result = await service.getAsync(Field(5)); - console.log(`Received ${result?.map((x) => x.toString())}`); - - expect(result).toBeDefined(); - expect(result![0].toBigInt()).toBe(100n); - - await service.setAsync(Field(5), undefined); - await service.commit(); - - const result2 = await service.getAsync(Field(5)); - expect(result2).toBeUndefined(); - - await db.client.$disconnect(); - }); -}); diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index ee2abcefa..cd1e9a67e 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -2,7 +2,6 @@ import { noop } from "@proto-kit/common"; import { CachedStateService } from "../../state/state/CachedStateService"; -import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; import { sequencerModule, SequencerModule, @@ -17,7 +16,6 @@ import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; -import { InMemoryStateService } from "@proto-kit/module"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; @sequencerModule() diff --git a/packages/stack/jest.config.cjs b/packages/stack/jest.config.cjs deleted file mode 100644 index 87e143bf4..000000000 --- a/packages/stack/jest.config.cjs +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("../../jest.config.cjs"); diff --git a/packages/stack/package.json b/packages/stack/package.json deleted file mode 100644 index 669ab57ff..000000000 --- a/packages/stack/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@proto-kit/stack", - "license": "MIT", - "private": false, - "type": "module", - "version": "0.1.1-develop.267+b252853", - "scripts": { - "build": "tsc -p tsconfig.json", - "dev": "tsc -p tsconfig.json --watch", - "lint": "eslint ./src ./test", - "test:file": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads ../../node_modules/jest/bin/jest.js", - "test": "npm run test:file -- ./src/** ./test/**", - "test:watch": "npm run test:file -- ./src/** ./test/** --watch", - "start": "npm run test:file -- test/start/run-stack.test.ts" - }, - "main": "dist/index.js", - "publishConfig": { - "access": "public" - }, - "dependencies": { - "lodash": "^4.17.21", - "loglevel": "^1.8.1" - }, - "peerDependencies": { - "@proto-kit/common": "*", - "@proto-kit/module": "*", - "@proto-kit/protocol": "*", - "@proto-kit/sequencer": "*", - "@proto-kit/sdk": "*", - "@proto-kit/api": "*", - "@proto-kit/persistance": "*", - "reflect-metadata": "^0.1.13", - "o1js": "0.13.1", - "tsyringe": "^4.7.0" - }, - "devDependencies": { - "@jest/globals": "^29.5.0", - "@types/lodash": "^4.14.194" - }, - "gitHead": "b2528538c73747d000cc3ea99ee26ee415d8248d" -} diff --git a/packages/stack/test/start/run-stack.test.ts b/packages/stack/test/start/run-stack.test.ts deleted file mode 100644 index 00ac8e976..000000000 --- a/packages/stack/test/start/run-stack.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { startServer } from "./start"; -import { sleep } from "@proto-kit/common"; - -describe("run graphql", () => { - it("run", async () => { - const server = await startServer(); - await sleep(1000000000); - }, 1000000000); -}); diff --git a/packages/stack/test/start/start.ts b/packages/stack/test/start/start.ts deleted file mode 100644 index c6962a68b..000000000 --- a/packages/stack/test/start/start.ts +++ /dev/null @@ -1,337 +0,0 @@ -import "reflect-metadata"; -import { Field, PrivateKey, PublicKey, UInt64 } from "o1js"; -import { - Runtime, - runtimeMethod, - RuntimeModule, - runtimeModule, - state, -} from "@proto-kit/module"; -import { - AccountStateModule, - BlockHeightHook, - Option, - State, - StateMap, - VanillaProtocol, -} from "@proto-kit/protocol"; -import { Presets, log, sleep } from "@proto-kit/common"; -import { - AsyncStateService, - BlockProducerModule, - InMemoryDatabase, - LocalTaskQueue, - LocalTaskWorkerModule, - ManualBlockTrigger, - NoopBaseLayer, - PendingTransaction, - PrivateMempool, - Sequencer, - TimedBlockTrigger, - UnprovenProducerModule, - UnsignedTransaction, -} from "@proto-kit/sequencer"; -import { - BlockStorageResolver, - GraphqlSequencerModule, - GraphqlServer, - MempoolResolver, - NodeStatusResolver, - QueryGraphqlModule, - UnprovenBlockResolver, -} from "@proto-kit/api"; - -import { container } from "tsyringe"; -import { - PrismaBlockStorage, - PrismaDatabaseConnection, -} from "@proto-kit/persistance"; -import { RedisConnection } from "@proto-kit/persistance/dist/RedisConnection"; -import { - AppChain, - BlockStorageNetworkStateModule, - InMemorySigner, - InMemoryTransactionSender, - StateServiceQueryModule, -} from "@proto-kit/sdk"; - -log.setLevel(log.levels.INFO); - -function createNewTx() { - const pk = PrivateKey.random(); - - const tx = new UnsignedTransaction({ - nonce: UInt64.zero, - args: [Field(1)], - methodId: Field(1), - sender: pk.toPublicKey(), - }).sign(pk); - - console.log(tx.toJSON()); -} -createNewTx(); - -@runtimeModule() -export class Balances extends RuntimeModule { - /** - * We use `satisfies` here in order to be able to access - * presets by key in a type safe way. - */ - public static presets = {} satisfies Presets; - - @state() public balances = StateMap.from( - PublicKey, - UInt64 - ); - - @state() public totalSupply = State.from(UInt64); - - @runtimeMethod() - public getBalance(address: PublicKey): Option { - return this.balances.get(address); - } - - @runtimeMethod() - public addBalance(address: PublicKey, balance: UInt64) { - const totalSupply = this.totalSupply.get(); - this.totalSupply.set(totalSupply.orElse(UInt64.zero).add(balance)); - - const previous = this.balances.get(address); - this.balances.set(address, previous.orElse(UInt64.zero).add(balance)); - } -} - -export async function startServer() { - log.setLevel("DEBUG"); - - const appChain = AppChain.from({ - runtime: Runtime.from({ - modules: { - Balances, - }, - - config: { - Balances: {}, - }, - }), - - protocol: VanillaProtocol.from( - { AccountStateModule, BlockHeightHook }, - { - AccountStateModule: {}, - StateTransitionProver: {}, - BlockProver: {}, - BlockHeightHook: {}, - } - ), - - sequencer: Sequencer.from({ - modules: { - Database: PrismaDatabaseConnection, - Redis: RedisConnection, - // Database: InMemoryDatabase, - Mempool: PrivateMempool, - GraphqlServer, - LocalTaskWorkerModule, - BaseLayer: NoopBaseLayer, - BlockProducerModule, - UnprovenProducerModule, - BlockTrigger: TimedBlockTrigger, - // BlockTrigger: ManualBlockTrigger, - TaskQueue: LocalTaskQueue, - - Graphql: GraphqlSequencerModule.from({ - modules: { - MempoolResolver, - QueryGraphqlModule, - BlockStorageResolver, - UnprovenBlockResolver, - NodeStatusResolver, - }, - - config: { - MempoolResolver: {}, - QueryGraphqlModule: {}, - BlockStorageResolver: {}, - NodeStatusResolver: {}, - UnprovenBlockResolver: {}, - }, - }), - }, - }), - - modules: { - Signer: InMemorySigner, - TransactionSender: InMemoryTransactionSender, - QueryTransportModule: StateServiceQueryModule, - NetworkStateTransportModule: BlockStorageNetworkStateModule, - }, - }); - - appChain.configure({ - Runtime: { - Balances: {}, - }, - - Protocol: { - BlockProver: {}, - StateTransitionProver: {}, - AccountStateModule: {}, - BlockHeightHook: {}, - }, - - Sequencer: { - GraphqlServer: { - port: 8080, - host: "0.0.0.0", - graphiql: true, - }, - - Graphql: { - QueryGraphqlModule: {}, - MempoolResolver: {}, - BlockStorageResolver: {}, - NodeStatusResolver: {}, - UnprovenBlockResolver: {}, - }, - - Redis: { - url: "redis://localhost:6379/", - password: "password", - }, - - Database: {}, - Mempool: {}, - BlockProducerModule: {}, - LocalTaskWorkerModule: {}, - BaseLayer: {}, - TaskQueue: {}, - UnprovenProducerModule: {}, - - BlockTrigger: { - blockInterval: 15000, - settlementInterval: 30000, - }, - }, - - TransactionSender: {}, - QueryTransportModule: {}, - NetworkStateTransportModule: {}, - - Signer: { - signer: PrivateKey.random(), - }, - }); - - await appChain.start(container.createChildContainer()); - - const pk = PublicKey.fromBase58( - "B62qmETai5Y8vvrmWSU8F4NX7pTyPqYLMhc1pgX3wD8dGc2wbCWUcqP" - ); - console.log(pk.toJSON()); - - // const storage = appChain.sequencer.dependencyContainer.resolve("UnprovenBlockQueue"); - // const b = await storage.getNewBlocks() - // console.log() - - // await appChain.sequencer.resolve("BlockTrigger").produceProven(); - - const balances = appChain.runtime.resolve("Balances"); - - const priv = PrivateKey.fromBase58( - "EKFEMDTUV2VJwcGmCwNKde3iE1cbu7MHhzBqTmBtGAd6PdsLTifY" - ); - - // const tx = appChain.transaction(priv.toPublicKey(), () => { - // balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - // }); - // appChain.resolve("Signer").config.signer = priv; - // await tx.sign(); - // await tx.send(); - // // console.log((tx.transaction as PendingTransaction).toJSON()) - // - // const tx2 = appChain.transaction( - // priv.toPublicKey(), - // () => { - // balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - // }, - // { nonce: 1 } - // ); - // await tx2.sign(); - // await tx2.send(); - // - // const { txs } = appChain.sequencer.resolve("Mempool").getTxs(); - // console.log(txs.map((tx) => tx.toJSON())); - // console.log(txs.map((tx) => tx.hash().toString())); - // - // console.log("Path:", balances.balances.getPath(pk).toString()); - - // const trigger = appChain.sequencer.resolve("BlockTrigger"); - // await trigger.produceBlock(); - // - // for (let i = 0; i < 12; i++) { - // const tx3 = appChain.transaction( - // priv.toPublicKey(), - // () => { - // balances.addBalance(PrivateKey.random().toPublicKey(), UInt64.from((i + 1) * 1000)); - // }, - // { nonce: i + 2 } - // ); - // await tx3.sign(); - // await tx3.send(); - // - // - // // await trigger.produceProven(); - // if (i % 2 === 1) { - // await trigger.produceUnproven(); - // if (i % 4 == 3) { - // await trigger.produceProven(); - // } - // } - // } - - const as = await appChain.query.protocol.AccountStateModule.accountState.get(priv.toPublicKey()); - let nonce = Number(as!.nonce.toBigInt()) - console.log(nonce) - - while(true){ - const tx = appChain.transaction(priv.toPublicKey(), () => { - balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - }, { nonce: nonce }); - appChain.resolve("Signer").config.signer = priv; - await tx.sign(); - await tx.send(); - // console.log((tx.transaction as PendingTransaction).toJSON()) - - const tx2 = appChain.transaction( - priv.toPublicKey(), - () => { - balances.addBalance(priv.toPublicKey(), UInt64.from(1000)); - }, - { nonce: nonce + 1 } - ); - await tx2.sign(); - await tx2.send(); - - nonce += 2; - - console.log("Added 2 txs to mempool") - - await sleep(15000); - } - - // const asyncState = - // appChain.sequencer.dependencyContainer.resolve( - // "AsyncStateService" - // ); - // await asyncState.setAsync(balances.balances.getPath(pk), [Field(100)]); - // await asyncState.setAsync(balances.totalSupply.path!, [Field(10_000)]); - - // appChain.query.runtime.Balances.totalSupply - - // await sleep(30000); - - return appChain; -} - -// await startServer(); diff --git a/packages/stack/tsconfig.json b/packages/stack/tsconfig.json deleted file mode 100644 index 70ad68aac..000000000 --- a/packages/stack/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "moduleResolution": "node", - "lib": ["DOM"] - }, - "include": ["./src/index.ts"], - "exclude": ["./dist/**/*.ts"] -} diff --git a/packages/stack/tsconfig.test.json b/packages/stack/tsconfig.test.json deleted file mode 100644 index 2822c6172..000000000 --- a/packages/stack/tsconfig.test.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./../../tsconfig.json", - "compilerOptions": { - "lib": ["DOM"] - }, - "include": [ - "./src/**/*.test.ts", - "./test/**/*.ts", - "./test/*.ts", - "./src/**/*.ts" - ] -} From dd6595fef450bb087cb6cd683d4261265bc4d31b Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 14 Feb 2024 21:45:20 +0100 Subject: [PATCH 53/54] Fixed tests --- packages/sdk/test/graphql/server.ts | 58 +++---------------- .../production/flow/ReductionTaskFlow.ts | 5 -- .../test/integration/BlockProduction.test.ts | 1 - .../test/merkle/AsyncMerkleTreeStore.test.ts | 9 ++- .../test/merkle/CachedMerkleStore.test.ts | 6 +- 5 files changed, 15 insertions(+), 64 deletions(-) diff --git a/packages/sdk/test/graphql/server.ts b/packages/sdk/test/graphql/server.ts index 4343c0850..e5a4d667d 100644 --- a/packages/sdk/test/graphql/server.ts +++ b/packages/sdk/test/graphql/server.ts @@ -1,5 +1,5 @@ import "reflect-metadata"; -import { CircuitString, Field, PrivateKey, PublicKey, UInt64 } from "o1js"; +import { PrivateKey, PublicKey, UInt64 } from "o1js"; import { Runtime, runtimeMethod, @@ -7,18 +7,10 @@ import { runtimeModule, state, } from "@proto-kit/module"; -import { - AccountStateModule, - BlockHeightHook, - Option, - State, - StateMap, - VanillaProtocol, -} from "@proto-kit/protocol"; -import { Presets, log, sleep, range } from "@proto-kit/common"; +import { Option, State, StateMap, VanillaProtocol } from "@proto-kit/protocol"; +import { log, Presets } from "@proto-kit/common"; import { BlockProducerModule, - InMemoryDatabase, LocalTaskQueue, LocalTaskWorkerModule, NoopBaseLayer, @@ -37,16 +29,13 @@ import { QueryGraphqlModule, UnprovenBlockResolver, } from "@proto-kit/api"; - -import { AppChain } from "../../src/appChain/AppChain"; -import { StateServiceQueryModule } from "../../src/query/StateServiceQueryModule"; -import { InMemorySigner } from "../../src/transaction/InMemorySigner"; -import { InMemoryTransactionSender } from "../../src/transaction/InMemoryTransactionSender"; import { container } from "tsyringe"; -import { BlockStorageNetworkStateModule } from "../../src/query/BlockStorageNetworkStateModule"; -import { MessageBoard, Post } from "./Post"; import { PrismaRedisDatabase } from "@proto-kit/persistance"; +import { AppChain, StateServiceQueryModule, InMemorySigner, InMemoryTransactionSender, BlockStorageNetworkStateModule } from "../../src"; + +import { MessageBoard } from "./Post"; + @runtimeModule() export class Balances extends RuntimeModule { /** @@ -169,7 +158,6 @@ export async function startServer() { MerkleWitnessResolver: {}, }, - // Database: {}, Database: { redis: { url: "redis://localhost:6379", @@ -177,10 +165,6 @@ export async function startServer() { }, prisma: {}, }, - // Redis: { - // url: "redis://localhost:6379", - // password: "password", - // }, Mempool: {}, BlockProducerModule: {}, @@ -246,33 +230,5 @@ export async function startServer() { await tx2.sign(); await tx2.send(); - let i = nonce + 2; - - // setInterval(async () => { - // try { - // const p = range(0, 1).map(async () => { - // const tx2 = await appChain.transaction( - // priv.toPublicKey(), - // () => { - // balances.addBalance( - // PrivateKey.random().toPublicKey(), - // UInt64.from(1000) - // ); - // }, - // { nonce: i++ } - // ); - // await tx2.sign(); - // await tx2.send(); - // }); - // - // await Promise.all(p) - // - // console.log(process.memoryUsage().heapUsed / 1024 / 1024 + "MB"); - // } catch (e) { - // console.log(e); - // } - // console.log("Sent new tx"); - // }, 8000); - return appChain; } diff --git a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts index cae404725..93f55214d 100644 --- a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts @@ -88,11 +88,6 @@ export class ReductionTaskFlow { log.error( `Flow ${this.flow.flowId} seems to have halted with ${this.flow.state.queue.length} elements left in the queue` ); - const json0 = BlockProverPublicInput.toJSON((this.flow.state.queue[0] as any)["publicInput"] as any); - const json1 = BlockProverPublicOutput.toJSON((this.flow.state.queue[0] as any)["publicOutput"] as any); - const json2 = BlockProverPublicInput.toJSON((this.flow.state.queue[1] as any)["publicInput"] as any); - const json3 = BlockProverPublicOutput.toJSON((this.flow.state.queue[1] as any)["publicOutput"] as any); - console.log(); } return { availableReductions: res, touchedIndizes }; diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index dfbe340b7..eee562b24 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -366,7 +366,6 @@ describe("block production", () => { expect(block).toBeDefined(); expect(batch!.bundles).toHaveLength(2); - expect(batch!.bundles[1]).toBe("0"); expect(block!.transactions).toHaveLength(2); const stateService = diff --git a/packages/sequencer/test/merkle/AsyncMerkleTreeStore.test.ts b/packages/sequencer/test/merkle/AsyncMerkleTreeStore.test.ts index ecd39b8e0..ca54c0994 100644 --- a/packages/sequencer/test/merkle/AsyncMerkleTreeStore.test.ts +++ b/packages/sequencer/test/merkle/AsyncMerkleTreeStore.test.ts @@ -1,7 +1,4 @@ -import { - InMemoryMerkleTreeStorage, - RollupMerkleTree, -} from "@proto-kit/common"; +import { InMemoryMerkleTreeStorage, RollupMerkleTree } from "@proto-kit/common"; import { beforeEach } from "@jest/globals"; import { Field, Poseidon } from "o1js"; import { log } from "@proto-kit/common"; @@ -43,7 +40,9 @@ describe("cachedMerkleTree", () => { Poseidon.hash([Field(0), Field(10)]).toBigInt() ); - expect(cached.getNode(0n, 254)).toBe(await store.getNodeAsync(0n, 254)); + const retrievedNodes = await store.getNodesAsync([{ key: 0n, level: 254 }]); + + expect(cached.getNode(0n, 254)).toStrictEqual(retrievedNodes[0]); expect(cached.getNode(0n, 254)).toBe(syncStore.getNode(0n, 254)); expect(cachedTree.getRoot().toBigInt()).toBe(tree.getRoot().toBigInt()); diff --git a/packages/sequencer/test/merkle/CachedMerkleStore.test.ts b/packages/sequencer/test/merkle/CachedMerkleStore.test.ts index 0a408ab70..bcb252120 100644 --- a/packages/sequencer/test/merkle/CachedMerkleStore.test.ts +++ b/packages/sequencer/test/merkle/CachedMerkleStore.test.ts @@ -5,7 +5,7 @@ import { Field } from "o1js"; import { CachedMerkleTreeStore, InMemoryAsyncMerkleTreeStore, - SyncCachedMerkleTreeStore + SyncCachedMerkleTreeStore, } from "../../src"; describe("cached merkle store", () => { @@ -22,7 +22,9 @@ describe("cached merkle store", () => { it("should cache correctly", async () => { expect.assertions(9); - await expect(mainStore.getNodeAsync(5n, 0)).resolves.toBe(10n); + await expect( + mainStore.getNodesAsync([{ key: 5n, level: 0 }]) + ).resolves.toStrictEqual([10n]); const cache1 = new CachedMerkleTreeStore(mainStore); const tree1 = new RollupMerkleTree(cache1); From 7a4f77f1f00ce47dbbe39ad849352e55bf3fcf65 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 14 Feb 2024 23:03:28 +0100 Subject: [PATCH 54/54] Added prisma client generation to build script --- packages/persistance/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/persistance/package.json b/packages/persistance/package.json index 739c39d32..9746e6c3e 100644 --- a/packages/persistance/package.json +++ b/packages/persistance/package.json @@ -5,7 +5,9 @@ "type": "module", "version": "0.1.1-develop.267+b252853", "scripts": { - "build": "tsc -p tsconfig.json", + "build": "npm run prisma-generate && tsc -p tsconfig.json", + "prisma-generate": "prisma generate", + "prisma-migrate": "prisma migrate deploy", "dev": "tsc -p tsconfig.json --watch", "lint": "eslint ./src ./test", "test:file": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads ../../node_modules/jest/bin/jest.js",