|
|
|
@ -1,23 +1,26 @@ |
|
|
|
#!/usr/bin/env python3 |
|
|
|
#!/usr/bin/env python3 |
|
|
|
from functools import cmp_to_key |
|
|
|
from functools import cmp_to_key |
|
|
|
|
|
|
|
|
|
|
|
import http.server |
|
|
|
|
|
|
|
import base64 |
|
|
|
import base64 |
|
|
|
|
|
|
|
import hashlib |
|
|
|
|
|
|
|
import html |
|
|
|
|
|
|
|
import http.server |
|
|
|
import io |
|
|
|
import io |
|
|
|
import json |
|
|
|
import json |
|
|
|
|
|
|
|
import os |
|
|
|
import threading |
|
|
|
import threading |
|
|
|
import time |
|
|
|
import time |
|
|
|
import hashlib |
|
|
|
|
|
|
|
import os |
|
|
|
|
|
|
|
import sys |
|
|
|
import sys |
|
|
|
from urllib.parse import parse_qs |
|
|
|
from datetime import datetime, timedelta |
|
|
|
from decimal import Decimal |
|
|
|
from decimal import Decimal |
|
|
|
from optparse import OptionParser |
|
|
|
from optparse import OptionParser |
|
|
|
|
|
|
|
from typing import Tuple, Union |
|
|
|
from twisted.internet import reactor |
|
|
|
from twisted.internet import reactor |
|
|
|
from datetime import datetime, timedelta |
|
|
|
from urllib.parse import parse_qs |
|
|
|
|
|
|
|
|
|
|
|
from jmbase.support import EXIT_FAILURE |
|
|
|
|
|
|
|
from jmbase import bintohex |
|
|
|
from jmbase import bintohex |
|
|
|
|
|
|
|
from jmbase.support import EXIT_FAILURE |
|
|
|
|
|
|
|
from jmbitcoin import bitcoin_unit_to_power, sat_to_unit, sat_to_unit_power |
|
|
|
from jmclient import FidelityBondMixin, get_interest_rate, check_and_start_tor |
|
|
|
from jmclient import FidelityBondMixin, get_interest_rate, check_and_start_tor |
|
|
|
from jmclient.fidelity_bond import FidelityBondProof |
|
|
|
from jmclient.fidelity_bond import FidelityBondProof |
|
|
|
|
|
|
|
|
|
|
|
@ -55,7 +58,6 @@ filtered_offername_list = sw0offers |
|
|
|
rotateObform = '<form action="rotateOb" method="post"><input type="submit" value="Rotate orderbooks"/></form>' |
|
|
|
rotateObform = '<form action="rotateOb" method="post"><input type="submit" value="Rotate orderbooks"/></form>' |
|
|
|
refresh_orderbook_form = '<form action="refreshorderbook" method="post"><input type="submit" value="Check for timed-out counterparties" /></form>' |
|
|
|
refresh_orderbook_form = '<form action="refreshorderbook" method="post"><input type="submit" value="Check for timed-out counterparties" /></form>' |
|
|
|
sorted_units = ('BTC', 'mBTC', 'μBTC', 'satoshi') |
|
|
|
sorted_units = ('BTC', 'mBTC', 'μBTC', 'satoshi') |
|
|
|
unit_to_power = {'BTC': 8, 'mBTC': 5, 'μBTC': 2, 'satoshi': 0} |
|
|
|
|
|
|
|
sorted_rel_units = ('%', '‱', 'ppm') |
|
|
|
sorted_rel_units = ('%', '‱', 'ppm') |
|
|
|
rel_unit_to_factor = {'%': 100, '‱': 1e4, 'ppm': 1e6} |
|
|
|
rel_unit_to_factor = {'%': 100, '‱': 1e4, 'ppm': 1e6} |
|
|
|
|
|
|
|
|
|
|
|
@ -82,24 +84,33 @@ def ordertype_display(ordertype, order, btc_unit, rel_unit): |
|
|
|
return ordertypes[ordertype] |
|
|
|
return ordertypes[ordertype] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cjfee_display(cjfee, order, btc_unit, rel_unit): |
|
|
|
def cjfee_display(cjfee: Union[Decimal, float, int], |
|
|
|
|
|
|
|
order: dict, |
|
|
|
|
|
|
|
btc_unit: str, |
|
|
|
|
|
|
|
rel_unit: str) -> str: |
|
|
|
if order['ordertype'] in ['swabsoffer', 'sw0absoffer']: |
|
|
|
if order['ordertype'] in ['swabsoffer', 'sw0absoffer']: |
|
|
|
return satoshi_to_unit(cjfee, order, btc_unit, rel_unit) |
|
|
|
val = sat_to_unit(cjfee, html.unescape(btc_unit)) |
|
|
|
|
|
|
|
if btc_unit == "BTC": |
|
|
|
|
|
|
|
return "%.8f" % val |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
return str(val) |
|
|
|
elif order['ordertype'] in ['reloffer', 'swreloffer', 'sw0reloffer']: |
|
|
|
elif order['ordertype'] in ['reloffer', 'swreloffer', 'sw0reloffer']: |
|
|
|
return str(Decimal(cjfee) * Decimal(rel_unit_to_factor[rel_unit])) + rel_unit |
|
|
|
return str(Decimal(cjfee) * Decimal(rel_unit_to_factor[rel_unit])) + rel_unit |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def satoshi_to_unit_power(sat, power): |
|
|
|
|
|
|
|
return ("%." + str(power) + "f") % float( |
|
|
|
|
|
|
|
Decimal(sat) / Decimal(10 ** power)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def satoshi_to_unit(sat, order, btc_unit, rel_unit): |
|
|
|
|
|
|
|
return satoshi_to_unit_power(sat, unit_to_power[btc_unit]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def order_str(s, order, btc_unit, rel_unit): |
|
|
|
def order_str(s, order, btc_unit, rel_unit): |
|
|
|
return str(s) |
|
|
|
return str(s) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bond_value_to_str(bond_value: Decimal, btc_unit: str) -> str: |
|
|
|
|
|
|
|
if btc_unit == "BTC": |
|
|
|
|
|
|
|
return "%.16f" % bond_value |
|
|
|
|
|
|
|
elif btc_unit == "mBTC": |
|
|
|
|
|
|
|
return "%.10f" % bond_value |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
return str(bond_value) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_offerbook_table_heading(btc_unit, rel_unit): |
|
|
|
def create_offerbook_table_heading(btc_unit, rel_unit): |
|
|
|
col = ' <th>{1}</th>\n' # .format(field,label) |
|
|
|
col = ' <th>{1}</th>\n' # .format(field,label) |
|
|
|
tableheading = '<table class="tftable sortable" border="1">\n <tr>' + ''.join( |
|
|
|
tableheading = '<table class="tftable sortable" border="1">\n <tr>' + ''.join( |
|
|
|
@ -319,7 +330,7 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
return get_graph_html(fig) + ("<br/><a href='?scale=log'>log scale</a>" if |
|
|
|
return get_graph_html(fig) + ("<br/><a href='?scale=log'>log scale</a>" if |
|
|
|
bins == 30 else "<br/><a href='?'>linear</a>") |
|
|
|
bins == 30 else "<br/><a href='?'>linear</a>") |
|
|
|
|
|
|
|
|
|
|
|
def create_fidelity_bond_table(self, btc_unit): |
|
|
|
def create_fidelity_bond_table(self, btc_unit: str) -> Tuple[str, str]: |
|
|
|
if jm_single().bc_interface == None: |
|
|
|
if jm_single().bc_interface == None: |
|
|
|
with self.taker.dblock: |
|
|
|
with self.taker.dblock: |
|
|
|
fbonds = self.taker.db.execute("SELECT * FROM fidelitybonds;").fetchall() |
|
|
|
fbonds = self.taker.db.execute("SELECT * FROM fidelitybonds;").fetchall() |
|
|
|
@ -339,12 +350,12 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
else: |
|
|
|
else: |
|
|
|
(fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times) =\ |
|
|
|
(fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times) =\ |
|
|
|
get_fidelity_bond_data(self.taker) |
|
|
|
get_fidelity_bond_data(self.taker) |
|
|
|
total_btc_committed_str = satoshi_to_unit( |
|
|
|
total_btc_committed_str = str(sat_to_unit( |
|
|
|
sum([utxo_data["value"] for _, utxo_data in fidelity_bond_data]), |
|
|
|
sum([utxo_data["value"] for _, utxo_data in fidelity_bond_data]), |
|
|
|
None, btc_unit, 0) |
|
|
|
html.unescape(btc_unit))) |
|
|
|
|
|
|
|
|
|
|
|
RETARGET_INTERVAL = 2016 |
|
|
|
RETARGET_INTERVAL = 2016 |
|
|
|
elem = lambda e: "<td>" + e + "</td>" |
|
|
|
elem = lambda e: f"<td>{e}</td>" |
|
|
|
bondtable = "" |
|
|
|
bondtable = "" |
|
|
|
for (bond_data, utxo_data), bond_value, conf_time in zip( |
|
|
|
for (bond_data, utxo_data), bond_value, conf_time in zip( |
|
|
|
fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times): |
|
|
|
fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times): |
|
|
|
@ -354,9 +365,11 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
conf_time_str = "No data" |
|
|
|
conf_time_str = "No data" |
|
|
|
utxo_value_str = "No data" |
|
|
|
utxo_value_str = "No data" |
|
|
|
else: |
|
|
|
else: |
|
|
|
bond_value_str = satoshi_to_unit_power(bond_value, 2*unit_to_power[btc_unit]) |
|
|
|
bond_value_str = bond_value_to_str(sat_to_unit_power(bond_value, |
|
|
|
|
|
|
|
2 * bitcoin_unit_to_power(html.unescape(btc_unit))), |
|
|
|
|
|
|
|
html.unescape(btc_unit)) |
|
|
|
conf_time_str = str(datetime.utcfromtimestamp(0) + timedelta(seconds=conf_time)) |
|
|
|
conf_time_str = str(datetime.utcfromtimestamp(0) + timedelta(seconds=conf_time)) |
|
|
|
utxo_value_str = satoshi_to_unit(utxo_data["value"], None, btc_unit, 0) |
|
|
|
utxo_value_str = sat_to_unit(utxo_data["value"], html.unescape(btc_unit)) |
|
|
|
bondtable += ("<tr>" |
|
|
|
bondtable += ("<tr>" |
|
|
|
+ elem(bond_data.maker_nick) |
|
|
|
+ elem(bond_data.maker_nick) |
|
|
|
+ elem(bintohex(bond_data.utxo[0]) + ":" + str(bond_data.utxo[1])) |
|
|
|
+ elem(bintohex(bond_data.utxo[0]) + ":" + str(bond_data.utxo[1])) |
|
|
|
@ -391,7 +404,7 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
choose_units_form + create_bonds_table_heading(btc_unit) + bondtable + "</table>" |
|
|
|
choose_units_form + create_bonds_table_heading(btc_unit) + bondtable + "</table>" |
|
|
|
+ decodescript_tip) |
|
|
|
+ decodescript_tip) |
|
|
|
|
|
|
|
|
|
|
|
def create_sybil_resistance_page(self, btc_unit): |
|
|
|
def create_sybil_resistance_page(self, btc_unit: str) -> Tuple[str, str]: |
|
|
|
if jm_single().bc_interface == None: |
|
|
|
if jm_single().bc_interface == None: |
|
|
|
return "", "Calculations unavailable, requires configured bitcoin node." |
|
|
|
return "", "Calculations unavailable, requires configured bitcoin node." |
|
|
|
|
|
|
|
|
|
|
|
@ -412,7 +425,7 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
mainbody += ("Assuming the makers in the offerbook right now are not sybil attackers, " |
|
|
|
mainbody += ("Assuming the makers in the offerbook right now are not sybil attackers, " |
|
|
|
+ "how much would a sybil attacker starting now have to sacrifice to succeed in their" |
|
|
|
+ "how much would a sybil attacker starting now have to sacrifice to succeed in their" |
|
|
|
+ " attack with 95% probability. Honest weight=" |
|
|
|
+ " attack with 95% probability. Honest weight=" |
|
|
|
+ satoshi_to_unit_power(honest_weight, 2*unit_to_power[btc_unit]) + " " + btc_unit |
|
|
|
+ str(sat_to_unit_power(honest_weight, 2 * bitcoin_unit_to_power(html.unescape(btc_unit)))) + " " + btc_unit |
|
|
|
+ "<sup>" + bond_exponent + "</sup><br/>Also assumes that takers " |
|
|
|
+ "<sup>" + bond_exponent + "</sup><br/>Also assumes that takers " |
|
|
|
+ "are not price-sensitive and that their max " |
|
|
|
+ "are not price-sensitive and that their max " |
|
|
|
+ "coinjoin fee is configured high enough that they dont exclude any makers.") |
|
|
|
+ "coinjoin fee is configured high enough that they dont exclude any makers.") |
|
|
|
@ -440,7 +453,7 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
interest_rate, timelock) |
|
|
|
interest_rate, timelock) |
|
|
|
else: |
|
|
|
else: |
|
|
|
coins_per_sybil = sybil.weight_to_burned_coins(success_sybil_weight) |
|
|
|
coins_per_sybil = sybil.weight_to_burned_coins(success_sybil_weight) |
|
|
|
row += ("<td>" + satoshi_to_unit(coins_per_sybil*makercount, None, btc_unit, 0) |
|
|
|
row += ("<td>" + str(sat_to_unit(coins_per_sybil * makercount, html.unescape(btc_unit))) |
|
|
|
+ "</td>") |
|
|
|
+ "</td>") |
|
|
|
row += "</tr>" |
|
|
|
row += "</tr>" |
|
|
|
mainbody += row |
|
|
|
mainbody += row |
|
|
|
@ -472,7 +485,9 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
foregone_value = (sybil.coins_burned_to_weight(sum(sacrificed_values)) |
|
|
|
foregone_value = (sybil.coins_burned_to_weight(sum(sacrificed_values)) |
|
|
|
- total_sybil_weight) |
|
|
|
- total_sybil_weight) |
|
|
|
mainbody += ("<tr><td>" + makercount_str + "</td><td>" + str(round(success_prob * 100.0, 5)) |
|
|
|
mainbody += ("<tr><td>" + makercount_str + "</td><td>" + str(round(success_prob * 100.0, 5)) |
|
|
|
+ "%</td><td>" + satoshi_to_unit_power(foregone_value, 2*unit_to_power[btc_unit]) |
|
|
|
+ "%</td><td>" + bond_value_to_str(sat_to_unit_power( |
|
|
|
|
|
|
|
foregone_value, 2 * bitcoin_unit_to_power( |
|
|
|
|
|
|
|
html.unescape(btc_unit))), html.unescape(btc_unit)) |
|
|
|
+ "</td></tr>") |
|
|
|
+ "</td></tr>") |
|
|
|
if makercount == len(weights): |
|
|
|
if makercount == len(weights): |
|
|
|
break |
|
|
|
break |
|
|
|
@ -480,7 +495,7 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
|
|
|
|
|
|
|
|
return heading2, mainbody |
|
|
|
return heading2, mainbody |
|
|
|
|
|
|
|
|
|
|
|
def create_orderbook_table(self, btc_unit, rel_unit): |
|
|
|
def create_orderbook_table(self, btc_unit: str, rel_unit: str) -> Tuple[int, str]: |
|
|
|
result = '' |
|
|
|
result = '' |
|
|
|
try: |
|
|
|
try: |
|
|
|
self.taker.dblock.acquire(True) |
|
|
|
self.taker.dblock.acquire(True) |
|
|
|
@ -532,15 +547,25 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
parsed_bond.locktime, |
|
|
|
parsed_bond.locktime, |
|
|
|
mediantime, |
|
|
|
mediantime, |
|
|
|
interest_rate) |
|
|
|
interest_rate) |
|
|
|
row["bondvalue"] = satoshi_to_unit_power(bond_value, 2*unit_to_power[btc_unit]) |
|
|
|
row["bondvalue"] = bond_value_to_str(sat_to_unit_power( |
|
|
|
|
|
|
|
bond_value, |
|
|
|
|
|
|
|
2 * bitcoin_unit_to_power(html.unescape(btc_unit))), |
|
|
|
|
|
|
|
html.unescape(btc_unit)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _okd_satoshi_to_unit(sat, order, btc_unit, rel_unit): |
|
|
|
|
|
|
|
val = sat_to_unit(sat, html.unescape(btc_unit)) |
|
|
|
|
|
|
|
if btc_unit == "BTC": |
|
|
|
|
|
|
|
return "%.8f" % val |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
return str(val) |
|
|
|
|
|
|
|
|
|
|
|
order_keys_display = (('ordertype', ordertype_display), |
|
|
|
order_keys_display = (('ordertype', ordertype_display), |
|
|
|
('counterparty', do_nothing), |
|
|
|
('counterparty', do_nothing), |
|
|
|
('oid', order_str), |
|
|
|
('oid', order_str), |
|
|
|
('cjfee', cjfee_display), |
|
|
|
('cjfee', cjfee_display), |
|
|
|
('txfee', satoshi_to_unit), |
|
|
|
('txfee', _okd_satoshi_to_unit), |
|
|
|
('minsize', satoshi_to_unit), |
|
|
|
('minsize', _okd_satoshi_to_unit), |
|
|
|
('maxsize', satoshi_to_unit), |
|
|
|
('maxsize', _okd_satoshi_to_unit), |
|
|
|
('bondvalue', do_nothing)) |
|
|
|
('bondvalue', do_nothing)) |
|
|
|
|
|
|
|
|
|
|
|
def _cmp(x, y): |
|
|
|
def _cmp(x, y): |
|
|
|
@ -561,8 +586,8 @@ class OrderbookPageRequestHeader(http.server.SimpleHTTPRequestHandler): |
|
|
|
for o in sorted(rows, key=cmp_to_key(orderby_cmp)): |
|
|
|
for o in sorted(rows, key=cmp_to_key(orderby_cmp)): |
|
|
|
result += ' <tr>\n' |
|
|
|
result += ' <tr>\n' |
|
|
|
for key, displayer in order_keys_display: |
|
|
|
for key, displayer in order_keys_display: |
|
|
|
result += ' <td>' + displayer(o[key], o, btc_unit, |
|
|
|
result += ' <td>' + str(displayer(o[key], o, btc_unit, |
|
|
|
rel_unit) + '</td>\n' |
|
|
|
rel_unit)) + '</td>\n' |
|
|
|
result += ' </tr>\n' |
|
|
|
result += ' </tr>\n' |
|
|
|
return len(rows), result |
|
|
|
return len(rows), result |
|
|
|
|
|
|
|
|
|
|
|
|