diff --git a/packages/library/src/hooks/TransactionFeeHook.ts b/packages/library/src/hooks/TransactionFeeHook.ts index dfa325b8f..20d9b0adc 100644 --- a/packages/library/src/hooks/TransactionFeeHook.ts +++ b/packages/library/src/hooks/TransactionFeeHook.ts @@ -80,12 +80,12 @@ export class TransactionFeeHook extends ProvableTransactionHook ({ height: height.toString() })) public async pushBlock(block: Block): Promise { log.trace( "Pushing block to DB. Txs:", @@ -96,12 +100,15 @@ export class PrismaBlockStorage const { prismaClient } = this.connection; - await prismaClient.transaction.createMany({ - data: block.transactions.map((txr) => - this.transactionMapper.mapOut(txr.tx) - ), - skipDuplicates: true, - }); + // Note: We can assume all transactions are already in the DB here, because the + // mempool shares the same table as this one. But that could change in the future, + // then transaction have to be inserted-if-missing + // await prismaClient.transaction.createMany({ + // data: block.transactions.map((txr) => + // this.transactionMapper.mapOut(txr.tx) + // ), + // skipDuplicates: true, + // }); await prismaClient.block.create({ data: { diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 1aed65a38..3da8c7225 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -61,6 +61,7 @@ export class PrismaStateService implements AsyncStateService { this.cache = []; } + @trace("db.state.getMany") public async getMany(keys: Field[]): Promise { const records = await this.connection.prismaClient.state.findMany({ where: { diff --git a/packages/sequencer/src/logging/ConsoleTracer.ts b/packages/sequencer/src/logging/ConsoleTracer.ts index 990177d3c..2b03d79f9 100644 --- a/packages/sequencer/src/logging/ConsoleTracer.ts +++ b/packages/sequencer/src/logging/ConsoleTracer.ts @@ -68,6 +68,25 @@ export class ConsoleTracer implements Tracer { this.clearTraces(); } + activeManualTraceStack: [string, number][] = []; + + public startTrace(name: string) { + const startTime = Date.now(); + + this.activeManualTraceStack.push([name, startTime]); + } + + public endTrace() { + const [name, startTime] = this.activeManualTraceStack.pop()!; + const duration = Date.now() - startTime; + + if (name in this.store) { + this.store[name].push({ duration }); + } else { + this.store[name] = [{ duration }]; + } + } + public async trace( name: string, f: () => Promise, diff --git a/packages/sequencer/src/mempool/PendingTransaction.ts b/packages/sequencer/src/mempool/PendingTransaction.ts index b7b3dcd58..fd1fedfec 100644 --- a/packages/sequencer/src/mempool/PendingTransaction.ts +++ b/packages/sequencer/src/mempool/PendingTransaction.ts @@ -28,45 +28,57 @@ export type UnsignedTransactionBody = { }; export class UnsignedTransaction implements UnsignedTransactionBody { - public methodId: Field; + public readonly methodId: Field; - public nonce: UInt64; + public readonly nonce: UInt64; - public sender: PublicKey; + public readonly sender: PublicKey; - public argsFields: Field[]; + public readonly argsFields: Field[]; - public auxiliaryData: string[]; + public readonly auxiliaryData: string[]; - public isMessage: boolean; + public readonly isMessage: boolean; - public constructor(data: { - methodId: Field; - nonce: UInt64; - sender: PublicKey; - argsFields: Field[]; - auxiliaryData: string[]; - isMessage: boolean; - }) { + public constructor( + data: { + methodId: Field; + nonce: UInt64; + sender: PublicKey; + argsFields: Field[]; + auxiliaryData: string[]; + isMessage: boolean; + }, + memoizedHash?: Field + ) { this.methodId = data.methodId; this.nonce = data.nonce; this.sender = data.sender; this.argsFields = data.argsFields; this.auxiliaryData = data.auxiliaryData; this.isMessage = data.isMessage; + + if (memoizedHash !== undefined) { + this.memoizedHash = memoizedHash; + } } public argsHash(): Field { return Poseidon.hash(this.argsFields); } + private memoizedHash?: Field = undefined; + public hash(): Field { - return Poseidon.hash([ - this.methodId, - ...this.sender.toFields(), - ...this.nonce.toFields(), - this.argsHash(), - ]); + if (this.memoizedHash === undefined) { + this.memoizedHash = Poseidon.hash([ + this.methodId, + ...this.sender.toFields(), + ...this.nonce.toFields(), + this.argsHash(), + ]); + } + return this.memoizedHash; } public getSignatureData(): Field[] { @@ -124,29 +136,35 @@ export class PendingTransaction extends UnsignedTransaction { public static fromJSON( object: PendingTransactionJSONType ): PendingTransaction { - return new PendingTransaction({ - methodId: Field.fromJSON(object.methodId), - nonce: UInt64.from(object.nonce), - sender: PublicKey.fromBase58(object.sender), - argsFields: object.argsFields.map((x) => Field.fromJSON(x)), - signature: Signature.fromJSON(object.signature), - auxiliaryData: object.auxiliaryData.slice(), - isMessage: object.isMessage, - }); + return new PendingTransaction( + { + methodId: Field.fromJSON(object.methodId), + nonce: UInt64.from(object.nonce), + sender: PublicKey.fromBase58(object.sender), + argsFields: object.argsFields.map((x) => Field.fromJSON(x)), + signature: Signature.fromJSON(object.signature), + auxiliaryData: object.auxiliaryData.slice(), + isMessage: object.isMessage, + }, + Field(object.hash) + ); } public signature: Signature; - public constructor(data: { - methodId: Field; - nonce: UInt64; - sender: PublicKey; - signature: Signature; - argsFields: Field[]; - auxiliaryData: string[]; - isMessage: boolean; - }) { - super(data); + public constructor( + data: { + methodId: Field; + nonce: UInt64; + sender: PublicKey; + signature: Signature; + argsFields: Field[]; + auxiliaryData: string[]; + isMessage: boolean; + }, + memoizedHash?: Field + ) { + super(data, memoizedHash); this.signature = data.signature; } diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index c57553c3d..067205d02 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -78,9 +78,7 @@ export class PrivateMempool const success = await this.transactionStorage.pushUserTransaction(tx); if (success) { this.events.emit("mempool-transaction-added", tx); - log.trace( - `Transaction added to mempool: ${tx.hash().toString()} (${(await this.transactionStorage.getPendingUserTransactions()).length} transactions in mempool)` - ); + log.trace(`Transaction added to mempool: ${tx.hash().toString()}`); } else { log.error( `Transaction ${tx.hash().toString()} rejected: already exists in mempool` diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts index ece360797..385789a25 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts @@ -104,13 +104,12 @@ export class BlockProductionService { const lastResult = lastBlockWithResult.result; const lastBlock = lastBlockWithResult.block; - const executionResults: TransactionExecutionResult[] = []; const incomingMessagesList = new MinaActionsHashList( Field(lastBlock.toMessagesHash) ); - let blockState: BlockTrackers = { + const blockState: BlockTrackers = { blockHashRoot: Field(lastResult.blockHashRoot), eternalTransactionsList: new TransactionHashList( lastBlock.toEternalTransactionsHash @@ -133,28 +132,13 @@ export class BlockProductionService { UntypedStateTransition.fromStateTransition(transition) ); - for (const tx of transactions) { - try { - // Create execution trace - const [newState, executionTrace] = - // eslint-disable-next-line no-await-in-loop - await this.transactionExecutionService.createExecutionTrace( - stateService, - tx, - networkState, - blockState - ); - - blockState = newState; - - // Push result to results and transaction onto bundle-hash - executionResults.push(executionTrace); - } catch (error) { - if (error instanceof Error) { - log.error("Error in inclusion of tx, skipping", error); - } - } - } + const [newBlockState, executionResults] = + await this.transactionExecutionService.createExecutionTraces( + stateService, + transactions, + networkState, + blockState + ); const previousBlockHash = lastResult.blockHash === 0n ? undefined : Field(lastResult.blockHash); @@ -168,9 +152,10 @@ export class BlockProductionService { const block: Omit = { transactions: executionResults, - transactionsHash: blockState.transactionList.commitment, + transactionsHash: newBlockState.transactionList.commitment, fromEternalTransactionsHash: lastBlock.toEternalTransactionsHash, - toEternalTransactionsHash: blockState.eternalTransactionsList.commitment, + toEternalTransactionsHash: + newBlockState.eternalTransactionsList.commitment, height: lastBlock.hash.toBigInt() !== 0n ? lastBlock.height.add(1) : Field(0), fromBlockHashRoot: Field(lastResult.blockHashRoot), diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index 05d25c279..0e12cd7a1 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -21,6 +21,8 @@ import { MethodPublicOutput, toBeforeTransactionHookArgument, toAfterTransactionHookArgument, + ProvableStateTransition, + DefaultProvableHashList, } from "@proto-kit/protocol"; import { Bool, Field } from "o1js"; import { AreProofsEnabled, log, mapSequential } from "@proto-kit/common"; @@ -30,7 +32,6 @@ import { RuntimeModule, RuntimeModulesRecord, toEventsHash, - toStateTransitionsHash, } from "@proto-kit/module"; // eslint-disable-next-line import/no-extraneous-dependencies import zip from "lodash/zip"; @@ -142,6 +143,18 @@ function extractEvents( ); } +// TODO Also use this in tracing as a replacement of toStateTransitionHash +export function toStateTransitionHashNonProvable( + stateTransitions: StateTransition[] +) { + const reduced = reduceStateTransitions(stateTransitions); + const list = new DefaultProvableHashList(ProvableStateTransition); + + reduced.map((st) => st.toProvable()).forEach((st) => list.push(st)); + + return list.commitment; +} + export async function executeWithExecutionContext( method: () => Promise, contextInputs: RuntimeMethodExecutionData, @@ -291,7 +304,60 @@ export class TransactionExecutionService { ); } - @trace("block.transaction", ([, tx, networkState]) => ({ + public addTransactionToBlockProverState( + state: BlockTrackers, + tx: PendingTransaction + ): BlockTrackers { + const signedTransaction = tx.toProtocolTransaction(); + // Add tx to commitments + return this.blockProver.addTransactionToBundle( + state, + Bool(tx.isMessage), + signedTransaction.transaction + ); + } + + public async createExecutionTraces( + asyncStateService: CachedStateService, + transactions: PendingTransaction[], + networkState: NetworkState, + state: BlockTrackers + ): Promise<[BlockTrackers, TransactionExecutionResult[]]> { + let blockState = state; + const executionResults: TransactionExecutionResult[] = []; + + const networkStateHash = networkState.hash(); + + for (const tx of transactions) { + try { + const newState = this.addTransactionToBlockProverState(state, tx); + + // Create execution trace + const executionTrace = + // eslint-disable-next-line no-await-in-loop + await this.createExecutionTrace( + asyncStateService, + tx, + { networkState, hash: networkStateHash }, + blockState, + newState + ); + + blockState = newState; + + // Push result to results and transaction onto bundle-hash + executionResults.push(executionTrace); + } catch (error) { + if (error instanceof Error) { + log.error("Error in inclusion of tx, skipping", error); + } + } + } + + return [blockState, executionResults]; + } + + @trace("block.transaction", ([, tx, { networkState }]) => ({ height: networkState.block.height.toString(), methodId: tx.methodId.toString(), isMessage: tx.isMessage, @@ -299,9 +365,13 @@ export class TransactionExecutionService { public async createExecutionTrace( asyncStateService: CachedStateService, tx: PendingTransaction, - networkState: NetworkState, - state: BlockTrackers - ): Promise<[BlockTrackers, TransactionExecutionResult]> { + { + networkState, + hash: networkStateHash, + }: { networkState: NetworkState; hash: Field }, + state: BlockTrackers, + newState: BlockTrackers + ): Promise { // TODO Use RecordingStateService -> async asProver needed const recordingStateService = new CachedStateService(asyncStateService); @@ -328,10 +398,16 @@ export class TransactionExecutionService { networkState, state ); - const beforeTxHookResult = await this.executeProtocolHooks( - beforeTxArguments, - async (hook, hookArgs) => await hook.beforeTransaction(hookArgs), - "beforeTx" + const beforeTxHookResult = await this.tracer.trace( + "block.transaction.before.execute", + () => + this.executeProtocolHooks( + beforeTxArguments, + async (hook, hookArgs) => { + await hook.beforeTransaction(hookArgs); + }, + "beforeTx" + ) ); const beforeHookEvents = extractEvents(beforeTxHookResult, "beforeTxHook"); @@ -339,10 +415,9 @@ export class TransactionExecutionService { beforeTxHookResult.stateTransitions ); - const runtimeResult = await this.executeRuntimeMethod( - method, - args, - runtimeContextInputs + const runtimeResult = await this.tracer.trace( + "block.transaction.execute", + () => this.executeRuntimeMethod(method, args, runtimeContextInputs) ); traceLogSTs("STs:", runtimeResult.stateTransitions); @@ -354,11 +429,9 @@ export class TransactionExecutionService { ); } - // Add runtime to commitments - const newState = this.blockProver.addTransactionToBundle( - state, - Bool(tx.isMessage), - signedTransaction.transaction + const eventsHash = toEventsHash(runtimeResult.events); + const stateTransitionsHash = toStateTransitionHashNonProvable( + runtimeResult.stateTransitions ); // Execute afterTransaction hook @@ -368,20 +441,22 @@ export class TransactionExecutionService { newState, new MethodPublicOutput({ status: runtimeResult.status, - networkStateHash: networkState.hash(), + networkStateHash: networkStateHash, isMessage: Bool(tx.isMessage), transactionHash: tx.hash(), - eventsHash: toEventsHash(runtimeResult.events), - stateTransitionsHash: toStateTransitionsHash( - runtimeResult.stateTransitions - ), + eventsHash, + stateTransitionsHash, }) ); - const afterTxHookResult = await this.executeProtocolHooks( - afterTxArguments, - async (hook, hookArgs) => await hook.afterTransaction(hookArgs), - "afterTx" + const afterTxHookResult = await this.tracer.trace( + "block.transaction.after.execute", + () => + this.executeProtocolHooks( + afterTxArguments, + async (hook, hookArgs) => await hook.afterTransaction(hookArgs), + "afterTx" + ) ); const afterHookEvents = extractEvents(afterTxHookResult, "afterTxHook"); await recordingStateService.applyStateTransitions( @@ -407,16 +482,13 @@ export class TransactionExecutionService { runtimeResult.status ); - return [ - state, - { - tx, - status: runtimeResult.status, - statusMessage: runtimeResult.statusMessage, + return { + tx, + status: runtimeResult.status, + statusMessage: runtimeResult.statusMessage, - stateTransitions, - events: beforeHookEvents.concat(runtimeResultEvents, afterHookEvents), - }, - ]; + stateTransitions, + events: beforeHookEvents.concat(runtimeResultEvents, afterHookEvents), + }; } } diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index 2824ba418..cc1167015 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -88,6 +88,15 @@ export class CachedStateService const remote = await this.parent?.getMany(remoteKeys); + if (remote !== undefined) { + // Update the remotely fetched keys into local cache + await mapSequential(remote, async ({ key, value }) => { + if (this.getNullAware(key) === undefined) { + await this.set(key, value); + } + }); + } + return local.concat(remote ?? []); } diff --git a/packages/sequencer/test-integration/benchmarks/tps.test.ts b/packages/sequencer/test-integration/benchmarks/tps.test.ts index 64e22ab13..fe4e27436 100644 --- a/packages/sequencer/test-integration/benchmarks/tps.test.ts +++ b/packages/sequencer/test-integration/benchmarks/tps.test.ts @@ -108,7 +108,9 @@ export async function createAppChain() { maximumBlockSize: 100, }, BlockTrigger: {}, - Mempool: {}, + Mempool: { + validationEnabled: false, + }, }, Signer: { signer: PrivateKey.random(), @@ -125,7 +127,7 @@ export async function createAppChain() { const timeout = 600000; -describe("tps", () => { +describe.skip("tps", () => { let appChain: Awaited>; let privateKeys: PrivateKey[] = []; let balances: Balances; @@ -177,6 +179,7 @@ describe("tps", () => { tracer.enableManualOutputs(); await fundKeys(200); + tracer.printSummary(); } catch (e) { console.error(e); throw e;