Skip to content

Commit 1a49ed3

Browse files
committed
Add network chaos to fork tests
1 parent 5a8ce3b commit 1a49ed3

File tree

8 files changed

+368
-72
lines changed

8 files changed

+368
-72
lines changed

forks/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,53 @@ The CLI provides statistics including:
9999
- Fork detection rate
100100
- Average forks per run
101101

102+
### Network Chaos Testing
103+
104+
The fork test can inject network chaos (latency, jitter, packet loss) to simulate adverse network conditions. This helps identify forks that occur under realistic network stress.
105+
106+
**Requirements:**
107+
- Network chaos requires `--env local`
108+
- Multinode Docker containers must be running (`./dev/up`)
109+
- Requires `sudo` access for `tc` and `iptables` commands
110+
111+
**Chaos Levels:**
112+
113+
| Level | Delay Range | Jitter Range | Packet Loss | Interval |
114+
|--------|-------------|--------------|-------------|----------|
115+
| low | 50-150ms | 0-50ms | 0-2% | 15s |
116+
| medium | 100-300ms | 0-75ms | 0-3.5% | 10s |
117+
| high | 100-500ms | 0-100ms | 0-5% | 10s |
118+
119+
**Usage:**
120+
121+
```bash
122+
# Run with default (medium) chaos
123+
yarn fork --env local --chaos-enabled
124+
125+
# Run with high chaos level
126+
yarn fork --env local --chaos-enabled --chaos-level high
127+
128+
# Run 50 iterations with low chaos
129+
yarn fork --count 50 --env local --chaos-enabled --chaos-level low
130+
```
131+
132+
**How it works:**
133+
1. Initializes Docker container handles for all multinode nodes
134+
2. Applies random network conditions (within preset ranges) at regular intervals
135+
3. Runs the fork test as normal while chaos is active
136+
4. Cleans up network rules when test completes (even if test fails)
137+
138+
**Example output:**
139+
```
140+
NETWORK CHAOS PARAMETERS
141+
chaosEnabled: true
142+
chaosLevel: high
143+
delay: 100-500ms
144+
jitter: 0-100ms
145+
packetLoss: 0-5%
146+
interval: 10000ms
147+
```
148+
102149
### Log processing features
103150

104151
- **Clean slate**: Removes old logs and data before starting

