Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use nostr as cache system #1362

Merged
merged 34 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3e460bb
Use nostr as cache system
KoalaSat Jul 1, 2024
97bb2dc
Prepare for other events
KoalaSat Jul 1, 2024
bdc10eb
Switch to strfry
KoalaSat Jul 15, 2024
cbb063f
NIP corrections
KoalaSat Jul 15, 2024
6635722
Addapt Docker
KoalaSat Jul 15, 2024
a6c2130
Testing
KoalaSat Jul 16, 2024
c597cc8
Better tags
KoalaSat Jul 16, 2024
c1e9da5
Test entrypoint
KoalaSat Jul 17, 2024
b7f9f18
Working
KoalaSat Jul 19, 2024
334587b
Sync filtering
KoalaSat Jul 19, 2024
7accfcf
Torify strfry sync
KoalaSat Jul 20, 2024
be3bd80
CR
KoalaSat Jul 20, 2024
9b7240e
Real onions
KoalaSat Jul 21, 2024
c28ba4a
Some NIP fixes
KoalaSat Jul 21, 2024
ae45029
Filter as variable in sync.sh
KoalaSat Jul 22, 2024
ae18acd
Fix unmutable conf
KoalaSat Jul 24, 2024
7d0b3ba
Fix unmutable conf 2
KoalaSat Jul 24, 2024
fda906b
Fix unmutable conf 3
KoalaSat Jul 24, 2024
73189af
Tesnet sync
KoalaSat Jul 24, 2024
c0d0872
Tesnet onions
KoalaSat Jul 24, 2024
ab1a488
Docker write access
KoalaSat Jul 24, 2024
507082d
Logs to docker
KoalaSat Jul 24, 2024
e3e530c
executable sync
KoalaSat Jul 24, 2024
dd35a3e
executable sync
KoalaSat Jul 24, 2024
19fc1f0
Remove test dockers
KoalaSat Aug 6, 2024
af3a03c
Async to sync
KoalaSat Aug 7, 2024
032a48a
Review nostr tags
KoalaSat Aug 7, 2024
1c7b2a8
Fix integer tag
KoalaSat Aug 7, 2024
cad1a86
Fix integer tag
KoalaSat Aug 7, 2024
3433f45
Fix url tag
KoalaSat Aug 7, 2024
d79f27b
Remove created at
KoalaSat Aug 7, 2024
7ef8a1b
strfry bug free version
KoalaSat Aug 9, 2024
58d21d9
Better sync urls
KoalaSat Aug 9, 2024
e9620e3
Merge branch 'main' into use-nostr-as-cache-system
KoalaSat Aug 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,6 @@ SLASHED_BOND_REWARD_SPLIT = 0.5

# Username for HTLCs escrows
ESCROW_USERNAME = 'admin'

#Social
NOSTR_NSEC = 'nsec1vxhs2zc4kqe0dhz4z2gfrdyjsrwf8pg3neeqx6w4nl8djfzdp0dqwd6rxh'
49 changes: 39 additions & 10 deletions api/logics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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.tasks import send_devfund_donation, send_notification, nostr_send_order_event
from api.utils import get_minning_fee, validate_onchain_address, location_country
from chat.models import Message

Expand Down Expand Up @@ -704,19 +704,19 @@ def payout_amount(cls, order, user):

