Fix auto-sweep database persistence bug
Auto-sweep functions were not persisting swept_tx_hash to database because the modified crypto_payment objects weren't being added to the session. - Add dbsession parameter to auto_sweep_payment functions - Add explicit dbsession.add(crypto_payment) calls when sweep succeeds - Update tests to verify swept_tx_hash is set and payment added to session - Fix existing tests to work with new function signatures This resolves the issue where swept payments had NULL swept_tx_hash despite successful fund transfers, which caused infinite processing loops.
This commit is contained in:
parent
b2a47f6789
commit
171fc916b7
2 changed files with 82 additions and 10 deletions
|
|
@ -47,21 +47,21 @@ def get_coin_config(coin_type):
|
|||
return COIN_CONFIGS.get(coin_type, COIN_CONFIGS["XMR"])
|
||||
|
||||
|
||||
def auto_sweep_payment(client, crypto_payment: CryptoPayment):
|
||||
def auto_sweep_payment(client, crypto_payment: CryptoPayment, dbsession=None):
|
||||
"""Auto-sweep funds from a confirmed payment to the shop's cold wallet."""
|
||||
logger.info(f"Starting auto-sweep check for payment {crypto_payment.id}")
|
||||
|
||||
# Dispatch to coin-specific sweep function
|
||||
if crypto_payment.coin_type == "XMR":
|
||||
return auto_sweep_payment_xmr(client, crypto_payment)
|
||||
return auto_sweep_payment_xmr(client, crypto_payment, dbsession)
|
||||
elif crypto_payment.coin_type == "DOGE":
|
||||
return auto_sweep_payment_doge(client, crypto_payment)
|
||||
return auto_sweep_payment_doge(client, crypto_payment, dbsession)
|
||||
else:
|
||||
logger.error(f"Unsupported coin type for sweep: {crypto_payment.coin_type}")
|
||||
return False
|
||||
|
||||
|
||||
def auto_sweep_payment_xmr(client, crypto_payment: CryptoPayment):
|
||||
def auto_sweep_payment_xmr(client, crypto_payment: CryptoPayment, dbsession=None):
|
||||
"""Auto-sweep XMR funds from a confirmed payment to the shop's cold wallet."""
|
||||
logger.info(f"Starting XMR auto-sweep check for payment {crypto_payment.id}")
|
||||
|
||||
|
|
@ -102,6 +102,8 @@ def auto_sweep_payment_xmr(client, crypto_payment: CryptoPayment):
|
|||
crypto_payment.swept_amount = crypto_payment.received_amount
|
||||
crypto_payment.swept_tx_hash = "pooled_sweep"
|
||||
crypto_payment.swept_timestamp = now_timestamp()
|
||||
if dbsession:
|
||||
dbsession.add(crypto_payment)
|
||||
return True
|
||||
|
||||
# Sweep all available balance minus a small buffer for fees
|
||||
|
|
@ -135,6 +137,8 @@ def auto_sweep_payment_xmr(client, crypto_payment: CryptoPayment):
|
|||
crypto_payment.swept_tx_hash = tx_hash
|
||||
crypto_payment.swept_timestamp = now_timestamp()
|
||||
crypto_payment.swept_network_fee = fee
|
||||
if dbsession:
|
||||
dbsession.add(crypto_payment)
|
||||
logger.info(
|
||||
f"Auto-sweep successful for payment {crypto_payment.id}! TX: {tx_hash}, Swept: {total_swept} atomic units, Fee: {fee} atomic units"
|
||||
)
|
||||
|
|
@ -148,7 +152,7 @@ def auto_sweep_payment_xmr(client, crypto_payment: CryptoPayment):
|
|||
return False
|
||||
|
||||
|
||||
def auto_sweep_payment_doge(client, crypto_payment: CryptoPayment):
|
||||
def auto_sweep_payment_doge(client, crypto_payment: CryptoPayment, dbsession=None):
|
||||
"""Auto-sweep DOGE funds from a confirmed payment to the shop's cold wallet."""
|
||||
logger.info(f"Starting DOGE auto-sweep check for payment {crypto_payment.id}")
|
||||
|
||||
|
|
@ -185,6 +189,8 @@ def auto_sweep_payment_doge(client, crypto_payment: CryptoPayment):
|
|||
crypto_payment.swept_amount = crypto_payment.received_amount
|
||||
crypto_payment.swept_tx_hash = "pooled_sweep"
|
||||
crypto_payment.swept_timestamp = now_timestamp()
|
||||
if dbsession:
|
||||
dbsession.add(crypto_payment)
|
||||
return True
|
||||
|
||||
# Sweep all balance minus a small buffer for fees
|
||||
|
|
@ -215,6 +221,8 @@ def auto_sweep_payment_doge(client, crypto_payment: CryptoPayment):
|
|||
crypto_payment.swept_timestamp = now_timestamp()
|
||||
# Note: DOGE RPC doesn't return fee info easily, so we leave it None
|
||||
crypto_payment.swept_network_fee = None
|
||||
if dbsession:
|
||||
dbsession.add(crypto_payment)
|
||||
|
||||
logger.info(
|
||||
f"DOGE auto-sweep successful for payment {crypto_payment.id}! TX: {tx_hash}, Swept: {sweep_amount} DOGE"
|
||||
|
|
@ -644,7 +652,7 @@ def process_payment(
|
|||
logger.info(
|
||||
f"Funds unlocked ({unlocked_balance} atomic units) - attempting sweep for payment {crypto_payment.id}"
|
||||
)
|
||||
sweep_success = auto_sweep_payment(client, crypto_payment)
|
||||
sweep_success = auto_sweep_payment(client, crypto_payment, env_request.dbsession)
|
||||
if sweep_success and crypto_payment.is_swept:
|
||||
logger.info(f"Payment {crypto_payment.id} swept successfully")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -659,8 +659,8 @@ class CryptoWatcherIntegrationTests(unittest.TestCase):
|
|||
|
||||
process_payment(mock_request, payment, transfers, client=mock_client)
|
||||
|
||||
# Auto-sweep should be called after balance check
|
||||
mock_sweep.assert_called_with(mock_client, payment)
|
||||
# Auto-sweep should be called after balance check with dbsession
|
||||
mock_sweep.assert_called_with(mock_client, payment, mock_request.dbsession)
|
||||
self.assertEqual(mock_sweep.call_count, 1)
|
||||
|
||||
|
||||
|
|
@ -766,6 +766,41 @@ class AutoSweepTests(unittest.TestCase):
|
|||
result = auto_sweep_payment(mock_client, payment)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_auto_sweep_database_session_update(self):
|
||||
"""Test that auto-sweep properly updates payment object in database session."""
|
||||
mock_client = MagicMock()
|
||||
mock_client._call.side_effect = [
|
||||
# First call - get_balance
|
||||
{"unlocked_balance": 1000000000000}, # 1 XMR
|
||||
# Second call - sweep_all
|
||||
{"tx_hash": "sweep_tx_123", "amount_list": [1000000000000], "fee": 5000000000},
|
||||
]
|
||||
|
||||
# Create a mock dbsession
|
||||
mock_dbsession = MagicMock()
|
||||
|
||||
# Create a mock payment
|
||||
payment = MagicMock()
|
||||
payment.id = "test_payment_id"
|
||||
payment.shop_sweep_to_address = "cold_wallet_address"
|
||||
payment.account_index = 0
|
||||
payment.received_amount = 500000000000 # 0.5 XMR
|
||||
payment.is_swept = False
|
||||
payment.coin_type = "XMR"
|
||||
|
||||
# Test the sweep with dbsession
|
||||
result = auto_sweep_payment(mock_client, payment, mock_dbsession)
|
||||
self.assertTrue(result)
|
||||
|
||||
# Verify that swept_tx_hash was set
|
||||
self.assertEqual(payment.swept_tx_hash, "sweep_tx_123")
|
||||
self.assertEqual(payment.swept_amount, 1000000000000)
|
||||
self.assertEqual(payment.swept_network_fee, 5000000000)
|
||||
self.assertIsNotNone(payment.swept_timestamp)
|
||||
|
||||
# Verify that the payment was added to the database session
|
||||
mock_dbsession.add.assert_called_once_with(payment)
|
||||
|
||||
|
||||
class DogecoinWatcherUnitTests(unittest.TestCase):
|
||||
"""Unit tests for Dogecoin crypto_watcher functions."""
|
||||
|
|
@ -935,7 +970,7 @@ class DogecoinWatcherUnitTests(unittest.TestCase):
|
|||
result = auto_sweep_payment(mock_client, doge_payment)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_doge_sweep.assert_called_once_with(mock_client, doge_payment)
|
||||
mock_doge_sweep.assert_called_once_with(mock_client, doge_payment, None)
|
||||
|
||||
# Test XMR dispatch
|
||||
xmr_payment = MagicMock()
|
||||
|
|
@ -948,7 +983,7 @@ class DogecoinWatcherUnitTests(unittest.TestCase):
|
|||
result = auto_sweep_payment(mock_client, xmr_payment)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_xmr_sweep.assert_called_once_with(mock_client, xmr_payment)
|
||||
mock_xmr_sweep.assert_called_once_with(mock_client, xmr_payment, None)
|
||||
|
||||
# Test unsupported coin type
|
||||
unknown_payment = MagicMock()
|
||||
|
|
@ -957,6 +992,35 @@ class DogecoinWatcherUnitTests(unittest.TestCase):
|
|||
result = auto_sweep_payment(mock_client, unknown_payment)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_auto_sweep_payment_doge_database_session_update(self):
|
||||
"""Test that DOGE auto-sweep properly updates payment object in database session."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.getbalance.return_value = 10.0 # 10 DOGE
|
||||
mock_client.sendtoaddress.return_value = "doge_sweep_tx_456"
|
||||
|
||||
# Create a mock dbsession
|
||||
mock_dbsession = MagicMock()
|
||||
|
||||
# Create a mock payment
|
||||
payment = MagicMock()
|
||||
payment.id = "doge_payment_id"
|
||||
payment.shop_sweep_to_address = "DColdWalletAddress123"
|
||||
payment.received_amount = 500000000 # 5 DOGE in koinu
|
||||
payment.is_swept = False
|
||||
payment.coin_type = "DOGE"
|
||||
|
||||
# Test the sweep with dbsession
|
||||
result = auto_sweep_payment_doge(mock_client, payment, mock_dbsession)
|
||||
self.assertTrue(result)
|
||||
|
||||
# Verify that swept_tx_hash was set
|
||||
self.assertEqual(payment.swept_tx_hash, "doge_sweep_tx_456")
|
||||
self.assertEqual(payment.swept_amount, 990000000) # 9.9 DOGE in koinu (10 - 0.1 fee buffer)
|
||||
self.assertIsNotNone(payment.swept_timestamp)
|
||||
|
||||
# Verify that the payment was added to the database session
|
||||
mock_dbsession.add.assert_called_once_with(payment)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue