Skip to content

Task 1-1: Transaction Retry on BlockhashNotFound / Slippage

Phase: 1 Priority: High Module: solana Depends on: Không có Reference: docs/bountyhunter-blockchain-p2/details/feature-solana-integration/SPEC.md

Background

Solana transactions thất bại với lỗi BlockhashNotFound khi blockhash hết hạn (window ~400ms) — phổ biến khi network congestion cao. SlippageToleranceExceeded xảy ra trong AMM swaps khi giá thay đổi quá nhanh. Hiện tại SolanaService không có retry logic cho các lỗi transient này, dẫn đến job fail ngay cả khi resend với blockhash mới sẽ thành công.

Tasks

Note: SolanaUtil là class quản lý Solana Connection — đây là nơi tốt nhất để thêm sendWithRetry method. Import sendAndConfirmTransaction, Transaction từ @solana/web3.js. commitment level nên là 'confirmed' hoặc 'finalized' tùy usecase. Max 3 retries là đủ; retry quá nhiều có thể gây duplicate transaction nếu tx đã được broadcast.

  • [ ] Tạo method sendWithRetry trong SolanaUtil:
    async sendWithRetry(
      transaction: Transaction,
      signers: Signer[],
      options?: { maxRetries?: number; commitment?: Commitment },
    ): Promise<TransactionSignature> {
      const maxRetries = options?.maxRetries ?? 3;
      const commitment = options?.commitment ?? 'confirmed';
    
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
          // Fetch fresh blockhash on every attempt
          const { blockhash, lastValidBlockHeight } =
            await this.connection.getLatestBlockhash(commitment);
          transaction.recentBlockhash = blockhash;
          transaction.lastValidBlockHeight = lastValidBlockHeight;
    
          const signature = await sendAndConfirmTransaction(
            this.connection,
            transaction,
            signers,
            { commitment, skipPreflight: false },
          );
          return signature;
        } catch (error) {
          const isRetryable =
            error.message?.includes('BlockhashNotFound') ||
            error.message?.includes('block height exceeded') ||
            error.message?.includes('SlippageToleranceExceeded');
    
          if (isRetryable && attempt < maxRetries - 1) {
            this.logger.warn(
              `[SOLANA] sendWithRetry attempt=${attempt + 1}/${maxRetries} retryable error: ${error.message}`
            );
            await new Promise((r) => setTimeout(r, 500 * (attempt + 1))); // backoff
            continue;
          }
          throw error;
        }
      }
    }
    
  • [ ] Thay thế sendAndConfirmTransaction calls trực tiếp trong SolanaService bằng this.solanaUtil.sendWithRetry(...)
  • [ ] Đảm bảo skipPreflight: false để bắt lỗi simulation trước khi broadcast
  • [ ] Log attempt number và error type để debug

Verification / Acceptance Criteria

  • [ ] Simulate BlockhashNotFound error lần đầu → retry lần 2 với blockhash mới → thành công
  • [ ] Simulate SlippageToleranceExceeded error → retry với slippage mới hoặc fail sau 3 lần
  • [ ] Non-retryable error (e.g., InsufficientFunds) → không retry, throw ngay lập tức
  • [ ] Max 3 retries → sau lần thứ 3 throw error bình thường để Bull queue xử lý
  • [ ] Log rõ ràng attempt=X/3 cho mỗi retry
  • [ ] TypeScript compile không có lỗi

Files to Modify

  • src/solana/utils/solana.util.ts
  • src/solana/solana.service.ts (thay thế direct sendAndConfirmTransaction calls)