mirror of
https://github.com/Reckless-Satoshi/robosats.git
synced 2024-09-11 11:55:26 +03:00
Feat: add coordinator opt for geoblocked countries (#1258)
* Add location validator * Add bad location tests
This commit is contained in:
parent
ebc1bb70fa
commit
34ef099573
@ -58,6 +58,12 @@ SECRET_KEY = 'django-insecure-6^&6uw$b5^en%(cu2kc7_o)(mgpazx#j_znwlym0vxfamn2uo-
|
|||||||
# e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
|
# e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
|
||||||
ONION_LOCATION = ''
|
ONION_LOCATION = ''
|
||||||
|
|
||||||
|
# Geoblocked countries (will reject F2F trades).
|
||||||
|
# List of A3 country codes (see fhttps://en.wikipedia.org/wiki/ISO_3166-1_alpha-3)
|
||||||
|
# Leave empty '' to allow all countries.
|
||||||
|
# Example 'NOR,USA,CZE'.
|
||||||
|
GEOBLOCKED_COUNTRIES = 'ABW,AFG,AGO'
|
||||||
|
|
||||||
# Link to robosats alternative site (shown in frontend in statsfornerds so users can switch mainnet/testnet)
|
# Link to robosats alternative site (shown in frontend in statsfornerds so users can switch mainnet/testnet)
|
||||||
ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion'
|
ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion'
|
||||||
ALTERNATIVE_NAME = 'RoboSats Mainnet'
|
ALTERNATIVE_NAME = 'RoboSats Mainnet'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from decouple import config
|
from decouple import config, Csv
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q, Sum
|
from django.db.models import Q, Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -9,7 +9,7 @@ from django.utils import timezone
|
|||||||
from api.lightning.node import LNNode
|
from api.lightning.node import LNNode
|
||||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
||||||
from api.tasks import send_devfund_donation, send_notification
|
from api.tasks import send_devfund_donation, send_notification
|
||||||
from api.utils import get_minning_fee, validate_onchain_address
|
from api.utils import get_minning_fee, validate_onchain_address, location_country
|
||||||
from chat.models import Message
|
from chat.models import Message
|
||||||
|
|
||||||
FEE = float(config("FEE"))
|
FEE = float(config("FEE"))
|
||||||
@ -29,6 +29,8 @@ MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float(
|
|||||||
config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED")
|
config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
GEOBLOCKED_COUNTRIES = config("GEOBLOCKED_COUNTRIES", cast=Csv(), default=[])
|
||||||
|
|
||||||
|
|
||||||
class Logics:
|
class Logics:
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -137,6 +139,19 @@ class Logics:
|
|||||||
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate_location(cls, order) -> bool:
|
||||||
|
if not (order.latitude or order.longitude):
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
country = location_country(order.longitude, order.latitude)
|
||||||
|
if country in GEOBLOCKED_COUNTRIES:
|
||||||
|
return False, {
|
||||||
|
"bad_request": f"The coordinator does not support orders in {country}"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return True, None
|
||||||
|
|
||||||
def validate_amount_within_range(order, amount):
|
def validate_amount_within_range(order, amount):
|
||||||
if amount > float(order.max_amount) or amount < float(order.min_amount):
|
if amount > float(order.max_amount) or amount < float(order.min_amount):
|
||||||
return False, {
|
return False, {
|
||||||
|
27
api/utils.py
27
api/utils.py
@ -479,6 +479,33 @@ def is_valid_token(token: str) -> bool:
|
|||||||
return all(c in charset for c in token)
|
return all(c in charset for c in token)
|
||||||
|
|
||||||
|
|
||||||
|
def location_country(lon: float, lat: float) -> str:
|
||||||
|
"""
|
||||||
|
Returns the country code of a lon/lat location
|
||||||
|
"""
|
||||||
|
|
||||||
|
from shapely.geometry import shape, Point
|
||||||
|
from shapely.prepared import prep
|
||||||
|
|
||||||
|
# Load the GeoJSON data from a local file
|
||||||
|
with open("frontend/static/assets/geo/countries-coastline-10km.geo.json") as f:
|
||||||
|
countries_geojeson = json.load(f)
|
||||||
|
|
||||||
|
# Prepare the countries for reverse geocoding
|
||||||
|
countries = {}
|
||||||
|
for feature in countries_geojeson["features"]:
|
||||||
|
geom = feature["geometry"]
|
||||||
|
country_code = feature["properties"]["A3"]
|
||||||
|
countries[country_code] = prep(shape(geom))
|
||||||
|
|
||||||
|
point = Point(lon, lat)
|
||||||
|
for country_code, geom in countries.items():
|
||||||
|
if geom.contains(point):
|
||||||
|
return country_code
|
||||||
|
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
def objects_to_hyperlinks(logs: str) -> str:
|
def objects_to_hyperlinks(logs: str) -> str:
|
||||||
"""
|
"""
|
||||||
Parses strings that have Object(ID,NAME) that match API models.
|
Parses strings that have Object(ID,NAME) that match API models.
|
||||||
|
@ -166,6 +166,10 @@ class MakerView(CreateAPIView):
|
|||||||
if not valid:
|
if not valid:
|
||||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
valid, context = Logics.validate_location(order)
|
||||||
|
if not valid:
|
||||||
|
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
order.save()
|
order.save()
|
||||||
order.log(
|
order.log(
|
||||||
f"Order({order.id},{order}) created by Robot({request.user.robot.id},{request.user})"
|
f"Order({order.id},{order}) created by Robot({request.user.robot.id},{request.user})"
|
||||||
|
@ -22,6 +22,7 @@ psycopg2==2.9.9
|
|||||||
SQLAlchemy==2.0.16
|
SQLAlchemy==2.0.16
|
||||||
django-import-export==3.3.8
|
django-import-export==3.3.8
|
||||||
requests[socks]
|
requests[socks]
|
||||||
|
shapely==2.0.4
|
||||||
python-gnupg==0.5.2
|
python-gnupg==0.5.2
|
||||||
daphne==4.1.0
|
daphne==4.1.0
|
||||||
drf-spectacular==0.27.2
|
drf-spectacular==0.27.2
|
||||||
|
@ -239,6 +239,38 @@ class TradeTest(BaseAPITestCase):
|
|||||||
self.assertIsNone(data["taker"], "New order's taker is not null")
|
self.assertIsNone(data["taker"], "New order's taker is not null")
|
||||||
self.assert_order_logs(data["id"])
|
self.assert_order_logs(data["id"])
|
||||||
|
|
||||||
|
def test_make_order_on_blocked_country(self):
|
||||||
|
"""
|
||||||
|
Test the creation of an F2F order on a geoblocked location
|
||||||
|
"""
|
||||||
|
trade = Trade(
|
||||||
|
self.client,
|
||||||
|
# latitude and longitud in Aruba. One of the countries blocked in the example conf.
|
||||||
|
maker_form={
|
||||||
|
"type": 0,
|
||||||
|
"currency": 1,
|
||||||
|
"has_range": True,
|
||||||
|
"min_amount": 21,
|
||||||
|
"max_amount": 101.7,
|
||||||
|
"payment_method": "Advcash Cash F2F",
|
||||||
|
"is_explicit": False,
|
||||||
|
"premium": 3.34,
|
||||||
|
"public_duration": 69360,
|
||||||
|
"escrow_duration": 8700,
|
||||||
|
"bond_size": 3.5,
|
||||||
|
"latitude": -11.8014, # Angola AGO
|
||||||
|
"longitude": 17.3575,
|
||||||
|
},
|
||||||
|
) # init of Trade calls make_order() with the default maker form.
|
||||||
|
data = trade.response.json()
|
||||||
|
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
self.assertResponse(trade.response)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
data["bad_request"], "The coordinator does not support orders in AGO"
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_order_created(self):
|
def test_get_order_created(self):
|
||||||
"""
|
"""
|
||||||
Tests the creation of an order and the first request to see details,
|
Tests the creation of an order and the first request to see details,
|
||||||
|
@ -98,8 +98,8 @@ class Trade:
|
|||||||
|
|
||||||
response = self.client.post(path, maker_form, **headers)
|
response = self.client.post(path, maker_form, **headers)
|
||||||
|
|
||||||
if response.status_code == 201:
|
|
||||||
self.response = response
|
self.response = response
|
||||||
|
if response.status_code == 201:
|
||||||
self.order_id = response.json()["id"]
|
self.order_id = response.json()["id"]
|
||||||
|
|
||||||
def get_order(self, robot_index=1, first_encounter=False):
|
def get_order(self, robot_index=1, first_encounter=False):
|
||||||
|
Loading…
Reference in New Issue
Block a user