Fix race condition onchain (#388)

* Fix race condition swaps

* Collect new phrases

* Increase random delay interval
This commit is contained in:
Reckless_Satoshi 2023-03-14 17:23:11 +00:00 committed by GitHub
parent 2fd4a0123e
commit 42f208fad4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 120 additions and 33 deletions

View File

@ -1,6 +1,5 @@
import hashlib
import os
import random
import secrets
import time
from base64 import b64decode
@ -129,7 +128,7 @@ class LNNode:
}
@classmethod
def pay_onchain(cls, onchainpayment, valid_code=1, on_mempool_code=2):
def pay_onchain(cls, onchainpayment, queue_code=5, on_mempool_code=2):
"""Send onchain transaction for buyer payouts"""
if config("DISABLE_ONCHAIN", cast=bool):
@ -144,9 +143,12 @@ class LNNode:
)
# Cheap security measure to ensure there has been some non-deterministic time between request and DB check
time.sleep(random.uniform(0.5, 10))
delay = (
secrets.randbelow(2**256) / (2**256) * 10
) # Random uniform 0 to 5 secs with good entropy
time.sleep(3 + delay)
if onchainpayment.status == valid_code:
if onchainpayment.status == queue_code:
# Changing the state to "MEMPO" should be atomic with SendCoins.
onchainpayment.status = on_mempool_code
onchainpayment.save()

View File

@ -481,9 +481,9 @@ class Logics:
"bad_request": "Only orders in dispute accept dispute statements"
}
if len(statement) > 5000:
if len(statement) > 10000:
return False, {
"bad_statement": "The statement is longer than 5000 characters"
"bad_statement": "The statement is longer than 10000 characters"
}
if len(statement) < 100:
@ -554,7 +554,7 @@ class Logics:
confirmed = onchain_payment.balance.onchain_confirmed
reserve = 300000 # We assume a reserve of 300K Sats (3 times higher than LND's default anchor reserve)
pending_txs = OnchainPayment.objects.filter(
status=OnchainPayment.Status.VALID
status__in=[OnchainPayment.Status.VALID, OnchainPayment.Status.QUEUE]
).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
if pending_txs is None:
@ -1049,13 +1049,6 @@ class Logics:
safe_cltv_expiry_secs = cltv_expiry_secs * MAX_MINING_NETWORK_SPEEDUP_EXPECTED
# Convert to blocks using assummed average block time (~8 mins/block)
cltv_expiry_blocks = int(safe_cltv_expiry_secs / (BLOCK_TIME * 60))
print(
invoice_concept,
" cltv_expiry_hours:",
cltv_expiry_secs / 3600,
" cltv_expiry_blocks:",
cltv_expiry_blocks,
)
return cltv_expiry_blocks
@ -1442,20 +1435,16 @@ class Logics:
else:
if not order.payout_tx.status == OnchainPayment.Status.VALID:
return False
valid = LNNode.pay_onchain(
order.payout_tx,
valid_code=OnchainPayment.Status.VALID,
on_mempool_code=OnchainPayment.Status.MEMPO,
)
if valid:
else:
# Add onchain payment to queue
order.status = Order.Status.SUC
order.payout_tx.status = OnchainPayment.Status.QUEUE
order.payout_tx.save()
order.save()
send_message.delay(order.id, "trade_successful")
order.contract_finalization_time = timezone.now()
order.save()
return True
return False
@classmethod
def confirm_fiat(cls, order, user):

View File

@ -8,7 +8,7 @@ from django.utils import timezone
from api.lightning.node import LNNode
from api.logics import Logics
from api.models import LNPayment, Order
from api.models import LNPayment, OnchainPayment, Order
from api.tasks import follow_send_payment, send_message
MACAROON = b64decode(config("LND_MACAROON_BASE64"))
@ -16,7 +16,7 @@ MACAROON = b64decode(config("LND_MACAROON_BASE64"))
class Command(BaseCommand):
help = "Follows all active hold invoices"
help = "Follows all active hold invoices, sends out payments"
rest = 5 # seconds between consecutive checks for invoice updates
def handle(self, *args, **options):
@ -130,6 +130,14 @@ class Command(BaseCommand):
self.stdout.write(str(debug))
def send_payments(self):
"""
Checks for invoices and onchain payments that are due to be paid.
Sends the payments.
"""
self.send_ln_payments()
self.send_onchain_payments()
def send_ln_payments(self):
"""
Checks for invoices that are due to pay; i.e., INFLIGHT status and 0 routing_attempts.
Checks if any payment is due for retry, and tries to pay it.
@ -153,8 +161,36 @@ class Command(BaseCommand):
queryset = queryset.union(queryset_retries)
for lnpayment in queryset:
follow_send_payment(lnpayment.payment_hash)
if len(queryset) > 0:
for lnpayment in queryset:
follow_send_payment.delay(lnpayment.payment_hash)
def send_onchain_payments(self):
queryset = OnchainPayment.objects.filter(
status=OnchainPayment.Status.QUEUE,
)
if len(queryset) > 0:
for onchainpayment in queryset:
# Checks that this onchain payment is part of an order with a settled escrow
if not hasattr(onchainpayment, "order_paid_TX"):
self.stdout.write(
f"Onchain payment {str(onchainpayment)} has no parent order!"
)
return
order = onchainpayment.order_paid_TX
if order.trade_escrow.status == LNPayment.Status.SETLED:
# Sends out onchainpayment
LNNode.pay_onchain(
onchainpayment,
OnchainPayment.Status.QUEUE,
OnchainPayment.Status.MEMPO,
)
else:
self.stdout.write(
f"Onchain payment {str(onchainpayment)} for order {str(order)} escrow is not settled!"
)
def update_order_status(self, lnpayment):
"""Background process following LND hold invoices