forks/cli.ts

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from "path";
44
import { cleanAllRawLogs, cleanForksLogs } from "@helpers/analyzer";
55
import "dotenv/config";
66
import {
7+
chaosPresets,
78
epochRotationOperations,
89
groupCount,
910
installationCount,
@@ -15,13 +16,16 @@ import {
1516
targetEpoch,
1617
testName,
1718
workerNames,
19+
type ChaosLevel,
1820
} from "./config";
1921

2022
interface ForkOptions {
2123
count: number; // Number of times to run the process (default: 100)
2224
cleanAll: boolean; // Clean all raw logs before starting
2325
removeNonMatching: boolean; // Remove non-matching logs
2426
env?: string; // XMTP environment (local, dev, production)
27+
chaosEnabled: boolean; // Enable network chaos
28+
chaosLevel: ChaosLevel; // Chaos level (low, medium, high)
2529
}
2630

2731
function showHelp() {
@@ -37,13 +41,22 @@ OPTIONS:
3741
--remove-non-matching Remove logs that don't contain fork content [default: true]
3842
--no-remove-non-matching Keep logs that don't contain fork content
3943
--env <environment> XMTP environment (local, dev, production) [default: dev]
44+
--chaos-enabled Enable network chaos testing (requires --env local)
45+
--chaos-level <level> Chaos level: low, medium, high [default: medium]
4046
-h, --help Show this help message
4147
48+
CHAOS LEVELS:
49+
low - Delay: 50-150ms, Jitter: 0-50ms, Loss: 0-2%, Interval: 15s
50+
medium - Delay: 100-300ms, Jitter: 0-75ms, Loss: 0-3.5%, Interval: 10s
51+
high - Delay: 100-500ms, Jitter: 0-100ms, Loss: 0-5%, Interval: 10s
52+
4253
EXAMPLES:
43-
yarn fork # Run 100 times and get stats
44-
yarn fork --count 50 # Run 50 times
45-
yarn fork --clean-all # Clean all raw logs before starting
46-
yarn fork --count 200 --env local # Run 200 times on local environment
54+
yarn fork # Run 100 times and get stats
55+
yarn fork --count 50 # Run 50 times
56+
yarn fork --clean-all # Clean all raw logs before starting
57+
yarn fork --count 200 --env local # Run 200 times on local environment
58+
yarn fork --env local --chaos-enabled # Run with medium network chaos
59+
yarn fork --env local --chaos-enabled --chaos-level high # Run with high chaos
4760
4861
For more information, see: forks/README.md
4962
`);
@@ -63,16 +76,21 @@ function getForkCount(): number {
6376
/**
6477
* Run the fork test (suppress output)
6578
*/
66-
function runForkTest(env?: string): void {
67-
const envFlag = env ? `--env ${env}` : "";
79+
function runForkTest(options: ForkOptions): void {
80+
const envFlag = options.env ? `--env ${options.env}` : "";
6881
const command = `yarn test forks ${envFlag} --log warn --file`.trim();
6982

7083
try {
7184
execSync(command, {
7285
stdio: "ignore",
73-
env: { ...process.env },
86+
env: {
87+
...process.env,
88+
CHAOS_ENABLED: options.chaosEnabled ? "true" : "false",
89+
CHAOS_LEVEL: options.chaosLevel,
90+
},
7491
});
7592
} catch {
93+
console.log("Error running fork test");
7694
// Test may fail if forks are detected, that's expected
7795
// We'll analyze the logs afterward
7896
}
@@ -81,7 +99,7 @@ function runForkTest(env?: string): void {
8199
/**
82100
* Log fork matrix parameters from shared config
83101
*/
84-
function logForkMatrixParameters(): void {
102+
function logForkMatrixParameters(options: ForkOptions): void {
85103
console.info("\nFORK MATRIX PARAMETERS");
86104
console.info("-".repeat(60));
87105
console.info(`groupCount: ${groupCount}`);
@@ -97,6 +115,18 @@ function logForkMatrixParameters(): void {
97115
console.info(`randomInboxIdsCount: ${randomInboxIdsCount}`);
98116
console.info(`installationCount: ${installationCount}`);
99117
console.info(`testName: ${testName}`);
118+
119+
if (options.chaosEnabled) {
120+
const preset = chaosPresets[options.chaosLevel];
121+
console.info("\nNETWORK CHAOS PARAMETERS");
122+
console.info(`chaosEnabled: true`);
123+
console.info(`chaosLevel: ${options.chaosLevel}`);
124+
console.info(` delay: ${preset.delayMin}-${preset.delayMax}ms`);
125+
console.info(` jitter: ${preset.jitterMin}-${preset.jitterMax}ms`);
126+
console.info(` packetLoss: ${preset.lossMin}-${preset.lossMax}%`);
127+
console.info(` interval: ${preset.interval}ms`);
128+
}
129+
100130
console.info("-".repeat(60) + "\n");
101131
}
102132

@@ -108,8 +138,17 @@ async function runForkDetection(options: ForkOptions): Promise<void> {
108138
console.info("XMTP Fork Detection CLI");
109139
console.info("=".repeat(60));
110140

141+
// Validate chaos requirements
142+
if (options.chaosEnabled && options.env !== "local") {
143+
console.error("\n❌ Error: Network chaos testing requires --env local");
144+
console.error(
145+
"Network chaos manipulates Docker containers which are only available in local environment.\n",
146+
);
147+
process.exit(1);
148+
}
149+
111150
// Log fork matrix parameters once
112-
logForkMatrixParameters();
151+
logForkMatrixParameters(options);
113152

114153
console.info(`Running fork detection process ${options.count} time(s)...\n`);
115154

@@ -130,7 +169,7 @@ async function runForkDetection(options: ForkOptions): Promise<void> {
130169
// Run the test N times
131170
for (let i = 1; i <= options.count; i++) {
132171
// Run the fork test (silently)
133-
runForkTest(options.env);
172+
runForkTest(options);
134173

135174
// Clean and analyze fork logs after the test (suppress output)
136175
const originalConsoleDebug = console.debug;
@@ -211,6 +250,8 @@ async function main() {
211250
cleanAll: false,
212251
removeNonMatching: true,
213252
env: process.env.XMTP_ENV || "dev",
253+
chaosEnabled: false,
254+
chaosLevel: "medium",
214255
};
215256

216257
// Parse arguments
@@ -260,6 +301,25 @@ async function main() {
260301
process.exit(1);
261302
}
262303
break;
304+
case "--chaos-enabled":
305+
options.chaosEnabled = true;
306+
break;
307+
case "--chaos-level":
308+
if (i + 1 < args.length) {
309+
const level = args[i + 1] as ChaosLevel;
310+
if (!["low", "medium", "high"].includes(level)) {
311+
console.error("--chaos-level must be one of: low, medium, high");
312+
process.exit(1);
313+
}
314+
options.chaosLevel = level;
315+
i++; // Skip next argument
316+
} else {
317+
console.error(
318+
"--chaos-level flag requires a value (e.g., --chaos-level medium)",
319+
);
320+
process.exit(1);
321+
}
322+
break;
263323
default:
264324
console.error(`Unknown option: ${arg}`);
265325
console.error("Use --help for usage information");

forks/config.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,70 @@ export const otherOperations = {
2424
createInstallation: true, // creates a new installation for a random worker
2525
sendMessage: true, // sends a message to the group
2626
};
27-
export const targetEpoch = 20n; // The target epoch to stop the test (epochs are when performing forks to the group)
27+
export const targetEpoch = 150n; // The target epoch to stop the test (epochs are when performing forks to the group)
2828
export const network = process.env.XMTP_ENV; // Network environment setting
2929
export const randomInboxIdsCount = 10; // How many inboxIds to use randomly in the add/remove operations
3030
export const installationCount = 2; // How many installations to use randomly in the createInstallation operations
3131
export const testName = "forks";
32+
33+
// Network chaos configuration
34+
export type ChaosLevel = "low" | "medium" | "high";
35+
36+
export interface ChaosPreset {
37+
delayMin: number; // Minimum delay in ms
38+
delayMax: number; // Maximum delay in ms
39+
jitterMin: number; // Minimum jitter in ms
40+
jitterMax: number; // Maximum jitter in ms
41+
lossMin: number; // Minimum packet loss percentage (0-100)
42+
lossMax: number; // Maximum packet loss percentage (0-100)
43+
interval: number; // How often to apply chaos in ms
44+
}
45+
46+
export const chaosPresets: Record<ChaosLevel, ChaosPreset> = {
47+
low: {
48+
delayMin: 50,
49+
delayMax: 150,
50+
jitterMin: 0,
51+
jitterMax: 50,
52+
lossMin: 0,
53+
lossMax: 2,
54+
interval: 15000, // 15 seconds
55+
},
56+
medium: {
57+
delayMin: 100,
58+
delayMax: 300,
59+
jitterMin: 0,
60+
jitterMax: 75,
61+
lossMin: 0,
62+
lossMax: 3.5,
63+
interval: 10000, // 10 seconds
64+
},
65+
high: {
66+
delayMin: 100,
67+
delayMax: 500,
68+
jitterMin: 0,
69+
jitterMax: 100,
70+
lossMin: 0,
71+
lossMax: 5,
72+
interval: 10000, // 10 seconds
73+
},
74+
};
75+
76+
export interface ChaosConfig {
77+
enabled: boolean;
78+
level: ChaosLevel;
79+
}
80+
81+
// Parse chaos config from environment
82+
export const chaosConfig: ChaosConfig = {
83+
enabled: process.env.CHAOS_ENABLED === "true",
84+
level: (process.env.CHAOS_LEVEL as ChaosLevel) || "medium",
85+
};
86+
87+
// Multinode container names for local environment chaos testing
88+
export const multinodeContainers = [
89+
"multinode-node1-1",
90+
"multinode-node2-1",
91+
"multinode-node3-1",
92+
"multinode-node4-1",
93+
];

0 commit comments

Comments
 (0)