|
| 1 | +# OpenZeppelin Relayer - Gas Price Caching Example |
| 2 | + |
| 3 | +This example demonstrates how to configure **gas price caching** for EVM networks using OpenZeppelin Relayer. Gas price caching improves performance by reducing RPC calls and implementing a **stale-while-revalidate (SWR)** strategy that serves cached data immediately while refreshing in the background. |
| 4 | + |
| 5 | +This simple example uses **Sepolia testnet** to showcase gas price caching functionality. |
| 6 | + |
| 7 | +## Key Features Demonstrated |
| 8 | + |
| 9 | +- **Gas Price Caching Configuration**: Configure caching behavior with custom timings |
| 10 | +- **Performance Optimization**: Reduce RPC calls and improve response times |
| 11 | +- **Direct Network Configuration**: Network defined directly in `config.json` for simplicity |
| 12 | + |
| 13 | +## How Gas Price Caching Works |
| 14 | + |
| 15 | +The gas price cache uses a **stale-while-revalidate** strategy: |
| 16 | + |
| 17 | +1. **Fresh Data**: When data is fresh (within `stale_after_ms`), it's served directly from cache |
| 18 | +2. **Stale Data**: When data is stale but not expired, it's served immediately while a background refresh is triggered |
| 19 | +3. **Expired Data**: When data is expired (after `expire_after_ms`), the cache returns no data and the service makes fresh RPC calls |
| 20 | + |
| 21 | +### Cache Configuration Parameters |
| 22 | + |
| 23 | +```json |
| 24 | +{ |
| 25 | + "gas_price_cache": { |
| 26 | + "enabled": true, |
| 27 | + "stale_after_ms": 20000, // 20 seconds - when to trigger background refresh |
| 28 | + "expire_after_ms": 45000 // 45 seconds - when to force synchronous refresh |
| 29 | + } |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +- **`enabled`**: Enable/disable caching for the network |
| 34 | +- **`stale_after_ms`**: Milliseconds after which data is considered stale (triggers background refresh) |
| 35 | +- **`expire_after_ms`**: Milliseconds after which data expires (cache returns no data, forces direct RPC calls) |
| 36 | + |
| 37 | +## Configuration Structure |
| 38 | + |
| 39 | +In this example, networks are defined directly in the main `config.json` file with different cache configurations: |
| 40 | + |
| 41 | +### Network Configuration with Gas Price Caching |
| 42 | + |
| 43 | +```json |
| 44 | +{ |
| 45 | + "relayers": [ |
| 46 | + { |
| 47 | + "id": "sepolia-example", |
| 48 | + "name": "Sepolia Gas Cache Example", |
| 49 | + "network": "sepolia", |
| 50 | + "network_type": "evm" |
| 51 | + } |
| 52 | + ], |
| 53 | + "signers": [...], |
| 54 | + "notifications": [...], |
| 55 | + "networks": [ |
| 56 | + { |
| 57 | + "network": "sepolia", |
| 58 | + "chain_id": 11155111, |
| 59 | + "type": "evm", |
| 60 | + "is_testnet": true, |
| 61 | + "rpc_urls": [ |
| 62 | + "https://sepolia.drpc.org", |
| 63 | + "https://1rpc.io/sepolia" |
| 64 | + ], |
| 65 | + "gas_price_cache": { |
| 66 | + "enabled": true, |
| 67 | + "stale_after_ms": 20000, |
| 68 | + "expire_after_ms": 45000 |
| 69 | + } |
| 70 | + } |
| 71 | + ] |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +### Cache Configuration for Sepolia |
| 76 | + |
| 77 | +The example uses balanced cache timings suitable for testnet development: |
| 78 | + |
| 79 | +```json |
| 80 | +{ |
| 81 | + "gas_price_cache": { |
| 82 | + "enabled": true, |
| 83 | + "stale_after_ms": 20000, // 20 seconds - fresh enough for testing |
| 84 | + "expire_after_ms": 45000 // 45 seconds - reasonable expiry |
| 85 | + } |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +## Getting Started |
| 90 | + |
| 91 | +### Prerequisites |
| 92 | + |
| 93 | +- [Docker](https://docs.docker.com/get-docker/) |
| 94 | +- [Docker Compose](https://docs.docker.com/compose/install/) |
| 95 | +- Rust (for key generation tools) |
| 96 | + |
| 97 | +### Step 1: Clone the Repository |
| 98 | + |
| 99 | +```bash |
| 100 | +git clone https://github.com/OpenZeppelin/openzeppelin-relayer |
| 101 | +cd openzeppelin-relayer |
| 102 | +``` |
| 103 | + |
| 104 | +### Step 2: Create a Signer |
| 105 | + |
| 106 | +Create a new signer keystore using the provided key generation tool: |
| 107 | + |
| 108 | +```bash |
| 109 | +cargo run --example create_key -- \ |
| 110 | + --password <DEFINE_YOUR_PASSWORD> \ |
| 111 | + --output-dir examples/gas-price-caching/config/keys \ |
| 112 | + --filename local-signer.json |
| 113 | +``` |
| 114 | + |
| 115 | +**Note**: Replace `<DEFINE_YOUR_PASSWORD>` with a strong password for the keystore. |
| 116 | + |
| 117 | +### Step 3: Environment Configuration |
| 118 | + |
| 119 | +Create the environment file: |
| 120 | + |
| 121 | +```bash |
| 122 | +cp examples/gas-price-caching/.env.example examples/gas-price-caching/.env |
| 123 | +``` |
| 124 | + |
| 125 | +Update the `.env` file with your configuration: |
| 126 | + |
| 127 | +- `REDIS_URL`: Redis server URL |
| 128 | +- `KEYSTORE_PASSPHRASE`: The password you used for the keystore |
| 129 | +- `WEBHOOK_SIGNING_KEY`: Generate using `cargo run --example generate_uuid` |
| 130 | +- `API_KEY`: Generate using `cargo run --example generate_uuid` |
| 131 | + |
| 132 | +### Step 4: Configure Webhook URL |
| 133 | + |
| 134 | +Update the `url` field in the notifications section of `config/config.json`. For testing, you can use [Webhook.site](https://webhook.site) to get a test URL. |
| 135 | + |
| 136 | +### Step 5: Set Environment Variables |
| 137 | + |
| 138 | +Before running any commands to interact with the API, export your API key as an environment variable: |
| 139 | + |
| 140 | +```bash |
| 141 | +export API_KEY="your-api-key-here" |
| 142 | +``` |
| 143 | + |
| 144 | +### Step 6: Run the Service |
| 145 | + |
| 146 | +Start the service with Docker Compose: |
| 147 | + |
| 148 | +```bash |
| 149 | +docker compose -f examples/gas-price-caching/docker-compose.yaml up |
| 150 | +``` |
| 151 | + |
| 152 | +The service will be available at `http://localhost:8080/api/v1` |
| 153 | + |
| 154 | +## Testing Gas Price Caching |
| 155 | + |
| 156 | +### Check Available Relayers |
| 157 | + |
| 158 | +```bash |
| 159 | +curl -X GET http://localhost:8080/api/v1/relayers \ |
| 160 | + -H "Content-Type: application/json" \ |
| 161 | + -H "Authorization: Bearer $API_KEY" |
| 162 | +``` |
| 163 | + |
| 164 | +### Test Transaction with Cached Gas Prices |
| 165 | + |
| 166 | +```bash |
| 167 | +curl -X POST http://localhost:8080/api/v1/relayers/sepolia-example/transactions \ |
| 168 | + -H "Content-Type: application/json" \ |
| 169 | + -H "Authorization: Bearer $API_KEY" \ |
| 170 | + -d '{ |
| 171 | + "value": 1000000000000000, |
| 172 | + "data": "0x", |
| 173 | + "to": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", |
| 174 | + "gas_limit": 21000, |
| 175 | + "speed": "average" |
| 176 | + }' |
| 177 | +``` |
| 178 | + |
| 179 | +### Monitor Cache Performance |
| 180 | + |
| 181 | +Check the logs to see cache behavior: |
| 182 | + |
| 183 | +```bash |
| 184 | +docker compose -f examples/gas-price-caching/docker-compose.yaml logs -f relayer |
| 185 | +``` |
| 186 | + |
| 187 | +Look for log messages like: |
| 188 | +- `"Updated gas price snapshot for chain_id 11155111 in background"` - Background refresh |
| 189 | + |
| 190 | +## Cache Configuration Best Practices |
| 191 | + |
| 192 | +### RPC Provider Limits |
| 193 | + |
| 194 | +- **Rate-Limited Providers**: Longer cache times to reduce calls |
| 195 | +- **Premium Providers**: Shorter cache times for fresher data |
| 196 | +- **Public RPCs**: Balanced approach to avoid hitting limits |
| 197 | + |
| 198 | + |
| 199 | +### Network Congestion Patterns |
| 200 | + |
| 201 | +- **High Congestion Networks**: Shorter cache for rapid price changes |
| 202 | +- **Stable Networks**: Longer cache for consistent pricing |
| 203 | +- **Predictable Patterns**: Adjust cache based on known traffic patterns |
| 204 | + |
| 205 | +## Performance Benefits |
| 206 | + |
| 207 | +### Reduced RPC Calls |
| 208 | + |
| 209 | +- **Before**: Every gas price request hits RPC |
| 210 | +- **After**: Most requests served from cache |
| 211 | + |
| 212 | +### Better User Experience |
| 213 | + |
| 214 | +- **Reduced Failures**: Less dependency on RPC availability |
| 215 | +- **Smoother Operations**: No delays during high traffic |
| 216 | + |
| 217 | +## Troubleshooting |
| 218 | + |
| 219 | +### Cache Not Working |
| 220 | + |
| 221 | +1. Check that `gas_price_cache.enabled` is `true` in network config |
| 222 | +2. Verify network configuration is loaded correctly |
| 223 | +3. Check logs for cache-related errors |
| 224 | + |
| 225 | +### High RPC Usage Despite Caching |
| 226 | + |
| 227 | +1. Verify cache timings are appropriate for your use case |
| 228 | +2. Check if cache timings are too short (causing frequent refreshes) |
| 229 | +3. Monitor cache hit rates in logs |
| 230 | + |
| 231 | +### Stale Gas Prices |
| 232 | + |
| 233 | +1. Reduce `stale_after_ms` for more frequent background refreshes |
| 234 | +2. Check if RPC provider is returning outdated data |
| 235 | +3. Verify background refresh is working (check logs) |
| 236 | + |
| 237 | +## When to Use Gas Price Caching |
| 238 | + |
| 239 | +### ✅ **Ideal Use Cases** |
| 240 | + |
| 241 | +- **High-Volume Applications**: Many transactions per minute |
| 242 | +- **User-Facing Applications**: Need fast response times |
| 243 | +- **Rate-Limited RPCs**: Need to reduce API calls |
| 244 | + |
| 245 | +### ⚠️ **Use with Caution** |
| 246 | + |
| 247 | +- **MEV Applications**: May need very fresh gas prices |
| 248 | +- **Arbitrage Trading**: Timing-critical applications |
| 249 | +- **Emergency Transactions**: When gas price accuracy is critical |
| 250 | + |
| 251 | +### ❌ **Not Recommended** |
| 252 | + |
| 253 | +- **Single-Use Scripts**: Overhead not worth it |
| 254 | +- **Very Low Transactions**: Cache rarely used |
| 255 | +- **Real-Time Trading**: Need absolute latest prices |
| 256 | + |
| 257 | +## See Also |
| 258 | + |
| 259 | +- [Network Configuration JSON File Example](../network-configuration-json-file/README.md) - Shows how to use separate JSON files with inheritance |
| 260 | +- [Network Configuration Config File Example](../network-configuration-config-file/README.md) - Direct config file approach (similar to this example) |
| 261 | +- [Basic Example](../basic-example/README.md) - Simple relayer setup without caching |
0 commit comments