Skip to content

Commit 8256e68

Browse files
committed
Merge branch 'refactor/sequencing' into feature/st-prover-v3
2 parents 9dd8c82 + 9a23eec commit 8256e68

File tree

5 files changed

+437
-379
lines changed

5 files changed

+437
-379
lines changed

packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import { Block, BlockWithResult } from "../../../storage/model/Block";
2222
import { CachedStateService } from "../../../state/state/CachedStateService";
2323
import { MessageStorage } from "../../../storage/repositories/MessageStorage";
2424

25-
import { TransactionExecutionService } from "./TransactionExecutionService";
25+
import { BlockProductionService } from "./BlockProductionService";
26+
import { BlockResultService } from "./BlockResultService";
2627

2728
export interface BlockConfig {
2829
allowEmptyBlock?: boolean;
@@ -44,7 +45,8 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
4445
private readonly blockQueue: BlockQueue,
4546
@inject("BlockTreeStore")
4647
private readonly blockTreeStore: AsyncMerkleTreeStore,
47-
private readonly executionService: TransactionExecutionService,
48+
private readonly productionService: BlockProductionService,
49+
private readonly resultService: BlockResultService,
4850
@inject("MethodIdResolver")
4951
private readonly methodIdResolver: MethodIdResolver,
5052
@inject("Runtime") private readonly runtime: Runtime<RuntimeModulesRecord>
@@ -121,7 +123,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
121123
// Generate metadata for next block
122124

123125
// TODO: make async of production in the future
124-
const result = await this.executionService.generateMetadataForNextBlock(
126+
const result = await this.resultService.generateMetadataForNextBlock(
125127
block,
126128
this.unprovenMerkleStore,
127129
this.blockTreeStore,
@@ -189,7 +191,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
189191
this.unprovenStateService
190192
);
191193

192-
const block = await this.executionService.createBlock(
194+
const block = await this.productionService.createBlock(
193195
cachedStateService,
194196
txs,
195197
metadata,
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { inject, injectable, Lifecycle, scoped } from "tsyringe";
2+
import {
3+
DefaultProvableHashList,
4+
MandatoryProtocolModulesRecord,
5+
MinaActions,
6+
MinaActionsHashList,
7+
NetworkState,
8+
Protocol,
9+
ProtocolModulesRecord,
10+
ProvableBlockHook,
11+
} from "@proto-kit/protocol";
12+
import { Field } from "o1js";
13+
import { log } from "@proto-kit/common";
14+
15+
import {
16+
Block,
17+
BlockWithResult,
18+
TransactionExecutionResult,
19+
} from "../../../storage/model/Block";
20+
import { CachedStateService } from "../../../state/state/CachedStateService";
21+
import { PendingTransaction } from "../../../mempool/PendingTransaction";
22+
23+
import { TransactionExecutionService } from "./TransactionExecutionService";
24+
25+
@injectable()
26+
@scoped(Lifecycle.ContainerScoped)
27+
export class BlockProductionService {
28+
private readonly blockHooks: ProvableBlockHook<unknown>[];
29+
30+
public constructor(
31+
@inject("Protocol")
32+
protocol: Protocol<MandatoryProtocolModulesRecord & ProtocolModulesRecord>,
33+
private readonly transactionExecutionService: TransactionExecutionService
34+
) {
35+
this.blockHooks =
36+
protocol.dependencyContainer.resolveAll("ProvableBlockHook");
37+
}
38+
39+
/**
40+
* Main entry point for creating a unproven block with everything
41+
* attached that is needed for tracing
42+
*/
43+
public async createBlock(
44+
stateService: CachedStateService,
45+
transactions: PendingTransaction[],
46+
lastBlockWithResult: BlockWithResult,
47+
allowEmptyBlocks: boolean
48+
): Promise<Block | undefined> {
49+
const lastResult = lastBlockWithResult.result;
50+
const lastBlock = lastBlockWithResult.block;
51+
const executionResults: TransactionExecutionResult[] = [];
52+
53+
const transactionsHashList = new DefaultProvableHashList(Field);
54+
const eternalTransactionsHashList = new DefaultProvableHashList(
55+
Field,
56+
Field(lastBlock.toEternalTransactionsHash)
57+
);
58+
59+
const incomingMessagesList = new MinaActionsHashList(
60+
Field(lastBlock.toMessagesHash)
61+
);
62+
63+
// Get used networkState by executing beforeBlock() hooks
64+
const networkState = await this.blockHooks.reduce<Promise<NetworkState>>(
65+
async (reduceNetworkState, hook) =>
66+
await hook.beforeBlock(await reduceNetworkState, {
67+
blockHashRoot: Field(lastResult.blockHashRoot),
68+
eternalTransactionsHash: lastBlock.toEternalTransactionsHash,
69+
stateRoot: Field(lastResult.stateRoot),
70+
transactionsHash: Field(0),
71+
networkStateHash: lastResult.afterNetworkState.hash(),
72+
incomingMessagesHash: lastBlock.toMessagesHash,
73+
}),
74+
Promise.resolve(lastResult.afterNetworkState)
75+
);
76+
77+
for (const tx of transactions) {
78+
try {
79+
// Create execution trace
80+
const executionTrace =
81+
// eslint-disable-next-line no-await-in-loop
82+
await this.transactionExecutionService.createExecutionTrace(
83+
stateService,
84+
tx,
85+
networkState
86+
);
87+
88+
// Push result to results and transaction onto bundle-hash
89+
executionResults.push(executionTrace);
90+
if (!tx.isMessage) {
91+
transactionsHashList.push(tx.hash());
92+
eternalTransactionsHashList.push(tx.hash());
93+
} else {
94+
const actionHash = MinaActions.actionHash(
95+
tx.toRuntimeTransaction().hashData()
96+
);
97+
98+
incomingMessagesList.push(actionHash);
99+
}
100+
} catch (error) {
101+
if (error instanceof Error) {
102+
log.error("Error in inclusion of tx, skipping", error);
103+
}
104+
}
105+
}
106+
107+
const previousBlockHash =
108+
lastResult.blockHash === 0n ? undefined : Field(lastResult.blockHash);
109+
110+
if (executionResults.length === 0 && !allowEmptyBlocks) {
111+
log.info(
112+
"After sequencing, block has no sequencable transactions left, skipping block"
113+
);
114+
return undefined;
115+
}
116+
117+
const block: Omit<Block, "hash"> = {
118+
transactions: executionResults,
119+
transactionsHash: transactionsHashList.commitment,
120+
fromEternalTransactionsHash: lastBlock.toEternalTransactionsHash,
121+
toEternalTransactionsHash: eternalTransactionsHashList.commitment,
122+
height:
123+
lastBlock.hash.toBigInt() !== 0n ? lastBlock.height.add(1) : Field(0),
124+
fromBlockHashRoot: Field(lastResult.blockHashRoot),
125+
fromMessagesHash: lastBlock.toMessagesHash,
126+
toMessagesHash: incomingMessagesList.commitment,
127+
previousBlockHash,
128+
129+
networkState: {
130+
before: new NetworkState(lastResult.afterNetworkState),
131+
during: networkState,
132+
},
133+
};
134+
135+
const hash = Block.hash(block);
136+
137+
return {
138+
...block,
139+
hash,
140+
};
141+
}
142+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { Bool, Field, Poseidon } from "o1js";
2+
import { RollupMerkleTree } from "@proto-kit/common";
3+
import {
4+
BlockHashMerkleTree,
5+
BlockHashTreeEntry,
6+
BlockProverState,
7+
MandatoryProtocolModulesRecord,
8+
NetworkState,
9+
Protocol,
10+
ProtocolModulesRecord,
11+
ProvableBlockHook,
12+
RuntimeTransaction,
13+
} from "@proto-kit/protocol";
14+
import { inject, injectable, Lifecycle, scoped } from "tsyringe";
15+
16+
import {
17+
Block,
18+
BlockResult,
19+
TransactionExecutionResult,
20+
} from "../../../storage/model/Block";
21+
import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore";
22+
import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore";
23+
import { UntypedStateTransition } from "../helpers/UntypedStateTransition";
24+
import type { StateRecord } from "../BatchProducerModule";
25+
26+
import { executeWithExecutionContext } from "./TransactionExecutionService";
27+
28+
function collectStateDiff(
29+
stateTransitions: UntypedStateTransition[]
30+
): StateRecord {
31+
return stateTransitions.reduce<Record<string, Field[] | undefined>>(
32+
(state, st) => {
33+
if (st.toValue.isSome.toBoolean()) {
34+
state[st.path.toString()] = st.toValue.value;
35+
}
36+
return state;
37+
},
38+
{}
39+
);
40+
}
41+
42+
function createCombinedStateDiff(transactions: TransactionExecutionResult[]) {
43+
// Flatten diff list into a single diff by applying them over each other
44+
return transactions
45+
.map((tx) => {
46+
const transitions = tx.protocolTransitions.concat(
47+
tx.status.toBoolean() ? tx.stateTransitions : []
48+
);
49+
return collectStateDiff(transitions);
50+
})
51+
.reduce<StateRecord>((accumulator, diff) => {
52+
// accumulator properties will be overwritten by diff's values
53+
return Object.assign(accumulator, diff);
54+
}, {});
55+
}
56+
57+
@injectable()
58+
@scoped(Lifecycle.ContainerScoped)
59+
export class BlockResultService {
60+
private readonly blockHooks: ProvableBlockHook<unknown>[];
61+
62+
public constructor(
63+
@inject("Protocol")
64+
protocol: Protocol<MandatoryProtocolModulesRecord & ProtocolModulesRecord>
65+
) {
66+
this.blockHooks =
67+
protocol.dependencyContainer.resolveAll("ProvableBlockHook");
68+
}
69+
70+
public async generateMetadataForNextBlock(
71+
block: Block,
72+
merkleTreeStore: AsyncMerkleTreeStore,
73+
blockHashTreeStore: AsyncMerkleTreeStore,
74+
modifyTreeStore = true
75+
): Promise<BlockResult> {
76+
const combinedDiff = createCombinedStateDiff(block.transactions);
77+
78+
const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore);
79+
const tree = new RollupMerkleTree(inMemoryStore);
80+
const blockHashInMemoryStore = new CachedMerkleTreeStore(
81+
blockHashTreeStore
82+
);
83+
const blockHashTree = new BlockHashMerkleTree(blockHashInMemoryStore);
84+
85+
await inMemoryStore.preloadKeys(Object.keys(combinedDiff).map(BigInt));
86+
87+
// In case the diff is empty, we preload key 0 in order to
88+
// retrieve the root, which we need later
89+
if (Object.keys(combinedDiff).length === 0) {
90+
await inMemoryStore.preloadKey(0n);
91+
}
92+
93+
// TODO This can be optimized a lot (we are only interested in the root at this step)
94+
await blockHashInMemoryStore.preloadKey(block.height.toBigInt());
95+
96+
Object.entries(combinedDiff).forEach(([key, state]) => {
97+
const treeValue = state !== undefined ? Poseidon.hash(state) : Field(0);
98+
tree.setLeaf(BigInt(key), treeValue);
99+
});
100+
101+
const stateRoot = tree.getRoot();
102+
const fromBlockHashRoot = blockHashTree.getRoot();
103+
104+
const state: BlockProverState = {
105+
stateRoot,
106+
transactionsHash: block.transactionsHash,
107+
networkStateHash: block.networkState.during.hash(),
108+
eternalTransactionsHash: block.toEternalTransactionsHash,
109+
blockHashRoot: fromBlockHashRoot,
110+
incomingMessagesHash: block.toMessagesHash,
111+
};
112+
113+
// TODO Set StateProvider for @state access to state
114+
const context = {
115+
networkState: block.networkState.during,
116+
transaction: RuntimeTransaction.dummyTransaction(),
117+
};
118+
119+
const executionResult = await executeWithExecutionContext(
120+
async () =>
121+
await this.blockHooks.reduce<Promise<NetworkState>>(
122+
async (networkState, hook) =>
123+
await hook.afterBlock(await networkState, state),
124+
Promise.resolve(block.networkState.during)
125+
),
126+
context
127+
);
128+
129+
const { stateTransitions, methodResult } = executionResult;
130+
131+
// Update the block hash tree with this block
132+
blockHashTree.setLeaf(
133+
block.height.toBigInt(),
134+
new BlockHashTreeEntry({
135+
blockHash: Poseidon.hash([block.height, state.transactionsHash]),
136+
closed: Bool(true),
137+
}).hash()
138+
);
139+
const blockHashWitness = blockHashTree.getWitness(block.height.toBigInt());
140+
const newBlockHashRoot = blockHashTree.getRoot();
141+
await blockHashInMemoryStore.mergeIntoParent();
142+
143+
if (modifyTreeStore) {
144+
await inMemoryStore.mergeIntoParent();
145+
}
146+
147+
return {
148+
afterNetworkState: methodResult,
149+
stateRoot: stateRoot.toBigInt(),
150+
blockHashRoot: newBlockHashRoot.toBigInt(),
151+
blockHashWitness,
152+
153+
blockStateTransitions: stateTransitions.map((st) =>
154+
UntypedStateTransition.fromStateTransition(st)
155+
),
156+
blockHash: block.hash.toBigInt(),
157+
};
158+
}
159+
}

0 commit comments

Comments
 (0)