mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Fix onchain payouts bug
This commit is contained in:
parent
aa445418d5
commit
5723cde20e
@ -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):
|
||||||
|
@ -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")
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user