diff --git a/api/lightning/node.py b/api/lightning/node.py index 400de766..51c3ef1f 100644 --- a/api/lightning/node.py +++ b/api/lightning/node.py @@ -16,6 +16,8 @@ from . import lightning_pb2_grpc as lightningstub from . import router_pb2 as routerrpc 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) ####### @@ -127,7 +129,7 @@ class LNNode: } @classmethod - def pay_onchain(cls, onchainpayment): + def pay_onchain(cls, onchainpayment, valid_code=1, on_mempool_code=2): """Send onchain transaction for buyer payouts""" if config("DISABLE_ONCHAIN", cast=bool): @@ -141,14 +143,24 @@ class LNNode: spend_unconfirmed=True, ) - response = cls.lightningstub.SendCoins( - request, metadata=[("macaroon", MACAROON.hex())] - ) + # Cheap security measure to ensure there has been some non-deterministic time between request and DB check + time.sleep(random.uniform(0.5, 10)) - onchainpayment.txid = response.txid - onchainpayment.save() + if onchainpayment.status == valid_code: + # 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 def cancel_return_hold_invoice(cls, payment_hash): diff --git a/api/logics.py b/api/logics.py index 2f2a84fd..d718bd04 100644 --- a/api/logics.py +++ b/api/logics.py @@ -734,7 +734,11 @@ class Logics: if not order.taker_bond: return False, {"bad_request": "Wait for your order to be taken."} 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 ): return False, { @@ -753,7 +757,7 @@ class Logics: payout = LNNode.validate_ln_invoice(invoice, num_satoshis) if not payout["valid"]: - return False, payout['context'] + return False, payout["context"] order.payout, _ = LNPayment.objects.update_or_create( concept=LNPayment.Concepts.PAYBUYER, @@ -763,10 +767,10 @@ class Logics: receiver=user, # if there is a LNPayment matching these above, it updates that one with defaults below. defaults={ - "invoice":invoice, - "status":LNPayment.Status.VALIDI, - "num_satoshis":num_satoshis, - "description": payout['description'], + "invoice": invoice, + "status": LNPayment.Status.VALIDI, + "num_satoshis": num_satoshis, + "description": payout["description"], "payment_hash": payout["payment_hash"], "created_at": payout["created_at"], "expires_at": payout["expires_at"], @@ -1429,10 +1433,12 @@ class Logics: if not order.payout_tx.status == OnchainPayment.Status.VALID: 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: - order.payout_tx.status = OnchainPayment.Status.MEMPO - order.payout_tx.save() order.status = Order.Status.SUC order.save() send_message.delay(order.id, "trade_successful") diff --git a/frontend/src/components/TradeBox/index.js b/frontend/src/components/TradeBox/index.js index b28e9b36..c40acd5f 100644 --- a/frontend/src/components/TradeBox/index.js +++ b/frontend/src/components/TradeBox/index.js @@ -6,7 +6,6 @@ import { ToggleButtonGroup, ToggleButton, IconButton, - Box, Link, Paper, Rating, @@ -27,6 +26,7 @@ import { DialogContentText, DialogTitle, } from '@mui/material'; +import { LoadingButton } from '@mui/lab'; import QRCode from 'react-qr-code'; import Countdown, { zeroPad } from 'react-countdown'; import Chat from './EncryptedChat'; @@ -61,6 +61,10 @@ class TradeBox extends Component { super(props); this.state = { openConfirmFiatReceived: false, + loadingButtonFiatSent: false, + loadingButtonFiatReceived: false, + loadingSubmitInvoice: false, + loadingSubmitAddress: false, openConfirmDispute: false, receiveTab: 0, address: '', @@ -232,7 +236,15 @@ class TradeBox extends Component { - + { + this.setState({ loadingButtonFiatReceived: true }); + this.handleClickTotallyConfirmFiatReceived(); + }} + > + {t('Confirm')} + ); @@ -627,7 +639,7 @@ class TradeBox extends Component { }; handleClickSubmitInvoiceButton = () => { - this.setState({ badInvoice: false }); + this.setState({ badInvoice: false, loadingSubmitInvoice: true }); apiClient .post('/api/order/?order_id=' + this.props.data.id, { @@ -636,7 +648,8 @@ class TradeBox extends Component { }) .then( (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 = () => { - this.setState({ badInvoice: false }); + this.setState({ badInvoice: false, loadingSubmitAddress: true }); apiClient .post('/api/order/?order_id=' + this.props.data.id, { @@ -669,7 +682,8 @@ class TradeBox extends Component { }) .then( (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 { /> - + @@ -940,13 +955,14 @@ class TradeBox extends Component {
- +
@@ -1192,7 +1208,10 @@ class TradeBox extends Component { .post('/api/order/?order_id=' + this.props.data.id, { action: 'confirm', }) - .then((data) => this.props.completeSetState(data)); + .then((data) => { + this.props.completeSetState(data), + this.setState({ loadingButtonFiatSent: false, loadingButtonFiatReceived: false }); + }); }; handleRatingUserChange = (e) => { @@ -1224,10 +1243,14 @@ class TradeBox extends Component { + {t('Submit')} + {this.showBondIsReturned()} diff --git a/frontend/src/components/UnsafeAlert.js b/frontend/src/components/UnsafeAlert.js index 79d4f41d..73ddea2f 100644 --- a/frontend/src/components/UnsafeAlert.js +++ b/frontend/src/components/UnsafeAlert.js @@ -84,9 +84,11 @@ class UnsafeAlert extends Component { > {t('You are not using RoboSats privately')} - Some features are disabled for your protection (e.g. chat) and you will not be - able to complete a trade without them. To protect your privacy and fully enable - RoboSats, use + + Some features are disabled for your protection (e.g. chat) and you will not be + able to complete a trade without them. To protect your privacy and fully enable + RoboSats, use{' '} + Tor Browser diff --git a/frontend/views.py b/frontend/views.py index 88f10f26..5639a2a3 100644 --- a/frontend/views.py +++ b/frontend/views.py @@ -8,6 +8,7 @@ def basic(request, *args, **kwargs): context = {"ONION_LOCATION": config("ONION_LOCATION")} return render(request, "frontend/basic.html", context=context) + def pro(request, *args, **kwargs): context = {"ONION_LOCATION": config("ONION_LOCATION")} return render(request, "frontend/pro.html", context=context)