Fix onchain payouts bug

This commit is contained in:
Reckless_Satoshi 2022-10-22 07:23:22 -07:00
parent aa445418d5
commit 5723cde20e
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
5 changed files with 82 additions and 36 deletions

View File

@ -16,6 +16,8 @@ from . import lightning_pb2_grpc as lightningstub
from . import router_pb2 as routerrpc from . import router_pb2 as routerrpc
from . import router_pb2_grpc as routerstub from . import router_pb2_grpc as routerstub
import time, random
####### #######
# Should work with LND (c-lightning in the future if there are features that deserve the work) # Should work with LND (c-lightning in the future if there are features that deserve the work)
####### #######
@ -127,7 +129,7 @@ class LNNode:
} }
@classmethod @classmethod
def pay_onchain(cls, onchainpayment): def pay_onchain(cls, onchainpayment, valid_code=1, on_mempool_code=2):
"""Send onchain transaction for buyer payouts""" """Send onchain transaction for buyer payouts"""
if config("DISABLE_ONCHAIN", cast=bool): if config("DISABLE_ONCHAIN", cast=bool):
@ -141,14 +143,24 @@ class LNNode:
spend_unconfirmed=True, spend_unconfirmed=True,
) )
response = cls.lightningstub.SendCoins( # Cheap security measure to ensure there has been some non-deterministic time between request and DB check
request, metadata=[("macaroon", MACAROON.hex())] time.sleep(random.uniform(0.5, 10))
)
onchainpayment.txid = response.txid if onchainpayment.status == valid_code:
onchainpayment.save() # Changing the state to "MEMPO" should be atomic with SendCoins.
onchainpayment.status = on_mempool_code
onchainpayment.save()
response = cls.lightningstub.SendCoins(
request, metadata=[("macaroon", MACAROON.hex())]
)
return True onchainpayment.txid = response.txid
onchainpayment.save()
return True
elif onchainpayment.status == on_mempool_code:
# Bug, double payment attempted
return True
@classmethod @classmethod
def cancel_return_hold_invoice(cls, payment_hash): def cancel_return_hold_invoice(cls, payment_hash):

View File

