You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
5.2 KiB
132 lines
5.2 KiB
#! /usr/bin/env python |
|
from __future__ import absolute_import, print_function |
|
|
|
import base64 |
|
import pprint |
|
import random |
|
import sqlite3 |
|
import sys |
|
import time |
|
import threading |
|
import json |
|
from decimal import InvalidOperation, Decimal |
|
|
|
from daemon.protocol import JM_VERSION |
|
from base.support import get_log, joinmarket_alert, DUST_THRESHOLD |
|
from daemon.irc import B_PER_SEC |
|
log = get_log() |
|
|
|
|
|
class JMTakerError(Exception): |
|
pass |
|
|
|
|
|
class OrderbookWatch(object): |
|
|
|
def set_msgchan(self, msgchan): |
|
self.msgchan = msgchan |
|
self.msgchan.register_orderbookwatch_callbacks(self.on_order_seen, |
|
self.on_order_cancel) |
|
self.msgchan.register_channel_callbacks( |
|
self.on_welcome, self.on_set_topic, None, self.on_disconnect, |
|
self.on_nick_leave, None) |
|
|
|
self.dblock = threading.Lock() |
|
con = sqlite3.connect(":memory:", check_same_thread=False) |
|
con.row_factory = sqlite3.Row |
|
self.db = con.cursor() |
|
self.db.execute("CREATE TABLE orderbook(counterparty TEXT, " |
|
"oid INTEGER, ordertype TEXT, minsize INTEGER, " |
|
"maxsize INTEGER, txfee INTEGER, cjfee TEXT);") |
|
|
|
@staticmethod |
|
def on_set_topic(newtopic): |
|
chunks = newtopic.split('|') |
|
for msg in chunks[1:]: |
|
try: |
|
msg = msg.strip() |
|
params = msg.split(' ') |
|
min_version = int(params[0]) |
|
max_version = int(params[1]) |
|
alert = msg[msg.index(params[1]) + len(params[1]):].strip() |
|
except ValueError, IndexError: |
|
continue |
|
if min_version < JM_VERSION < max_version: |
|
print('=' * 60) |
|
print('JOINMARKET ALERT') |
|
print(alert) |
|
print('=' * 60) |
|
joinmarket_alert[0] = alert |
|
|
|
def on_order_seen(self, counterparty, oid, ordertype, minsize, maxsize, |
|
txfee, cjfee): |
|
try: |
|
self.dblock.acquire(True) |
|
if int(oid) < 0 or int(oid) > sys.maxint: |
|
log.debug("Got invalid order ID: " + oid + " from " + |
|
counterparty) |
|
return (False, []) |
|
# delete orders eagerly, so in case a buggy maker sends an |
|
# invalid offer, we won't accidentally !fill based on the ghost |
|
# of its previous message. |
|
self.db.execute( |
|
("DELETE FROM orderbook WHERE counterparty=? " |
|
"AND oid=?;"), (counterparty, oid)) |
|
# now validate the remaining fields |
|
if int(minsize) < 0 or int(minsize) > 21 * 10**14: |
|
log.debug("Got invalid minsize: {} from {}".format( |
|
minsize, counterparty)) |
|
return (False, []) |
|
if int(minsize) < DUST_THRESHOLD: |
|
minsize = DUST_THRESHOLD |
|
log.debug("{} has dusty minsize, capping at {}".format( |
|
counterparty, minsize)) |
|
# do not pass return, go not drop this otherwise fine offer |
|
if int(maxsize) < 0 or int(maxsize) > 21 * 10**14: |
|
log.debug("Got invalid maxsize: " + maxsize + " from " + |
|
counterparty) |
|
return (False, []) |
|
if int(txfee) < 0: |
|
log.debug("Got invalid txfee: {} from {}".format(txfee, |
|
counterparty)) |
|
return (False, []) |
|
if int(minsize) > int(maxsize): |
|
|
|
fmt = ("Got minsize bigger than maxsize: {} - {} " |
|
"from {}").format |
|
log.debug(fmt(minsize, maxsize, counterparty)) |
|
return (False, []) |
|
if ordertype == 'absoffer' and not isinstance(cjfee, int): |
|
try: |
|
cjfee = int(cjfee) |
|
except ValueError: |
|
log.debug("Got non integer coinjoin fee: " + str(cjfee) + |
|
" for an absoffer from " + counterparty) |
|
return (False, []) |
|
self.db.execute( |
|
'INSERT INTO orderbook VALUES(?, ?, ?, ?, ?, ?, ?);', |
|
(counterparty, oid, ordertype, minsize, maxsize, txfee, |
|
str(Decimal(cjfee)))) # any parseable Decimal is a valid cjfee |
|
except InvalidOperation: |
|
log.debug("Got invalid cjfee: " + cjfee + " from " + counterparty) |
|
except Exception as e: |
|
log.debug("Error parsing order " + oid + " from " + counterparty) |
|
log.debug("Exception was: " + repr(e)) |
|
finally: |
|
self.dblock.release() |
|
return (True, []) |
|
|
|
def on_order_cancel(self, counterparty, oid): |
|
with self.dblock: |
|
self.db.execute( |
|
("DELETE FROM orderbook WHERE " |
|
"counterparty=? AND oid=?;"), (counterparty, oid)) |
|
|
|
def on_nick_leave(self, nick): |
|
with self.dblock: |
|
self.db.execute('DELETE FROM orderbook WHERE counterparty=?;', |
|
(nick,)) |
|
|
|
def on_disconnect(self): |
|
with self.dblock: |
|
self.db.execute('DELETE FROM orderbook;')
|
|
|