Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,19 @@
"@types/validator": "^13.11.6",
"@types/web-push": "^3.6.4",
"@types/ws": "^8.5.12",
"autocannon": "^7.15.0",
"eslint": "^9.0.0",
"openapi-fetch": "^0.13.5",
"openapi-typescript": "^7.4.4",
"pino-pretty": "^13.0.0",
"prisma": "^5.22.0",
"ts-morph": "^24.0.0",
"tsx": "^4.19.0",
"typescript": "^5.6.0",
"typescript-eslint": "^8.0.0",
"vitest": "^4.1.1",
"autocannon": "^7.15.0",
"openapi-fetch": "^0.13.5",
"openapi-typescript": "^7.4.4",
"pino-pretty": "^13.0.0"
"vitest": "^4.1.1"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.62.2"
}
}
67 changes: 67 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ enum PaymentStatus {
failed
cancelled
refunded
pending_review
}

enum ReorgEventStatus {
detected
investigating
resolved
false_positive
}

enum TransactionReorgStatus {
pending_review
re_verified
confirmed
rolled_back
}

enum PaymentType {
Expand Down Expand Up @@ -119,6 +134,7 @@ model Payment {
user User? @relation(fields: [userId], references: [id])
project Project? @relation(fields: [projectId], references: [id])
milestone Milestone? @relation(fields: [milestoneId], references: [id])
transactionReorgs TransactionReorg[]

@@index([tenantId, createdAt])
@@index([status], map: "payments_active_status_idx")
Expand Down Expand Up @@ -648,3 +664,54 @@ model ApiVersionEndpoint {
@@map("api_version_endpoints")
}

// ─── Chain Reorganization Models ─────────────────────────────────────────────

model ReorgEvent {
id String @id @default(uuid())
network String
detectedAt DateTime @default(now()) @map("detected_at")
reorgDepth Int @map("reorg_depth")
safetyThreshold Int @map("safety_threshold")
canonicalBlockHash String @map("canonical_block_hash")
orphanedBlockHash String @map("orphaned_block_hash")
fromBlockNumber Int @map("from_block_number")
toBlockNumber Int @map("to_block_number")
status ReorgEventStatus @default(detected)
resolvedAt DateTime? @map("resolved_at")
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

affectedTransactions TransactionReorg[]

@@index([network, detectedAt])
@@index([status])
@@index([reorgDepth])
@@map("reorg_events")
}

model TransactionReorg {
id String @id @default(uuid())
reorgEventId String @map("reorg_event_id")
txHash String @map("tx_hash")
paymentId String? @map("payment_id")
network String
status TransactionReorgStatus @default(pending_review)
originalBlock Int? @map("original_block")
reorgDetails Json? @map("reorg_details")
reVerifiedAt DateTime? @map("re_verified_at")
resolvedAt DateTime? @map("resolved_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

reorgEvent ReorgEvent @relation(fields: [reorgEventId], references: [id])
payment Payment? @relation(fields: [paymentId], references: [id])

@@index([reorgEventId])
@@index([txHash])
@@index([paymentId])
@@index([status])
@@index([network, createdAt])
@@map("transaction_reorgs")
}

23 changes: 23 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ import { coldStartMonitorRouter } from './routes/cold-start-monitor.js';
import { rateLimitAnalyticsRouter } from './routes/rate-limit-analytics.js';
import { startScheduledRotation, stopScheduledRotation } from './config/credential-rotation.js';
import devDevRouter from './routes/dev/reload.js';
import { reorgRouter } from './routes/reorg.js';
import { getReorgDetector } from './services/chain/reorg-detector.js';

// Validate environment variables at startup
validateEnv();
Expand Down Expand Up @@ -268,6 +270,7 @@ apiV1Router.use('/escrow', escrowRouter);
apiV1Router.use('/disputes', disputesRouter);
apiV1Router.use('/withdrawals', withdrawalsRouter);
apiV1Router.use('/swap', swapSimulationRouter);
apiV1Router.use('/chain/reorgs', reorgRouter);
apiV1Router.get('/compression/metrics', (_req, res) => {
res.json(getCompressionMetrics());
});
Expand Down Expand Up @@ -456,6 +459,19 @@ server.listen(config.server.port, () => {
// Webhook worker
startWebhookWorker();

// Chain reorganization detector (Issue #514)
if (
process.env.ETHEREUM_RPC_URL ||
process.env.POLYGON_RPC_URL ||
process.env.STELLAR_RPC_URL
) {
getReorgDetector().start().then(() => {
console.log('[ReorgDetector] Chain reorg monitoring started');
}).catch((err: Error) => {
console.error('[ReorgDetector] Startup error:', err.message);
});
}

// Auto-escalation cron
setInterval(async () => {
const count = await disputeService.processEscalations();
Expand Down Expand Up @@ -525,6 +541,13 @@ const shutdown = (signal: string) => {
console.error('Error stopping batch processor:', err);
}

try {
getReorgDetector().stop();
console.log('Reorg detector stopped.');
} catch (err) {
console.error('Error stopping reorg detector:', err);
}

clearInterval(analyticsInterval);

try {
Expand Down
Loading