Skip to content

Commit be9c4b3

Browse files
authored
Merge pull request #417 from proto-kit/feature/independent-trigger-timers
Made TimedBlockTrigger use independent timers
2 parents 89bd285 + 90a8dd6 commit be9c4b3

8 files changed

Lines changed: 72 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1515
- Separated settlement and bridging functionally, so now settlement can be used without bridging [#376](https://github.com/proto-kit/framework/pull/376)
1616
- Added nightly releases via pkg.pr.new [#384](https://github.com/proto-kit/framework/pull/384)
1717
- Introduced Changelog [#378](https://github.com/proto-kit/framework/pull/378)
18+
19+
### Removed
20+
21+
- Removed `tick` event on `BlockTrigger` [#417](https://github.com/proto-kit/framework/pull/417)
22+

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ export class PrismaTransactionStorage implements TransactionStorage {
4949
return txs.map((tx) => this.transactionMapper.mapIn(tx));
5050
}
5151

52+
public async countPendingUserTransactions() {
53+
const { prismaClient } = this.connection;
54+
55+
return await prismaClient.transaction.count({
56+
where: {
57+
executionResult: {
58+
is: null,
59+
},
60+
isMessage: {
61+
equals: false,
62+
},
63+
inputPaths: {
64+
is: null,
65+
},
66+
},
67+
});
68+
}
69+
5270
public async removeTx(hashes: string[], type: "included" | "dropped") {
5371
// In our schema, included txs are simply just linked with blocks, so we only
5472
// need to delete if we drop a tx

packages/sequencer/src/helpers/BusyGuard.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import { log } from "@proto-kit/common";
44
* Mostly useful for production of blocks, batches and tasks.
55
*/
66
export function ensureNotBusy<T>() {
7-
let inProgress = false;
8-
9-
return function innerFunction(
7+
return function innerFunction<R>(
108
_target: T,
119
methodName: string,
12-
descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
10+
descriptor: TypedPropertyDescriptor<
11+
(...args: any[]) => Promise<R | undefined>
12+
>
1313
): void {
14+
let inProgress = false;
15+
1416
const originalMethod = descriptor.value!;
1517

1618
descriptor.value = async function wrapped(this: T, ...args: unknown[]) {
1719
if (inProgress) {
18-
log.trace(`${methodName} is in use at the moment.`);
20+
log.trace(`${methodName} is in use at the moment, skipping execution.`);
1921
return undefined;
2022
}
2123

packages/sequencer/src/mempool/Mempool.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ export interface Mempool<Events extends MempoolEvents = MempoolEvents>
2222
getMandatoryTxs: () => Promise<PendingTransaction[]>;
2323

2424
removeTxs: (included: string[], dropped: string[]) => Promise<void>;
25+
26+
length: () => Promise<number>;
2527
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@ export class PrivateMempool
4747
}
4848

4949
public async length(): Promise<number> {
50-
const txs = await this.transactionStorage.getPendingUserTransactions(0);
51-
return txs.length;
50+
const numUserTxs =
51+
await this.transactionStorage.countPendingUserTransactions();
52+
53+
const messages = await this.getMandatoryTxs();
54+
55+
return numUserTxs + messages.length;
5256
}
5357

5458
public async add(tx: PendingTransaction): Promise<boolean> {
Lines changed: 28 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { inject, injectable } from "tsyringe";
22
import { log } from "@proto-kit/common";
3-
import gcd from "compute-gcd";
43

54
import { closeable, Closeable } from "../../../sequencer/builder/Closeable";
65
import { BatchProducerModule } from "../BatchProducerModule";
@@ -14,37 +13,23 @@ import {
1413
} from "../../../settlement/BridgingModule";
1514
import { ensureNotBusy } from "../../../helpers/BusyGuard";
1615

17-
import { BlockEvents, BlockTriggerBase } from "./BlockTrigger";
16+
import { BlockTriggerBase } from "./BlockTrigger";
1817

1918
export interface TimedBlockTriggerConfig {
20-
/**
21-
* Interval for the tick event to be fired.
22-
* The time x of any block trigger time is always guaranteed to be
23-
* tick % x == 0.
24-
* Value has to be a divisor of gcd(blockInterval, settlementInterval).
25-
* If it doesn't satisfy this requirement, this config will not be respected
26-
*/
27-
tick?: number;
2819
settlementInterval?: number;
2920
blockInterval: number;
3021
produceEmptyBlocks?: boolean;
3122

3223
settlementTokenConfig: SettlementTokenConfig;
3324
}
3425

35-
export interface TimedBlockTriggerEvent extends BlockEvents {
36-
tick: [number];
37-
}
38-
3926
@injectable()
4027
@closeable()
4128
export class TimedBlockTrigger
42-
extends BlockTriggerBase<TimedBlockTriggerConfig, TimedBlockTriggerEvent>
29+
extends BlockTriggerBase<TimedBlockTriggerConfig>
4330
implements Closeable
4431
{
45-
// There is no real type for interval ids somehow, so any it is
46-
47-
private interval?: any;
32+
private intervals: NodeJS.Timeout[] = [];
4833

4934
public constructor(
5035
@inject("BatchProducerModule", { isOptional: true })
@@ -69,26 +54,6 @@ export class TimedBlockTrigger
6954
);
7055
}
7156

72-
private getTimerInterval(): number {
73-
const { settlementInterval, blockInterval, tick } = this.config;
74-
75-
let timerInterval =
76-
settlementInterval !== undefined
77-
? gcd(settlementInterval, blockInterval)
78-
: blockInterval;
79-
80-
const definedTick = tick ?? 1000;
81-
if (definedTick <= timerInterval) {
82-
// Check if tick is a divisor of the calculated interval
83-
const div = timerInterval / definedTick;
84-
if (Math.floor(div) === div) {
85-
timerInterval = definedTick;
86-
}
87-
}
88-
89-
return timerInterval;
90-
}
91-
9257
public async start(): Promise<void> {
9358
log.info("Starting timed block trigger");
9459
const { settlementInterval, blockInterval } = this.config;
@@ -102,43 +67,39 @@ export class TimedBlockTrigger
10267
);
10368
}
10469

105-
const timerInterval = this.getTimerInterval();
106-
107-
let totalTime = 0;
108-
this.interval = setInterval(async () => {
109-
totalTime += timerInterval;
110-
111-
this.events.emit("tick", totalTime);
112-
70+
const blockIntervalId = setInterval(async () => {
11371
try {
11472
// Trigger unproven blocks
115-
if (totalTime % blockInterval === 0) {
116-
await this.produceUnprovenBlock();
117-
}
118-
119-
// Trigger proven (settlement) blocks
120-
// Only produce settlements if a time has been set
121-
// otherwise treat as unproven-only
122-
if (
123-
settlementInterval !== undefined &&
124-
totalTime % settlementInterval === 0
125-
) {
126-
await this.tryProduceSettlement();
127-
}
73+
await this.produceUnprovenBlock();
12874
} catch (error) {
12975
log.error(error);
13076
}
131-
}, timerInterval);
77+
}, blockInterval);
78+
this.intervals.push(blockIntervalId);
79+
80+
if (settlementInterval !== undefined) {
81+
const settlementIntervalId = setInterval(async () => {
82+
try {
83+
// Trigger settlement
84+
await this.tryProduceSettlement();
85+
} catch (error) {
86+
log.error(error);
87+
}
88+
}, settlementInterval);
89+
this.intervals.push(settlementIntervalId);
90+
}
13291

13392
await super.start();
13493
}
13594

95+
// This is technically not necessary since produceBlock checks business down the line
96+
// but we save a bunch of DB checks before
97+
@ensureNotBusy()
13698
private async produceUnprovenBlock() {
137-
// TODO Optimize towards mempool.length()
138-
const mempoolTxs = await this.mempool.getTxs(0);
99+
const mempoolLength = await this.mempool.length();
139100
// Produce a block if either produceEmptyBlocks is true or we have more
140-
// than 1 tx in mempool
141-
if (mempoolTxs.length > 0 || (this.config.produceEmptyBlocks ?? true)) {
101+
// than 1 tx in mempool or messages
102+
if (mempoolLength > 0 || (this.config.produceEmptyBlocks ?? true)) {
142103
await this.produceBlock();
143104
}
144105
}
@@ -152,7 +113,8 @@ export class TimedBlockTrigger
152113
}
153114

154115
public async close(): Promise<void> {
155-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
156-
clearInterval(this.interval);
116+
this.intervals.forEach((interval) => {
117+
clearInterval(interval);
118+
});
157119
}
158120
}

packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ export class InMemoryTransactionStorage implements TransactionStorage {
6666
return this.queue.slice(from, to).map(({ tx }) => tx);
6767
}
6868

69+
public async countPendingUserTransactions() {
70+
return (await this.getPendingUserTransactions(0)).length;
71+
}
72+
6973
public async pushUserTransaction(
7074
tx: PendingTransaction,
7175
priority: number

packages/sequencer/src/storage/repositories/TransactionStorage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface TransactionStorage {
1111
limit?: number
1212
) => Promise<PendingTransaction[]>;
1313

14+
countPendingUserTransactions: () => Promise<number>;
15+
1416
removeTx: (txHashes: string[], type: "included" | "dropped") => Promise<void>;
1517

1618
/**

0 commit comments

Comments
 (0)