|
| 1 | +# How It Works: EOA Worker Transaction Processing |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The EOA (Externally Owned Account) Worker is a single worker per EOA:chain combination that processes all transactions for that specific EOA. It manages transaction lifecycle from queuing to confirmation, with robust error handling and nonce management. |
| 6 | + |
| 7 | +## Core Architecture |
| 8 | + |
| 9 | +### Data Structures |
| 10 | + |
| 11 | +The worker maintains several key data structures: |
| 12 | + |
| 13 | +- **`pending_txns`**: Queue of transaction IDs waiting to be sent |
| 14 | +- **`success_txns`**: Sorted set mapping nonces to transaction hashes for sent transactions |
| 15 | +- **`hash_to_id`**: Hash map from transaction hash to transaction ID |
| 16 | +- **`tx_data`**: Hash map from transaction ID to full transaction JSON data |
| 17 | +- **`borrowed_txns`**: Hash map for crash recovery of prepared transactions |
| 18 | +- **`recycled_nonces`**: Sorted set of available recycled nonces |
| 19 | +- **`optimistic_nonce`**: Next nonce to use for new transactions |
| 20 | +- **`last_chain_nonce`**: Cached chain nonce for comparison |
| 21 | +- **`eoa_health`**: Health status including funding state and last check timestamp |
| 22 | + |
| 23 | +### Main Worker Loop |
| 24 | + |
| 25 | +The worker runs in a continuous loop with three main phases: |
| 26 | + |
| 27 | +1. **Recovery Phase**: Recovers any borrowed transactions from crashes |
| 28 | +2. **Confirmation Phase**: Checks for mined transactions and handles failures |
| 29 | +3. **Send Phase**: Sends new transactions while managing nonce allocation |
| 30 | + |
| 31 | +## Transaction Flow Diagram |
| 32 | + |
| 33 | +```mermaid |
| 34 | +flowchart TD |
| 35 | + A["🚀 EOA Worker Start"] --> B["Main Worker Loop"] |
| 36 | + B --> C["1. Recover Borrowed State"] |
| 37 | + B --> D["2. Confirm Flow"] |
| 38 | + B --> E["3. Send Flow"] |
| 39 | + B --> F["💤 Sleep(WORKER_CYCLE_DELAY)"] |
| 40 | + F --> B |
| 41 | +
|
| 42 | + %% Recovery Flow |
| 43 | + C --> C1["Check borrowed_txns"] |
| 44 | + C1 --> C2{Any borrowed<br/>transactions?} |
| 45 | + C2 -->|Yes| C3["For each borrowed tx:<br/>Rebroadcast prepared_tx"] |
| 46 | + C2 -->|No| D |
| 47 | + C3 --> C4["RPC Send Transaction"] |
| 48 | + C4 --> C5{Result Type?} |
| 49 | + C5 -->|Deterministic Failure| C6["❌ Requeue to pending_txns<br/>Add nonce to recycled_nonces"] |
| 50 | + C5 -->|Success/Indeterminate| C7["✅ Add to success_txns<br/>Update hash_to_id"] |
| 51 | + C6 --> C8["Remove from borrowed_txns"] |
| 52 | + C7 --> C8 |
| 53 | + C8 --> C9{More borrowed<br/>transactions?} |
| 54 | + C9 -->|Yes| C3 |
| 55 | + C9 -->|No| D |
| 56 | +
|
| 57 | + %% Confirmation Flow |
| 58 | + D --> D1["Get current_chain_nonce"] |
| 59 | + D1 --> D2{Chain nonce<br/>changed?} |
| 60 | + D2 -->|No| E |
| 61 | + D2 -->|Yes| D3["Get pending hashes for<br/>nonces < current_chain_nonce"] |
| 62 | + D3 --> D4["For each pending hash:<br/>Get transaction receipt"] |
| 63 | + D4 --> D5{Receipt exists?} |
| 64 | + D5 -->|Yes| D6["✅ Transaction mined<br/>Add to confirmed_tx_ids<br/>Cleanup success_txns"] |
| 65 | + D5 -->|No| D7["❌ Transaction failed<br/>Add to failed_tx_ids"] |
| 66 | + D6 --> D8{More hashes<br/>to check?} |
| 67 | + D7 --> D8 |
| 68 | + D8 -->|Yes| D4 |
| 69 | + D8 -->|No| D9["Requeue failed transactions<br/>to pending_txns"] |
| 70 | + D9 --> D10["Update last_chain_nonce"] |
| 71 | + D10 --> E |
| 72 | +
|
| 73 | + %% Send Flow |
| 74 | + E --> E1["Check EOA Health"] |
| 75 | + E1 --> E2{EOA funded?} |
| 76 | + E2 -->|No| B |
| 77 | + E2 -->|Yes| E3["Process Recycled Nonces"] |
| 78 | + E3 --> E4["Check in-flight count"] |
| 79 | + E4 --> E5{Too many<br/>in-flight?} |
| 80 | + E5 -->|Yes| B |
| 81 | + E5 -->|No| E6["Process New Transactions"] |
| 82 | + E6 --> B |
| 83 | +
|
| 84 | + %% Process Recycled Nonces |
| 85 | + E3 --> E3A{recycled_nonces<br/>> MAX_RECYCLED?} |
| 86 | + E3A -->|Yes| E3B["🧹 Clear all recycled nonces"] |
| 87 | + E3A -->|No| E3C{recycled_nonces > 0<br/>AND pending_txns > 0?} |
| 88 | + E3B --> E4 |
| 89 | + E3C -->|Yes| E3D["Pop min nonce<br/>Dequeue tx_id"] |
| 90 | + E3C -->|No| E3E{Still recycled<br/>nonces?} |
| 91 | + E3D --> E3F["Send transaction with nonce"] |
| 92 | + E3F --> E3C |
| 93 | + E3E -->|Yes| E3G["Send no-op transaction"] |
| 94 | + E3E -->|No| E4 |
| 95 | + E3G --> E3E |
| 96 | +
|
| 97 | + %% Process New Transactions |
| 98 | + E6 --> E6A{sent_count < max_count<br/>AND pending_txns > 0?} |
| 99 | + E6A -->|Yes| E6B["Dequeue tx_id<br/>Get next nonce"] |
| 100 | + E6A -->|No| B |
| 101 | + E6B --> E6C["Send transaction with nonce"] |
| 102 | + E6C --> E6D["Increment sent_count"] |
| 103 | + E6D --> E6A |
| 104 | +
|
| 105 | + %% Send Transaction with Nonce |
| 106 | + E3F --> ST1["Get transaction data"] |
| 107 | + E6C --> ST1 |
| 108 | + E3G --> ST1 |
| 109 | + ST1 --> ST2["Prepare complete transaction"] |
| 110 | + ST2 --> ST3["Store in borrowed_txns"] |
| 111 | + ST3 --> ST4["RPC Send Transaction"] |
| 112 | + ST4 --> ST5{Result Type?} |
| 113 | + ST5 -->|Deterministic Failure| ST6["❌ Requeue tx_id<br/>Add nonce to recycled<br/>Mark EOA unfunded"] |
| 114 | + ST5 -->|Success/Indeterminate| ST7["✅ Add to success_txns<br/>Update hash_to_id"] |
| 115 | + ST6 --> ST8["Remove from borrowed_txns"] |
| 116 | + ST7 --> ST8 |
| 117 | + ST8 --> ST9["Return to caller"] |
| 118 | +
|
| 119 | + %% Health Check |
| 120 | + E1 --> E1A{Time since last<br/>check > threshold?} |
| 121 | + E1A -->|Yes| E1B["Get EOA balance"] |
| 122 | + E1A -->|No| E2 |
| 123 | + E1B --> E1C["Update eoa_health.funded<br/>Update last_check"] |
| 124 | + E1C --> E2 |
| 125 | +
|
| 126 | + %% Styling |
| 127 | + classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:2px |
| 128 | + classDef process fill:#f3e5f5,stroke:#4a148c,stroke-width:2px |
| 129 | + classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px |
| 130 | + classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px |
| 131 | + classDef failure fill:#ffebee,stroke:#c62828,stroke-width:2px |
| 132 | + classDef cleanup fill:#f1f8e9,stroke:#558b2f,stroke-width:2px |
| 133 | +
|
| 134 | + class A,B,F startEnd |
| 135 | + class C,D,E,E1,E3,E4,E6,C1,C3,C4,D1,D3,D4,D9,D10,E3D,E3F,E6B,E6C,E6D,ST1,ST2,ST3,ST4,E1B,E1C process |
| 136 | + class C2,C5,C9,D2,D5,D8,E2,E5,E3A,E3C,E3E,E6A,ST5,E1A decision |
| 137 | + class C7,D6,ST7 success |
| 138 | + class C6,D7,ST6 failure |
| 139 | + class C8,D9,E3B,ST8,ST9 cleanup |
| 140 | +``` |
| 141 | + |
| 142 | +The above diagram illustrates the complete transaction processing flow. The worker operates in a continuous loop, processing three main phases sequentially. |
| 143 | + |
| 144 | +## Detailed Phase Breakdown |
| 145 | + |
| 146 | +### 1. Recovery Phase (`recover_borrowed_state`) |
| 147 | + |
| 148 | +**Purpose**: Recover from crashes by handling any transactions that were prepared but not fully processed. |
| 149 | + |
| 150 | +**Process**: |
| 151 | + |
| 152 | +- Iterates through all transactions in `borrowed_txns` |
| 153 | +- Rebroadcasts each prepared transaction to the RPC |
| 154 | +- Classifies results: |
| 155 | + - **Deterministic failures**: Requeues transaction and recycles nonce |
| 156 | + - **Success/Indeterminate**: Assumes sent and adds to success tracking |
| 157 | +- Cleans up borrowed state |
| 158 | + |
| 159 | +**Key Insight**: This provides crash resilience by ensuring no prepared transactions are lost. |
| 160 | + |
| 161 | +### 2. Confirmation Phase (`confirm_flow`) |
| 162 | + |
| 163 | +**Purpose**: Identify completed transactions and handle failures. |
| 164 | + |
| 165 | +**Process**: |
| 166 | + |
| 167 | +- Compares current chain nonce with cached `last_chain_nonce` |
| 168 | +- If unchanged, skips confirmation (no progress on chain) |
| 169 | +- For progressed nonces, checks transaction receipts |
| 170 | +- Categorizes results: |
| 171 | + - **Mined transactions**: Removes from tracking, adds to confirmed set |
| 172 | + - **Failed/Dropped transactions**: Adds to failed set for requeuing |
| 173 | +- Requeues failed transactions (deduplicated against confirmed ones) |
| 174 | +- Updates cached chain nonce |
| 175 | + |
| 176 | +**Key Insight**: Uses nonce progression to efficiently identify which transactions need confirmation checks. |
| 177 | + |
| 178 | +### 3. Send Phase (`send_flow`) |
| 179 | + |
| 180 | +**Purpose**: Send new transactions while managing nonce allocation and capacity. |
| 181 | + |
| 182 | +**Components**: |
| 183 | + |
| 184 | +#### A. Health Check |
| 185 | + |
| 186 | +- Periodically checks EOA balance |
| 187 | +- Skips sending if insufficient funds |
| 188 | +- Prevents wasteful RPC calls when EOA is unfunded |
| 189 | + |
| 190 | +#### B. Recycled Nonce Processing |
| 191 | + |
| 192 | +- **Overflow Protection**: Clears all recycled nonces if too many accumulate |
| 193 | +- **Reuse Priority**: Fills recycled nonces before using fresh ones |
| 194 | +- **No-op Transactions**: Sends empty transactions for unused recycled nonces |
| 195 | + |
| 196 | +#### C. New Transaction Processing |
| 197 | + |
| 198 | +- **Capacity Management**: Limits in-flight transactions to `MAX_IN_FLIGHT` |
| 199 | +- **Fresh Nonce Allocation**: Uses optimistic nonce counter for new transactions |
| 200 | +- **Batch Processing**: Sends multiple transactions up to available capacity |
| 201 | + |
| 202 | +## Error Classification System |
| 203 | + |
| 204 | +### Deterministic Failures |
| 205 | + |
| 206 | +- Invalid signature |
| 207 | +- Malformed transaction |
| 208 | +- Invalid transaction format |
| 209 | +- **Action**: Immediate requeue + nonce recycling |
| 210 | + |
| 211 | +### Success Cases |
| 212 | + |
| 213 | +- Explicit success response |
| 214 | +- "already known" (duplicate) |
| 215 | +- "nonce too low" (already mined) |
| 216 | +- **Action**: Add to success tracking |
| 217 | + |
| 218 | +### Indeterminate Cases |
| 219 | + |
| 220 | +- Network timeouts |
| 221 | +- Temporary RPC failures |
| 222 | +- Unknown errors |
| 223 | +- **Action**: Assume sent (optimistic approach) |
| 224 | + |
| 225 | +## Nonce Management Strategy |
| 226 | + |
| 227 | +### Optimistic Nonce Counter |
| 228 | + |
| 229 | +- Maintains local counter independent of chain state |
| 230 | +- Increments immediately when sending transactions |
| 231 | +- Allows parallel transaction preparation |
| 232 | + |
| 233 | +### Recycled Nonce Pool |
| 234 | + |
| 235 | +- Reuses nonces from failed transactions |
| 236 | +- Prevents nonce gaps in the sequence |
| 237 | +- Bounded size to prevent memory leaks |
| 238 | + |
| 239 | +### Chain Nonce Synchronization |
| 240 | + |
| 241 | +- Periodically syncs with actual chain state |
| 242 | +- Used for confirmation and capacity calculations |
| 243 | +- Handles chain reorganizations gracefully |
| 244 | + |
| 245 | +## Key Design Decisions |
| 246 | + |
| 247 | +### 1. Single Worker Per EOA:Chain |
| 248 | + |
| 249 | +- **Benefit**: Eliminates nonce conflicts between workers |
| 250 | +- **Trade-off**: Limits parallelism but ensures consistency |
| 251 | + |
| 252 | +### 2. Optimistic Sending |
| 253 | + |
| 254 | +- **Benefit**: Higher throughput by not waiting for confirmations |
| 255 | +- **Trade-off**: Requires robust error handling and recovery |
| 256 | + |
| 257 | +### 3. Borrowed Transaction Pattern |
| 258 | + |
| 259 | +- **Benefit**: Crash resilience without complex state management |
| 260 | +- **Trade-off**: Slight overhead for state tracking |
| 261 | + |
| 262 | +### 4. Bounded In-Flight Transactions |
| 263 | + |
| 264 | +- **Benefit**: Prevents memory leaks and excessive RPC usage |
| 265 | +- **Trade-off**: May limit throughput during high-volume periods |
| 266 | + |
| 267 | +### 5. Recycled Nonce Cleanup |
| 268 | + |
| 269 | +- **Benefit**: Prevents unbounded memory growth |
| 270 | +- **Trade-off**: May create temporary nonce gaps |
| 271 | + |
| 272 | +## Configuration Parameters |
| 273 | + |
| 274 | +- **`MAX_IN_FLIGHT`**: Maximum concurrent unconfirmed transactions |
| 275 | +- **`MAX_RECYCLED_NONCES`**: Maximum recycled nonces before cleanup |
| 276 | +- **`WORKER_CYCLE_DELAY`**: Sleep time between worker iterations |
| 277 | +- **`HEALTH_CHECK_INTERVAL`**: Frequency of EOA balance checks |
| 278 | +- **`MIN_BALANCE_THRESHOLD`**: Minimum balance to consider EOA funded |
| 279 | + |
| 280 | +## Monitoring and Observability |
| 281 | + |
| 282 | +The worker exposes several metrics for monitoring: |
| 283 | + |
| 284 | +- **Queue Depth**: Size of `pending_txns` queue |
| 285 | +- **In-Flight Count**: `optimistic_nonce - last_chain_nonce` |
| 286 | +- **Success Rate**: Ratio of confirmed to sent transactions |
| 287 | +- **Recycled Nonce Count**: Size of recycled nonce pool |
| 288 | +- **Health Status**: EOA funding state and last check time |
| 289 | + |
| 290 | +## Failure Modes and Recovery |
| 291 | + |
| 292 | +### Common Failure Scenarios |
| 293 | + |
| 294 | +1. **EOA Runs Out of Funds** |
| 295 | + |
| 296 | + - **Detection**: Balance check during health verification |
| 297 | + - **Recovery**: Automatic retry once funds are restored |
| 298 | + |
| 299 | +2. **Network Partitions** |
| 300 | + |
| 301 | + - **Detection**: RPC call failures during any phase |
| 302 | + - **Recovery**: Continues processing with cached state until network restored |
| 303 | + |
| 304 | +3. **Worker Crashes** |
| 305 | + |
| 306 | + - **Detection**: Restart detection during recovery phase |
| 307 | + - **Recovery**: Borrowed transaction rebroadcast ensures no loss |
| 308 | + |
| 309 | +4. **Chain Reorganizations** |
| 310 | + - **Detection**: Chain nonce inconsistencies |
| 311 | + - **Recovery**: Confirmation phase handles dropped transactions |
| 312 | + |
| 313 | +This architecture provides a robust, scalable solution for managing EOA transactions with strong consistency guarantees and graceful failure handling. |
0 commit comments