Skip to content

Commit 41674b6

Browse files
authored
Merge pull request #380 from proto-kit/feature/block-prover-v3
Block prover v3
2 parents e4b9e3c + 4a79498 commit 41674b6

36 files changed

+1513
-1109
lines changed

packages/common/src/trees/sparse/RollupMerkleTree.ts

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ import { TypedClass } from "../../types";
77
import { MerkleTreeStore } from "./MerkleTreeStore";
88
import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage";
99

10+
/**
11+
* More efficient version of `maybeSwapBad` which
12+
* reuses an intermediate variable
13+
*/
14+
export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] {
15+
const m = b.toField().mul(x.sub(y)); // b*(x - y)
16+
const x1 = y.add(m); // y + b*(x - y)
17+
const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x)
18+
return [x1, y2];
19+
}
20+
1021
export class StructTemplate extends Struct({
1122
path: Provable.Array(Field, 0),
1223
isLeft: Provable.Array(Bool, 0),
@@ -22,6 +33,11 @@ export interface AbstractMerkleWitness extends StructTemplate {
2233
*/
2334
calculateRoot(hash: Field): Field;
2435

36+
calculateRootIncrement(
37+
index: Field,
38+
leaf: Field
39+
): [Field, AbstractMerkleWitness];
40+
2541
/**
2642
* Calculates the index of the leaf node that belongs to this Witness.
2743
* @returns Index of the leaf.
@@ -119,6 +135,24 @@ export interface AbstractMerkleTreeClass {
119135
* It also holds the Witness class under tree.WITNESS
120136
*/
121137
export function createMerkleTree(height: number): AbstractMerkleTreeClass {
138+
function generateZeroes() {
139+
const zeroes = [0n];
140+
for (let index = 1; index < height; index += 1) {
141+
const previousLevel = Field(zeroes[index - 1]);
142+
zeroes.push(Poseidon.hash([previousLevel, previousLevel]).toBigInt());
143+
}
144+
return zeroes;
145+
}
146+
147+
let zeroCache: bigint[] | undefined = undefined;
148+
149+
function getZeroes() {
150+
if (zeroCache === undefined) {
151+
zeroCache = generateZeroes();
152+
}
153+
return zeroCache;
154+
}
155+
122156
/**
123157
* The {@link RollupMerkleWitness} class defines a circuit-compatible base class
124158
* for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof).
@@ -147,14 +181,74 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {
147181

148182
for (let index = 1; index < n; ++index) {
149183
const isLeft = this.isLeft[index - 1];
150-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
184+
151185
const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]);
152186
hash = Poseidon.hash([left, right]);
153187
}
154188

155189
return hash;
156190
}
157191

192+
public calculateRootIncrement(
193+
leafIndex: Field,
194+
leaf: Field
195+
): [Field, RollupMerkleWitness] {
196+
// This won't generate any constraints, since it's purely a computation on constants
197+
const zero = getZeroes();
198+
199+
if (zero.length === 0) {
200+
throw new Error("Zeroes not initialized");
201+
}
202+
const zeroes = zero.map((x) => Field(x));
203+
204+
let hash = leaf;
205+
const n = this.height();
206+
207+
let notDiverged = Bool(true);
208+
const newPath = leafIndex.add(1).toBits();
209+
newPath.push(Bool(false));
210+
211+
const newSiblings: Field[] = [];
212+
const newIsLefts: Bool[] = [];
213+
214+
for (let index = 0; index < n - 1; ++index) {
215+
const isLeft = this.isLeft[index];
216+
const sibling = this.path[index];
217+
218+
const newIsLeft = newPath[index].not();
219+
220+
// Bool(true) default for root level
221+
let convergesNextLevel = Bool(true);
222+
if (index < n - 2) {
223+
convergesNextLevel = newPath[index + 1]
224+
.equals(this.isLeft[index + 1])
225+
.not();
226+
}
227+
228+
const nextSibling = Provable.if(
229+
convergesNextLevel.and(notDiverged),
230+
hash,
231+
Provable.if(notDiverged, zeroes[index], sibling)
232+
);
233+
234+
notDiverged = notDiverged.and(convergesNextLevel.not());
235+
236+
newSiblings.push(nextSibling);
237+
newIsLefts.push(newIsLeft);
238+
239+
const [left, right] = maybeSwap(isLeft, hash, sibling);
240+
hash = Poseidon.hash([left, right]);
241+
}
242+
243+
return [
244+
hash,
245+
new RollupMerkleWitness({
246+
isLeft: newIsLefts,
247+
path: newSiblings,
248+
}),
249+
];
250+
}
251+
158252
/**
159253
* Calculates the index of the leaf node that belongs to this Witness.
160254
* @returns Index of the leaf.
@@ -215,6 +309,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {
215309
});
216310
}
217311
}
312+
218313
return class AbstractRollupMerkleTree implements AbstractMerkleTree {
219314
public static HEIGHT = height;
220315

@@ -238,13 +333,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {
238333

239334
public constructor(store: MerkleTreeStore) {
240335
this.store = store;
241-
this.zeroes = [0n];
242-
for (let index = 1; index < AbstractRollupMerkleTree.HEIGHT; index += 1) {
243-
const previousLevel = Field(this.zeroes[index - 1]);
244-
this.zeroes.push(
245-
Poseidon.hash([previousLevel, previousLevel]).toBigInt()
246-
);
247-
}
336+
this.zeroes = generateZeroes();
248337
}
249338

250339
public assertIndexRange(index: bigint) {
@@ -414,14 +503,3 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {
414503

415504
export class RollupMerkleTree extends createMerkleTree(256) {}
416505
export class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {}
417-
418-
/**
419-
* More efficient version of `maybeSwapBad` which
420-
* reuses an intermediate variable
421-
*/
422-
export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] {
423-
const m = b.toField().mul(x.sub(y)); // b*(x - y)
424-
const x1 = y.add(m); // y + b*(x - y)
425-
const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x)
426-
return [x1, y2];
427-
}

packages/common/src/utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ export function yieldSequential<Source, State, Target>(
8787
array,
8888
async ([state, collectedTargets], curr, index, arr) => {
8989
const [newState, addition] = await callbackfn(state, curr, index, arr);
90-
return [newState, collectedTargets.concat(addition)];
90+
// The reason we wrap this in an array here is for a special case where Target is a tuple
91+
// or array itself. In this case, js interprets by flattening the Value in the array
92+
// (which it does when a function (like concat) uses a spread operator and the
93+
// input is an array)
94+
return [newState, collectedTargets.concat([addition])];
9195
},
9296
[initialValue, []]
9397
);
@@ -105,6 +109,18 @@ export function mapSequential<T, R>(
105109
}, Promise.resolve([]));
106110
}
107111

