Feat: add coordinator opt for geoblocked countries (#1258)

* Add location validator

* Add bad location tests
This commit is contained in:
Reckless_Satoshi 2024-04-29 22:58:03 +00:00 committed by GitHub
parent ebc1bb70fa
commit 34ef099573
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 88 additions and 3 deletions

View File

@ -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'

View File

@ -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, {

View File

@ -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.

View File

@ -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})"

View File

@ -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

View File

@ -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,

View File

@ -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):