if context["invoice_amount"] < MIN_SWAP_AMOUNT:
context["swap_allowed"] = False
context[
"swap_failure_reason"
] = f"Order amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats"
context["swap_failure_reason"] = (
f"Order amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats"
)
order.log(
f"Onchain payment option was not offered: amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats",
level="WARN",
)
return True, context
elif context["invoice_amount"] > MAX_SWAP_AMOUNT:
context["swap_allowed"] = False
context[
"swap_failure_reason"
] = f"Order amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats"
context["swap_failure_reason"] = (
f"Order amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats"
)
order.log(
f"Onchain payment option was not offered: amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats",
level="WARN",
Expand All @@ -741,9 +741,9 @@ def payout_amount(cls, order, user):
)
if not valid:
context["swap_allowed"] = False
context[
"swap_failure_reason"
] = "Not enough onchain liquidity available to offer a swap"
context["swap_failure_reason"] = (
"Not enough onchain liquidity available to offer a swap"
)
order.log(
"Onchain payment option was not offered: onchain liquidity available to offer a swap",
level="WARN",
Expand Down Expand Up @@ -1019,6 +1019,8 @@ def cancel_order(cls, order, user, state=None):
order.log("Order expired while waiting for maker bond")
order.log("Maker bond was cancelled")

nostr_send_order_event.delay(order_id=order.id)

return True, None

# 2.a) When maker cancels after bond
Expand All @@ -1039,6 +1041,8 @@ def cancel_order(cls, order, user, state=None):
order.log("Order cancelled by maker while public or paused")
order.log("Maker bond was <b>unlocked</b>")

nostr_send_order_event.delay(order_id=order.id)

return True, None

# 2.b) When maker cancels after bond and before taker bond is locked
Expand All @@ -1058,6 +1062,8 @@ def cancel_order(cls, order, user, state=None):
order.log("Maker bond was <b>unlocked</b>")
order.log("Taker bond was <b>cancelled</b>")

nostr_send_order_event.delay(order_id=order.id)

return True, None

# 3) When taker cancels before bond
Expand All @@ -1070,6 +1076,8 @@ def cancel_order(cls, order, user, state=None):

order.log("Taker cancelled before locking the bond")

nostr_send_order_event.delay(order_id=order.id)

return True, None

# 4) When taker or maker cancel after bond (before escrow)
Expand Down Expand Up @@ -1099,6 +1107,8 @@ def cancel_order(cls, order, user, state=None):
order.log("Maker bond was <b>settled</b>")
order.log("Taker bond was <b>unlocked</b>")

nostr_send_order_event.delay(order_id=order.id)

return True, None

# 4.b) When taker cancel after bond (before escrow)
Expand All @@ -1121,6 +1131,8 @@ def cancel_order(cls, order, user, state=None):
order.log("Taker bond was <b>settled</b>")
order.log("Maker bond was <b>unlocked</b>")

nostr_send_order_event.delay(order_id=order.id)

return True, None

# 5) When trade collateral has been posted (after escrow)
Expand All @@ -1136,6 +1148,9 @@ def cancel_order(cls, order, user, state=None):
order.log(
f"Taker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation"
)

nostr_send_order_event.delay(order_id=order.id)

return True, None

# if the taker had asked, and now the maker does: cancel order, return everything
Expand All @@ -1144,6 +1159,9 @@ def cancel_order(cls, order, user, state=None):
order.log(
f"Maker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation"
)

nostr_send_order_event.delay(order_id=order.id)

return True, None

# Otherwise just make true the asked for cancel flags
Expand Down Expand Up @@ -1181,6 +1199,8 @@ def collaborative_cancel(cls, order):
order.update_status(Order.Status.CCA)
send_notification.delay(order_id=order.id, message="collaborative_cancelled")

nostr_send_order_event.delay(order_id=order.id)

order.log("Order was collaboratively cancelled")
order.log("Maker bond was <b>unlocked</b>")
order.log("Taker bond was <b>unlocked</b>")
Expand Down Expand Up @@ -1208,6 +1228,8 @@ def publish_order(cls, order):

order.save() # update all fields

nostr_send_order_event.delay(order_id=order.id)

order.log(f"Order({order.id},{str(order)}) is public in the order book")
return

Expand Down Expand Up @@ -1350,6 +1372,9 @@ def finalize_contract(cls, order):
except Exception:
pass
send_notification.delay(order_id=order.id, message="order_taken_confirmed")

nostr_send_order_event.delay(order_id=order.id)

order.log(
f"<b>Contract formalized.</b> Maker: Robot({order.maker.robot.id},{order.maker}). Taker: Robot({order.taker.robot.id},{order.taker}). API median price {order.currency.exchange_rate} {dict(Currency.currency_choices)[order.currency.currency]}/BTC. Premium is {order.premium}%. Contract size {order.last_satoshis} Sats"
)
Expand Down Expand Up @@ -1741,11 +1766,15 @@ def pause_unpause_public_order(order, user):
order.log(
f"Robot({user.robot.id},{user.username}) paused the public order"
)

nostr_send_order_event.delay(order_id=order.id)
elif order.status == Order.Status.PAU:
order.update_status(Order.Status.PUB)
order.log(
f"Robot({user.robot.id},{user.username}) made public the paused order"
)

nostr_send_order_event.delay(order_id=order.id)
else:
order.log(
f"Robot({user.robot.id},{user.username}) tried to pause/unpause an order that was not public or paused",
Expand Down
98 changes: 98 additions & 0 deletions api/nostr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import pygeohash
import hashlib
import uuid

from asgiref.sync import sync_to_async
from nostr_sdk import Keys, Client, EventBuilder, NostrSigner, Kind, Tag
from api.models import Order
from decouple import config


class Nostr:
"""Simple nostr events manager to be used as a cache system for clients"""

async def send_order_event(self, order):
"""Creates the event and sends it to the coordinator relay"""

if config("NOSTR_NSEC", cast=str, default="") == "":
return

print("Sending nostr event")

# Initialize with coordinator Keys
keys = Keys.parse(config("NOSTR_NSEC", cast=str))
signer = NostrSigner.keys(keys)
client = Client(signer)

# Add relays and connect
await client.add_relays(["ws://localhost:7777"])
await client.connect()

robot_name = await self.get_robot_name(order)
currency = await self.get_robot_currency(order)

event = EventBuilder(
Kind(38383), "", self.generate_tags(order, robot_name, currency)
).to_event(keys)
await client.send_event(event)
print(f"Nostr event sent: {event.as_json()}")

@sync_to_async
def get_robot_name(self, order):
return order.maker.username

@sync_to_async
def get_robot_currency(self, order):
return str(order.currency)

def generate_tags(self, order, robot_name, currency):
hashed_id = hashlib.md5(
f"{config("COORDINATOR_ALIAS", cast=str)}{order.id}".encode("utf-8")
).hexdigest()

tags = [
Tag.parse(["d", str(uuid.UUID(hashed_id))]),
Tag.parse(["name", robot_name]),
Tag.parse(["k", "sell" if order.type == Order.Types.SELL else "buy"]),
Tag.parse(["f", currency]),
Tag.parse(["s", self.get_status_tag(order)]),
Tag.parse(["amt", "0"]),
Tag.parse(
["fa"] + [str(order.amount)]
if not order.has_range
else [str(order.min_amount), str(order.max_amount)]
),
Tag.parse(["pm"] + order.payment_method.split(" ")),
Tag.parse(["premium", str(order.premium)]),
Tag.parse(
[
"source",
f"http://{config("HOST_NAME")}/order/{config("COORDINATOR_ALIAS", cast=str).lower()}/{order.id}",
]
),
Tag.parse(["expiration", str(int(order.expires_at.timestamp()))]),
Tag.parse(["y", "robosats", config("COORDINATOR_ALIAS", cast=str).lower()]),
Tag.parse(["n", str(config("NETWORK"))]),
Tag.parse(["layer"] + self.get_layer_tag(order)),
Tag.parse(["bond", str(order.bond_size)]),
Tag.parse(["z", "order"]),
]

if order.latitude and order.longitude:
tags.extend(
[Tag.parse(["g", pygeohash.encode(order.latitude, order.longitude)])]
)

return tags

def get_status_tag(self, order):
if order.status == Order.Status.PUB:
return "pending"
else:
return "success"

def get_layer_tag(self, order):
if order.type == Order.Types.SELL:
return ["onchain", "lightning"]
else:
return ["lightning"]
15 changes: 15 additions & 0 deletions api/tasks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from asgiref.sync import async_to_sync
from celery import shared_task
from celery.exceptions import SoftTimeLimitExceeded

Expand Down Expand Up @@ -251,6 +252,20 @@ def cache_market():
return


@shared_task(name="", ignore_result=True, time_limit=120)
def nostr_send_order_event(order_id=None):
if order_id:
from api.models import Order
from api.nostr import Nostr

order = Order.objects.get(id=order_id)

nostr = Nostr()
async_to_sync(nostr.send_order_event)(order)

return


@shared_task(name="send_notification", ignore_result=True, time_limit=120)
def send_notification(order_id=None, chat_message_id=None, message=None):
if order_id:
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ services:
volumes:
- ./node/db:/var/lib/postgresql/data

strfry:
build: ./docker/strfry
container_name: strfry-dev
restart: unless-stopped
volumes:
- ./docker/strfry/strfry.conf:/etc/strfry.conf:ro
- ./docker/strfry/onion_urls.txt:/app/onion_urls.txt:ro
- ./node/strfry/db:/app/strfry-db:rw
network_mode: service:tor

# # Postgresql for CLN
# postgres-cln:
# image: postgres:14.2-alpine
Expand Down
3 changes: 2 additions & 1 deletion docker-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ services:
- "9998:9998"
- "5432:5432"
- "6379:6379"
- "7777:7777"
volumes:
- bitcoin:/bitcoin/.bitcoin/
- ./tests/bitcoind/entrypoint.sh:/entrypoint.sh
Expand Down Expand Up @@ -182,7 +183,7 @@ services:
# celery-worker:
# image: backend-image
# pull_policy: never
# container_name: celery-worker
# container_name: test-celery-worker
# restart: always
# environment:
# DEVELOPMENT: True
Expand Down
41 changes: 41 additions & 0 deletions docker/strfry/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
FROM ubuntu:jammy
ENV TZ=Europe/London

RUN apt update && apt install -y --no-install-recommends \
git g++ make pkg-config libtool ca-certificates \
libssl-dev zlib1g-dev liblmdb-dev libflatbuffers-dev \
libsecp256k1-dev libzstd-dev

# setup app
RUN git clone https://github.com/KoalaSat/strfry /app

WORKDIR /app

RUN git submodule update --init
RUN make setup-golpe
RUN make clean
RUN make -j4

RUN apt update && apt install -y --no-install-recommends \
liblmdb0 libflatbuffers1 libsecp256k1-0 libb2-1 libzstd1 torsocks cron\
&& rm -rf /var/lib/apt/lists/*

RUN echo "TorAddress 127.0.0.1" >> /etc/tor/torsocks.conf
RUN echo "TorPort 9050" >> /etc/tor/torsocks.conf

# Setting up crontab
COPY crontab /etc/cron.d/crontab
RUN chmod 0644 /etc/cron.d/crontab
RUN crontab /etc/cron.d/crontab

# Setting up entrypoints
COPY sync.sh /etc/strfry/sync.sh
COPY entrypoint.sh /etc/strfry/entrypoint.sh

RUN chmod +x /etc/strfry/entrypoint.sh
RUN chmod +x /etc/strfry/sync.sh

#Setting up logs
RUN touch /var/log/cron.log && chmod 0644 /var/log/cron.log

ENTRYPOINT ["/etc/strfry/entrypoint.sh"]
24 changes: 24 additions & 0 deletions docker/strfry/crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
*/1 * * * * torsocks /etc/strfry/sync.sh >> /var/log/cron.log 2>&1
Loading