lunalib 1.7.3__py3-none-any.whl → 1.7.9__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/mempool.py +1 -0
- lunalib/core/wallet.py +119 -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.7.9.dist-info}/METADATA +1 -1
- lunalib-1.7.9.dist-info/RECORD +77 -0
- lunalib-1.7.3.dist-info/RECORD +0 -34
- {lunalib-1.7.3.dist-info → lunalib-1.7.9.dist-info}/WHEEL +0 -0
- {lunalib-1.7.3.dist-info → lunalib-1.7.9.dist-info}/top_level.txt +0 -0
lunalib/.gitignore
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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,30 @@ 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
|
+
# Only count as confirmed if confirmations >= 6
|
|
497
|
+
if confirmations is not None and confirmations >= 6:
|
|
498
|
+
total_balance += amount
|
|
446
499
|
elif direction == "incoming":
|
|
447
500
|
total_balance += amount
|
|
448
501
|
elif direction == "outgoing":
|
|
@@ -453,9 +506,9 @@ class LunaWallet:
|
|
|
453
506
|
return max(0.0, total_balance)
|
|
454
507
|
|
|
455
508
|
def _compute_pending_totals(
|
|
456
|
-
self, pending_txs: List[Dict], address: str
|
|
509
|
+
self, pending_txs: List[Dict], address: str, current_height: int = None
|
|
457
510
|
) -> Tuple[float, float]:
|
|
458
|
-
"""Return (pending_outgoing, pending_incoming) for an address."""
|
|
511
|
+
"""Return (pending_outgoing, pending_incoming) for an address, including pending rewards (<6 confs)."""
|
|
459
512
|
pending_out = 0.0
|
|
460
513
|
pending_in = 0.0
|
|
461
514
|
|
|
@@ -463,14 +516,26 @@ class LunaWallet:
|
|
|
463
516
|
for tx in pending_txs:
|
|
464
517
|
from_norm = self._normalize_address(tx.get("from") or tx.get("sender"))
|
|
465
518
|
to_norm = self._normalize_address(tx.get("to") or tx.get("receiver"))
|
|
519
|
+
tx_type = (tx.get("type") or "").lower()
|
|
520
|
+
amount = float(tx.get("amount", 0) or 0)
|
|
521
|
+
block_height = tx.get("block_height")
|
|
522
|
+
confirmations = None
|
|
523
|
+
if block_height is not None and current_height is not None:
|
|
524
|
+
try:
|
|
525
|
+
confirmations = int(current_height) - int(block_height) + 1
|
|
526
|
+
except Exception:
|
|
527
|
+
confirmations = None
|
|
466
528
|
|
|
467
529
|
if from_norm == target_norm:
|
|
468
|
-
amount = float(tx.get("amount", 0) or 0)
|
|
469
530
|
fee = float(tx.get("fee", 0) or tx.get("gas", 0) or 0)
|
|
470
531
|
pending_out += amount + fee
|
|
471
532
|
if to_norm == target_norm:
|
|
472
|
-
|
|
473
|
-
|
|
533
|
+
# For rewards, only count as pending if confirmations < 6
|
|
534
|
+
if tx_type == "reward" or tx.get("from") == "network":
|
|
535
|
+
if confirmations is not None and confirmations < 6:
|
|
536
|
+
pending_in += amount
|
|
537
|
+
else:
|
|
538
|
+
pending_in += amount
|
|
474
539
|
|
|
475
540
|
return pending_out, pending_in
|
|
476
541
|
|
|
@@ -503,7 +568,7 @@ class LunaWallet:
|
|
|
503
568
|
return updated
|
|
504
569
|
|
|
505
570
|
def refresh_all_wallet_balances(self) -> Dict[str, Dict[str, float]]:
|
|
506
|
-
"""Scan blockchain
|
|
571
|
+
"""Scan blockchain and mempool for all wallets, update balances and pending info."""
|
|
507
572
|
addresses = list(self.wallets.keys())
|
|
508
573
|
if not addresses:
|
|
509
574
|
return {}
|
|
@@ -516,15 +581,44 @@ class LunaWallet:
|
|
|
516
581
|
mempool = MempoolManager()
|
|
517
582
|
|
|
518
583
|
confirmed_map = blockchain.scan_transactions_for_addresses(addresses)
|
|
519
|
-
pending_map = mempool.get_pending_transactions_for_addresses(
|
|
520
|
-
addresses, fetch_remote=True
|
|
521
|
-
)
|
|
584
|
+
pending_map = mempool.get_pending_transactions_for_addresses(addresses, fetch_remote=True)
|
|
522
585
|
|
|
523
586
|
# Reset caches with the latest snapshots
|
|
524
587
|
self._confirmed_tx_cache = confirmed_map
|
|
525
588
|
self._pending_tx_cache = pending_map
|
|
526
589
|
|
|
527
|
-
|
|
590
|
+
# Recompute balances and pending for all wallets
|
|
591
|
+
updated: Dict[str, Dict[str, float]] = {}
|
|
592
|
+
for addr in addresses:
|
|
593
|
+
confirmed = confirmed_map.get(addr, [])
|
|
594
|
+
pending = pending_map.get(addr, [])
|
|
595
|
+
# 最新のブロック高を取得
|
|
596
|
+
try:
|
|
597
|
+
current_height = blockchain.get_blockchain_height()
|
|
598
|
+
except Exception:
|
|
599
|
+
current_height = None
|
|
600
|
+
total_balance = max(0.0, self._compute_confirmed_balance(confirmed))
|
|
601
|
+
pending_out, pending_in = self._compute_pending_totals(pending, addr, current_height)
|
|
602
|
+
available_balance = max(0.0, total_balance - pending_out)
|
|
603
|
+
|
|
604
|
+
wallet_data = self.wallets.get(addr, {})
|
|
605
|
+
wallet_data["balance"] = total_balance
|
|
606
|
+
wallet_data["available_balance"] = available_balance
|
|
607
|
+
wallet_data["pending_out"] = pending_out
|
|
608
|
+
wallet_data["pending_in"] = pending_in
|
|
609
|
+
|
|
610
|
+
if addr == self.current_wallet_address:
|
|
611
|
+
self.balance = total_balance
|
|
612
|
+
self.available_balance = available_balance
|
|
613
|
+
|
|
614
|
+
updated[addr] = {
|
|
615
|
+
"balance": total_balance,
|
|
616
|
+
"available_balance": available_balance,
|
|
617
|
+
"pending_out": pending_out,
|
|
618
|
+
"pending_in": pending_in,
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return updated
|
|
528
622
|
|
|
529
623
|
except Exception as e:
|
|
530
624
|
print(f"DEBUG: Error refreshing all wallet balances: {e}")
|
|
@@ -543,11 +637,12 @@ class LunaWallet:
|
|
|
543
637
|
confirmed = self._confirmed_tx_cache.get(addr, [])
|
|
544
638
|
pending = self._pending_tx_cache.get(addr, [])
|
|
545
639
|
|
|
640
|
+
# pending_in/pending_outはwallet_dataに格納済み
|
|
546
641
|
overview[addr] = {
|
|
547
642
|
"balance": wallet_data.get("balance", 0.0),
|
|
548
643
|
"available_balance": wallet_data.get("available_balance", 0.0),
|
|
549
|
-
"pending_out":
|
|
550
|
-
"pending_in":
|
|
644
|
+
"pending_out": wallet_data.get("pending_out", 0.0),
|
|
645
|
+
"pending_in": wallet_data.get("pending_in", 0.0),
|
|
551
646
|
}
|
|
552
647
|
|
|
553
648
|
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)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from lunalib.core.crypto import KeyManager
|
|
3
|
+
|
|
4
|
+
class TestCrypto:
|
|
5
|
+
def test_key_generation(self):
|
|
6
|
+
"""Test cryptographic key generation"""
|
|
7
|
+
key_manager = KeyManager()
|
|
8
|
+
|
|
9
|
+
private_key = key_manager.generate_private_key()
|
|
10
|
+
public_key = key_manager.derive_public_key(private_key)
|
|
11
|
+
address = key_manager.derive_address(public_key)
|
|
12
|
+
|
|
13
|
+
assert len(private_key) == 64 # 32 bytes in hex
|
|
14
|
+
assert len(public_key) == 64 # 32 bytes in hex
|
|
15
|
+
assert address.startswith("LUN_")
|
|
16
|
+
|
|
17
|
+
def test_data_signing(self):
|
|
18
|
+
"""Test data signing and verification"""
|
|
19
|
+
key_manager = KeyManager()
|
|
20
|
+
|
|
21
|
+
private_key = key_manager.generate_private_key()
|
|
22
|
+
public_key = key_manager.derive_public_key(private_key)
|
|
23
|
+
|
|
24
|
+
test_data = "Hello, Luna Library!"
|
|
25
|
+
signature = key_manager.sign_data(test_data, private_key)
|
|
26
|
+
|
|
27
|
+
# Basic signature format check
|
|
28
|
+
assert len(signature) == 64
|
|
29
|
+
assert all(c in "0123456789abcdef" for c in signature.lower())
|
|
30
|
+
|
|
31
|
+
def test_address_generation_uniqueness(self):
|
|
32
|
+
"""Test that addresses are unique"""
|
|
33
|
+
key_manager = KeyManager()
|
|
34
|
+
|
|
35
|
+
addresses = set()
|
|
36
|
+
for _ in range(10):
|
|
37
|
+
private_key = key_manager.generate_private_key()
|
|
38
|
+
public_key = key_manager.derive_public_key(private_key)
|
|
39
|
+
address = key_manager.derive_address(public_key)
|
|
40
|
+
addresses.add(address)
|
|
41
|
+
|
|
42
|
+
assert len(addresses) == 10 # All addresses should be unique
|