Skip to content

Commit 8a9385c

Browse files
authored
Merge pull request #251 from proto-kit/refactor/sequencing
Refactoring sequencing modules
2 parents 2f2900f + c7d11f8 commit 8a9385c

File tree

5 files changed

+439
-380
lines changed

5 files changed

+439
-380
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import { CachedStateService } from "../../../state/state/CachedStateService";
2727
import { MessageStorage } from "../../../storage/repositories/MessageStorage";
2828
import { Database } from "../../../storage/Database";
2929

30-
import { TransactionExecutionService } from "./TransactionExecutionService";
30+
import { BlockProductionService } from "./BlockProductionService";
31+
import { BlockResultService } from "./BlockResultService";
3132

3233
export interface BlockConfig {
3334
allowEmptyBlock?: boolean;
@@ -49,7 +50,8 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
4950
private readonly blockQueue: BlockQueue,
5051
@inject("BlockTreeStore")
5152
private readonly blockTreeStore: AsyncMerkleTreeStore,
52-
private readonly executionService: TransactionExecutionService,
53+
private readonly productionService: BlockProductionService,
54+
private readonly resultService: BlockResultService,
5355
@inject("MethodIdResolver")
5456
private readonly methodIdResolver: MethodIdResolver,
5557
@inject("Runtime") private readonly runtime: Runtime<RuntimeModulesRecord>,
@@ -107,7 +109,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
107109

108110
public async generateMetadata(block: Block): Promise<BlockResult> {
109111
const { result, blockHashTreeStore, treeStore } =
110-
await this.executionService.generateMetadataForNextBlock(
112+
await this.resultService.generateMetadataForNextBlock(
111113
block,
112114
this.unprovenMerkleStore,
113115
this.blockTreeStore
@@ -212,7 +214,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
212214
this.unprovenStateService
213215
);
214216

215-
const block = await this.executionService.createBlock(
217+
const block = await this.productionService.createBlock(
216218
cachedStateService,
217219
txs,
218220
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: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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<{
76+
result: BlockResult;
77+
treeStore: CachedMerkleTreeStore;
78+
blockHashTreeStore: CachedMerkleTreeStore;
79+
}> {
80+
const combinedDiff = createCombinedStateDiff(block.transactions);
81+
82+
const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore);
83+
const tree = new RollupMerkleTree(inMemoryStore);
84+
const blockHashInMemoryStore = new CachedMerkleTreeStore(
85+
blockHashTreeStore
86+
);
87+
const blockHashTree = new BlockHashMerkleTree(blockHashInMemoryStore);
88+
89+
await inMemoryStore.preloadKeys(Object.keys(combinedDiff).map(BigInt));
90+
91+
// In case the diff is empty, we preload key 0 in order to
92+
// retrieve the root, which we need later
93+
if (Object.keys(combinedDiff).length === 0) {
94+
await inMemoryStore.preloadKey(0n);
95+
}
96+
97+
// TODO This can be optimized a lot (we are only interested in the root at this step)
98+
await blockHashInMemoryStore.preloadKey(block.height.toBigInt());
99+
100+
Object.entries(combinedDiff).forEach(([key, state]) => {
101+
const treeValue = state !== undefined ? Poseidon.hash(state) : Field(0);
102+
tree.setLeaf(BigInt(key), treeValue);
103+
});
104+
105+
const stateRoot = tree.getRoot();
106+
const fromBlockHashRoot = blockHashTree.getRoot();
107+
108+
const state: BlockProverState = {
109+
stateRoot,
110+
transactionsHash: block.transactionsHash,
111+
networkStateHash: block.networkState.during.hash(),
112+
eternalTransactionsHash: block.toEternalTransactionsHash,
113+
blockHashRoot: fromBlockHashRoot,
114+
incomingMessagesHash: block.toMessagesHash,
115+
};
116+
117+
// TODO Set StateProvider for @state access to state
118+
const context = {
119+
networkState: block.networkState.during,
120+
transaction: RuntimeTransaction.dummyTransaction(),
121+
};
122+
123+
const executionResult = await executeWithExecutionContext(
124+
async () =>
125+
await this.blockHooks.reduce<Promise<NetworkState>>(
126+
async (networkState, hook) =>
127+
await hook.afterBlock(await networkState, state),
128+
Promise.resolve(block.networkState.during)
129+
),
130+
context
131+
);
132+
133+
const { stateTransitions, methodResult } = executionResult;
134+
135+
// Update the block hash tree with this block
136+
blockHashTree.setLeaf(
137+
block.height.toBigInt(),
138+
new BlockHashTreeEntry({
139+
blockHash: Poseidon.hash([block.height, state.transactionsHash]),
140+
closed: Bool(true),
141+
}).hash()
142+
);
143+
const blockHashWitness = blockHashTree.getWitness(block.height.toBigInt());
144+
const newBlockHashRoot = blockHashTree.getRoot();
145+
146+
return {
147+
result: {
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+
treeStore: inMemoryStore,
159+
blockHashTreeStore: blockHashInMemoryStore,
160+
};
161+
}
162+
}

0 commit comments

Comments
 (0)