security+reliability: remove public decrypt endpoint, add panic recovery for goroutines

Security:
- Remove /api/crypto/decrypt from public routes. The endpoint allowed
  anyone to decrypt ciphertext without authentication. Internal callers
  (exchange/model handlers) use the service directly and are behind auth.

Reliability:
- Add safe.Go / safe.GoNamed panic recovery wrapper (safe/go.go).
  Previously 31 goroutines had zero recover() calls — a single panic
  in any trader goroutine would crash the entire process.
- Apply safe.GoNamed to all trader launch paths:
  - StartAll, RestoreRunning, LoadSingleTrader auto-start
  - API handler start/restart endpoints
- Panics are now logged with full stack traces instead of crashing.
This commit is contained in:
shinchan-zhai
2026-03-23 10:15:02 +08:00
parent 25e470dfbb
commit 7c668cd7ef
4 changed files with 81 additions and 15 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"nofx/logger"
"nofx/safe"
"nofx/store"
"nofx/trader"
"sort"
@@ -87,12 +88,14 @@ func (tm *TraderManager) StartAll() {
logger.Info("🚀 Starting all traders...")
for id, t := range tm.traders {
go func(traderID string, at *trader.AutoTrader) {
traderID, at := id, t
safe.GoNamed("trader-"+at.GetName(), func() {
logger.Infof("▶️ Starting %s...", at.GetName())
if err := at.Run(); err != nil {
logger.Infof("❌ %s runtime error: %v", at.GetName(), err)
}
}(id, t)
})
_ = traderID
}
}
@@ -135,12 +138,13 @@ func (tm *TraderManager) AutoStartRunningTraders(st *store.Store) {
startedCount := 0
for id, t := range tm.traders {
if runningTraderIDs[id] {
go func(traderID string, at *trader.AutoTrader) {
at := t
safe.GoNamed("trader-restore-"+at.GetName(), func() {
logger.Infof("▶️ Auto-restoring %s...", at.GetName())
if err := at.Run(); err != nil {
logger.Infof("❌ %s runtime error: %v", at.GetName(), err)
}
}(id, t)
})
startedCount++
}
}
@@ -726,15 +730,16 @@ func (tm *TraderManager) addTraderFromStore(traderCfg *store.Trader, aiModelCfg
// Auto-start if trader was running before shutdown
if traderCfg.IsRunning {
logger.Infof("🔄 Auto-starting trader '%s' (was running before shutdown)...", traderCfg.Name)
go func(trader *trader.AutoTrader, traderName, traderID, userID string) {
if err := trader.Run(); err != nil {
logger.Warnf("⚠️ Trader '%s' stopped with error: %v", traderName, err)
autoStartTrader, autoStartName, autoStartID, autoStartUserID := at, traderCfg.Name, traderCfg.ID, traderCfg.UserID
safe.GoNamed("trader-autostart-"+autoStartName, func() {
if err := autoStartTrader.Run(); err != nil {
logger.Warnf("⚠️ Trader '%s' stopped with error: %v", autoStartName, err)
// Update database to reflect stopped state
if st != nil {
_ = st.Trader().UpdateStatus(userID, traderID, false)
_ = st.Trader().UpdateStatus(autoStartUserID, autoStartID, false)
}
}
}(at, traderCfg.Name, traderCfg.ID, traderCfg.UserID)
})
logger.Infof("✅ Trader '%s' auto-started successfully", traderCfg.Name)
}