mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-27 14:22:58 +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
|
||||
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)
|
||||
ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion'
|
||||
ALTERNATIVE_NAME = 'RoboSats Mainnet'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import math
|
||||
from datetime import timedelta
|
||||
|
||||
from decouple import config
|
||||
from decouple import config, Csv
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q, Sum
|
||||
from django.utils import timezone
|
||||
@ -9,7 +9,7 @@ from django.utils import timezone
|
||||
from api.lightning.node import LNNode
|
||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
||||
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
|
||||
|
||||
FEE = float(config("FEE"))
|
||||
@ -29,6 +29,8 @@ MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float(
|
||||
config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED")
|
||||
)
|
||||
|
||||
GEOBLOCKED_COUNTRIES = config("GEOBLOCKED_COUNTRIES", cast=Csv(), default=[])
|
||||
|
||||
|
||||
class Logics:
|
||||
@classmethod
|
||||
@ -137,6 +139,19 @@ class Logics:
|
||||
|
||||
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):
|
||||
if amount > float(order.max_amount) or amount < float(order.min_amount):
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
Parses strings that have Object(ID,NAME) that match API models.
|
||||
|
@ -166,6 +166,10 @@ class MakerView(CreateAPIView):
|
||||
if not valid:
|
||||
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.log(
|
||||
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
|
||||
django-import-export==3.3.8
|
||||
requests[socks]
|
||||
shapely==2.0.4
|
||||
python-gnupg==0.5.2
|
||||
daphne==4.1.0
|
||||
drf-spectacular==0.27.2
|
||||
|
@ -239,6 +239,38 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertIsNone(data["taker"], "New order's taker is not null")
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
self.response = response
|
||||
if response.status_code == 201:
|
||||
self.response = response
|
||||
self.order_id = response.json()["id"]
|
||||
|
||||
def get_order(self, robot_index=1, first_encounter=False):
|
||||
|
Loading…
Reference in New Issue
Block a user