Add setup background threads. Minor fixes and cosmetic.

This commit is contained in:
Reckless_Satoshi 2022-01-30 07:18:03 -08:00
parent 64115a8bb5
commit 58ecb607c3
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
9 changed files with 147 additions and 52 deletions

View File

@ -122,22 +122,22 @@ class LNNode():
lnpayment.save()
return True
@classmethod
def check_until_invoice_locked(cls, payment_hash, expiration):
'''Checks until hold invoice is locked.
When invoice is locked, returns true.
If time expires, return False.'''
# Experimental, might need asyncio. Best if subscribing all invoices and running a background task
# Maybe best to pass LNpayment object and change status live.
# @classmethod
# def check_until_invoice_locked(cls, payment_hash, expiration):
# '''Checks until hold invoice is locked.
# When invoice is locked, returns true.
# If time expires, return False.'''
# # Experimental, might need asyncio. Best if subscribing all invoices and running a background task
# # Maybe best to pass LNpayment object and change status live.
request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
for invoice in cls.invoicesstub.SubscribeSingleInvoice(request):
print(invoice)
if timezone.now > expiration:
break
if invoice.state == 3: # True if hold invoice is accepted.
return True
return False
# request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
# for invoice in cls.invoicesstub.SubscribeSingleInvoice(request):
# print(invoice)
# if timezone.now > expiration:
# break
# if invoice.state == 3: # True if hold invoice is accepted.
# return True
# return False
@classmethod

View File

@ -50,9 +50,9 @@ class Logics():
def validate_order_size(order):
'''Validates if order is withing limits in satoshis at t0'''
if order.t0_satoshis > MAX_TRADE:
return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'}
return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now, but the limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'}
if order.t0_satoshis < MIN_TRADE:
return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now, but the limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
return True, None
@classmethod
@ -386,11 +386,12 @@ class Logics():
return True, None
# 2) When maker cancels after bond
'''The order dissapears from book and goes to cancelled. Maker is charged the bond to prevent DDOS
on the LN node and order book. TODO Only charge a small part of the bond (requires maker submitting an invoice)'''
'''The order dissapears from book and goes to cancelled. If strict, maker is charged the bond
to prevent DDOS on the LN node and order book. If not strict, maker is returned
the bond (more user friendly).'''
elif order.status == Order.Status.PUB and order.maker == user:
#Settle the maker bond (Maker loses the bond for cancelling public order)
if cls.settle_bond(order.maker_bond):
if cls.return_bond(order.maker_bond): # strict: cls.settle_bond(order.maker_bond):
order.status = Order.Status.UCA
order.save()
return True, None

View File

@ -39,6 +39,9 @@ class MakerView(CreateAPIView):
def post(self,request):
serializer = self.serializer_class(data=request.data)
if not request.user.is_authenticated:
return Response({'bad_request':'Woops! It seems you do not have a robot avatar'}, status.HTTP_400_BAD_REQUEST)
if not serializer.is_valid(): return Response(status=status.HTTP_400_BAD_REQUEST)
type = serializer.data.get('type')
@ -413,15 +416,16 @@ class UserView(APIView):
def delete(self,request):
''' Pressing "give me another" deletes the logged in user '''
user = request.user
if not user:
if not user.is_authenticated:
return Response(status.HTTP_403_FORBIDDEN)
# Only delete if user life is shorter than 30 minutes. Helps deleting users by mistake
# Only delete if user life is shorter than 30 minutes. Helps to avoid deleting users by mistake
if user.date_joined < (timezone.now() - timedelta(minutes=30)):
return Response(status.HTTP_400_BAD_REQUEST)
# Check if it is not a maker or taker!
if not Logics.validate_already_maker_or_taker(user):
not_participant, _, _ = Logics.validate_already_maker_or_taker(user)
if not not_participant:
return Response({'bad_request':'User cannot be deleted while he is part of an order'}, status.HTTP_400_BAD_REQUEST)
logout(request)

View File