112+
export function unzip<A, B>(array: [A, B][]): [A[], B[]] {
113+
const as = array.map(([a]) => a);
114+
const bs = array.map(([, b]) => b);
115+
return [as, bs];
116+
}
117+
118+
export function assertSizeOneOrTwo<T>(arr: T[]): asserts arr is [T] | [T, T] {
119+
if (!(arr.length === 1 || arr.length === 2)) {
120+
throw new Error("Given array not size 1 or 2");
121+
}
122+
}
123+
108124
/**
109125
* Computes a dummy value for the given value type.
110126
*

packages/common/test/trees/MerkleTree.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,35 @@ describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => {
217217
tree.getNode(0, index);
218218
}).toThrow("Index greater than maximum leaf number");
219219
});
220+
221+
it("witness incrementing", () => {
222+
tree.setLeaf(0n, Field(3256));
223+
tree.setLeaf(1n, Field(3256));
224+
tree.setLeaf(2n, Field(3256));
225+
226+
const witness = tree.getWitness(3n);
227+
228+
const [root, newWitness] = witness.calculateRootIncrement(
229+
Field(3),
230+
Field(1234)
231+
);
232+
tree.setLeaf(3n, Field(1234));
233+
234+
expect(tree.getRoot().toString()).toStrictEqual(root.toString());
235+
expect(newWitness.calculateIndex().toString()).toStrictEqual("4");
236+
237+
const [root2, newWitness2] = newWitness.calculateRootIncrement(
238+
Field(4),
239+
Field(4321)
240+
);
241+
tree.setLeaf(4n, Field(4321));
242+
243+
expect(tree.getRoot().toString()).toStrictEqual(root2.toString());
244+
expect(newWitness2.calculateIndex().toString()).toStrictEqual("5");
245+
246+
const root3 = newWitness2.calculateRoot(Field(555));
247+
tree.setLeaf(5n, Field(555));
248+
249+
expect(tree.getRoot().toString()).toStrictEqual(root3.toString());
250+
});
220251
});

packages/module/src/method/runtimeMethod.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ const errors = {
2525
runtimeNotProvided: (name: string) =>
2626
new Error(`Runtime was not provided for module: ${name}`),
2727

28-
methodInputsNotProvided: () =>
29-
new Error(
30-
"Method execution inputs not provided, provide them via context.inputs"
31-
),
32-
3328
runtimeNameNotSet: () => new Error("Runtime name was not set"),
3429

3530
fieldNotConstant: (name: string) =>

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

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

4242
const teardown = async () => {
43-
await appChain.sequencer.resolve("Database").close();
43+
await appChain.close();
4444
};
4545

4646
beforeAll(async () => {

packages/protocol/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export * from "./prover/accumulators/StateTransitionReductionList";
2222
export * from "./prover/accumulators/AppliedBatchHashList";
2323
export * from "./prover/accumulators/WitnessedRootHashList";
2424
export * from "./prover/accumulators/TransactionHashList";
25+
export * from "./prover/accumulators/BlockHashList";
2526
export * from "./prover/block/BlockProver";
2627
export * from "./prover/block/BlockProvable";
2728
export * from "./prover/block/accummulators/RuntimeVerificationKeyTree";

packages/protocol/src/protocol/ProvableBlockHook.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,52 @@ import { NoConfig } from "@proto-kit/common";
33

44
import { NetworkState } from "../model/network/NetworkState";
55
import {
6-
BlockProverState,
76
BlockProverPublicInput,
7+
BlockArguments,
8+
BlockProverState,
89
} from "../prover/block/BlockProvable";
910

1011
import { TransitioningProtocolModule } from "./TransitioningProtocolModule";
1112

1213
export type ProvableHookBlockState = Pick<
13-
BlockProverPublicInput,
14-
| "transactionsHash"
15-
| "eternalTransactionsHash"
16-
| "incomingMessagesHash"
17-
| "blockHashRoot"
14+
BlockProverPublicInput & BlockArguments,
15+
"eternalTransactionsHash" | "incomingMessagesHash" | "blockHashRoot"
1816
>;
1917

20-
export function toProvableHookBlockState(
18+
export function toBeforeBlockHookArgument(
2119
state: Pick<
2220
BlockProverState,
23-
| "transactionList"
24-
| "eternalTransactionsList"
25-
| "incomingMessages"
26-
| "blockHashRoot"
21+
"eternalTransactionsList" | "incomingMessages" | "blockHashRoot"
2722
>
2823
) {
29-
const {
30-
transactionList,
31-
eternalTransactionsList,
32-
incomingMessages,
33-
blockHashRoot,
34-
} = state;
24+
const { eternalTransactionsList, incomingMessages, blockHashRoot } = state;
3525
return {
36-
transactionsHash: transactionList.commitment,
3726
eternalTransactionsHash: eternalTransactionsList.commitment,
3827
incomingMessagesHash: incomingMessages.commitment,
3928
blockHashRoot,
4029
};
4130
}
4231

32+
export function toAfterBlockHookArgument(
33+
state: Pick<
34+
BlockProverState,
35+
"eternalTransactionsList" | "incomingMessages" | "blockHashRoot"
36+
>,
37+
stateRoot: Field,
38+
transactionsHash: Field
39+
) {
40+
return {
41+
...toBeforeBlockHookArgument(state),
42+
stateRoot,
43+
transactionsHash,
44+
};
45+
}
46+
4347
export interface BeforeBlockHookArguments extends ProvableHookBlockState {}
4448

4549
export interface AfterBlockHookArguments extends BeforeBlockHookArguments {
4650
stateRoot: Field;
51+
transactionsHash: Field;
4752
}
4853

4954
// Purpose is to build transition from -> to network state

packages/protocol/src/protocol/ProvableTransactionHook.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
import { NoConfig } from "@proto-kit/common";
2-
import { Signature } from "o1js";
2+
import { Field, Signature } from "o1js";
33

44
import { RuntimeTransaction } from "../model/transaction/RuntimeTransaction";
55
import { NetworkState } from "../model/network/NetworkState";
66
import { MethodPublicOutput } from "../model/MethodPublicOutput";
77
import {
8-
TransactionProverPublicInput,
98
TransactionProverState,
109
TransactionProverTransactionArguments,
1110
} from "../prover/transaction/TransactionProvable";
1211

1312
import { TransitioningProtocolModule } from "./TransitioningProtocolModule";
1413

15-
export type ProvableHookTransactionState = Pick<
16-
TransactionProverPublicInput,
17-
"transactionsHash" | "eternalTransactionsHash" | "incomingMessagesHash"
18-
>;
14+
export type ProvableHookTransactionState = {
15+
transactionsHash: Field;
16+
eternalTransactionsHash: Field;
17+
incomingMessagesHash: Field;
18+
};
1919

2020
export function toProvableHookTransactionState(
2121
state: Pick<
2222
TransactionProverState,
2323
"transactionList" | "eternalTransactionsList" | "incomingMessages"
2424
>
25-
) {
25+
): ProvableHookTransactionState {
2626
const { transactionList, eternalTransactionsList, incomingMessages } = state;
2727
return {
2828
transactionsHash: transactionList.commitment,

0 commit comments

Comments
 (0)