File size: 10,979 Bytes
af68acb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
"""
Cloud Credits System for SACCP Network
Handles credit tracking, earning, and spending in the distributed network
"""

import json
import sqlite3
import time
from datetime import datetime
from typing import Optional, List, Dict, Any
from enum import Enum
from dataclasses import dataclass
from pydantic import BaseModel


class TransactionType(str, Enum):
    EARNED = "earned"
    SPENT = "spent"
    TRANSFERRED = "transferred"


class CreditReason(str, Enum):
    TASK_COMPLETION = "task_completion"
    RESOURCE_CONTRIBUTION = "resource_contribution"
    SERVICE_PURCHASE = "service_purchase"
    REFERRAL_BONUS = "referral_bonus"
    STAKING_REWARD = "staking_reward"


@dataclass
class CreditTransaction:
    """Represents a credit transaction"""
    transaction_id: str
    node_id: str
    amount: float
    transaction_type: TransactionType
    reason: CreditReason
    timestamp: int
    service_type: Optional[str] = None
    metadata: Optional[Dict[str, Any]] = None


class CreditBalance(BaseModel):
    """Model for node credit balance"""
    node_id: str
    balance: float
    total_earned: float
    total_spent: float
    last_updated: int


class CreditsSystem:
    """Main system for managing cloud credits in the SACCP network"""
    
    def __init__(self, db_path: str = "./saccp_credits.db"):
        self.db_path = db_path
        self._init_db()
    
    def _init_db(self):
        """Initialize the credits database"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # Create balances table
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS balances (
                node_id TEXT PRIMARY KEY,
                balance REAL DEFAULT 0.0,
                total_earned REAL DEFAULT 0.0,
                total_spent REAL DEFAULT 0.0,
                last_updated INTEGER
            )
        ''')
        
        # Create transactions table
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS transactions (
                transaction_id TEXT PRIMARY KEY,
                node_id TEXT NOT NULL,
                amount REAL NOT NULL,
                transaction_type TEXT NOT NULL,
                reason TEXT NOT NULL,
                timestamp INTEGER NOT NULL,
                service_type TEXT,
                metadata TEXT  -- JSON string
            )
        ''')
        
        conn.commit()
        conn.close()
    
    def add_credits(self, node_id: str, amount: float, reason: CreditReason, 
                    service_type: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> bool:
        """Add credits to a node's balance"""
        if amount <= 0:
            return False
            
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        try:
            # Get current balance
            cursor.execute('SELECT balance, total_earned FROM balances WHERE node_id = ?', (node_id,))
            result = cursor.fetchone()
            
            if result:
                current_balance, total_earned = result
                new_balance = current_balance + amount
                new_total_earned = total_earned + amount
            else:
                new_balance = amount
                new_total_earned = amount
                # Insert new record if it doesn't exist
                cursor.execute('''
                    INSERT INTO balances (node_id, balance, total_earned, total_spent, last_updated)
                    VALUES (?, ?, ?, ?, ?)
                ''', (node_id, 0.0, 0.0, 0.0, int(time.time())))
            
            # Update balance
            cursor.execute('''
                UPDATE balances 
                SET balance = ?, total_earned = ?, last_updated = ?
                WHERE node_id = ?
            ''', (new_balance, new_total_earned, int(time.time()), node_id))
            
            # Record transaction
            transaction_id = f"credit_{int(time.time())}_{node_id}_{hash(str(time.time()))}"
            cursor.execute('''
                INSERT INTO transactions 
                (transaction_id, node_id, amount, transaction_type, reason, timestamp, service_type, metadata)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                transaction_id,
                node_id,
                amount,
                TransactionType.EARNED.value,
                reason.value,
                int(time.time()),
                service_type,
                json.dumps(metadata) if metadata else None
            ))
            
            conn.commit()
            return True
        except Exception as e:
            conn.rollback()
            print(f"Error adding credits: {e}")
            return False
        finally:
            conn.close()
    
    def spend_credits(self, node_id: str, amount: float, reason: CreditReason,
                      service_type: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> bool:
        """Spend credits from a node's balance"""
        if amount <= 0:
            return False
            
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        try:
            # Get current balance
            cursor.execute('SELECT balance FROM balances WHERE node_id = ?', (node_id,))
            result = cursor.fetchone()
            
            if not result:
                return False  # Node doesn't exist
                
            current_balance = result[0]
            if current_balance < amount:
                return False  # Insufficient credits
            
            # Update balance
            new_balance = current_balance - amount
            cursor.execute('''
                UPDATE balances 
                SET balance = ?, total_spent = total_spent + ?, last_updated = ?
                WHERE node_id = ?
            ''', (new_balance, amount, int(time.time()), node_id))
            
            # Record transaction
            transaction_id = f"debit_{int(time.time())}_{node_id}_{hash(str(time.time()))}"
            cursor.execute('''
                INSERT INTO transactions 
                (transaction_id, node_id, amount, transaction_type, reason, timestamp, service_type, metadata)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                transaction_id,
                node_id,
                -amount,  # Negative because it's a debit
                TransactionType.SPENT.value,
                reason.value,
                int(time.time()),
                service_type,
                json.dumps(metadata) if metadata else None
            ))
            
            conn.commit()
            return True
        except Exception as e:
            conn.rollback()
            print(f"Error spending credits: {e}")
            return False
        finally:
            conn.close()
    
    def get_balance(self, node_id: str) -> CreditBalance:
        """Get credit balance for a node"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT balance, total_earned, total_spent, last_updated 
            FROM balances 
            WHERE node_id = ?
        ''', (node_id,))
        
        result = cursor.fetchone()
        conn.close()
        
        if result:
            balance, total_earned, total_spent, last_updated = result
            return CreditBalance(
                node_id=node_id,
                balance=balance,
                total_earned=total_earned,
                total_spent=total_spent,
                last_updated=last_updated
            )
        else:
            # Return default values if node doesn't exist
            return CreditBalance(
                node_id=node_id,
                balance=0.0,
                total_earned=0.0,
                total_spent=0.0,
                last_updated=int(time.time())
            )
    
    def get_transaction_history(self, node_id: str, limit: int = 50) -> List[CreditTransaction]:
        """Get transaction history for a node"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT transaction_id, amount, transaction_type, reason, timestamp, service_type, metadata
            FROM transactions 
            WHERE node_id = ?
            ORDER BY timestamp DESC
            LIMIT ?
        ''', (node_id, limit))
        
        rows = cursor.fetchall()
        conn.close()
        
        transactions = []
        for row in rows:
            transaction_id, amount, trans_type, reason, timestamp, service_type, metadata_str = row
            metadata = json.loads(metadata_str) if metadata_str else None
            
            transaction = CreditTransaction(
                transaction_id=transaction_id,
                node_id=node_id,
                amount=amount,
                transaction_type=TransactionType(trans_type),
                reason=CreditReason(reason),
                timestamp=timestamp,
                service_type=service_type,
                metadata=metadata
            )
            transactions.append(transaction)
        
        return transactions
    
    def transfer_credits(self, from_node_id: str, to_node_id: str, amount: float, 
                         reason: CreditReason = CreditReason.TRANSFERRED) -> bool:
        """Transfer credits from one node to another"""
        if amount <= 0:
            return False
            
        # First spend from sender
        if not self.spend_credits(from_node_id, amount, reason):
            return False
            
        # Then add to receiver
        if not self.add_credits(to_node_id, amount, reason):
            # Rollback: if adding to receiver fails, refund sender
            self.add_credits(from_node_id, amount, CreditReason.REFUND, 
                           metadata={"original_transaction": "transfer_failed"})
            return False
            
        return True
    
    def get_top_nodes_by_balance(self, limit: int = 10) -> List[Dict[str, Any]]:
        """Get top nodes by credit balance"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT node_id, balance, total_earned, total_spent
            FROM balances
            ORDER BY balance DESC
            LIMIT ?
        ''', (limit,))
        
        rows = cursor.fetchall()
        conn.close()
        
        top_nodes = []
        for row in rows:
            node_id, balance, total_earned, total_spent = row
            top_nodes.append({
                "node_id": node_id,
                "balance": balance,
                "total_earned": total_earned,
                "total_spent": total_spent
            })
        
        return top_nodes


# Global instance of the credits system
credits_system = CreditsSystem()