Skip to content
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/configuration/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const EXECUTOR = {
},

QUEUE: {
QUEUE_PROCESSOR_INTERVAL: 3000, // 3 seconds (changed from 60000)
QUEUE_PROCESSOR_INTERVAL: 15000, // 15 seconds (increased from 3 seconds for CU optimization)
MAX_BATCH_SIZE: 100, // Maximum number of deposits per batch
MIN_BATCH_SIZE: 1, // Minimum number of deposits per batch
MAX_RETRIES: 3, // Maximum number of retries per transaction
Expand Down
49 changes: 41 additions & 8 deletions src/configuration/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,20 @@ export function calculateGasLimit(
}

/**
* Polls for a transaction receipt with retries
* Polls for a transaction receipt with exponential backoff
*/
export async function pollForReceipt(
txHash: string,
provider: ethers.Provider,
logger: Logger,
confirmations: number = 1,
): Promise<EthersTransactionReceipt | null> {
const maxAttempts = 30; // Try for about 5 minutes with 10-second intervals
const pollingInterval = 10000; // 10 seconds
const maxAttempts = 15; // Reduced from 30
const initialPollingInterval = 3000; // Start with 3 seconds
const maxPollingInterval = 30000; // Cap at 30 seconds
const backoffMultiplier = 1.5; // Exponential backoff factor

let pollingInterval = initialPollingInterval;

for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
Expand All @@ -127,8 +131,19 @@ export async function pollForReceipt(
)) as unknown as EthersTransactionReceipt;

if (!receipt) {
// If no receipt yet, wait and try again
// If no receipt yet, wait with exponential backoff
logger.debug('Transaction receipt not found, retrying with backoff', {
txHash,
attempt: attempt + 1,
nextPollingIntervalMs: pollingInterval,
});
await new Promise((resolve) => setTimeout(resolve, pollingInterval));

// Increase polling interval for next attempt (exponential backoff)
pollingInterval = Math.min(
pollingInterval * backoffMultiplier,
maxPollingInterval,
);
continue;
}

Expand All @@ -137,20 +152,38 @@ export async function pollForReceipt(
const receiptConfirmations = currentBlock - receipt.blockNumber + 1;

if (receiptConfirmations >= confirmations) {
logger.info('Transaction confirmed', {
txHash,
blockNumber: receipt.blockNumber,
confirmations: receiptConfirmations,
attemptsTaken: attempt + 1,
});
return receipt;
}

// Not enough confirmations, wait and try again
await new Promise((resolve) => setTimeout(resolve, pollingInterval));
// Not enough confirmations, wait with current interval (no backoff for confirmations)
logger.debug('Waiting for confirmations', {
txHash,
currentConfirmations: receiptConfirmations,
requiredConfirmations: confirmations,
});
await new Promise((resolve) =>
setTimeout(resolve, Math.min(pollingInterval, 5000)),
); // Cap confirmation polling at 5s
} catch (error) {
logger.warn('Error polling for receipt', {
error: error instanceof Error ? error.message : String(error),
txHash,
attempt,
attempt: attempt + 1,
nextPollingIntervalMs: pollingInterval,
});

// Wait and try again
// Wait with exponential backoff on error
await new Promise((resolve) => setTimeout(resolve, pollingInterval));
pollingInterval = Math.min(
pollingInterval * backoffMultiplier,
maxPollingInterval,
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const CONFIG = {
| 'warn'
| 'error',
databaseType: (process.env.DB || 'json') as 'json' | 'supabase',
pollInterval: parseInt(process.env.POLL_INTERVAL || '15'),
pollInterval: parseInt(process.env.POLL_INTERVAL || '30'), // Increased from 15 to 30 seconds
maxBlockRange: parseInt(process.env.MAX_BLOCK_RANGE || '2000'),
maxRetries: parseInt(process.env.MAX_RETRIES || '5'),
reorgDepth: parseInt(process.env.REORG_DEPTH || '64'),
Expand Down
95 changes: 63 additions & 32 deletions src/executor/strategies/BaseExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export class BaseExecutor implements IExecutor {
private readonly config: ExecutorConfig;
private readonly gasCostEstimator: GasCostEstimator;
private readonly simulationService: SimulationService | null;
private cachedGasPrice: bigint;
private lastGasPriceUpdate: number;
private readonly GAS_PRICE_CACHE_TTL = 30000; // 30 seconds cache

/**
* Creates a new BaseExecutor instance
Expand Down Expand Up @@ -129,6 +132,8 @@ export class BaseExecutor implements IExecutor {
this.processingInterval = null;
this.provider = provider;
this.config = config;
this.cachedGasPrice = 0n;
this.lastGasPriceUpdate = 0;

// Initialize simulation service (with fallback)
try {
Expand Down Expand Up @@ -391,28 +396,10 @@ export class BaseExecutor implements IExecutor {
};
}

// Get current gas price and calculate gas cost
const feeData = await this.provider.getFeeData();
if (!feeData.gasPrice) {
const error = new TransactionValidationError(
'Failed to get gas price from provider',
{
feeData: feeData,
},
);
if (this.errorLogger) {
await this.errorLogger.warn(error, {
context: 'base-executor-gas-price-missing',
});
}
return {
isValid: false,
error,
};
}

// Get current gas price and calculate gas cost using cached method
const gasPrice = await this.getCachedGasPrice();
const gasBoostMultiplier = BigInt(100 + this.config.gasBoostPercentage);
const boostedGasPrice = (feeData.gasPrice * gasBoostMultiplier) / 100n;
const boostedGasPrice = (gasPrice * gasBoostMultiplier) / 100n;
const estimatedGasCost =
boostedGasPrice * profitability.estimates.gas_estimate;

Expand Down Expand Up @@ -720,6 +707,58 @@ export class BaseExecutor implements IExecutor {
return this.provider.getBalance(this.wallet.address);
}

/**
* Gets cached gas price or fetches fresh if cache expired
* @returns Gas price in wei
*/
private async getCachedGasPrice(): Promise<bigint> {
try {
const now = Date.now();

// Use cached gas price if it's still fresh
if (
this.cachedGasPrice > 0n &&
now - this.lastGasPriceUpdate < this.GAS_PRICE_CACHE_TTL
) {
return this.cachedGasPrice;
}

// Fetch fresh gas price
const feeData = await this.provider.getFeeData();
const gasPrice = feeData.gasPrice || 0n;

// Handle very low gas prices (sub 1 gwei)
const MIN_GAS_PRICE_GWEI = 1n;
const MIN_GAS_PRICE_WEI = MIN_GAS_PRICE_GWEI * 10n ** 9n;

const adjustedGasPrice =
gasPrice < MIN_GAS_PRICE_WEI ? MIN_GAS_PRICE_WEI : gasPrice;

// Handle zero gas price
const finalGasPrice =
adjustedGasPrice === 0n ? 3n * 10n ** 9n : adjustedGasPrice; // 3 gwei fallback

this.cachedGasPrice = finalGasPrice;
this.lastGasPriceUpdate = now;

return finalGasPrice;
} catch (error) {
this.logger.warn('Failed to get gas price, using cached or fallback', {
error: error instanceof Error ? error.message : String(error),
cachedGasPrice: this.cachedGasPrice.toString(),
});

if (this.errorLogger) {
await this.errorLogger.warn(error as Error, {
context: 'get-cached-gas-price',
});
}

// Use cached value if available, otherwise use fallback
return this.cachedGasPrice > 0n ? this.cachedGasPrice : 3n * 10n ** 9n; // 3 gwei fallback
}
}

/**
* Starts the queue processor
*/
Expand Down Expand Up @@ -1073,17 +1112,7 @@ export class BaseExecutor implements IExecutor {
throw error;
}

// Get current gas price with buffer
const feeData = await this.provider.getFeeData();
if (!feeData.gasPrice) {
const error = new Error('Failed to get gas price from provider');
if (this.errorLogger) {
await this.errorLogger.warn(error, {
context: 'base-executor-estimate-gas-no-gas-price',
});
}
throw error;
}
// Gas price will be retrieved via cached method in calculateGasParameters

const tipReceiver = this.config.defaultTipReceiver || this.wallet.address;
if (!tipReceiver) {
Expand Down Expand Up @@ -1172,11 +1201,13 @@ export class BaseExecutor implements IExecutor {
finalGasLimit: bigint;
boostedGasPrice: bigint;
}> {
const cachedGasPrice = await this.getCachedGasPrice();
return calculateGasParameters(
this.provider,
gasEstimate,
this.config.gasBoostPercentage,
this.logger,
cachedGasPrice,
);
}

Expand Down
12 changes: 10 additions & 2 deletions src/executor/strategies/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,20 @@ export async function calculateGasParameters(
gasEstimate: bigint,
gasBoostPercentage: number,
logger: Logger,
cachedGasPrice?: bigint,
): Promise<{
finalGasLimit: bigint;
boostedGasPrice: bigint;
}> {
const finalGasLimit = calculateGasLimit(gasEstimate, 1, logger);
const feeData = await provider.getFeeData();
const baseGasPrice = feeData.gasPrice || 0n;

let baseGasPrice: bigint;
if (cachedGasPrice !== undefined) {
baseGasPrice = cachedGasPrice;
} else {
const feeData = await provider.getFeeData();
baseGasPrice = feeData.gasPrice || 0n;
}

// Ensure minimum gas price for stability
const MIN_GAS_PRICE_WEI = 1000000000n; // 1 gwei
Expand All @@ -119,6 +126,7 @@ export async function calculateGasParameters(
actualGasPriceGwei: Number(baseGasPrice) / 1e9,
minGasPriceGwei: 1,
usingMinimum: true,
usingCachedPrice: cachedGasPrice !== undefined,
});
}

Expand Down
Loading
Loading