@ -93,7 +93,7 @@ export default class BookPage extends Component {
renderCell: (params) => {return (
<ListItemButton style={{ cursor: "pointer" }}>
<ListItemAvatar>
<Avatar alt={params.row.robosat} src={params.row.avatar} />
<Avatar className="flippedSmallAvatar" alt={params.row.robosat} src={params.row.avatar} />
</ListItemAvatar>
<ListItemText primary={params.row.robosat}/>
</ListItemButton>
@ -147,7 +147,7 @@ export default class BookPage extends Component {
{ field: 'robosat', headerName: 'Robot', width: 80,
renderCell: (params) => {return (
<ListItemButton style={{ cursor: "pointer" }}>
<Avatar alt={params.row.robosat} src={params.row.avatar} />
<Avatar className="flippedSmallAvatar" alt={params.row.robosat} src={params.row.avatar} />
</ListItemButton>
);
} },

View File

@ -17,6 +17,7 @@ import SendIcon from '@mui/icons-material/Send';
import PublicIcon from '@mui/icons-material/Public';
import NumbersIcon from '@mui/icons-material/Numbers';
import PasswordIcon from '@mui/icons-material/Password';
import ContentCopy from "@mui/icons-material/ContentCopy";
// pretty numbers
function pn(x) {
@ -198,7 +199,7 @@ export default class BottomBar extends Component {
</Typography>
</ListItemText>
<ListItemAvatar>
<Avatar className='avatar'
<Avatar className='profileAvatar'
sx={{ width: 65, height:65 }}
alt={this.props.nickname}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
@ -226,14 +227,21 @@ export default class BottomBar extends Component {
<ListItemIcon>
<PasswordIcon/>
</ListItemIcon>
<ListItemText secondary="Your token.">
<ListItemText secondary="Your token">
{this.props.token ?
<TextField
disabled
label='Store safely'
value={this.props.token }
variant='filled'
size='small'/>
size='small'
InputProps={{
endAdornment:
<IconButton onClick= {()=>navigator.clipboard.writeText(this.props.token)}>
<ContentCopy />
</IconButton>,
}}
/>
:
'Cannot remember'}
</ListItemText>
@ -258,7 +266,7 @@ bottomBarDesktop =()=>{
<ListItemButton onClick={this.handleClickOpenProfile} >
<ListItemAvatar sx={{ width: 30, height: 30 }} >
<Badge badgeContent={(this.state.active_order_id > 0 & !this.state.profileShown) ? "": null} color="primary">
<Avatar className='rotatedAvatar' sx={{margin: 0, top: -13}}
<Avatar className='flippedSmallAvatar' sx={{margin: 0, top: -13}}
alt={this.props.nickname}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>
@ -462,7 +470,7 @@ bottomBarPhone =()=>{
<Grid item xs={1.6}>
<IconButton onClick={this.handleClickOpenProfile} sx={{margin: 0, top: -13, }} >
<Badge badgeContent={(this.state.active_order_id >0 & !this.state.profileShown) ? "1": null} color="primary">
<Avatar className='rotatedAvatar'
<Avatar className='flippedSmallAvatar'
alt={this.props.nickname}
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
/>

View File

@ -309,7 +309,7 @@ export default class OrderPage extends Component {
// If maker and Waiting for Bond. Or if taker and Waiting for bond.
// Simply allow to cancel without showing the cancel dialog.
if ((this.state.is_maker & this.state.status == 0) || this.state.is_taker & this.state.status == 3){
if ((this.state.is_maker & [0,1].includes(this.state.status)) || this.state.is_taker & this.state.status == 3){
return(
<Grid item xs={12} align="center">
<Button variant='contained' color='secondary' onClick={this.handleClickConfirmCancelButton}>Cancel</Button>
@ -317,7 +317,7 @@ export default class OrderPage extends Component {
)}
// If the order does not yet have an escrow deposited. Show dialog
// to confirm forfeiting the bond
if ([1,3,6,7].includes(this.state.status)){
if ([3,6,7].includes(this.state.status)){
return(
<div id="openDialogCancelButton">
<Grid item xs={12} align="center">
@ -354,7 +354,7 @@ export default class OrderPage extends Component {
<List dense="true">
<ListItem >
<ListItemAvatar sx={{ width: 56, height: 56 }}>
<Avatar
<Avatar className="flippedSmallAvatar"
alt={this.state.maker_nick}
src={window.location.origin +'/static/assets/avatars/' + this.state.maker_nick + '.png'}
/>
@ -370,7 +370,7 @@ export default class OrderPage extends Component {
<ListItem align="left">
<ListItemText primary={this.state.taker_nick + (this.state.type ? " (Buyer)" : " (Seller)")} secondary="Order taker"/>
<ListItemAvatar >
<Avatar
<Avatar className="smallAvatar"
alt={this.state.maker_nick}
src={window.location.origin +'/static/assets/avatars/' + this.state.taker_nick + '.png'}
/>

View File

@ -3,7 +3,7 @@ import { Button , Dialog, Grid, Typography, TextField, ButtonGroup, CircularProg
import { Link } from 'react-router-dom'
import Image from 'material-ui-image'
import InfoDialog from './InfoDialog'
import PublishIcon from '@mui/icons-material/Publish';
import SmartToyIcon from '@mui/icons-material/SmartToy';
import CasinoIcon from '@mui/icons-material/Casino';
import ContentCopy from "@mui/icons-material/ContentCopy";
@ -161,14 +161,11 @@ export default class UserGenPage extends Component {
}
<Grid container align="center">
<Grid item xs={12} align="center">
<IconButton sx={{top:6}} onClick= {()=>navigator.clipboard.writeText(this.state.token)}>
<ContentCopy sx={{width:18, height:18}} />
</IconButton>
<TextField
<TextField sx={{maxWidth: 280}}
//sx={{ input: { color: 'purple' } }}
InputLabelProps={{
style: { color: 'green' },
}}
// InputLabelProps={{
// style: { color: 'green' },
// }}
error={this.state.bad_request}
label='Store your token safely'
required='true'
@ -183,16 +180,21 @@ export default class UserGenPage extends Component {
this.handleClickSubmitToken();
}
}}
InputProps={{
startAdornment:
<IconButton onClick= {()=>navigator.clipboard.writeText(this.state.token)}>
<ContentCopy color={this.state.tokenHasChanged ? 'inherit' : 'primary' } sx={{width:18, height:18}} />
</IconButton>,
endAdornment:
<IconButton onClick={this.handleClickNewRandomToken}><CasinoIcon/></IconButton>,
}}
/>
<IconButton sx={{top:8}} onClick={this.handleClickNewRandomToken}>
<CasinoIcon />
</IconButton>
</Grid>
</Grid>
<Grid item xs={12} align="center">
<Button disabled={!this.state.tokenHasChanged} type="submit" size='small' onClick= {this.handleClickSubmitToken}>
<PublishIcon />
<span> Generate Robot</span>
<SmartToyIcon sx={{width:18, height:18}} />
<span> Generate Robot</span>
</Button>
</Grid>
<Grid item xs={12} align="center">

View File

@ -39,7 +39,7 @@ body {
.profileNickname {
margin: 0;
left: -22px;
left: -16px;
}
.newAvatar {
@ -51,14 +51,19 @@ body {
width: 200px;
}
.avatar {
.profileAvatar {
border: 0.5px solid #555;
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
left: 35px;
}
.rotatedAvatar {
transform: scaleX(-1);
.smallAvatar {
border: 0.5px solid #555;
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
}
.flippedSmallAvatar {
transform: scaleX(-1);
border: 0.3px solid #555;
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
}

View File

@ -133,4 +133,79 @@ Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4
### Launch the React render
from frontend/ directory
`npm run dev`
`npm run dev`
## Robosats background threads.
There is 3 processes that run asynchronously: two admin commands and a celery beat scheduler.
The celery worker will run the task of caching external API market prices and cleaning(deleting) the generated robots that were never used.
`celery -A robosats worker --beat -l debug -S django`
The admin commands are used to keep an eye on the state of LND hold invoices and check whether orders have expired
```
python3 manage.py follow_invoices
python3 manage.py clean_order
```
It might be best to set up system services to continuously run these background processes.
### Follow invoices admin command as system service
Create `/etc/systemd/system/follow_invoices.service` and edit with:
```
[Unit]
Description=RoboSats Follow LND Invoices
After=lnd.service
StartLimitIntervalSec=0
[Service]
WorkingDirectory=/home/<USER>/robosats/
StandardOutput=file:/home/<USER>/robosats/follow_invoices.log
StandardError=file:/home/<USER>/robosats/follow_invoices.log
Type=simple
Restart=always
RestartSec=1
User=<USER>
ExecStart=python3 manage.py follow_invoices
[Install]
WantedBy=multi-user.target
```
Then launch it with
```
systemctl start follow_invoices
systemctl enable follow_invoices
```
### Clean orders admin command as system service
Create `/etc/systemd/system/clean_orders.service` and edit with (replace <USER> for your username):
```
[Unit]
Description=RoboSats Clean Orders
After=lnd.service
StartLimitIntervalSec=0
[Service]
WorkingDirectory=/home/<USER>/robosats/
StandardOutput=file:/home/<USER>/robosats/clean_orders.log
StandardError=file:/home/<USER>/robosats/clean_orders.log
Type=simple
Restart=always
RestartSec=1
User=<USER>
ExecStart=python3 manage.py clean_orders
[Install]
WantedBy=multi-user.target
```
Then launch it with
```
systemctl start clean_orders
systemctl enable clean_orders
```