dubs-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeper State Service
|
|
3
|
+
*
|
|
4
|
+
* Manages keeper state in PostgreSQL for:
|
|
5
|
+
* - Round lifecycle tracking
|
|
6
|
+
* - Recovery after crashes
|
|
7
|
+
* - Multi-keeper coordination
|
|
8
|
+
* - Monitoring and alerting
|
|
9
|
+
*
|
|
10
|
+
* NOTE: Gracefully degrades to no-op when DATABASE_URL is not set (local dev)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { pool } = require('./db'); // Shared database pool
|
|
14
|
+
|
|
15
|
+
class KeeperStateService {
|
|
16
|
+
constructor() {
|
|
17
|
+
// Check if database is configured
|
|
18
|
+
this.enabled = !!process.env.DATABASE_URL;
|
|
19
|
+
|
|
20
|
+
if (this.enabled) {
|
|
21
|
+
// Use shared pool from services/db.js
|
|
22
|
+
this.pool = pool;
|
|
23
|
+
console.log('📊 Keeper state service: Database enabled');
|
|
24
|
+
} else {
|
|
25
|
+
console.log('⚠️ Keeper state service: No DATABASE_URL set - running without state persistence (local dev mode)');
|
|
26
|
+
this.pool = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Helper to check if DB operations should run
|
|
31
|
+
_shouldRun() {
|
|
32
|
+
return this.enabled && this.pool;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize a new round in the database
|
|
37
|
+
*/
|
|
38
|
+
async createRound(roundId, initialStatus = 'open') {
|
|
39
|
+
if (!this._shouldRun()) return null;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const result = await this.pool.query(
|
|
43
|
+
`INSERT INTO keeper_rounds (round_id, status)
|
|
44
|
+
VALUES ($1, $2)
|
|
45
|
+
ON CONFLICT (round_id) DO NOTHING
|
|
46
|
+
RETURNING *`,
|
|
47
|
+
[roundId, initialStatus]
|
|
48
|
+
);
|
|
49
|
+
console.log(`📝 Created round ${roundId} in DB with status: ${initialStatus}`);
|
|
50
|
+
return result.rows[0];
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`❌ Failed to create round ${roundId}:`, error.message);
|
|
53
|
+
return null; // Don't throw - allow keeper to continue
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get round state from database
|
|
59
|
+
*/
|
|
60
|
+
async getRound(roundId) {
|
|
61
|
+
if (!this._shouldRun()) return null;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const result = await this.pool.query(
|
|
65
|
+
'SELECT * FROM keeper_rounds WHERE round_id = $1',
|
|
66
|
+
[roundId]
|
|
67
|
+
);
|
|
68
|
+
return result.rows[0] || null;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`❌ Failed to get round ${roundId}:`, error.message);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Update round status and metadata
|
|
77
|
+
*/
|
|
78
|
+
async updateRound(roundId, updates) {
|
|
79
|
+
if (!this._shouldRun()) return null;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const fields = [];
|
|
83
|
+
const values = [roundId];
|
|
84
|
+
let paramIndex = 2;
|
|
85
|
+
|
|
86
|
+
// Build dynamic UPDATE query
|
|
87
|
+
if (updates.status) {
|
|
88
|
+
fields.push(`status = $${paramIndex++}`);
|
|
89
|
+
values.push(updates.status);
|
|
90
|
+
}
|
|
91
|
+
if (updates.locked_at) {
|
|
92
|
+
fields.push(`locked_at = $${paramIndex++}`);
|
|
93
|
+
values.push(updates.locked_at);
|
|
94
|
+
}
|
|
95
|
+
if (updates.revealed_at) {
|
|
96
|
+
fields.push(`revealed_at = $${paramIndex++}`);
|
|
97
|
+
values.push(updates.revealed_at);
|
|
98
|
+
}
|
|
99
|
+
if (updates.resolved_at) {
|
|
100
|
+
fields.push(`resolved_at = $${paramIndex++}`);
|
|
101
|
+
values.push(updates.resolved_at);
|
|
102
|
+
}
|
|
103
|
+
if (updates.reset_at) {
|
|
104
|
+
fields.push(`reset_at = $${paramIndex++}`);
|
|
105
|
+
values.push(updates.reset_at);
|
|
106
|
+
}
|
|
107
|
+
if (updates.retry_count !== undefined) {
|
|
108
|
+
fields.push(`retry_count = $${paramIndex++}`);
|
|
109
|
+
values.push(updates.retry_count);
|
|
110
|
+
}
|
|
111
|
+
if (updates.last_error !== undefined) {
|
|
112
|
+
fields.push(`last_error = $${paramIndex++}`);
|
|
113
|
+
values.push(updates.last_error);
|
|
114
|
+
}
|
|
115
|
+
if (updates.last_attempt_at) {
|
|
116
|
+
fields.push(`last_attempt_at = $${paramIndex++}`);
|
|
117
|
+
values.push(updates.last_attempt_at);
|
|
118
|
+
}
|
|
119
|
+
if (updates.total_pot !== undefined) {
|
|
120
|
+
fields.push(`total_pot = $${paramIndex++}`);
|
|
121
|
+
values.push(updates.total_pot);
|
|
122
|
+
}
|
|
123
|
+
if (updates.entry_count !== undefined) {
|
|
124
|
+
fields.push(`entry_count = $${paramIndex++}`);
|
|
125
|
+
values.push(updates.entry_count);
|
|
126
|
+
}
|
|
127
|
+
if (updates.winner_pubkey) {
|
|
128
|
+
fields.push(`winner_pubkey = $${paramIndex++}`);
|
|
129
|
+
values.push(updates.winner_pubkey);
|
|
130
|
+
}
|
|
131
|
+
if (updates.win_amount !== undefined) {
|
|
132
|
+
fields.push(`win_amount = $${paramIndex++}`);
|
|
133
|
+
values.push(updates.win_amount);
|
|
134
|
+
}
|
|
135
|
+
if (updates.lock_signature) {
|
|
136
|
+
fields.push(`lock_signature = $${paramIndex++}`);
|
|
137
|
+
values.push(updates.lock_signature);
|
|
138
|
+
}
|
|
139
|
+
if (updates.reveal_signature) {
|
|
140
|
+
fields.push(`reveal_signature = $${paramIndex++}`);
|
|
141
|
+
values.push(updates.reveal_signature);
|
|
142
|
+
}
|
|
143
|
+
if (updates.resolve_signature) {
|
|
144
|
+
fields.push(`resolve_signature = $${paramIndex++}`);
|
|
145
|
+
values.push(updates.resolve_signature);
|
|
146
|
+
}
|
|
147
|
+
if (updates.reset_signature) {
|
|
148
|
+
fields.push(`reset_signature = $${paramIndex++}`);
|
|
149
|
+
values.push(updates.reset_signature);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (fields.length === 0) {
|
|
153
|
+
console.warn(`⚠️ No fields to update for round ${roundId}`);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const query = `
|
|
158
|
+
UPDATE keeper_rounds
|
|
159
|
+
SET ${fields.join(', ')}
|
|
160
|
+
WHERE round_id = $1
|
|
161
|
+
RETURNING *
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const result = await this.pool.query(query, values);
|
|
165
|
+
return result.rows[0];
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(`❌ Failed to update round ${roundId}:`, error.message);
|
|
168
|
+
return null; // Don't throw - allow keeper to continue
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Log a keeper action for debugging
|
|
174
|
+
*/
|
|
175
|
+
async logAction(roundId, action, success, errorMessage = null, signature = null, durationMs = null) {
|
|
176
|
+
if (!this._shouldRun()) return;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await this.pool.query(
|
|
180
|
+
`INSERT INTO keeper_actions (round_id, action, success, error_message, signature, duration_ms)
|
|
181
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
182
|
+
[roundId, action, success, errorMessage, signature, durationMs]
|
|
183
|
+
);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Don't throw - logging failures shouldn't break the keeper
|
|
186
|
+
console.error(`⚠️ Failed to log action:`, error.message);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Find rounds that are stuck (not completed after 5 minutes)
|
|
192
|
+
*/
|
|
193
|
+
async getStuckRounds() {
|
|
194
|
+
if (!this._shouldRun()) return [];
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const result = await this.pool.query(
|
|
198
|
+
`SELECT * FROM stuck_rounds`
|
|
199
|
+
);
|
|
200
|
+
return result.rows;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`❌ Failed to get stuck rounds:`, error.message);
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get the current round that needs processing
|
|
209
|
+
* Returns the round with highest round_id that's not resolved
|
|
210
|
+
*/
|
|
211
|
+
async getCurrentActiveRound() {
|
|
212
|
+
if (!this._shouldRun()) return null;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await this.pool.query(
|
|
216
|
+
`SELECT * FROM keeper_rounds
|
|
217
|
+
WHERE status != 'resolved'
|
|
218
|
+
ORDER BY round_id DESC
|
|
219
|
+
LIMIT 1`
|
|
220
|
+
);
|
|
221
|
+
return result.rows[0] || null;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error(`❌ Failed to get active round:`, error.message);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Increment retry count for a round
|
|
230
|
+
*/
|
|
231
|
+
async incrementRetry(roundId, errorMessage) {
|
|
232
|
+
if (!this._shouldRun()) return;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
await this.pool.query(
|
|
236
|
+
`UPDATE keeper_rounds
|
|
237
|
+
SET retry_count = retry_count + 1,
|
|
238
|
+
last_error = $2,
|
|
239
|
+
last_attempt_at = NOW()
|
|
240
|
+
WHERE round_id = $1`,
|
|
241
|
+
[roundId, errorMessage]
|
|
242
|
+
);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error(`❌ Failed to increment retry:`, error.message);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get health summary
|
|
250
|
+
*/
|
|
251
|
+
async getHealthSummary() {
|
|
252
|
+
if (!this._shouldRun()) return null;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const result = await this.pool.query('SELECT * FROM keeper_health_summary');
|
|
256
|
+
return result.rows[0];
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error(`❌ Failed to get health summary:`, error.message);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Record health check
|
|
265
|
+
*/
|
|
266
|
+
async recordHealth(metrics) {
|
|
267
|
+
if (!this._shouldRun()) return;
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await this.pool.query(
|
|
271
|
+
`INSERT INTO keeper_health (rounds_completed, consecutive_failures, last_success_round, last_error, uptime_seconds)
|
|
272
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
273
|
+
[
|
|
274
|
+
metrics.roundsCompleted,
|
|
275
|
+
metrics.consecutiveFailures,
|
|
276
|
+
metrics.lastSuccessRound,
|
|
277
|
+
metrics.lastError,
|
|
278
|
+
metrics.uptimeSeconds
|
|
279
|
+
]
|
|
280
|
+
);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error(`⚠️ Failed to record health:`, error.message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Clean up old action logs (keep last 1000 or last 7 days)
|
|
288
|
+
*/
|
|
289
|
+
async cleanupOldLogs() {
|
|
290
|
+
if (!this._shouldRun()) return;
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
await this.pool.query(
|
|
294
|
+
`DELETE FROM keeper_actions
|
|
295
|
+
WHERE id NOT IN (
|
|
296
|
+
SELECT id FROM keeper_actions
|
|
297
|
+
ORDER BY timestamp DESC
|
|
298
|
+
LIMIT 1000
|
|
299
|
+
)
|
|
300
|
+
AND timestamp < NOW() - INTERVAL '7 days'`
|
|
301
|
+
);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error(`⚠️ Failed to cleanup logs:`, error.message);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get recent actions for a round
|
|
309
|
+
*/
|
|
310
|
+
async getRoundActions(roundId, limit = 20) {
|
|
311
|
+
if (!this._shouldRun()) return [];
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const result = await this.pool.query(
|
|
315
|
+
`SELECT * FROM keeper_actions
|
|
316
|
+
WHERE round_id = $1
|
|
317
|
+
ORDER BY timestamp DESC
|
|
318
|
+
LIMIT $2`,
|
|
319
|
+
[roundId, limit]
|
|
320
|
+
);
|
|
321
|
+
return result.rows;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error(`❌ Failed to get round actions:`, error.message);
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check if round is stuck (based on status and last update time)
|
|
330
|
+
*/
|
|
331
|
+
isRoundStuck(dbRound) {
|
|
332
|
+
if (!dbRound) return false;
|
|
333
|
+
|
|
334
|
+
const stuckStatuses = ['locking', 'revealing', 'resolving', 'resetting'];
|
|
335
|
+
if (!stuckStatuses.includes(dbRound.status)) return false;
|
|
336
|
+
|
|
337
|
+
const updatedAt = new Date(dbRound.updated_at);
|
|
338
|
+
const now = new Date();
|
|
339
|
+
const minutesStuck = (now - updatedAt) / 1000 / 60;
|
|
340
|
+
|
|
341
|
+
return minutesStuck > 5; // Stuck if no update in 5 minutes
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Close database connection
|
|
346
|
+
*/
|
|
347
|
+
async close() {
|
|
348
|
+
if (this.pool) {
|
|
349
|
+
await this.pool.end();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = KeeperStateService;
|
|
355
|
+
|