-
Notifications
You must be signed in to change notification settings - Fork 592
feat(rebalancer-sim): Add simulation harness for warp route rebalancer testing #7903
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
Merged
Merged
Changes from 14 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
006f305
plan: Add rebalancer simulation harness design
nambrot ab266c7
feat(rebalancer-sim): Add simulation harness for warp route rebalance…
nambrot 1bf45b7
feat(rebalancer-sim): Add separate signers and parallel message proce…
nambrot bdaccb2
feat(rebalancer-sim): Add MessageTracker for off-chain message tracking
nambrot 2d95f16
feat(rebalancer-sim): Add balancedTraffic scenario generator
nambrot 56007f1
feat(rebalancer-sim): Enhance scenario configs with defaults and expe…
nambrot 218a9eb
chore(rebalancer-sim): Regenerate scenarios with new format and balan…
nambrot b99d738
test(rebalancer-sim): Update tests to use scenario defaults and expec…
nambrot 20af934
feat(rebalancer-sim): Add test utilities and inflight guard test
nambrot 51b9241
feat(rebalancer-sim): Add RealRebalancerService for comparison testing
nambrot 9ef29fa
fix(rebalancer-sim): Clean up signal handlers from RebalancerService
nambrot bc1589c
fix(rebalancer-sim): Fix state leaks causing multi-run test failures
nambrot 980845a
fix(rebalancer-sim): Run both rebalancers by default for comparison
nambrot c3a4fca
fix(rebalancer-sim): Per-test anvil isolation for reliable multi-reba…
nambrot e27de79
feat(rebalancer-sim): Add HTML timeline visualizer for simulation res…
nambrot 9173026
feat(rebalancer-sim): Improve visualizer with computed balances and r…
nambrot 94e3ec3
feat(rebalancer-sim): Add rebalance e2e latency tracking
nambrot 7854021
fix(rebalancer-sim): Disable provider polling to reduce RPC contention
nambrot a4cd977
fix(rebalancer-sim): Wait for RealRebalancerService initialization
nambrot 006deb8
feat(rebalancer-sim): Add diagnostic logging to MessageTracker
nambrot bcf117a
fix(rebalancer-sim): Add cleanup between rebalancer comparison runs
nambrot d0c2952
refactor(rebalancer-sim): Simplify RealRebalancerRunner to in-process…
nambrot 5c4c20f
fix(rebalancer-sim): Fix bridge to pull tokens and use actual balance…
nambrot db1c910
feat(rebalancer-sim): Improve balance curve visualization
nambrot 53260a7
fix(rebalancer-sim): Fix transfer bar stacking overflow in visualizer
nambrot ba8589e
feat(rebalancer-sim): Add balance hover tooltips and config panel to …
nambrot 26347db
feat(rebalancer-sim): Add scenario metadata to visualizer
nambrot bce1afa
chore(rebalancer-sim): Ignore entire results folder in gitignore
nambrot 94a805a
Merge origin/main into feat/rebalancer-simulation-harness
nambrot e7d408e
fix(rebalancer-sim): Fix CI failures
nambrot 74d74c2
fix(rebalancer-sim): Fix lint errors
nambrot c1c4251
docs(rebalancer-sim): Update README to match implementation
nambrot 018fbd4
docs(rebalancer-sim): Add design decisions, usage examples, and futur…
nambrot 33ed6d1
fix(rebalancer-sim): Address PR #7903 review comments (#7960)
nambrot 7b438ba
fix(rebalancer-sim): Address PR #7903 human review comments (#7964)
nambrot f96999c
fix(rebalancer-sim): Rename rebalancer types and fix CLI rebalancer c…
nambrot e745538
fix(rebalancer-sim): Address final PR #7903 review comments
nambrot f05fe46
fix(rebalancer-sim): Fix CI test execution and format JSON files
nambrot 3ea6378
fix(rebalancer-sim): Address PR review feedback
nambrot 85781f6
refactor(rebalancer-sim): Rename CLIRebalancerRunner to ProductionReb…
nambrot 0372b3f
fix(rebalancer-sim): Address PR review feedback
nambrot cdd5a89
refactor(rebalancer-sim): Flatten directory structure and consolidate…
nambrot 77ddb29
ci(rebalancer-sim): Move to separate workflow with path filters
nambrot 02b5c6a
fix(solidity): MockValueTransferBridge now transfers tokens with Safe…
nambrot f7b4c60
fix(rebalancer-sim): Fix scenarios directory path resolution
nambrot ce54159
fix(ci): Use underscore separator in rebalancer-sim matrix test names
nambrot 3a09572
refactor(rebalancer-sim): Unify inflight-guard test with full-simulat…
nambrot 32565bf
fix(rebalancer-sim): Add balanceTimeline to visualization data
nambrot 83f0db8
style: Format inflight-guard.json with prettier
nambrot 62a9cfa
fix(cli): Fix warp-rebalancer e2e test to simulate bridge delivery co…
nambrot 9922620
chore: Add JSON to lint-staged precommit hook
nambrot eb05905
fix(rebalancer-sim): Address PR #7903 review comments
nambrot ee2935f
fix(rebalancer-sim): Cleanup simulation engine issues from PR review …
nambrot afbae12
refactor(rebalancer-sim): Use testcontainers for anvil setup (#8004)
nambrot-agent 7f37f6c
Fix prettier
nambrot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| .env | ||
| dist | ||
| cache | ||
|
|
||
| # Simulation results (generated at runtime) | ||
| results/*.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "import": ["tsx"], | ||
| "extension": ["ts"], | ||
| "timeout": 120000, | ||
| "exit": true | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,271 @@ | ||
| # Rebalancer Simulation Harness | ||
|
|
||
| A fast, real-time simulation framework for testing Hyperlane warp route rebalancers against synthetic transfer scenarios. | ||
|
|
||
| ## Purpose | ||
|
|
||
| This simulator helps answer questions like: | ||
|
|
||
| - Does the rebalancer respond correctly to liquidity imbalances? | ||
| - How quickly does the rebalancer restore balance after a traffic surge? | ||
| - What happens when bridge delays cause the rebalancer to over-correct? | ||
| - How do different rebalancer strategies compare on the same traffic pattern? | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────────────┐ | ||
| │ SimulationEngine │ | ||
| │ Orchestrates scenario execution, rebalancer polling, KPI collection│ | ||
| └─────────────────────────────────────────────────────────────────────┘ | ||
| │ │ │ | ||
| ▼ ▼ ▼ | ||
| ┌───────────────┐ ┌────────────────┐ ┌─────────────────┐ | ||
| │ Scenario │ │ Rebalancer │ │ BridgeMock │ | ||
| │ Generator │ │ Runner │ │ Controller │ | ||
| │ │ │ │ │ │ | ||
| │ Creates │ │ Simplified │ │ Simulates slow │ | ||
| │ transfer │ │ rebalancer │ │ bridge delivery │ | ||
| │ patterns │ │ for testing │ │ with config- │ | ||
| │ │ │ │ │ urable delays │ | ||
| └───────────────┘ └────────────────┘ └─────────────────┘ | ||
| │ │ │ | ||
| └────────────────────┼────────────────────┘ | ||
| ▼ | ||
| ┌─────────────────────────────┐ | ||
| │ Multi-Domain Deployment │ | ||
| │ │ | ||
| │ Single Anvil instance │ | ||
| │ simulating N "chains" │ | ||
| │ via different domain IDs │ | ||
| │ │ | ||
| │ Each domain has: │ | ||
| │ - Mailbox (instant) │ | ||
| │ - WarpToken + Collateral │ | ||
| │ - Bridge (delayed) │ | ||
| └─────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Key Concepts | ||
|
|
||
| ### Warp Token Mechanics | ||
|
|
||
| Understanding collateral flow is critical: | ||
|
|
||
| ``` | ||
| User sends FROM chain A TO chain B: | ||
| - Chain A: User deposits collateral → WarpToken GAINS collateral | ||
| - Chain B: Recipient withdraws → WarpToken LOSES collateral | ||
|
|
||
| This is counterintuitive! Transfers TO a chain DRAIN its liquidity. | ||
| ``` | ||
|
|
||
| ### Two Message Paths | ||
|
|
||
| The simulator uses two different delivery mechanisms: | ||
|
|
||
| | Path | Mechanism | Delay | Use Case | | ||
| | -------------------- | ----------------------- | -------------------------- | ----------------------------------- | | ||
| | User transfers | MockMailbox | Instant | Simulates Hyperlane message passing | | ||
| | Rebalancer transfers | MockValueTransferBridge | Configurable (e.g., 500ms) | Simulates CCTP/bridge delays | | ||
|
|
||
| This separation is important because rebalancer transfers go through external bridges (CCTP, etc.) which have significant delays, while user transfers use Hyperlane's fast messaging. | ||
|
|
||
| ## Directory Structure | ||
|
|
||
| ``` | ||
| typescript/rebalancer-sim/ | ||
| ├── src/ | ||
| │ ├── deployment/ # Anvil + contract deployment | ||
| │ │ └── SimulationDeployment.ts | ||
| │ ├── scenario/ # Scenario generation & loading | ||
| │ │ ├── ScenarioGenerator.ts # Create synthetic scenarios | ||
| │ │ ├── ScenarioLoader.ts # Load from JSON files | ||
| │ │ └── types.ts # ScenarioFile, TransferScenario, etc. | ||
| │ ├── bridges/ # Bridge delay simulation | ||
| │ │ └── BridgeMockController.ts | ||
| │ ├── rebalancer/ # Rebalancer wrapper | ||
| │ │ └── HyperlaneRunner.ts # Simplified rebalancer for testing | ||
| │ ├── engine/ # Simulation orchestration | ||
| │ │ └── SimulationEngine.ts | ||
| │ └── kpi/ # Metrics collection | ||
| │ └── KPICollector.ts | ||
| ├── scenarios/ # Pre-generated scenario JSON files | ||
| ├── results/ # Test results (gitignored) | ||
| ├── scripts/ | ||
| │ └── generate-scenarios.ts | ||
| └── test/ | ||
| ├── scenarios/ # Unit tests for scenario generation | ||
| ├── utils/ # Test utilities (Anvil management) | ||
| └── integration/ # Full simulation tests | ||
| ``` | ||
|
|
||
| ## Scenario File Format | ||
|
|
||
| Each scenario JSON is self-contained with metadata, transfers, and default configurations: | ||
|
|
||
| ```json | ||
| { | ||
| "name": "extreme-drain-chain1", | ||
| "description": "Tests rebalancer response when one chain is rapidly drained.", | ||
| "expectedBehavior": "95% of transfers go TO chain1, draining collateral...", | ||
| "duration": 10000, | ||
| "chains": ["chain1", "chain2", "chain3"], | ||
| "transfers": [...], | ||
| "defaultInitialCollateral": "100000000000000000000", | ||
| "defaultTiming": { | ||
| "bridgeDeliveryDelay": 500, | ||
| "rebalancerPollingFrequency": 1000, | ||
| "userTransferInterval": 100 | ||
| }, | ||
| "defaultBridgeConfig": {...}, | ||
| "defaultStrategyConfig": {...}, | ||
| "expectations": { | ||
| "minCompletionRate": 0.9, | ||
| "shouldTriggerRebalancing": true | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Tests can use the defaults from JSON or override them for specific test needs. | ||
|
|
||
| ## Running Simulations | ||
|
|
||
| ### 1. Generate Scenarios (one-time) | ||
|
|
||
| ```bash | ||
| pnpm generate-scenarios | ||
| ``` | ||
|
|
||
| Creates JSON files in `scenarios/` with various traffic patterns. | ||
|
|
||
| ### 2. Run All Tests | ||
|
|
||
| ```bash | ||
| pnpm test | ||
| ``` | ||
|
|
||
| Tests automatically detect if Anvil is available. If not installed, integration tests are skipped. | ||
|
|
||
| ### 3. Run Specific Test | ||
|
|
||
| ```bash | ||
| pnpm mocha test/integration/full-simulation.test.ts | ||
| pnpm mocha test/integration/inflight-guard.test.ts | ||
| ``` | ||
|
|
||
| ### 4. View Results | ||
|
|
||
| Test results are saved to `results/` directory (gitignored): | ||
|
|
||
| ```bash | ||
| cat results/extreme-drain-chain1.json | ||
| ``` | ||
|
|
||
| **Note:** If Anvil is not installed, integration tests will be skipped. Install Foundry with: | ||
|
|
||
| ```bash | ||
| curl -L https://foundry.paradigm.xyz | bash && foundryup | ||
| ``` | ||
|
|
||
| ## Scenario Types | ||
|
|
||
| ### Predefined Scenarios (in `scenarios/`) | ||
|
|
||
| | Scenario | Description | Expected Behavior | | ||
| | -------------------------------- | ---------------------------------- | ------------------------- | | ||
| | `extreme-drain-chain1` | 95% of transfers TO chain1 | Heavy rebalancing needed | | ||
| | `extreme-accumulate-chain1` | 95% of transfers FROM chain1 | Heavy rebalancing needed | | ||
| | `large-unidirectional-to-chain1` | 5 large (20 token) transfers | Immediate imbalance | | ||
| | `whale-transfers` | 3 massive (30 token) transfers | Stress test response time | | ||
| | `balanced-bidirectional` | Uniform random traffic | Minimal rebalancing | | ||
| | `surge-to-chain1` | Traffic spike mid-scenario | Tests burst handling | | ||
| | `stress-high-volume` | 50 transfers, Poisson distribution | Load testing | | ||
| | `moderate-imbalance-chain1` | 70% of transfers to chain1 | Moderate rebalancing | | ||
| | `sustained-drain-chain3` | 30 transfers over 30s | Endurance test | | ||
|
|
||
| ## Test Organization | ||
|
|
||
| ### Unit Tests (`test/scenarios/`) | ||
|
|
||
| Test the scenario generation logic without running simulations: | ||
|
|
||
| - Does `unidirectionalFlow()` create correct transfer patterns? | ||
| - Does `randomTraffic()` distribute across all chains? | ||
| - Does serialization preserve BigInt amounts? | ||
|
|
||
| ### Integration Tests (`test/integration/`) | ||
|
|
||
| Run full simulations on Anvil: | ||
|
|
||
| | Test File | Purpose | | ||
| | ------------------------- | ---------------------------------------------------- | | ||
| | `deployment.test.ts` | Verifies multi-domain deployment works | | ||
| | `full-simulation.test.ts` | Runs predefined scenarios, saves results | | ||
| | `inflight-guard.test.ts` | Demonstrates over-rebalancing without inflight guard | | ||
|
|
||
| ### Why `inflight-guard.test.ts` is Separate | ||
|
|
||
| This test demonstrates a specific bug/limitation rather than testing a scenario type: | ||
|
|
||
| **What it proves:** Without tracking pending (inflight) transfers, the rebalancer sends redundant transfers because each poll sees "stale" on-chain balances. | ||
|
|
||
| **How it differs:** | ||
|
|
||
| - Uses custom inline scenario with extreme timing (3s bridge delay vs 200ms polling) | ||
| - Asserts on specific failure behavior (expects over-rebalancing) | ||
| - Documents a bug that needs fixing, not a passing scenario | ||
|
|
||
| ## KPIs Collected | ||
|
|
||
| ```typescript | ||
| interface SimulationKPIs { | ||
| totalTransfers: number; | ||
| completedTransfers: number; | ||
| completionRate: number; // 0-1, should be >0.9 with working rebalancer | ||
|
|
||
| averageLatency: number; // ms | ||
| p50Latency: number; | ||
| p95Latency: number; | ||
| p99Latency: number; | ||
|
|
||
| totalRebalances: number; | ||
| rebalanceVolume: bigint; // Total tokens moved by rebalancer | ||
|
|
||
| perChainMetrics: Record< | ||
| string, | ||
| { | ||
| initialBalance: bigint; | ||
| finalBalance: bigint; | ||
| transfersIn: number; | ||
| transfersOut: number; | ||
| } | ||
| >; | ||
| } | ||
| ``` | ||
|
|
||
| ## Current Limitations | ||
|
|
||
| 1. **Simplified Rebalancer**: The current `HyperlaneRunner` is a simplified implementation for testing, not the actual production rebalancer from `@hyperlane-xyz/rebalancer`. | ||
|
|
||
| 2. **No Inflight Guard**: The simplified rebalancer doesn't track pending transfers, causing over-rebalancing when bridge delays are long relative to polling frequency. | ||
|
|
||
| 3. **Single Anvil**: All "chains" run on one Anvil instance. Real cross-chain timing differences aren't simulated. | ||
|
|
||
| 4. **Instant User Transfers**: User transfers via MockMailbox are instant. Real Hyperlane has ~15-30 second finality. | ||
|
|
||
| 5. **No Gas Costs**: Gas costs aren't simulated. KPIs include rebalance count but not actual cost. | ||
|
|
||
| ## Future Work | ||
|
nambrot-agent marked this conversation as resolved.
|
||
|
|
||
| ### Phase 1: Integrate Real Rebalancer | ||
|
|
||
| - Wrap the actual `@hyperlane-xyz/rebalancer` service | ||
| - Add API for mocks (time stepping, explorer API mock) | ||
| - Support daemon mode with configurable polling | ||
|
|
||
| ### Phase 2: Inflight Guard Testing | ||
|
|
||
| - Mock Explorer API for inflight transfer tracking | ||
| - Test scenarios that specifically require inflight awareness | ||
| - Validate that real rebalancer avoids over-correction | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import MonorepoDefaults from '../../eslint.config.mjs'; | ||
|
|
||
| export default [ | ||
| ...MonorepoDefaults, | ||
| { | ||
| files: ['./src/**/*.ts'], | ||
| }, | ||
| { | ||
| rules: { | ||
| // Disable restricted imports for Node.js built-ins since simulation harness is Node.js-only | ||
| 'no-restricted-imports': ['off'], | ||
| // Allow console statements for simulation output | ||
| 'no-console': ['off'], | ||
| }, | ||
| }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ]; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| { | ||
| "name": "@hyperlane-xyz/rebalancer-sim", | ||
| "version": "0.1.0", | ||
| "description": "Fast real-time simulation framework for testing Hyperlane warp route rebalancers", | ||
| "type": "module", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "files": [ | ||
| "dist", | ||
| "src" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "clean": "rm -rf dist cache", | ||
| "dev": "tsc --watch", | ||
| "generate-scenarios": "tsx scripts/generate-scenarios.ts", | ||
| "lint": "eslint -c ./eslint.config.mjs ./src", | ||
| "prettier": "prettier --write ./src", | ||
| "test": "mocha --config .mocharc.json './test/**/*.test.ts' --exit", | ||
| "test:ci": "pnpm test" | ||
| }, | ||
| "dependencies": { | ||
| "@hyperlane-xyz/core": "workspace:*", | ||
| "@hyperlane-xyz/provider-sdk": "workspace:*", | ||
| "@hyperlane-xyz/registry": "catalog:", | ||
| "@hyperlane-xyz/rebalancer": "workspace:*", | ||
| "@hyperlane-xyz/sdk": "workspace:*", | ||
| "@hyperlane-xyz/utils": "workspace:*", | ||
| "ethers": "catalog:", | ||
| "pino": "catalog:", | ||
| "pino-pretty": "catalog:", | ||
| "zod": "catalog:" | ||
| }, | ||
| "devDependencies": { | ||
| "@hyperlane-xyz/tsconfig": "workspace:^", | ||
| "@types/chai": "catalog:", | ||
| "@types/chai-as-promised": "catalog:", | ||
| "@types/mocha": "catalog:", | ||
| "@types/node": "catalog:", | ||
| "chai": "catalog:", | ||
| "chai-as-promised": "catalog:", | ||
| "eslint": "catalog:", | ||
| "mocha": "catalog:", | ||
| "prettier": "catalog:", | ||
| "tsx": "catalog:", | ||
| "typescript": "catalog:" | ||
| }, | ||
| "engines": { | ||
| "node": ">=18" | ||
| }, | ||
| "repository": "https://github.com/hyperlane-xyz/hyperlane-monorepo", | ||
| "keywords": [ | ||
| "hyperlane", | ||
| "rebalancer", | ||
| "simulation", | ||
| "testing", | ||
| "warp-route" | ||
| ], | ||
| "author": "Abacus Works, Inc.", | ||
| "license": "Apache-2.0" | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.