lunalib 1.7.3__py3-none-any.whl → 1.8.0__py3-none-any.whl
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.
- lunalib/.gitignore +3 -0
- lunalib/__pycache__/__init__.cpython-310.pyc +0 -0
- lunalib/core/__pycache__/__init__.cpython-310.pyc +0 -0
- lunalib/core/__pycache__/blockchain.cpython-310.pyc +0 -0
- lunalib/core/__pycache__/crypto.cpython-310.pyc +0 -0
- lunalib/core/__pycache__/mempool.cpython-310.pyc +0 -0
- lunalib/core/__pycache__/wallet.cpython-310.pyc +0 -0
- lunalib/core/blockchain.py +13 -16
- lunalib/core/mempool.py +1 -0
- lunalib/core/wallet.py +121 -24
- lunalib/core/wallet_db.py +48 -47
- lunalib/gtx/__pycache__/__init__.cpython-310.pyc +0 -0
- lunalib/gtx/__pycache__/bill_registry.cpython-310.pyc +0 -0
- lunalib/gtx/__pycache__/digital_bill.cpython-310.pyc +0 -0
- lunalib/gtx/__pycache__/genesis.cpython-310.pyc +0 -0
- lunalib/mining/__pycache__/__init__.cpython-310.pyc +0 -0
- lunalib/mining/__pycache__/cuda_manager.cpython-310.pyc +0 -0
- lunalib/mining/__pycache__/difficulty.cpython-310.pyc +0 -0
- lunalib/mining/__pycache__/miner.cpython-310.pyc +0 -0
- lunalib/storage/__pycache__/__init__.cpython-310.pyc +0 -0
- lunalib/storage/__pycache__/cache.cpython-310.pyc +0 -0
- lunalib/storage/__pycache__/database.cpython-310.pyc +0 -0
- lunalib/storage/__pycache__/encryption.cpython-310.pyc +0 -0
- lunalib/tests/__pycache__/conftest.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_blockchain.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_crypto.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_gtx.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_mining.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_storage.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_transactions.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/__pycache__/test_wallet.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/conftest.py +41 -0
- lunalib/tests/init.py +0 -0
- lunalib/tests/integration/__pycache__/test_integration.cpython-310-pytest-9.0.1.pyc +0 -0
- lunalib/tests/integration/test_integration.py +62 -0
- lunalib/tests/test_blockchain.py +34 -0
- lunalib/tests/test_crypto.py +42 -0
- lunalib/tests/test_gtx.py +135 -0
- lunalib/tests/test_mining.py +244 -0
- lunalib/tests/test_security_suite.py +832 -0
- lunalib/tests/test_storage.py +84 -0
- lunalib/tests/test_transactions.py +103 -0
- lunalib/tests/test_wallet.py +91 -0
- lunalib/transactions/__pycache__/__init__.cpython-310.pyc +0 -0
- lunalib/transactions/__pycache__/security.cpython-310.pyc +0 -0
- lunalib/transactions/__pycache__/transactions.cpython-310.pyc +0 -0
- lunalib/transactions/__pycache__/validator.cpython-310.pyc +0 -0
- {lunalib-1.7.3.dist-info → lunalib-1.8.0.dist-info}/METADATA +1 -1
- lunalib-1.8.0.dist-info/RECORD +77 -0
- lunalib-1.7.3.dist-info/RECORD +0 -34
- {lunalib-1.7.3.dist-info → lunalib-1.8.0.dist-info}/WHEEL +0 -0
- {lunalib-1.7.3.dist-info → lunalib-1.8.0.dist-info}/top_level.txt +0 -0
lunalib/.gitignore
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
lunalib/core/blockchain.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from tqdm import tqdm
|
|
1
2
|
# blockchain.py - Updated version
|
|
2
3
|
|
|
3
4
|
from ..storage.cache import BlockchainCache
|
|
@@ -267,12 +268,12 @@ class BlockchainManager:
|
|
|
267
268
|
def get_blocks_range(self, start_height: int, end_height: int) -> List[Dict]:
|
|
268
269
|
"""Get range of blocks"""
|
|
269
270
|
blocks = []
|
|
270
|
-
|
|
271
|
+
|
|
271
272
|
# Check cache first
|
|
272
273
|
cached_blocks = self.cache.get_block_range(start_height, end_height)
|
|
273
274
|
if len(cached_blocks) == (end_height - start_height + 1):
|
|
274
275
|
return cached_blocks
|
|
275
|
-
|
|
276
|
+
|
|
276
277
|
try:
|
|
277
278
|
response = requests.get(
|
|
278
279
|
f'{self.endpoint_url}/blockchain/range?start={start_height}&end={end_height}',
|
|
@@ -286,15 +287,15 @@ class BlockchainManager:
|
|
|
286
287
|
self.cache.save_block(height, block.get('hash', ''), block)
|
|
287
288
|
else:
|
|
288
289
|
# Fallback: get blocks individually
|
|
289
|
-
for height in range(start_height, end_height + 1):
|
|
290
|
+
for height in tqdm(range(start_height, end_height + 1), desc="Get Blocks", leave=False):
|
|
290
291
|
block = self.get_block(height)
|
|
291
292
|
if block:
|
|
292
293
|
blocks.append(block)
|
|
293
294
|
time.sleep(0.01) # Be nice to the API
|
|
294
|
-
|
|
295
|
+
|
|
295
296
|
except Exception as e:
|
|
296
297
|
print(f"Get blocks range error: {e}")
|
|
297
|
-
|
|
298
|
+
|
|
298
299
|
return blocks
|
|
299
300
|
|
|
300
301
|
def get_mempool(self) -> List[Dict]:
|
|
@@ -322,22 +323,19 @@ class BlockchainManager:
|
|
|
322
323
|
"""Scan blockchain for transactions involving an address"""
|
|
323
324
|
if end_height is None:
|
|
324
325
|
end_height = self.get_blockchain_height()
|
|
325
|
-
|
|
326
|
+
|
|
326
327
|
print(f"[SCAN] Scanning transactions for {address} from block {start_height} to {end_height}")
|
|
327
|
-
|
|
328
|
+
|
|
328
329
|
transactions = []
|
|
329
|
-
|
|
330
|
-
# Scan in batches for efficiency
|
|
331
330
|
batch_size = 100
|
|
332
|
-
|
|
331
|
+
total_batches = ((end_height - start_height) // batch_size) + 1
|
|
332
|
+
for batch_start in tqdm(range(start_height, end_height + 1, batch_size), desc=f"Scan {address}", total=total_batches):
|
|
333
333
|
batch_end = min(batch_start + batch_size - 1, end_height)
|
|
334
|
-
print(f"[SCAN] Processing batch {batch_start}-{batch_end}...")
|
|
335
334
|
blocks = self.get_blocks_range(batch_start, batch_end)
|
|
336
|
-
|
|
337
335
|
for block in blocks:
|
|
338
336
|
block_transactions = self._find_address_transactions(block, address)
|
|
339
337
|
transactions.extend(block_transactions)
|
|
340
|
-
|
|
338
|
+
|
|
341
339
|
print(f"[SCAN] Found {len(transactions)} total transactions for {address}")
|
|
342
340
|
return transactions
|
|
343
341
|
|
|
@@ -389,11 +387,10 @@ class BlockchainManager:
|
|
|
389
387
|
results: Dict[str, List[Dict]] = {addr: [] for addr in addresses}
|
|
390
388
|
|
|
391
389
|
batch_size = 100
|
|
392
|
-
|
|
390
|
+
total_batches = ((end_height - start_height) // batch_size) + 1
|
|
391
|
+
for batch_start in tqdm(range(start_height, end_height + 1, batch_size), desc="Multi-Scan", total=total_batches):
|
|
393
392
|
batch_end = min(batch_start + batch_size - 1, end_height)
|
|
394
|
-
print(f"[MULTI-SCAN] Processing batch {batch_start}-{batch_end}...")
|
|
395
393
|
blocks = self.get_blocks_range(batch_start, batch_end)
|
|
396
|
-
|
|
397
394
|
for block in blocks:
|
|
398
395
|
collected = self._collect_transactions_for_addresses(block, normalized_map)
|
|
399
396
|
for original_addr, txs in collected.items():
|
lunalib/core/mempool.py
CHANGED
|
@@ -211,6 +211,7 @@ class MempoolManager:
|
|
|
211
211
|
to_norm = self._normalize_address(tx.get('to') or tx.get('receiver'))
|
|
212
212
|
if target_norm and (from_norm == target_norm or to_norm == target_norm):
|
|
213
213
|
transactions.append(tx)
|
|
214
|
+
print(f"[MEMPOOL] get_pending_transactions for {address}: {len(transactions)} txs returned")
|
|
214
215
|
return transactions
|
|
215
216
|
|
|
216
217
|
def get_pending_transactions_for_addresses(self, addresses: List[str], fetch_remote: bool = True) -> Dict[str, List[Dict]]:
|
lunalib/core/wallet.py
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
def update_all_balances(self):
|
|
2
|
+
"""Unified balance update: recalculates and persists balances."""
|
|
3
|
+
# 残高計算前にキャッシュを最新化
|
|
4
|
+
self._refresh_tx_caches()
|
|
5
|
+
available = self.calculate_available_balance()
|
|
6
|
+
# Optionally, persist to DB if needed
|
|
7
|
+
if self.current_wallet_address in self.wallets:
|
|
8
|
+
w = self.wallets[self.current_wallet_address]
|
|
9
|
+
if hasattr(self, 'db') and self.db:
|
|
10
|
+
self.db.save_wallet(
|
|
11
|
+
self.current_wallet_address,
|
|
12
|
+
w.get("label", ""),
|
|
13
|
+
w.get("public_key", ""),
|
|
14
|
+
w.get("encrypted_private_key", b""),
|
|
15
|
+
w.get("is_locked", True),
|
|
16
|
+
w.get("created", 0),
|
|
17
|
+
self.balance,
|
|
18
|
+
available
|
|
19
|
+
)
|
|
20
|
+
print(f"[WALLET] update_all_balances: balance={self.balance}, available={self.available_balance}")
|
|
21
|
+
|
|
22
|
+
def _refresh_tx_caches(self):
|
|
23
|
+
"""Force refresh of confirmed and pending tx caches for the current wallet."""
|
|
24
|
+
if not self.current_wallet_address:
|
|
25
|
+
return
|
|
26
|
+
from lunalib.core.blockchain import BlockchainManager
|
|
27
|
+
from lunalib.core.mempool import MempoolManager
|
|
28
|
+
blockchain = BlockchainManager()
|
|
29
|
+
mempool = MempoolManager()
|
|
30
|
+
confirmed = blockchain.scan_transactions_for_address(self.current_wallet_address)
|
|
31
|
+
pending = mempool.get_pending_transactions(self.current_wallet_address, fetch_remote=True)
|
|
32
|
+
self._confirmed_tx_cache[self.current_wallet_address] = confirmed
|
|
33
|
+
self._pending_tx_cache[self.current_wallet_address] = pending
|
|
34
|
+
|
|
1
35
|
import time
|
|
2
36
|
import hashlib
|
|
3
37
|
import json
|
|
@@ -16,6 +50,7 @@ from .wallet_db import WalletDB
|
|
|
16
50
|
class LunaWallet:
|
|
17
51
|
def __init__(self, data_dir=None):
|
|
18
52
|
self.data_dir = data_dir or os.path.expanduser("~/.lunawallet")
|
|
53
|
+
print(f"[LunaWallet] data_dir: {self.data_dir}")
|
|
19
54
|
self.db = WalletDB(self.data_dir)
|
|
20
55
|
self.wallets = {} # address -> wallet dict (in-memory cache)
|
|
21
56
|
self.current_wallet_address = None
|
|
@@ -399,16 +434,19 @@ class LunaWallet:
|
|
|
399
434
|
def calculate_available_balance(self) -> float:
|
|
400
435
|
"""Calculate available balance (total balance minus pending outgoing transactions)"""
|
|
401
436
|
try:
|
|
402
|
-
|
|
403
|
-
|
|
437
|
+
from lunalib.core.blockchain import BlockchainManager
|
|
438
|
+
blockchain = BlockchainManager()
|
|
439
|
+
current_height = blockchain.get_blockchain_height()
|
|
404
440
|
|
|
405
|
-
# Get
|
|
406
|
-
|
|
441
|
+
# Get confirmed transactions for this wallet
|
|
442
|
+
confirmed = self._confirmed_tx_cache.get(self.address, [])
|
|
443
|
+
total_balance = self._compute_confirmed_balance(confirmed, current_height)
|
|
407
444
|
|
|
408
|
-
#
|
|
409
|
-
|
|
445
|
+
# Get pending transactions for this wallet
|
|
446
|
+
pending = self._pending_tx_cache.get(self.address, [])
|
|
447
|
+
pending_out, pending_in = self._compute_pending_totals(pending, self.address, current_height)
|
|
410
448
|
|
|
411
|
-
available_balance = max(0.0, total_balance -
|
|
449
|
+
available_balance = max(0.0, total_balance - pending_out)
|
|
412
450
|
|
|
413
451
|
# Update both current wallet and wallets collection
|
|
414
452
|
self.available_balance = available_balance
|
|
@@ -421,11 +459,11 @@ class LunaWallet:
|
|
|
421
459
|
self.wallets[self.current_wallet_address]["balance"] = total_balance
|
|
422
460
|
|
|
423
461
|
print(
|
|
424
|
-
f"DEBUG: Balance calculated - Total: {total_balance}, Pending Out: {
|
|
462
|
+
f"DEBUG: Balance calculated - Total: {total_balance}, Pending Out: {pending_out}, Pending In: {pending_in}, Available: {available_balance}"
|
|
425
463
|
)
|
|
426
464
|
|
|
427
|
-
if
|
|
428
|
-
print(f"DEBUG: Also {
|
|
465
|
+
if pending_in > 0:
|
|
466
|
+
print(f"DEBUG: Also {pending_in} LUN incoming (pending)")
|
|
429
467
|
|
|
430
468
|
return available_balance
|
|
431
469
|
|
|
@@ -434,15 +472,31 @@ class LunaWallet:
|
|
|
434
472
|
return self.balance # Fallback to total balance
|
|
435
473
|
|
|
436
474
|
def _compute_confirmed_balance(self, transactions: List[Dict]) -> float:
|
|
437
|
-
"""Compute confirmed balance from a list of transactions."""
|
|
475
|
+
"""Compute confirmed balance from a list of transactions, using confirmation count for rewards."""
|
|
438
476
|
total_balance = 0.0
|
|
477
|
+
current_height = None
|
|
478
|
+
import inspect
|
|
479
|
+
# Try to get current_height from caller if passed
|
|
480
|
+
frame = inspect.currentframe().f_back
|
|
481
|
+
if frame and 'current_height' in frame.f_locals:
|
|
482
|
+
current_height = frame.f_locals['current_height']
|
|
439
483
|
for tx in transactions:
|
|
440
484
|
tx_type = (tx.get("type") or "").lower()
|
|
441
485
|
direction = tx.get("direction", "")
|
|
442
486
|
amount = float(tx.get("amount", 0) or 0)
|
|
487
|
+
block_height = tx.get("block_height")
|
|
488
|
+
confirmations = None
|
|
489
|
+
if block_height is not None and current_height is not None:
|
|
490
|
+
try:
|
|
491
|
+
confirmations = int(current_height) - int(block_height) + 1
|
|
492
|
+
except Exception:
|
|
493
|
+
confirmations = None
|
|
443
494
|
|
|
444
495
|
if tx_type == "reward" or tx.get("from") == "network":
|
|
445
|
-
|
|
496
|
+
print(f"[DEBUG] reward tx: {tx}, confirmations={confirmations}")
|
|
497
|
+
# block_heightやcurrent_heightがNoneならとりあえず加算
|
|
498
|
+
if confirmations is None or confirmations >= 6:
|
|
499
|
+
total_balance += amount
|
|
446
500
|
elif direction == "incoming":
|
|
447
501
|
total_balance += amount
|
|
448
502
|
elif direction == "outgoing":
|
|
@@ -450,12 +504,13 @@ class LunaWallet:
|
|
|
450
504
|
total_balance -= amount
|
|
451
505
|
total_balance -= fee
|
|
452
506
|
|
|
507
|
+
print(f"[DEBUG] computed confirmed balance: {total_balance}")
|
|
453
508
|
return max(0.0, total_balance)
|
|
454
509
|
|
|
455
510
|
def _compute_pending_totals(
|
|
456
|
-
self, pending_txs: List[Dict], address: str
|
|
511
|
+
self, pending_txs: List[Dict], address: str, current_height: int = None
|
|
457
512
|
) -> Tuple[float, float]:
|
|
458
|
-
"""Return (pending_outgoing, pending_incoming) for an address."""
|
|
513
|
+
"""Return (pending_outgoing, pending_incoming) for an address, including pending rewards (<6 confs)."""
|
|
459
514
|
pending_out = 0.0
|
|
460
515
|
pending_in = 0.0
|
|
461
516
|
|
|
@@ -463,14 +518,26 @@ class LunaWallet:
|
|
|
463
518
|
for tx in pending_txs:
|
|
464
519
|
from_norm = self._normalize_address(tx.get("from") or tx.get("sender"))
|
|
465
520
|
to_norm = self._normalize_address(tx.get("to") or tx.get("receiver"))
|
|
521
|
+
tx_type = (tx.get("type") or "").lower()
|
|
522
|
+
amount = float(tx.get("amount", 0) or 0)
|
|
523
|
+
block_height = tx.get("block_height")
|
|
524
|
+
confirmations = None
|
|
525
|
+
if block_height is not None and current_height is not None:
|
|
526
|
+
try:
|
|
527
|
+
confirmations = int(current_height) - int(block_height) + 1
|
|
528
|
+
except Exception:
|
|
529
|
+
confirmations = None
|
|
466
530
|
|
|
467
531
|
if from_norm == target_norm:
|
|
468
|
-
amount = float(tx.get("amount", 0) or 0)
|
|
469
532
|
fee = float(tx.get("fee", 0) or tx.get("gas", 0) or 0)
|
|
470
533
|
pending_out += amount + fee
|
|
471
534
|
if to_norm == target_norm:
|
|
472
|
-
|
|
473
|
-
|
|
535
|
+
# For rewards, only count as pending if confirmations < 6
|
|
536
|
+
if tx_type == "reward" or tx.get("from") == "network":
|
|
537
|
+
if confirmations is not None and confirmations < 6:
|
|
538
|
+
pending_in += amount
|
|
539
|
+
else:
|
|
540
|
+
pending_in += amount
|
|
474
541
|
|
|
475
542
|
return pending_out, pending_in
|
|
476
543
|
|
|
@@ -503,7 +570,7 @@ class LunaWallet:
|
|
|
503
570
|
return updated
|
|
504
571
|
|
|
505
572
|
def refresh_all_wallet_balances(self) -> Dict[str, Dict[str, float]]:
|
|
506
|
-
"""Scan blockchain
|
|
573
|
+
"""Scan blockchain and mempool for all wallets, update balances and pending info."""
|
|
507
574
|
addresses = list(self.wallets.keys())
|
|
508
575
|
if not addresses:
|
|
509
576
|
return {}
|
|
@@ -516,15 +583,44 @@ class LunaWallet:
|
|
|
516
583
|
mempool = MempoolManager()
|
|
517
584
|
|
|
518
585
|
confirmed_map = blockchain.scan_transactions_for_addresses(addresses)
|
|
519
|
-
pending_map = mempool.get_pending_transactions_for_addresses(
|
|
520
|
-
addresses, fetch_remote=True
|
|
521
|
-
)
|
|
586
|
+
pending_map = mempool.get_pending_transactions_for_addresses(addresses, fetch_remote=True)
|
|
522
587
|
|
|
523
588
|
# Reset caches with the latest snapshots
|
|
524
589
|
self._confirmed_tx_cache = confirmed_map
|
|
525
590
|
self._pending_tx_cache = pending_map
|
|
526
591
|
|
|
527
|
-
|
|
592
|
+
# Recompute balances and pending for all wallets
|
|
593
|
+
updated: Dict[str, Dict[str, float]] = {}
|
|
594
|
+
for addr in addresses:
|
|
595
|
+
confirmed = confirmed_map.get(addr, [])
|
|
596
|
+
pending = pending_map.get(addr, [])
|
|
597
|
+
# 最新のブロック高を取得
|
|
598
|
+
try:
|
|
599
|
+
current_height = blockchain.get_blockchain_height()
|
|
600
|
+
except Exception:
|
|
601
|
+
current_height = None
|
|
602
|
+
total_balance = max(0.0, self._compute_confirmed_balance(confirmed))
|
|
603
|
+
pending_out, pending_in = self._compute_pending_totals(pending, addr, current_height)
|
|
604
|
+
available_balance = max(0.0, total_balance - pending_out)
|
|
605
|
+
|
|
606
|
+
wallet_data = self.wallets.get(addr, {})
|
|
607
|
+
wallet_data["balance"] = total_balance
|
|
608
|
+
wallet_data["available_balance"] = available_balance
|
|
609
|
+
wallet_data["pending_out"] = pending_out
|
|
610
|
+
wallet_data["pending_in"] = pending_in
|
|
611
|
+
|
|
612
|
+
if addr == self.current_wallet_address:
|
|
613
|
+
self.balance = total_balance
|
|
614
|
+
self.available_balance = available_balance
|
|
615
|
+
|
|
616
|
+
updated[addr] = {
|
|
617
|
+
"balance": total_balance,
|
|
618
|
+
"available_balance": available_balance,
|
|
619
|
+
"pending_out": pending_out,
|
|
620
|
+
"pending_in": pending_in,
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return updated
|
|
528
624
|
|
|
529
625
|
except Exception as e:
|
|
530
626
|
print(f"DEBUG: Error refreshing all wallet balances: {e}")
|
|
@@ -543,11 +639,12 @@ class LunaWallet:
|
|
|
543
639
|
confirmed = self._confirmed_tx_cache.get(addr, [])
|
|
544
640
|
pending = self._pending_tx_cache.get(addr, [])
|
|
545
641
|
|
|
642
|
+
# pending_in/pending_outはwallet_dataに格納済み
|
|
546
643
|
overview[addr] = {
|
|
547
644
|
"balance": wallet_data.get("balance", 0.0),
|
|
548
645
|
"available_balance": wallet_data.get("available_balance", 0.0),
|
|
549
|
-
"pending_out":
|
|
550
|
-
"pending_in":
|
|
646
|
+
"pending_out": wallet_data.get("pending_out", 0.0),
|
|
647
|
+
"pending_in": wallet_data.get("pending_in", 0.0),
|
|
551
648
|
}
|
|
552
649
|
|
|
553
650
|
if include_transactions:
|
lunalib/core/wallet_db.py
CHANGED
|
@@ -1,64 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import hashlib
|
|
5
|
-
import base64
|
|
6
|
-
import json
|
|
7
|
-
import time
|
|
1
|
+
|
|
2
|
+
from lunalib.storage.database import WalletDatabase
|
|
3
|
+
|
|
8
4
|
|
|
9
5
|
class WalletDB:
|
|
10
6
|
def __init__(self, data_dir=None):
|
|
7
|
+
# data_dir is kept for compatibility, but db_path is passed to WalletDatabase
|
|
8
|
+
import os
|
|
11
9
|
self.data_dir = data_dir or os.path.expanduser("~/.lunawallet")
|
|
12
10
|
os.makedirs(self.data_dir, exist_ok=True)
|
|
13
11
|
self.db_path = os.path.join(self.data_dir, "wallets.db")
|
|
14
|
-
|
|
15
|
-
self.
|
|
16
|
-
|
|
17
|
-
def _init_db(self):
|
|
18
|
-
c = self.conn.cursor()
|
|
19
|
-
c.execute('''CREATE TABLE IF NOT EXISTS wallets (
|
|
20
|
-
address TEXT PRIMARY KEY,
|
|
21
|
-
label TEXT,
|
|
22
|
-
public_key TEXT,
|
|
23
|
-
encrypted_private_key BLOB,
|
|
24
|
-
is_locked INTEGER,
|
|
25
|
-
created REAL,
|
|
26
|
-
balance REAL,
|
|
27
|
-
available_balance REAL
|
|
28
|
-
)''')
|
|
29
|
-
self.conn.commit()
|
|
12
|
+
print(f"[WalletDB] data_dir: {self.data_dir}")
|
|
13
|
+
print(f"[WalletDB] db_path: {self.db_path}")
|
|
14
|
+
self.db = WalletDatabase(db_path=self.db_path)
|
|
30
15
|
|
|
31
16
|
def save_wallet(self, address, label, public_key, encrypted_private_key, is_locked, created, balance, available_balance):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
17
|
+
# Compose wallet_data dict for WalletDatabase
|
|
18
|
+
wallet_data = {
|
|
19
|
+
'address': address,
|
|
20
|
+
'label': label,
|
|
21
|
+
'public_key': public_key,
|
|
22
|
+
'encrypted_private_key': encrypted_private_key,
|
|
23
|
+
'balance': balance,
|
|
24
|
+
'created': created,
|
|
25
|
+
'metadata': {'is_locked': bool(is_locked), 'available_balance': available_balance}
|
|
26
|
+
}
|
|
27
|
+
return self.db.save_wallet(wallet_data)
|
|
37
28
|
|
|
38
29
|
def load_wallet(self, address):
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'public_key': row[2],
|
|
47
|
-
'encrypted_private_key': row[3],
|
|
48
|
-
'is_locked': bool(row[4]),
|
|
49
|
-
'created': row[5],
|
|
50
|
-
'balance': row[6],
|
|
51
|
-
'available_balance': row[7],
|
|
52
|
-
}
|
|
53
|
-
return None
|
|
30
|
+
w = self.db.load_wallet(address)
|
|
31
|
+
if w:
|
|
32
|
+
# For compatibility, flatten metadata fields
|
|
33
|
+
meta = w.get('metadata', {})
|
|
34
|
+
w['is_locked'] = meta.get('is_locked', False)
|
|
35
|
+
w['available_balance'] = meta.get('available_balance', 0.0)
|
|
36
|
+
return w
|
|
54
37
|
|
|
55
38
|
def list_wallets(self):
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
# Return list of tuples for compatibility: (address, label, is_locked, balance, available_balance)
|
|
40
|
+
import sqlite3
|
|
41
|
+
conn = sqlite3.connect(self.db_path)
|
|
42
|
+
cursor = conn.cursor()
|
|
43
|
+
cursor.execute('SELECT address, label, metadata, balance FROM wallets')
|
|
44
|
+
rows = cursor.fetchall()
|
|
45
|
+
conn.close()
|
|
46
|
+
result = []
|
|
47
|
+
for row in rows:
|
|
48
|
+
address, label, metadata_json, balance = row
|
|
49
|
+
meta = {}
|
|
50
|
+
try:
|
|
51
|
+
meta = json.loads(metadata_json) if metadata_json else {}
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
is_locked = meta.get('is_locked', False)
|
|
55
|
+
available_balance = meta.get('available_balance', 0.0)
|
|
56
|
+
result.append((address, label, is_locked, balance, available_balance))
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
59
|
|
|
60
60
|
def close(self):
|
|
61
|
-
|
|
61
|
+
# No persistent connection to close in WalletDatabase
|
|
62
|
+
pass
|
|
62
63
|
|
|
63
64
|
# Example usage:
|
|
64
65
|
if __name__ == "__main__":
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import tempfile
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from lunalib.core.wallet import LunaWallet
|
|
6
|
+
from lunalib.mining.miner import GenesisMiner
|
|
7
|
+
from lunalib.gtx.genesis import GTXGenesis
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def temp_dir():
|
|
11
|
+
"""Create temporary directory for test data"""
|
|
12
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
13
|
+
yield tmpdir
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def test_wallet(temp_dir):
|
|
17
|
+
"""Create a test wallet"""
|
|
18
|
+
wallet = LunaWallet(data_dir=temp_dir)
|
|
19
|
+
wallet_data = wallet.create_wallet("Test Wallet", "test_password")
|
|
20
|
+
return wallet, wallet_data
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def test_miner():
|
|
24
|
+
"""Create a test miner"""
|
|
25
|
+
return GenesisMiner()
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def test_gtx(temp_dir):
|
|
29
|
+
"""Create test GTX system"""
|
|
30
|
+
return GTXGenesis()
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def sample_transaction_data(test_wallet):
|
|
34
|
+
"""Create sample transaction data"""
|
|
35
|
+
wallet, wallet_data = test_wallet
|
|
36
|
+
return {
|
|
37
|
+
"from": wallet_data["address"],
|
|
38
|
+
"to": "LUN_test_recipient_12345",
|
|
39
|
+
"amount": 100.0,
|
|
40
|
+
"memo": "Test transaction"
|
|
41
|
+
}
|
lunalib/tests/init.py
ADDED
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import tempfile
|
|
3
|
+
from lunalib.core.wallet import LunaWallet
|
|
4
|
+
from lunalib.mining.miner import GenesisMiner
|
|
5
|
+
from lunalib.gtx.genesis import GTXGenesis
|
|
6
|
+
|
|
7
|
+
class TestIntegration:
|
|
8
|
+
def test_complete_workflow(self, temp_dir):
|
|
9
|
+
"""Test complete wallet -> mining -> GTX workflow"""
|
|
10
|
+
# Create wallet
|
|
11
|
+
wallet = LunaWallet(data_dir=temp_dir)
|
|
12
|
+
wallet_data = wallet.create_wallet("Integration Test", "test_pass")
|
|
13
|
+
|
|
14
|
+
# Initialize systems
|
|
15
|
+
miner = GenesisMiner()
|
|
16
|
+
gtx = GTXGenesis()
|
|
17
|
+
|
|
18
|
+
# Mock mining a bill
|
|
19
|
+
with pytest.MonkeyPatch().context() as m:
|
|
20
|
+
m.setattr(miner, '_perform_mining', lambda *args: {
|
|
21
|
+
"success": True,
|
|
22
|
+
"hash": "000integration",
|
|
23
|
+
"nonce": 999,
|
|
24
|
+
"mining_time": 1.0
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
# Mine a bill
|
|
28
|
+
bill = miner.mine_bill(1000, wallet_data['address'])
|
|
29
|
+
|
|
30
|
+
assert bill['success'] is True
|
|
31
|
+
assert bill['denomination'] == 1000
|
|
32
|
+
assert bill['luna_value'] == 1000
|
|
33
|
+
|
|
34
|
+
# Create proper bill data for verification
|
|
35
|
+
metadata_hash = "test_metadata_hash_12345"
|
|
36
|
+
signature = metadata_hash # This will pass METHOD 1 verification
|
|
37
|
+
|
|
38
|
+
# Register the bill in GTX system for verification
|
|
39
|
+
bill_info = {
|
|
40
|
+
'bill_serial': bill['bill_serial'],
|
|
41
|
+
'denomination': 1000,
|
|
42
|
+
'user_address': wallet_data['address'],
|
|
43
|
+
'hash': "000integration",
|
|
44
|
+
'mining_time': 1.0,
|
|
45
|
+
'difficulty': 4,
|
|
46
|
+
'luna_value': 1000,
|
|
47
|
+
'timestamp': bill['timestamp'],
|
|
48
|
+
'bill_data': {
|
|
49
|
+
'public_key': 'test_public_key',
|
|
50
|
+
'signature': signature, # Use the signature that matches metadata_hash
|
|
51
|
+
'metadata_hash': metadata_hash, # This must match signature for METHOD 1
|
|
52
|
+
'issued_to': wallet_data['address'],
|
|
53
|
+
'front_serial': bill['bill_serial'],
|
|
54
|
+
'type': 'GTX_Genesis',
|
|
55
|
+
'back_serial': '' # Add this to avoid errors
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
gtx.bill_registry.register_bill(bill_info)
|
|
59
|
+
|
|
60
|
+
# Verify the bill
|
|
61
|
+
verification = gtx.verify_bill(bill['bill_serial'])
|
|
62
|
+
assert verification['valid'] is True
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import Mock, patch
|
|
3
|
+
from lunalib.core.blockchain import BlockchainManager
|
|
4
|
+
|
|
5
|
+
class TestBlockchain:
|
|
6
|
+
@patch('lunalib.core.blockchain.requests.get')
|
|
7
|
+
def test_blockchain_height(self, mock_get):
|
|
8
|
+
"""Test blockchain height retrieval"""
|
|
9
|
+
mock_get.return_value.status_code = 200
|
|
10
|
+
mock_get.return_value.json.return_value = {'height': 1500}
|
|
11
|
+
|
|
12
|
+
blockchain = BlockchainManager()
|
|
13
|
+
height = blockchain.get_blockchain_height()
|
|
14
|
+
|
|
15
|
+
assert height == 1500
|
|
16
|
+
|
|
17
|
+
@patch('lunalib.core.blockchain.requests.get')
|
|
18
|
+
def test_network_connection(self, mock_get):
|
|
19
|
+
"""Test network connection checking"""
|
|
20
|
+
mock_get.return_value.status_code = 200
|
|
21
|
+
|
|
22
|
+
blockchain = BlockchainManager()
|
|
23
|
+
is_connected = blockchain.check_network_connection()
|
|
24
|
+
|
|
25
|
+
assert is_connected is True
|
|
26
|
+
|
|
27
|
+
def test_transaction_scanning(self):
|
|
28
|
+
"""Test transaction scanning functionality"""
|
|
29
|
+
blockchain = BlockchainManager()
|
|
30
|
+
|
|
31
|
+
# This would typically be mocked in a real test
|
|
32
|
+
# For now, test the method exists and returns expected type
|
|
33
|
+
transactions = blockchain.scan_transactions_for_address("LUN_test", 0, 10)
|
|
34
|
+
assert isinstance(transactions, list)
|