View File

@ -185,10 +185,11 @@ class OnchainPayment(models.Model):
class Status(models.IntegerChoices):
CREAT = 0, "Created" # User was given platform fees and suggested mining fees
VALID = 1, "Valid" # Valid onchain address submitted
VALID = 1, "Valid" # Valid onchain address and fee submitted
MEMPO = 2, "In mempool" # Tx is sent to mempool
CONFI = 3, "Confirmed" # Tx is confirme +2 blocks
CONFI = 3, "Confirmed" # Tx is confirmed +2 blocks
CANCE = 4, "Cancelled" # Cancelled tx
QUEUE = 5, "Queued" # Payment is queued to be sent out
def get_balance():
balance = BalanceLog.objects.create()
@ -441,10 +442,10 @@ class Order(models.Model):
# in dispute
is_disputed = models.BooleanField(default=False, null=False)
maker_statement = models.TextField(
max_length=5000, null=True, default=None, blank=True
max_length=10000, null=True, default=None, blank=True
)
taker_statement = models.TextField(
max_length=5000, null=True, default=None, blank=True
max_length=10000, null=True, default=None, blank=True
)
# LNpayments

View File

@ -501,7 +501,7 @@ class UpdateOrderSerializer(serializers.Serializer):
max_length=100, allow_null=True, allow_blank=True, default=None
)
statement = serializers.CharField(
max_length=10000, allow_null=True, allow_blank=True, default=None
max_length=11000, allow_null=True, allow_blank=True, default=None
)
action = serializers.ChoiceField(
choices=(

View File

@ -97,6 +97,7 @@ def follow_send_payment(hash):
payment_request=lnpayment.invoice,
fee_limit_sat=fee_limit_sat,
timeout_seconds=timeout_seconds,
allow_self_payment=True,
)
order = lnpayment.order_paid_LN

View File

@ -471,12 +471,15 @@ class OrderView(viewsets.ViewSet):
if order.is_swap:
data["num_satoshis"] = order.payout_tx.num_satoshis
data["sent_satoshis"] = order.payout_tx.sent_satoshis
data["network"] = str(config("NETWORK"))
if order.payout_tx.status in [
OnchainPayment.Status.MEMPO,
OnchainPayment.Status.CONFI,
]:
data["txid"] = order.payout_tx.txid
data["network"] = str(config("NETWORK"))
elif order.payout_tx.status == OnchainPayment.Status.QUEUE:
data["tx_queued"] = True
data["address"] = order.payout_tx.address
return Response(data, status.HTTP_200_OK)

View File

@ -11,6 +11,7 @@ import {
Tooltip,
IconButton,
Button,
CircularProgress,
} from '@mui/material';
import currencies from '../../../../static/assets/currencies.json';
import TradeSummary from '../TradeSummary';
@ -150,6 +151,43 @@ export const SuccessfulPrompt = ({
</Alert>
</Collapse>
<Collapse in={order.tx_queued && order.address != undefined}>
<Alert severity='info'>
<AlertTitle>
<CircularProgress sx={{ maxWidth: '0.8em', maxHeight: '0.8em' }} />
<a> </a>
{t('Sending coins to')}
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton
color='inherit'
onClick={() => {
systemClient.copyToClipboard(order.address);
}}
>
<ContentCopy sx={{ width: '0.8em', height: '0.8em' }} />
</IconButton>
</Tooltip>
</AlertTitle>
<Typography
variant='body2'
align='center'
sx={{ wordWrap: 'break-word', width: '15.71em' }}
>
<Link
target='_blank'
href={
'http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/' +
(order.network == 'testnet' ? 'testnet/' : '') +
'address/' +
order.address
}
>
{order.address}
</Link>
</Typography>
</Alert>
</Collapse>
<Grid item container alignItems='center' justifyContent='space-evenly'>
<Grid item>
<Button color='primary' variant='outlined' onClick={onClickStartAgain}>

View File

@ -49,7 +49,7 @@ const statusToDelay = [
100000, // 'In dispute'
999999, // 'Collaboratively cancelled'
10000, // 'Sending satoshis to buyer'
999999, // 'Sucessful trade'
60000, // 'Sucessful trade'
30000, // 'Failed lightning network routing'
300000, // 'Wait for dispute resolution'
300000, // 'Maker lost dispute'

View File

@ -94,6 +94,8 @@ export interface Order {
num_satoshis: number;
sent_satoshis: number;
txid: string;
tx_queued: boolean;
address: string;
network: 'mainnet' | 'testnet';
}

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats millora amb més liquiditat i usuaris. Explica-li a un amic bitcoiner sobre RoboSats!",
"Thank you for using Robosats!": "Gràcies per fer servir RoboSats!",
"Your TXID": "El teu TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Començar de nou",
"Renew": "Renovar",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats bude lepší s větší likviditou a uživateli. Pověz svým přátelům o Robosats!",
"Thank you for using Robosats!": "Děkujeme, že používáš Robosats!",
"Your TXID": "Tvé TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Začít znovu",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats wird noch besser mit mehr Nutzern und Liquidität. Erzähl einem Bitcoin-Freund von uns!",
"Thank you for using Robosats!": "Danke, dass du Robosats benutzt hast!",
"Your TXID": "Your TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Nochmal",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!",
"Thank you for using Robosats!": "Thank you for using Robosats!",
"Your TXID": "Your TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Start Again",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats mejora con más liquidez y usuarios. ¡Cuéntale a un amigo bitcoiner sobre RoboSats!",
"Thank you for using Robosats!": "¡Gracias por usar RoboSats!",
"Your TXID": "Tu TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Empezar de nuevo",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats hobetu egiten da likidezia eta erabiltzaile gehiagorekin. Aipatu Robosats lagun bitcoiner bati!",
"Thank you for using Robosats!": "Mila esker Robosats erabiltzeagatik!",
"Your TXID": "Zure TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Berriz Hasi",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats s'améliore avec plus de liquidité et d'utilisateurs. Parlez de Robosats à un ami bitcoiner!",
"Thank you for using Robosats!": "Merci d'utiliser Robosats!",
"Your TXID": "Your TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Recommencer",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats migliora se ha più liquidità ed utenti. Parla di Robosats ai tuoi amici bitcoiner!",
"Thank you for using Robosats!": "Grazie per aver usato Robosats!",
"Your TXID": "Your TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Ricomincia",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats staje się lepszy dzięki większej płynności i użytkownikom. Powiedz znajomemu bitcoinerowi o Robosats!",
"Thank you for using Robosats!": "Dziękujemy za korzystanie z Robosatów!",
"Your TXID": "Your TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Zacznij jeszcze raz",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats fica melhor com mais liquidez e usuários. Conte a um amigo bitcoiner sobre Robosats!",
"Thank you for using Robosats!": "Obrigado por usar Robosats!",
"Your TXID": "Sua TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Comece de novo",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats становится лучше с большей ликвидностью и пользователями. Расскажите другу-биткойнеру о Robosat!",
"Thank you for using Robosats!": "Спасибо за использование Robosats!",
"Your TXID": "Ваш TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Начать Снова",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats blir bättre med mer likviditet och fler användare. Berätta om RoboSats för en Bitcoiner-vän!",
"Thank you for using Robosats!": "Tack för att du använder RoboSats!",
"Your TXID": "Ditt TXID",
"Sending coins to": "Sending coins to",
"Start Again": "Börja om",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats จะดีขึ้นเมื่อมีสภาพคล่องและผู้ใช้งานมากขึ้น ช่วยกันชวนเพื่อนของคุณมาใช้ Robosats!",
"Thank you for using Robosats!": "ขอบคุณที่ใช้งาน Robosats!",
"Your TXID": "TXID ของคุณ",
"Sending coins to": "Sending coins to",
"Start Again": "Start Again",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats 会随着更多的流动性和更高的用户数量而变得更好。把 RoboSats 推荐给你的比特币朋友吧!",
"Thank you for using Robosats!": "感谢你使用 Robosats",
"Your TXID": "你的 TXID",
"Sending coins to": "Sending coins to",
"Start Again": "重新开始",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",

View File

@ -486,6 +486,7 @@
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats 會隨著更多的流動性和更高的用戶数量而變得更好。把 RoboSats 推薦給你的比特幣朋友吧!",
"Thank you for using Robosats!": "感謝你使用 Robosats!",
"Your TXID": "你的 TXID",
"Sending coins to": "Sending coins to",
"Start Again": "重新開始",
"Renew": "Renew",
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",