Skip to content

Commit a40d09e

Browse files
committed
Pulled apart TransactionExecutionService into multiple services
1 parent 8baeee9 commit a40d09e

File tree

4 files changed

+305
-263
lines changed

4 files changed

+305
-263
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { CachedStateService } from "../../../state/state/CachedStateService";
2323
import { MessageStorage } from "../../../storage/repositories/MessageStorage";
2424

2525
import { TransactionExecutionService } from "./TransactionExecutionService";
26+
import { BlockProductionService } from "./BlockProductionService";
27+
import { BlockResultService } from "./BlockResultService";
2628

2729
export interface BlockConfig {
2830
allowEmptyBlock?: boolean;
@@ -44,7 +46,8 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
4446
private readonly blockQueue: BlockQueue,
4547
@inject("BlockTreeStore")
4648
private readonly blockTreeStore: AsyncMerkleTreeStore,
47-
private readonly executionService: TransactionExecutionService,
49+
private readonly productionService: BlockProductionService,
50+
private readonly resultService: BlockResultService,
4851
@inject("MethodIdResolver")
4952
private readonly methodIdResolver: MethodIdResolver,
5053
@inject("Runtime") private readonly runtime: Runtime<RuntimeModulesRecord>
@@ -121,7 +124,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
121124
// Generate metadata for next block
122125

123126
// TODO: make async of production in the future
124-
const result = await this.executionService.generateMetadataForNextBlock(
127+
const result = await this.resultService.generateMetadataForNextBlock(
125128
block,
126129
this.unprovenMerkleStore,
127130
this.blockTreeStore,
@@ -189,7 +192,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
189192
this.unprovenStateService
190193
);
191194

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

0 commit comments

Comments
 (0)