The MockStellarService now includes comprehensive failure simulation capabilities to test how the application handles various Stellar network failures, timeouts, and error conditions.
- timeout - Request timeout errors
- network_error - General network connectivity issues
- service_unavailable - Stellar Horizon service unavailable
- bad_sequence - Transaction sequence number mismatch
- tx_failed - Transaction failed due to network congestion
- tx_insufficient_fee - Transaction fee too low
- connection_refused - Connection refused by server
- rate_limit_horizon - Horizon API rate limit exceeded
- partial_response - Incomplete/corrupted response data
- ledger_closed - Transaction missed ledger window
- Configurable Probability: Set failure rate from 0% to 100%
- Consecutive Failure Tracking: Monitor failure streaks
- Auto-Recovery: Automatically recover after N consecutive failures
- Retryable Errors: All simulated errors include retry guidance
- State Preservation: Failures don't corrupt internal state
const { getStellarService } = require('./src/config/stellar');
// Get mock stellar service
const stellarService = getStellarService();
// Enable timeout simulation (100% failure rate)
stellarService.enableFailureSimulation('timeout', 1.0);
// Try operation - will fail with timeout
try {
await stellarService.getBalance(publicKey);
} catch (error) {
console.log(error.message); // "Request timeout..."
console.log(error.details.retryable); // true
console.log(error.details.retryAfter); // 5000 (ms)
}
// Disable failure simulation
stellarService.disableFailureSimulation();// Simulate 30% failure rate
stellarService.enableFailureSimulation('network_error', 0.3);
// Some requests will fail, others will succeed
for (let i = 0; i < 10; i++) {
try {
await stellarService.getBalance(publicKey);
console.log('Success');
} catch (error) {
console.log('Failed');
}
}// Enable failure simulation
stellarService.enableFailureSimulation('timeout', 1.0);
// Set max consecutive failures before auto-recovery
stellarService.setMaxConsecutiveFailures(3);
// First 3 attempts will fail, then auto-recover
for (let i = 0; i < 5; i++) {
try {
await stellarService.getBalance(publicKey);
console.log(`Attempt ${i + 1}: Success`);
} catch (error) {
console.log(`Attempt ${i + 1}: Failed`);
}
}
// Output:
// Attempt 1: Failed
// Attempt 2: Failed
// Attempt 3: Failed
// Attempt 4: Success
// Attempt 5: Successdescribe('Timeout Handling', () => {
test('should handle timeout gracefully', async () => {
stellarService.enableFailureSimulation('timeout', 1.0);
await expect(
stellarService.getBalance(publicKey)
).rejects.toThrow(/timeout/i);
});
});describe('Retry Logic', () => {
test('should retry on transient errors', async () => {
stellarService.enableFailureSimulation('network_error', 1.0);
stellarService.setMaxConsecutiveFailures(2);
// Should succeed after 2 failures
const balance = await stellarService.getBalance(publicKey);
expect(balance).toBeDefined();
});
});describe('Transaction Failures', () => {
test('should handle bad sequence error', async () => {
stellarService.enableFailureSimulation('bad_sequence', 1.0);
await expect(
stellarService.sendDonation({
sourceSecret,
destinationPublic,
amount: '100',
memo: 'Test'
})
).rejects.toThrow(/bad_seq/i);
});
});describe('Concurrent Operations', () => {
test('should handle concurrent failures', async () => {
stellarService.enableFailureSimulation('timeout', 0.5);
stellarService.setMaxConsecutiveFailures(2);
const promises = Array(10).fill(null).map(() =>
stellarService.getBalance(publicKey)
);
const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled');
expect(successful.length).toBeGreaterThan(0);
});
});All simulated failures return errors with the following structure:
{
message: "Request timeout - Stellar network may be experiencing high load. Please try again.",
code: "TRANSACTION_FAILED",
details: {
retryable: true, // Whether this error can be retried
retryAfter: 5000 // Suggested retry delay in milliseconds
}
}| Error Type | Retryable | Retry Delay | Notes |
|---|---|---|---|
| timeout | Yes | 5000ms | Network may be slow |
| network_error | Yes | 3000ms | Connection issues |
| service_unavailable | Yes | 10000ms | Service maintenance |
| bad_sequence | Yes | 1000ms | Concurrent transaction |
| tx_failed | Yes | 2000ms | Network congestion |
| tx_insufficient_fee | Yes | 1000ms | Increase fee |
| connection_refused | Yes | 5000ms | Server unavailable |
| rate_limit_horizon | Yes | 60000ms | Rate limit exceeded |
| partial_response | Yes | 2000ms | Data corruption |
| ledger_closed | Yes | 5000ms | Missed ledger window |
afterEach(() => {
if (stellarService.disableFailureSimulation) {
stellarService.disableFailureSimulation();
}
});test('should handle intermittent failures', async () => {
stellarService.enableFailureSimulation('timeout', 0.5);
const results = [];
for (let i = 0; i < 10; i++) {
try {
await stellarService.getBalance(publicKey);
results.push('success');
} catch (error) {
results.push('failure');
}
}
expect(results).toContain('success');
expect(results).toContain('failure');
});test('should not corrupt state on failure', async () => {
const initialBalance = await stellarService.getBalance(publicKey);
stellarService.enableFailureSimulation('tx_failed', 1.0);
try {
await stellarService.sendDonation({...});
} catch (error) {
// Expected failure
}
stellarService.disableFailureSimulation();
const finalBalance = await stellarService.getBalance(publicKey);
expect(finalBalance.balance).toBe(initialBalance.balance);
});test('should fail after max retries', async () => {
stellarService.enableFailureSimulation('timeout', 1.0);
stellarService.setMaxConsecutiveFailures(0); // Never recover
await expect(
stellarService.getBalance(publicKey)
).rejects.toThrow();
});// Simulate high network congestion
stellarService.enableFailureSimulation('tx_failed', 0.7);
// Multiple transactions, some will fail
const results = await Promise.allSettled(
transactions.map(tx => stellarService.sendDonation(tx))
);// Phase 1: Service degradation (80% failure)
stellarService.enableFailureSimulation('service_unavailable', 0.8);
// ... perform operations ...
// Phase 2: Recovery (20% failure)
stellarService.enableFailureSimulation('service_unavailable', 0.2);
// ... perform operations ...
// Phase 3: Full recovery
stellarService.disableFailureSimulation();// Simulate hitting rate limits
stellarService.enableFailureSimulation('rate_limit_horizon', 1.0);
try {
await stellarService.getBalance(publicKey);
} catch (error) {
// Wait for suggested retry delay
await new Promise(resolve =>
setTimeout(resolve, error.details.retryAfter)
);
// Retry after delay
stellarService.disableFailureSimulation();
const balance = await stellarService.getBalance(publicKey);
}The failure simulation integrates seamlessly with existing tests:
describe('Donation Flow', () => {
test('should handle network failures during donation', async () => {
const donor = await stellarService.createWallet();
const recipient = await stellarService.createWallet();
await stellarService.fundTestnetWallet(donor.publicKey);
await stellarService.fundTestnetWallet(recipient.publicKey);
// Enable failure simulation
stellarService.enableFailureSimulation('timeout', 0.3);
stellarService.setMaxConsecutiveFailures(2);
// Donation should eventually succeed despite failures
const result = await stellarService.sendDonation({
sourceSecret: donor.secretKey,
destinationPublic: recipient.publicKey,
amount: '100',
memo: 'Test donation'
});
expect(result.transactionId).toBeDefined();
});
});- No Actual Network Delay: Failures are instant, no actual network latency
- Simplified Error Responses: Real Stellar errors may have more complex structures
- No Partial State Changes: Transactions either fully succeed or fully fail
- No Network Partition Simulation: Can't simulate split-brain scenarios
- No Byzantine Failures: Can't simulate malicious node behavior
- Add network latency simulation
- Support custom error messages
- Add failure injection at specific points
- Support failure patterns (e.g., every Nth request fails)
- Add metrics collection for failure analysis
- Support conditional failures based on operation type
- Add failure replay from logs
For issues or questions about failure simulation:
- Check test examples in
tests/stellar-network-failures.test.js - Review retry logic tests in
tests/stellar-retry-logic.test.js - See MockStellarService implementation in
src/services/MockStellarService.js