Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make interval waiting to bundle variable #405

Merged
merged 9 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/executor/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class Executor {
): Promise<ReplaceTransactionResult> {
const newRequest = { ...transactionInfo.transactionRequest }

let gasPriceParameters
let gasPriceParameters: GasPriceParameters
try {
gasPriceParameters =
await this.gasPriceManager.tryGetNetworkGasPrice()
Expand Down
104 changes: 72 additions & 32 deletions src/executor/executorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ function getTransactionsFromUserOperationEntries(
)
}

const MIN_INTERVAL = 100 // 0.1 seconds (100ms)
const MAX_INTERVAL = 1000 // Capped at 1 second (1000ms)
const SCALE_FACTOR = 10 // Interval increases by 5ms per task per minute
const RPM_WINDOW = 60000 // 1 minute window in ms

export class ExecutorManager {
private config: AltoConfig
private executor: Executor
Expand All @@ -58,9 +63,10 @@ export class ExecutorManager {
private reputationManager: InterfaceReputationManager
private unWatch: WatchBlocksReturnType | undefined
private currentlyHandlingBlock = false
private timer?: NodeJS.Timer
private gasPriceManager: GasPriceManager
private eventManager: EventManager
private opsCount: number[] = []
private bundlingMode: BundlingMode

constructor({
config,
Expand Down Expand Up @@ -96,24 +102,76 @@ export class ExecutorManager {
this.gasPriceManager = gasPriceManager
this.eventManager = eventManager

if (this.config.bundleMode === "auto") {
this.timer = setInterval(async () => {
await this.bundle()
}, this.config.maxBundleWait) as NodeJS.Timer
this.bundlingMode = this.config.bundleMode

if (this.bundlingMode === "auto") {
this.autoScalingBundling()
}
}

setBundlingMode(bundleMode: BundlingMode): void {
if (bundleMode === "auto" && !this.timer) {
this.timer = setInterval(async () => {
await this.bundle()
}, this.config.maxBundleWait) as NodeJS.Timer
} else if (bundleMode === "manual" && this.timer) {
clearInterval(this.timer)
this.timer = undefined
async setBundlingMode(bundleMode: BundlingMode): Promise<void> {
this.bundlingMode = bundleMode

if (bundleMode === "manual") {
await new Promise((resolve) =>
setTimeout(resolve, 2 * MAX_INTERVAL)
)
}

if (bundleMode === "auto") {
this.autoScalingBundling()
}
}

async autoScalingBundling() {
const now = Date.now()
this.opsCount = this.opsCount.filter(
(timestamp) => now - timestamp < RPM_WINDOW
)

const opsToBundle = await this.getOpsToBundle()

if (opsToBundle.length > 0) {
const opsCount: number = opsToBundle.length
const timestamp: number = Date.now()
this.opsCount.push(...Array(opsCount).fill(timestamp)) // Add timestamps for each task

await this.bundle(opsToBundle)
}

const rpm: number = this.opsCount.length
// Calculate next interval with linear scaling
const nextInterval: number = Math.min(
MIN_INTERVAL + rpm * SCALE_FACTOR, // Linear scaling
MAX_INTERVAL // Cap at 1000ms
)
if (this.bundlingMode === "auto") {
setTimeout(this.autoScalingBundling.bind(this), nextInterval)
}
}

async getOpsToBundle() {
const opsToBundle: UserOperationInfo[][] = []

while (true) {
const ops = await this.mempool.process(
this.config.maxGasPerBundle,
1
)
if (ops?.length > 0) {
opsToBundle.push(ops)
} else {
break
}
}

if (opsToBundle.length === 0) {
return []
}

return opsToBundle
}

async bundleNow(): Promise<Hash[]> {
const ops = await this.mempool.process(this.config.maxGasPerBundle, 1)
if (ops.length === 0) {
Expand Down Expand Up @@ -285,25 +343,7 @@ export class ExecutorManager {
return txHash
}

async bundle() {
const opsToBundle: UserOperationInfo[][] = []

while (true) {
const ops = await this.mempool.process(
this.config.maxGasPerBundle,
1
)
if (ops?.length > 0) {
opsToBundle.push(ops)
} else {
break
}
}

if (opsToBundle.length === 0) {
return
}

async bundle(opsToBundle: UserOperationInfo[][] = []) {
await Promise.all(
opsToBundle.map(async (ops) => {
const opEntryPointMap = new Map<
Expand Down
13 changes: 8 additions & 5 deletions src/rpc/rpcHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ import {
import { base, baseSepolia, optimism } from "viem/chains"
import type { NonceQueuer } from "./nonceQueuer"
import type { AltoConfig } from "../createConfig"
import { SignedAuthorization, SignedAuthorizationList } from "viem/experimental"
import type {
SignedAuthorization,
SignedAuthorizationList
} from "viem/experimental"

export interface IRpcEndpoint {
handleMethod(
Expand Down Expand Up @@ -248,7 +251,7 @@ export class RpcHandler implements IRpcEndpoint {
case "debug_bundler_setBundlingMode":
return {
method,
result: this.debug_bundler_setBundlingMode(
result: await this.debug_bundler_setBundlingMode(
...request.params
)
}
Expand Down Expand Up @@ -588,12 +591,12 @@ export class RpcHandler implements IRpcEndpoint {
return transactions[0]
}

debug_bundler_setBundlingMode(
async debug_bundler_setBundlingMode(
bundlingMode: BundlingMode
): BundlerSetBundlingModeResponseResult {
): Promise<BundlerSetBundlingModeResponseResult> {
this.ensureDebugEndpointsAreEnabled("debug_bundler_setBundlingMode")

this.executorManager.setBundlingMode(bundlingMode)
await this.executorManager.setBundlingMode(bundlingMode)
return "ok"
}

Expand Down
3 changes: 2 additions & 1 deletion test/e2e/alto-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"mempool-max-queued-ops": 10,
"enforce-unique-senders-per-bundle": false,
"code-override-support": true,
"enable-instant-bundling-endpoint": true
"enable-instant-bundling-endpoint": true,
"bundling-mode": "manual"
}
3 changes: 2 additions & 1 deletion test/e2e/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ export default async function setup({ provide }) {

const anvilInstance = anvil({
chainId: foundry.id,
port: 8485
port: 8485,
codeSizeLimit: 1000_000
})
await anvilInstance.start()
const anvilRpc = `http://${anvilInstance.host}:${anvilInstance.port}`
Expand Down
27 changes: 13 additions & 14 deletions test/e2e/tests/eth_sendUserOperation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,16 @@ describe.each([
]
})

await expect(async () => {
await firstClient.getUserOperationReceipt({
await expect(() =>
firstClient.getUserOperationReceipt({
hash: firstHash
})
}).rejects.toThrow(UserOperationReceiptNotFoundError)
await expect(async () => {
await secondClient.getUserOperationReceipt({
).rejects.toThrow(UserOperationReceiptNotFoundError)
await expect(() =>
secondClient.getUserOperationReceipt({
hash: secondHash
})
}).rejects.toThrow(UserOperationReceiptNotFoundError)
).rejects.toThrow(UserOperationReceiptNotFoundError)

await sendBundleNow({ altoRpc })

Expand Down Expand Up @@ -359,12 +359,7 @@ describe.each([
const nonceValueDiffs = [0n, 1n, 2n]

// Send 3 sequential user ops
const sendUserOperation = async (nonceValueDiff: bigint) => {
const nonce = (await entryPointContract.read.getNonce([
client.account.address,
nonceKey
])) as bigint

const sendUserOperation = (nonce: bigint) => {
return client.sendUserOperation({
calls: [
{
Expand All @@ -373,13 +368,17 @@ describe.each([
data: "0x"
}
],
nonce: nonce + nonceValueDiff
nonce: nonce
})
}
const opHashes: Hex[] = []
const nonce = (await entryPointContract.read.getNonce([
client.account.address,
nonceKey
])) as bigint

for (const nonceValueDiff of nonceValueDiffs) {
opHashes.push(await sendUserOperation(nonceValueDiff))
opHashes.push(await sendUserOperation(nonce + nonceValueDiff))
}

await sendBundleNow({ altoRpc })
Expand Down
Loading