Skip to content

Commit 6eeb669

Browse files
authored
Merge pull request #395 from proto-kit/feature/mempool-sorting
Mempool Sorting
2 parents d4cb77e + 9913367 commit 6eeb669

15 files changed

Lines changed: 198 additions & 43 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
88

99
### Added
1010

11+
- Added Mempool sorting [#395](https://github.com/proto-kit/framework/pull/395)
1112
- Introduced dynamic block building and JIT transaction fetching [#394](https://github.com/proto-kit/framework/pull/394)
1213
- Introduced block explorer [#381](https://github.com/proto-kit/framework/pull/381)
1314
- Added CircuitAnalysisModule for easy analysis of protocol circuits [#379](https://github.com/proto-kit/framework/pull/379)

packages/indexer/src/tasks/IndexPendingTxTask.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class IndexPendingTxTask
3030

3131
public async compute(input: PendingTransaction): Promise<string | void> {
3232
try {
33-
await this.transactionStorage.pushUserTransaction(input);
33+
await this.transactionStorage.pushUserTransaction(input, 0);
3434
return "";
3535
} catch (err) {
3636
log.error("Failed to process pending tx task", err);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- CreateTable
2+
CREATE TABLE "TransactionPriority" (
3+
"transactionHash" TEXT NOT NULL,
4+
"priority" BIGINT NOT NULL,
5+
6+
CONSTRAINT "TransactionPriority_pkey" PRIMARY KEY ("transactionHash")
7+
);
8+
9+
-- AddForeignKey
10+
ALTER TABLE "TransactionPriority" ADD CONSTRAINT "TransactionPriority_transactionHash_fkey" FOREIGN KEY ("transactionHash") REFERENCES "Transaction"("hash") ON DELETE RESTRICT ON UPDATE CASCADE;

packages/persistance/prisma/schema.prisma

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ model Transaction {
5454
executionResult TransactionExecutionResult?
5555
5656
IncomingMessageBatchTransaction IncomingMessageBatchTransaction[]
57+
58+
priority TransactionPriority?
59+
}
60+
61+
model TransactionPriority {
62+
transactionHash String
63+
64+
priority BigInt
65+
66+
Transaction Transaction @relation(fields: [transactionHash], references: [hash])
67+
68+
@@id([transactionHash])
5769
}
5870

5971
model TransactionExecutionResult {

packages/persistance/src/services/prisma/PrismaTransactionStorage.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ export class PrismaTransactionStorage implements TransactionStorage {
3434
equals: false,
3535
},
3636
},
37+
orderBy: {
38+
priority: {
39+
priority: "desc",
40+
},
41+
},
3742
skip: offset,
3843
take: limit,
3944
});
@@ -56,13 +61,27 @@ export class PrismaTransactionStorage implements TransactionStorage {
5661
}
5762
}
5863

59-
public async pushUserTransaction(tx: PendingTransaction): Promise<boolean> {
64+
public async pushUserTransaction(
65+
tx: PendingTransaction,
66+
priority: number
67+
): Promise<boolean> {
6068
const { prismaClient } = this.connection;
6169

62-
const result = await prismaClient.transaction.createMany({
63-
data: [this.transactionMapper.mapOut(tx)],
64-
skipDuplicates: true,
65-
});
70+
const transactionData = this.transactionMapper.mapOut(tx);
71+
72+
const [result] = await prismaClient.$transaction([
73+
prismaClient.transaction.createMany({
74+
data: [transactionData],
75+
skipDuplicates: true,
76+
}),
77+
78+
prismaClient.transactionPriority.create({
79+
data: {
80+
priority,
81+
transactionHash: transactionData.hash,
82+
},
83+
}),
84+
]);
6685

6786
return result.count === 1;
6887
}

packages/sdk/test/fees-multi-zkprograms.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,7 @@ describe("check fee analyzer", () => {
185185
},
186186
},
187187
Sequencer: {
188-
Mempool: {
189-
validationEnabled: true,
190-
},
188+
Mempool: {},
191189
},
192190
});
193191

packages/sequencer/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export * from "./mempool/Mempool";
44
export * from "./mempool/PendingTransaction";
55
export * from "./mempool/CompressedSignature";
66
export * from "./mempool/private/PrivateMempool";
7+
export * from "./mempool/sorting/MempoolSorting";
8+
export * from "./mempool/sorting/DefaultMempoolSorting";
79
export * from "./sequencer/executor/Sequencer";
810
export * from "./sequencer/executor/Sequenceable";
911
export * from "./sequencer/SequencerIdProvider";

packages/sequencer/src/mempool/private/PrivateMempool.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,38 @@ import { TransactionValidator } from "../verification/TransactionValidator";
1212
import { Tracer } from "../../logging/Tracer";
1313
import { trace } from "../../logging/trace";
1414
import { IncomingMessagesService } from "../../settlement/messages/IncomingMessagesService";
15+
import { MempoolSorting } from "../sorting/MempoolSorting";
16+
import { DefaultMempoolSorting } from "../sorting/DefaultMempoolSorting";
17+
18+
type PrivateMempoolConfig = {
19+
type?: "hybrid" | "private" | "based";
20+
};
1521

1622
@sequencerModule()
17-
export class PrivateMempool extends SequencerModule implements Mempool {
23+
export class PrivateMempool
24+
extends SequencerModule<PrivateMempoolConfig>
25+
implements Mempool
26+
{
1827
public readonly events = new EventEmitter<MempoolEvents>();
1928

29+
private readonly mempoolSorting: MempoolSorting;
30+
2031
public constructor(
2132
private readonly transactionValidator: TransactionValidator,
2233
@inject("TransactionStorage")
2334
private readonly transactionStorage: TransactionStorage,
2435
@inject("IncomingMessagesService", { isOptional: true })
2536
private readonly messageService: IncomingMessagesService | undefined,
26-
@inject("Tracer") public readonly tracer: Tracer
37+
@inject("Tracer") public readonly tracer: Tracer,
38+
@inject("MempoolSorting", { isOptional: true })
39+
mempoolSorting: MempoolSorting | undefined
2740
) {
2841
super();
42+
this.mempoolSorting = mempoolSorting ?? new DefaultMempoolSorting();
43+
}
44+
45+
private type() {
46+
return this.config.type ?? "hybrid";
2947
}
3048

3149
public async length(): Promise<number> {
@@ -36,7 +54,12 @@ export class PrivateMempool extends SequencerModule implements Mempool {
3654
public async add(tx: PendingTransaction): Promise<boolean> {
3755
const [txValid, error] = this.transactionValidator.validateTx(tx);
3856
if (txValid) {
39-
const success = await this.transactionStorage.pushUserTransaction(tx);
57+
const sortingValue = this.mempoolSorting!.presortingPriority(tx);
58+
59+
const success = await this.transactionStorage.pushUserTransaction(
60+
tx,
61+
sortingValue
62+
);
4063
if (success) {
4164
this.events.emit("mempool-transaction-added", tx);
4265
log.trace(`Transaction added to mempool: ${tx.hash().toString()}`);
@@ -69,14 +92,27 @@ export class PrivateMempool extends SequencerModule implements Mempool {
6992
offset?: number,
7093
limit?: number
7194
): Promise<PendingTransaction[]> {
72-
return await this.transactionStorage.getPendingUserTransactions(
95+
if (this.type() === "based") {
96+
return [];
97+
}
98+
99+
let txs = await this.transactionStorage.getPendingUserTransactions(
73100
offset ?? 0,
74101
limit
75102
);
103+
104+
if (this.mempoolSorting.enablePostSorting()) {
105+
txs = this.mempoolSorting.postSorting(txs);
106+
}
107+
108+
return txs;
76109
}
77110

78111
@trace("mempool.get_mandatory_txs")
79112
public async getMandatoryTxs(): Promise<PendingTransaction[]> {
113+
if (this.type() === "private") {
114+
return [];
115+
}
80116
return (await this.messageService?.getPendingMessages()) ?? [];
81117
}
82118

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { noop } from "@proto-kit/common";
2+
3+
import { PendingTransaction } from "../PendingTransaction";
4+
import {
5+
SequencerModule,
6+
sequencerModule,
7+
} from "../../sequencer/builder/SequencerModule";
8+
9+
import { MempoolSorting } from "./MempoolSorting";
10+
11+
@sequencerModule()
12+
export class DefaultMempoolSorting
13+
extends SequencerModule
14+
implements MempoolSorting
15+
{
16+
public async start() {
17+
noop();
18+
}
19+
20+
public enablePostSorting(): boolean {
21+
return false;
22+
}
23+
24+
public postSorting(transactions: PendingTransaction[]): PendingTransaction[] {
25+
return transactions;
26+
}
27+
28+
public presortingPriority(tx: PendingTransaction): number {
29+
// This means we order by first in, first out in the db
30+
return Date.UTC(2500, 0) - Date.now();
31+
}
32+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { PendingTransaction } from "../PendingTransaction";
2+
3+
export interface MempoolSorting {
4+
/**
5+
* Presorting happens on the backend (i.e. the DB), before the data travels to the sequencer.
6+
* It's very fast, but limited to only integer sorting.
7+
* The value returned here has to be static per transaction, since it will be sorted and
8+
* compared on the DB-side.
9+
*
10+
* @param tx
11+
* @returns Priority of the transaction - larger is better (therefore will be
12+
* put in the block first)
13+
*/
14+
presortingPriority(tx: PendingTransaction): number;
15+
16+
/**
17+
* Indicate whether to do pre-sorting (as it's expensive depending on your block size)
18+
*/
19+
enablePostSorting(): boolean;
20+
21+
/**
22+
* Postsorting happens on the sequencer-side. It's less fast but can take in any two
23+
* transactions and directly compare them based on arbitrary logic
24+
*/
25+
postSorting(transactions: PendingTransaction[]): PendingTransaction[];
26+
}

0 commit comments

Comments
 (0)