@ -734,7 +734,11 @@ class Logics:
if not order.taker_bond: if not order.taker_bond:
return False, {"bad_request": "Wait for your order to be taken."} return False, {"bad_request": "Wait for your order to be taken."}
if ( if (
not ( order.taker_bond.status == order.maker_bond.status == LNPayment.Status.LOCKED) not (
order.taker_bond.status
== order.maker_bond.status
== LNPayment.Status.LOCKED
)
and not order.status == Order.Status.FAI and not order.status == Order.Status.FAI
): ):
return False, { return False, {
@ -753,7 +757,7 @@ class Logics:
payout = LNNode.validate_ln_invoice(invoice, num_satoshis) payout = LNNode.validate_ln_invoice(invoice, num_satoshis)
if not payout["valid"]: if not payout["valid"]:
return False, payout['context'] return False, payout["context"]
order.payout, _ = LNPayment.objects.update_or_create( order.payout, _ = LNPayment.objects.update_or_create(
concept=LNPayment.Concepts.PAYBUYER, concept=LNPayment.Concepts.PAYBUYER,
@ -763,10 +767,10 @@ class Logics:
receiver=user, receiver=user,
# if there is a LNPayment matching these above, it updates that one with defaults below. # if there is a LNPayment matching these above, it updates that one with defaults below.
defaults={ defaults={
"invoice":invoice, "invoice": invoice,
"status":LNPayment.Status.VALIDI, "status": LNPayment.Status.VALIDI,
"num_satoshis":num_satoshis, "num_satoshis": num_satoshis,
"description": payout['description'], "description": payout["description"],
"payment_hash": payout["payment_hash"], "payment_hash": payout["payment_hash"],
"created_at": payout["created_at"], "created_at": payout["created_at"],
"expires_at": payout["expires_at"], "expires_at": payout["expires_at"],
@ -1429,10 +1433,12 @@ class Logics:
if not order.payout_tx.status == OnchainPayment.Status.VALID: if not order.payout_tx.status == OnchainPayment.Status.VALID:
return False return False
valid = LNNode.pay_onchain(order.payout_tx) valid = LNNode.pay_onchain(
order.payout_tx,
valid_code=OnchainPayment.Status.VALID,
on_mempool_code=OnchainPayment.Status.MEMPO,
)
if valid: if valid:
order.payout_tx.status = OnchainPayment.Status.MEMPO
order.payout_tx.save()
order.status = Order.Status.SUC order.status = Order.Status.SUC
order.save() order.save()
send_message.delay(order.id, "trade_successful") send_message.delay(order.id, "trade_successful")

View File

@ -6,7 +6,6 @@ import {
ToggleButtonGroup, ToggleButtonGroup,
ToggleButton, ToggleButton,
IconButton, IconButton,
Box,
Link, Link,
Paper, Paper,
Rating, Rating,
@ -27,6 +26,7 @@ import {
DialogContentText, DialogContentText,
DialogTitle, DialogTitle,
} from '@mui/material'; } from '@mui/material';
import { LoadingButton } from '@mui/lab';
import QRCode from 'react-qr-code'; import QRCode from 'react-qr-code';
import Countdown, { zeroPad } from 'react-countdown'; import Countdown, { zeroPad } from 'react-countdown';
import Chat from './EncryptedChat'; import Chat from './EncryptedChat';
@ -61,6 +61,10 @@ class TradeBox extends Component {
super(props); super(props);
this.state = { this.state = {
openConfirmFiatReceived: false, openConfirmFiatReceived: false,
loadingButtonFiatSent: false,
loadingButtonFiatReceived: false,
loadingSubmitInvoice: false,
loadingSubmitAddress: false,
openConfirmDispute: false, openConfirmDispute: false,
receiveTab: 0, receiveTab: 0,
address: '', address: '',
@ -232,7 +236,15 @@ class TradeBox extends Component {
<Button onClick={this.handleClickCloseConfirmFiatReceived} autoFocus> <Button onClick={this.handleClickCloseConfirmFiatReceived} autoFocus>
{t('Go back')} {t('Go back')}
</Button> </Button>
<Button onClick={this.handleClickTotallyConfirmFiatReceived}>{t('Confirm')}</Button> <LoadingButton
loading={this.state.loadingButtonFiatReceived}
onClick={() => {
this.setState({ loadingButtonFiatReceived: true });
this.handleClickTotallyConfirmFiatReceived();
}}
>
{t('Confirm')}
</LoadingButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );
@ -627,7 +639,7 @@ class TradeBox extends Component {
}; };
handleClickSubmitInvoiceButton = () => { handleClickSubmitInvoiceButton = () => {
this.setState({ badInvoice: false }); this.setState({ badInvoice: false, loadingSubmitInvoice: true });
apiClient apiClient
.post('/api/order/?order_id=' + this.props.data.id, { .post('/api/order/?order_id=' + this.props.data.id, {
@ -636,7 +648,8 @@ class TradeBox extends Component {
}) })
.then( .then(
(data) => (data) =>
this.setState({ badInvoice: data.bad_invoice }) & this.props.completeSetState(data), this.setState({ badInvoice: data.bad_invoice, loadingSubmitInvoice: false }) &
this.props.completeSetState(data),
); );
}; };
@ -659,7 +672,7 @@ class TradeBox extends Component {
}; };
handleClickSubmitAddressButton = () => { handleClickSubmitAddressButton = () => {
this.setState({ badInvoice: false }); this.setState({ badInvoice: false, loadingSubmitAddress: true });
apiClient apiClient
.post('/api/order/?order_id=' + this.props.data.id, { .post('/api/order/?order_id=' + this.props.data.id, {
@ -669,7 +682,8 @@ class TradeBox extends Component {
}) })
.then( .then(
(data) => (data) =>
this.setState({ badAddress: data.bad_address }) & this.props.completeSetState(data), this.setState({ badAddress: data.bad_address, loadingSubmitAddress: false }) &
this.props.completeSetState(data),
); );
}; };
@ -838,13 +852,14 @@ class TradeBox extends Component {
/> />
</Grid> </Grid>
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<Button <LoadingButton
loading={this.state.loadingSubmitInvoice}
onClick={this.handleClickSubmitInvoiceButton} onClick={this.handleClickSubmitInvoiceButton}
variant='contained' variant='contained'
color='primary' color='primary'
> >
{t('Submit')} {t('Submit')}
</Button> </LoadingButton>
</Grid> </Grid>
</Grid> </Grid>
</div> </div>
@ -940,13 +955,14 @@ class TradeBox extends Component {
<div style={{ height: 10 }} /> <div style={{ height: 10 }} />
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<Button <LoadingButton
loading={this.state.loadingSubmitAddress}
onClick={this.handleClickSubmitAddressButton} onClick={this.handleClickSubmitAddressButton}
variant='contained' variant='contained'
color='primary' color='primary'
> >
{t('Submit')} {t('Submit')}
</Button> </LoadingButton>
</Grid> </Grid>
</div> </div>
<List> <List>
@ -1192,7 +1208,10 @@ class TradeBox extends Component {
.post('/api/order/?order_id=' + this.props.data.id, { .post('/api/order/?order_id=' + this.props.data.id, {
action: 'confirm', action: 'confirm',
}) })
.then((data) => this.props.completeSetState(data)); .then((data) => {
this.props.completeSetState(data),
this.setState({ loadingButtonFiatSent: false, loadingButtonFiatReceived: false });
});
}; };
handleRatingUserChange = (e) => { handleRatingUserChange = (e) => {
@ -1224,10 +1243,14 @@ class TradeBox extends Component {
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<Button <Button
loading={this.state.loadingButtonFiatSent}
defaultValue='confirm' defaultValue='confirm'
variant='contained' variant='contained'
color='secondary' color='secondary'
onClick={this.handleClickConfirmButton} onClick={() => {
this.setState({ loadingButtonFiatSent: true });
this.handleClickConfirmButton();
}}
> >
{t('Confirm {{amount}} {{currencyCode}} sent', { {t('Confirm {{amount}} {{currencyCode}} sent', {
currencyCode: this.props.data.currencyCode, currencyCode: this.props.data.currencyCode,
@ -1249,7 +1272,8 @@ class TradeBox extends Component {
const { t } = this.props; const { t } = this.props;
return ( return (
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<Button <LoadingButton
loading={this.state.loadingButtonFiatReceived}
defaultValue='confirm' defaultValue='confirm'
variant='contained' variant='contained'
color='secondary' color='secondary'
@ -1265,7 +1289,7 @@ class TradeBox extends Component {
), ),
), ),
})} })}
</Button> </LoadingButton>
</Grid> </Grid>
); );
} }
@ -1732,13 +1756,14 @@ class TradeBox extends Component {
/> />
</Grid> </Grid>
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<Button <LoadingButton
loading={this.state.loadingSubmitInvoice}
onClick={this.handleClickSubmitInvoiceButton} onClick={this.handleClickSubmitInvoiceButton}
variant='contained' variant='contained'
color='primary' color='primary'
> >
Submit {t('Submit')}
</Button> </LoadingButton>
</Grid> </Grid>
{this.showBondIsReturned()} {this.showBondIsReturned()}
</Grid> </Grid>

View File

@ -84,9 +84,11 @@ class UnsafeAlert extends Component {
> >
<AlertTitle>{t('You are not using RoboSats privately')}</AlertTitle> <AlertTitle>{t('You are not using RoboSats privately')}</AlertTitle>
<Trans i18nKey='desktop_unsafe_alert'> <Trans i18nKey='desktop_unsafe_alert'>
<a>Some features are disabled for your protection (e.g. chat) and you will not be <a>
able to complete a trade without them. To protect your privacy and fully enable Some features are disabled for your protection (e.g. chat) and you will not be
RoboSats, use </a> able to complete a trade without them. To protect your privacy and fully enable
RoboSats, use{' '}
</a>
<Link href='https://www.torproject.org/download/' target='_blank'> <Link href='https://www.torproject.org/download/' target='_blank'>
Tor Browser Tor Browser
</Link> </Link>

View File

@ -8,6 +8,7 @@ def basic(request, *args, **kwargs):
context = {"ONION_LOCATION": config("ONION_LOCATION")} context = {"ONION_LOCATION": config("ONION_LOCATION")}
return render(request, "frontend/basic.html", context=context) return render(request, "frontend/basic.html", context=context)
def pro(request, *args, **kwargs): def pro(request, *args, **kwargs):
context = {"ONION_LOCATION": config("ONION_LOCATION")} context = {"ONION_LOCATION": config("ONION_LOCATION")}
return render(request, "frontend/pro.html", context=context) return render(request, "frontend/pro.html", context=context)