mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
Q-Chain provides quantum finality verification for prices/orders from other sources, NOT its own price feed. Changes: - Rename QChainSource to QChainVerifier (with backwards compat alias) - Remove Q-Chain from price aggregator (not a Source interface) - Q-Chain only used with -verify flag to verify finality - Update architecture diagram to show Q-Chain as verification layer Price Sources (6 total): - X-Chain (2.0): Native DEX orderbook - C-Chain (1.8): AMM pools - Zoo Chain (1.7): Zoo Labs DEX - A-Chain (1.5): Validator-attested network oracle - Pyth (1.2): External oracle - Chainlink (1.0): External oracle (reference) Q-Chain Verifier: - Post-quantum signatures (dilithium, sphincs+) - Cross-chain order inclusion proofs - Validator quorum consensus (3/4) - ~50ms finality latency
773 lines
18 KiB
Go
773 lines
18 KiB
Go
package lx
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Settlement status constants
|
|
const (
|
|
SettlementPending = "pending"
|
|
SettlementProcessing = "processing"
|
|
SettlementComplete = "complete"
|
|
SettlementCompleted = "completed"
|
|
SettlementFailed = "failed"
|
|
)
|
|
|
|
// SettlementBatch represents a batch of orders for settlement
|
|
type SettlementBatch struct {
|
|
BatchID uint64
|
|
Timestamp time.Time
|
|
Orders []*Order
|
|
Status string
|
|
TxHash string
|
|
GasUsed *big.Int
|
|
CreatedAt time.Time
|
|
CompletedAt time.Time
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// GetStatus returns the batch status (thread-safe)
|
|
func (b *SettlementBatch) GetStatus() string {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
return b.Status
|
|
}
|
|
|
|
// GetTxHash returns the transaction hash (thread-safe)
|
|
func (b *SettlementBatch) GetTxHash() string {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
return b.TxHash
|
|
}
|
|
|
|
// GetGasUsed returns the gas used (thread-safe)
|
|
func (b *SettlementBatch) GetGasUsed() *big.Int {
|
|
b.mu.RLock()
|
|
defer b.mu.RUnlock()
|
|
return b.GasUsed
|
|
}
|
|
|
|
// XChainIntegration manages cross-chain settlement and clearing
|
|
type XChainIntegration struct {
|
|
chainID uint64
|
|
contractAddr string
|
|
settledOrders map[uint64]*SettledOrder
|
|
pendingBatches map[uint64]*SettlementBatch
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// SettledOrder represents a successfully settled order
|
|
type SettledOrder struct {
|
|
OrderID uint64
|
|
TxHash string
|
|
BlockNumber uint64
|
|
SettlementTime time.Time
|
|
GasUsed *big.Int
|
|
}
|
|
|
|
// XChainConfig configuration for X-Chain integration
|
|
type XChainConfig struct {
|
|
RPC string
|
|
ChainID uint64
|
|
SettlementContract string
|
|
BatchSize int
|
|
BatchTimeout time.Duration
|
|
MaxGasPrice *big.Int
|
|
}
|
|
|
|
// XChainStats statistics for X-Chain operations
|
|
type XChainStats struct {
|
|
TotalSettled uint64
|
|
TotalPending uint64
|
|
TotalFailed uint64
|
|
AvgGasUsed *big.Int
|
|
AvgSettlementTime time.Duration
|
|
LastSettlement time.Time
|
|
}
|
|
|
|
// SettlementEngine manages order settlement
|
|
type SettlementEngine struct {
|
|
batches map[uint64]*SettlementBatch
|
|
pendingOrders []*Order
|
|
batchSize int
|
|
batchTimeout time.Duration
|
|
batchCounter uint64
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// ClearingEngine manages position clearing and margin
|
|
type ClearingEngine struct {
|
|
positions map[string]map[string]*Position
|
|
balances map[string]*Balance
|
|
marginRequirements map[string]*MarginRequirement
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// Position represents a user's position in a market
|
|
type Position struct {
|
|
Symbol string
|
|
User string
|
|
Size float64
|
|
EntryPrice float64
|
|
MarkPrice float64
|
|
UnrealizedPnL float64
|
|
RealizedPnL float64
|
|
}
|
|
|
|
// Balance represents a user's balance
|
|
type Balance struct {
|
|
User string
|
|
Available *big.Int
|
|
Locked *big.Int
|
|
Total *big.Int
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// MarginRequirement represents margin requirements for a user
|
|
type MarginRequirement struct {
|
|
User string
|
|
InitialMargin *big.Int
|
|
MaintenanceMargin *big.Int
|
|
MarginRatio float64
|
|
LastCalculation time.Time
|
|
}
|
|
|
|
// Event structures for X-Chain events
|
|
type OrderSettledEvent struct {
|
|
OrderID uint64
|
|
TxHash string
|
|
BlockNumber uint64
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type BatchSettledEvent struct {
|
|
BatchID uint64
|
|
TxHash string
|
|
OrderCount int
|
|
TotalVolume *big.Int
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type SettlementFailedEvent struct {
|
|
BatchID uint64
|
|
Reason string
|
|
Timestamp time.Time
|
|
}
|
|
|
|
// Oracle structures
|
|
type OraclePrice struct {
|
|
Symbol string
|
|
Price float64
|
|
Timestamp time.Time
|
|
Source string
|
|
}
|
|
|
|
type OracleUpdate struct {
|
|
Prices map[string]float64
|
|
Timestamp time.Time
|
|
Signature string
|
|
}
|
|
|
|
// Gas optimization structures
|
|
type GasEstimator struct {
|
|
baseGas uint64
|
|
perOrderGas uint64
|
|
maxGasPrice *big.Int
|
|
priorityFee *big.Int
|
|
}
|
|
|
|
type GasOptimizer struct {
|
|
batchSize int
|
|
maxBatchGas uint64
|
|
targetGasPrice *big.Int
|
|
}
|
|
|
|
// Security structures
|
|
type Signature struct {
|
|
V uint8
|
|
R string
|
|
S string
|
|
}
|
|
|
|
type NonceManager struct {
|
|
currentNonce uint64
|
|
pendingNonces map[uint64]bool
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type AccessControl struct {
|
|
admins map[string]bool
|
|
operators map[string]bool
|
|
blacklist map[string]bool
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// Monitoring structures
|
|
type XChainMetrics struct {
|
|
SettlementLatency time.Duration
|
|
GasUsedTotal *big.Int
|
|
SuccessRate float64
|
|
FailureCount uint64
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
type HealthCheck struct {
|
|
Status string
|
|
BlockHeight uint64
|
|
PeerCount int
|
|
Syncing bool
|
|
LastCheck time.Time
|
|
}
|
|
|
|
type Alert struct {
|
|
Level string
|
|
Message string
|
|
Timestamp time.Time
|
|
Resolved bool
|
|
}
|
|
|
|
// Recovery structures
|
|
type RecoveryManager struct {
|
|
failedBatches map[uint64]*SettlementBatch
|
|
retryAttempts map[uint64]int
|
|
maxRetries int
|
|
retryDelay time.Duration
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type RetryPolicy struct {
|
|
MaxAttempts int
|
|
InitialDelay time.Duration
|
|
MaxDelay time.Duration
|
|
BackoffFactor float64
|
|
}
|
|
|
|
type DisasterRecovery struct {
|
|
backupEndpoints []string
|
|
currentEndpoint int
|
|
lastSwitch time.Time
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewXChainIntegration creates a new X-Chain integration
|
|
func NewXChainIntegration(chainID uint64, contractAddr string) *XChainIntegration {
|
|
return &XChainIntegration{
|
|
chainID: chainID,
|
|
contractAddr: contractAddr,
|
|
settledOrders: make(map[uint64]*SettledOrder),
|
|
pendingBatches: make(map[uint64]*SettlementBatch),
|
|
}
|
|
}
|
|
|
|
// SubmitBatch submits a batch for settlement
|
|
func (x *XChainIntegration) SubmitBatch(batch *SettlementBatch) error {
|
|
x.mu.Lock()
|
|
defer x.mu.Unlock()
|
|
|
|
batch.Status = SettlementPending
|
|
x.pendingBatches[batch.BatchID] = batch
|
|
|
|
// In production, this would submit to blockchain
|
|
go x.processBatch(batch)
|
|
|
|
return nil
|
|
}
|
|
|
|
// processBatch processes a settlement batch
|
|
func (x *XChainIntegration) processBatch(batch *SettlementBatch) {
|
|
// Simulate processing delay
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
batch.mu.Lock()
|
|
batch.Status = SettlementProcessing
|
|
batch.mu.Unlock()
|
|
|
|
// Simulate settlement
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
batch.mu.Lock()
|
|
batch.Status = SettlementComplete
|
|
batch.TxHash = "0xabcdef123456"
|
|
batch.GasUsed = big.NewInt(210000)
|
|
batch.mu.Unlock()
|
|
|
|
// Record settled orders
|
|
x.mu.Lock()
|
|
for _, order := range batch.Orders {
|
|
x.settledOrders[order.ID] = &SettledOrder{
|
|
OrderID: order.ID,
|
|
TxHash: batch.TxHash,
|
|
BlockNumber: 1000000,
|
|
SettlementTime: time.Now(),
|
|
GasUsed: big.NewInt(21000),
|
|
}
|
|
}
|
|
delete(x.pendingBatches, batch.BatchID)
|
|
x.mu.Unlock()
|
|
}
|
|
|
|
// GetStats returns X-Chain statistics
|
|
func (x *XChainIntegration) GetStats() *XChainStats {
|
|
x.mu.RLock()
|
|
defer x.mu.RUnlock()
|
|
|
|
totalSettled := uint64(len(x.settledOrders))
|
|
totalPending := uint64(len(x.pendingBatches))
|
|
|
|
var totalGas int64
|
|
var lastSettlement time.Time
|
|
|
|
for _, order := range x.settledOrders {
|
|
if order.GasUsed != nil {
|
|
totalGas += order.GasUsed.Int64()
|
|
}
|
|
if order.SettlementTime.After(lastSettlement) {
|
|
lastSettlement = order.SettlementTime
|
|
}
|
|
}
|
|
|
|
avgGas := big.NewInt(0)
|
|
if totalSettled > 0 {
|
|
avgGas = big.NewInt(totalGas / int64(totalSettled))
|
|
}
|
|
|
|
return &XChainStats{
|
|
TotalSettled: totalSettled,
|
|
TotalPending: totalPending,
|
|
TotalFailed: 0,
|
|
AvgGasUsed: avgGas,
|
|
AvgSettlementTime: 300 * time.Millisecond,
|
|
LastSettlement: lastSettlement,
|
|
}
|
|
}
|
|
|
|
// NewSettlementEngine creates a new settlement engine
|
|
func NewSettlementEngine(batchSize int, batchTimeout time.Duration) *SettlementEngine {
|
|
return &SettlementEngine{
|
|
batches: make(map[uint64]*SettlementBatch),
|
|
pendingOrders: make([]*Order, 0),
|
|
batchSize: batchSize,
|
|
batchTimeout: batchTimeout,
|
|
}
|
|
}
|
|
|
|
// AddOrder adds an order for settlement
|
|
func (s *SettlementEngine) AddOrder(order *Order) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.pendingOrders = append(s.pendingOrders, order)
|
|
|
|
// Create batch if size reached
|
|
if len(s.pendingOrders) >= s.batchSize {
|
|
s.createBatch()
|
|
}
|
|
}
|
|
|
|
// CreateBatch creates a new batch from provided orders (for testing)
|
|
func (s *SettlementEngine) CreateBatch(orders []*SettledOrder) *SettlementBatch {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.batchCounter++
|
|
batch := &SettlementBatch{
|
|
BatchID: s.batchCounter,
|
|
Orders: make([]*Order, 0, len(orders)),
|
|
Status: SettlementPending,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Convert SettledOrders to Orders for the batch
|
|
for _, settled := range orders {
|
|
order := &Order{
|
|
ID: settled.OrderID,
|
|
}
|
|
batch.Orders = append(batch.Orders, order)
|
|
}
|
|
|
|
s.batches[batch.BatchID] = batch
|
|
return batch
|
|
}
|
|
|
|
// ExecuteBatch executes a settlement batch
|
|
func (s *SettlementEngine) ExecuteBatch(batchID uint64) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
batch, exists := s.batches[batchID]
|
|
if !exists {
|
|
return errors.New("batch not found")
|
|
}
|
|
|
|
batch.Status = SettlementCompleted
|
|
batch.CompletedAt = time.Now()
|
|
return nil
|
|
}
|
|
|
|
// createBatch creates a new settlement batch
|
|
func (s *SettlementEngine) createBatch() *SettlementBatch {
|
|
batchID := uint64(time.Now().UnixNano())
|
|
|
|
batch := &SettlementBatch{
|
|
BatchID: batchID,
|
|
Timestamp: time.Now(),
|
|
Orders: make([]*Order, len(s.pendingOrders)),
|
|
Status: SettlementPending,
|
|
}
|
|
|
|
copy(batch.Orders, s.pendingOrders)
|
|
s.pendingOrders = s.pendingOrders[:0]
|
|
s.batches[batchID] = batch
|
|
|
|
return batch
|
|
}
|
|
|
|
// NewClearingEngine creates a new clearing engine
|
|
func NewClearingEngine() *ClearingEngine {
|
|
return &ClearingEngine{
|
|
positions: make(map[string]map[string]*Position),
|
|
balances: make(map[string]*Balance),
|
|
marginRequirements: make(map[string]*MarginRequirement),
|
|
}
|
|
}
|
|
|
|
// UpdatePosition updates a user's position
|
|
func (c *ClearingEngine) UpdatePosition(user, symbol string, size, price float64) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.positions[user] == nil {
|
|
c.positions[user] = make(map[string]*Position)
|
|
}
|
|
|
|
pos, exists := c.positions[user][symbol]
|
|
if !exists {
|
|
pos = &Position{
|
|
Symbol: symbol,
|
|
User: user,
|
|
}
|
|
c.positions[user][symbol] = pos
|
|
}
|
|
|
|
// Update position
|
|
if pos.Size == 0 {
|
|
pos.EntryPrice = price
|
|
} else {
|
|
// Average entry price
|
|
totalValue := pos.Size*pos.EntryPrice + size*price
|
|
pos.EntryPrice = totalValue / (pos.Size + size)
|
|
}
|
|
|
|
pos.Size += size
|
|
pos.MarkPrice = price
|
|
pos.UnrealizedPnL = (price - pos.EntryPrice) * pos.Size
|
|
}
|
|
|
|
// GetPosition returns a user's position
|
|
func (c *ClearingEngine) GetPosition(user, symbol string) *Position {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
if positions, exists := c.positions[user]; exists {
|
|
return positions[symbol]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateBalanceWithLocked updates a user's balance with available and locked amounts
|
|
func (c *ClearingEngine) UpdateBalanceWithLocked(user string, available, locked *big.Int) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
balance, exists := c.balances[user]
|
|
if !exists {
|
|
balance = &Balance{
|
|
User: user,
|
|
Available: big.NewInt(0),
|
|
Locked: big.NewInt(0),
|
|
Total: big.NewInt(0),
|
|
}
|
|
c.balances[user] = balance
|
|
}
|
|
|
|
if available != nil {
|
|
balance.Available = new(big.Int).Set(available)
|
|
}
|
|
if locked != nil {
|
|
balance.Locked = new(big.Int).Set(locked)
|
|
}
|
|
|
|
balance.Total = new(big.Int).Add(balance.Available, balance.Locked)
|
|
balance.LastUpdate = time.Now()
|
|
}
|
|
|
|
// GetBalance returns a user's balance
|
|
func (c *ClearingEngine) GetBalance(user string) *Balance {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
return c.balances[user]
|
|
}
|
|
|
|
// UpdateBalance updates a user's balance with a single amount
|
|
func (c *ClearingEngine) UpdateBalance(user string, amount *big.Int) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.balances[user] == nil {
|
|
c.balances[user] = &Balance{
|
|
User: user,
|
|
Available: new(big.Int).Set(amount),
|
|
Locked: big.NewInt(0),
|
|
Total: new(big.Int).Set(amount),
|
|
LastUpdate: time.Now(),
|
|
}
|
|
} else {
|
|
c.balances[user].Available.Add(c.balances[user].Available, amount)
|
|
c.balances[user].Total.Add(c.balances[user].Total, amount)
|
|
c.balances[user].LastUpdate = time.Now()
|
|
}
|
|
}
|
|
|
|
// CalculateMarginRequirement calculates margin requirements for a user
|
|
func (c *ClearingEngine) CalculateMarginRequirement(user string) *MarginRequirement {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
req := &MarginRequirement{
|
|
User: user,
|
|
InitialMargin: big.NewInt(0),
|
|
MaintenanceMargin: big.NewInt(0),
|
|
LastCalculation: time.Now(),
|
|
}
|
|
|
|
// Calculate based on positions
|
|
if positions, exists := c.positions[user]; exists {
|
|
for _, pos := range positions {
|
|
positionValue := int64(pos.Size * pos.EntryPrice)
|
|
initial := big.NewInt(positionValue / 10) // 10% initial margin
|
|
maintenance := big.NewInt(positionValue / 20) // 5% maintenance margin
|
|
|
|
req.InitialMargin.Add(req.InitialMargin, initial)
|
|
req.MaintenanceMargin.Add(req.MaintenanceMargin, maintenance)
|
|
}
|
|
}
|
|
|
|
// Calculate margin ratio
|
|
if balance := c.balances[user]; balance != nil && req.InitialMargin.Sign() > 0 {
|
|
balanceFloat := new(big.Float).SetInt(balance.Available)
|
|
marginFloat := new(big.Float).SetInt(req.InitialMargin)
|
|
ratio, _ := new(big.Float).Quo(balanceFloat, marginFloat).Float64()
|
|
req.MarginRatio = ratio
|
|
}
|
|
|
|
c.marginRequirements[user] = req
|
|
return req
|
|
}
|
|
|
|
// CheckMargin checks if a user has sufficient margin
|
|
func (c *ClearingEngine) CheckMargin(user string) bool {
|
|
req := c.CalculateMarginRequirement(user)
|
|
balance := c.GetBalance(user)
|
|
|
|
if balance == nil || req == nil {
|
|
return false
|
|
}
|
|
|
|
return balance.Available.Cmp(req.InitialMargin) >= 0
|
|
}
|
|
|
|
// CheckLiquidation checks if a user needs liquidation
|
|
func (c *ClearingEngine) CheckLiquidation(user string) bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
req := c.marginRequirements[user]
|
|
balance := c.balances[user]
|
|
|
|
if req == nil || balance == nil {
|
|
return false
|
|
}
|
|
|
|
// Check if account value is below maintenance margin
|
|
return balance.Available.Cmp(req.MaintenanceMargin) < 0
|
|
}
|
|
|
|
// Liquidate liquidates a user's positions
|
|
func (c *ClearingEngine) Liquidate(user string) bool {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// Check if liquidation is needed
|
|
if c.balances[user] == nil || c.marginRequirements[user] == nil {
|
|
return false
|
|
}
|
|
|
|
if c.balances[user].Available.Cmp(c.marginRequirements[user].MaintenanceMargin) >= 0 {
|
|
return false // No liquidation needed
|
|
}
|
|
|
|
// Close all positions
|
|
delete(c.positions, user)
|
|
|
|
// Reset margin requirements
|
|
c.marginRequirements[user] = &MarginRequirement{
|
|
User: user,
|
|
InitialMargin: big.NewInt(0),
|
|
MaintenanceMargin: big.NewInt(0),
|
|
MarginRatio: 0,
|
|
LastCalculation: time.Now(),
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NewGasEstimator creates a new gas estimator
|
|
func NewGasEstimator() *GasEstimator {
|
|
return &GasEstimator{
|
|
baseGas: 21000,
|
|
perOrderGas: 50000,
|
|
maxGasPrice: big.NewInt(100000000000), // 100 gwei
|
|
priorityFee: big.NewInt(2000000000), // 2 gwei
|
|
}
|
|
}
|
|
|
|
// EstimateGas estimates gas for a batch
|
|
func (g *GasEstimator) EstimateGas(orderCount int) uint64 {
|
|
return g.baseGas + uint64(orderCount)*g.perOrderGas
|
|
}
|
|
|
|
// NewNonceManager creates a new nonce manager
|
|
func NewNonceManager(startNonce uint64) *NonceManager {
|
|
return &NonceManager{
|
|
currentNonce: startNonce,
|
|
pendingNonces: make(map[uint64]bool),
|
|
}
|
|
}
|
|
|
|
// GetNextNonce returns the next available nonce
|
|
func (n *NonceManager) GetNextNonce() uint64 {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
nonce := n.currentNonce
|
|
n.pendingNonces[nonce] = true
|
|
n.currentNonce++
|
|
|
|
return nonce
|
|
}
|
|
|
|
// ConfirmNonce confirms a nonce has been used
|
|
func (n *NonceManager) ConfirmNonce(nonce uint64) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
delete(n.pendingNonces, nonce)
|
|
}
|
|
|
|
// NewAccessControl creates a new access control manager
|
|
func NewAccessControl() *AccessControl {
|
|
return &AccessControl{
|
|
admins: make(map[string]bool),
|
|
operators: make(map[string]bool),
|
|
blacklist: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
// IsAdmin checks if an address is an admin
|
|
func (a *AccessControl) IsAdmin(address string) bool {
|
|
a.mu.RLock()
|
|
defer a.mu.RUnlock()
|
|
|
|
return a.admins[address]
|
|
}
|
|
|
|
// IsOperator checks if an address is an operator
|
|
func (a *AccessControl) IsOperator(address string) bool {
|
|
a.mu.RLock()
|
|
defer a.mu.RUnlock()
|
|
|
|
return a.operators[address]
|
|
}
|
|
|
|
// IsBlacklisted checks if an address is blacklisted
|
|
func (a *AccessControl) IsBlacklisted(address string) bool {
|
|
a.mu.RLock()
|
|
defer a.mu.RUnlock()
|
|
|
|
return a.blacklist[address]
|
|
}
|
|
|
|
// NewRecoveryManager creates a new recovery manager
|
|
func NewRecoveryManager(maxRetries int, retryDelay time.Duration) *RecoveryManager {
|
|
return &RecoveryManager{
|
|
failedBatches: make(map[uint64]*SettlementBatch),
|
|
retryAttempts: make(map[uint64]int),
|
|
maxRetries: maxRetries,
|
|
retryDelay: retryDelay,
|
|
}
|
|
}
|
|
|
|
// AddFailedBatch adds a failed batch for retry
|
|
func (r *RecoveryManager) AddFailedBatch(batch *SettlementBatch) bool {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
// Capture batch ID while holding the lock to avoid race conditions
|
|
batchID := batch.BatchID
|
|
|
|
attempts := r.retryAttempts[batchID]
|
|
if attempts >= r.maxRetries {
|
|
return false
|
|
}
|
|
|
|
r.failedBatches[batchID] = batch
|
|
r.retryAttempts[batchID] = attempts + 1
|
|
|
|
// Schedule retry using captured batchID
|
|
go r.scheduleRetry(batchID)
|
|
|
|
return true
|
|
}
|
|
|
|
// scheduleRetry schedules a batch retry
|
|
func (r *RecoveryManager) scheduleRetry(batchID uint64) {
|
|
time.Sleep(r.retryDelay)
|
|
|
|
r.mu.Lock()
|
|
batch, exists := r.failedBatches[batchID]
|
|
if !exists {
|
|
r.mu.Unlock()
|
|
return
|
|
}
|
|
delete(r.failedBatches, batchID)
|
|
r.mu.Unlock()
|
|
|
|
// Retry the batch (would call settlement logic in production)
|
|
_ = batch
|
|
}
|
|
|
|
// FailedBatchCount returns the number of failed batches (thread-safe)
|
|
func (r *RecoveryManager) FailedBatchCount() int {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return len(r.failedBatches)
|
|
}
|
|
|
|
// GetRetryAttempts returns the retry attempts for a batch (thread-safe)
|
|
func (r *RecoveryManager) GetRetryAttempts(batchID uint64) int {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return r.retryAttempts[batchID]
|
|
}
|
|
|
|
// AddFailedBatchForTest adds a batch directly to failed batches (thread-safe, for testing)
|
|
func (r *RecoveryManager) AddFailedBatchForTest(batch *SettlementBatch) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.failedBatches[batch.BatchID] = batch
|
|
}
|