#!/usr/bin/env python3 from functools import cmp_to_key import base64 import hashlib import html import http.server import io import json import os import threading import time import sys from datetime import datetime, timedelta from decimal import Decimal from optparse import OptionParser from typing import Tuple, Union from twisted.internet import reactor from urllib.parse import parse_qs 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.fidelity_bond import FidelityBondProof import sybil_attack_calculations as sybil from jmbase import get_log log = get_log() try: import matplotlib except: log.warning("matplotlib not found, charts will not be available. " "Do `pip install matplotlib` in the joinmarket virtual environment.") if 'matplotlib' in sys.modules: # https://stackoverflow.com/questions/2801882/generating-a-png-with-matplotlib-when-display-is-undefined matplotlib.use('Agg') import matplotlib.pyplot as plt from jmclient import jm_single, load_program_config, calc_cj_fee, \ get_mchannels, add_base_options from jmdaemon import (OrderbookWatch, MessageChannelCollection, OnionMessageChannel, IRCMessageChannel) #TODO this is only for base58, find a solution for a client without jmbitcoin import jmbitcoin as btc from jmdaemon.protocol import * bond_exponent = None #Initial state: allow only SW offer types sw0offers = list(filter(lambda x: x[0:3] == 'sw0', offername_list)) swoffers = list(filter(lambda x: x[0:3] == 'swa' or x[0:3] == 'swr', offername_list)) filtered_offername_list = sw0offers rotateObform = '
' refresh_orderbook_form = '
' sorted_units = ('BTC', 'mBTC', 'μBTC', 'satoshi') sorted_rel_units = ('%', '‱', 'ppm') rel_unit_to_factor = {'%': 100, '‱': 1e4, 'ppm': 1e6} def calc_depth_data(db, value): pass def get_graph_html(fig): imbuf = io.BytesIO() fig.savefig(imbuf, format='png') b64 = base64.b64encode(imbuf.getvalue()).decode('utf-8') return '' # callback functions for displaying order data def do_nothing(arg, order, btc_unit, rel_unit): return arg def ordertype_display(ordertype, order, btc_unit, rel_unit): ordertypes = {'sw0absoffer': 'Native SW Absolute Fee', 'sw0reloffer': 'Native SW Relative Fee', 'swabsoffer': 'SW Absolute Fee', 'swreloffer': 'SW Relative Fee'} return ordertypes[ordertype] def cjfee_display(cjfee: Union[Decimal, float, int], order: dict, btc_unit: str, rel_unit: str) -> str: if order['ordertype'] in ['swabsoffer', 'sw0absoffer']: 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']: return str(Decimal(cjfee) * Decimal(rel_unit_to_factor[rel_unit])) + rel_unit def order_str(s, order, btc_unit, rel_unit): 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): col = ' {1}\n' # .format(field,label) tableheading = '\n ' + ''.join( [ col.format('ordertype', 'Type'), col.format('counterparty', 'Counterparty'), col.format('oid', 'Order ID'), col.format('cjfee', 'Fee'), col.format('txfee', 'Miner Fee Contribution / ' + btc_unit), col.format('minsize', 'Minimum Size / ' + btc_unit), col.format('maxsize', 'Maximum Size / ' + btc_unit), col.format('bondvalue', 'Bond value / ' + btc_unit + '' + bond_exponent + '') ]) + ' ' return tableheading def create_bonds_table_heading(btc_unit): tableheading = ('
' + '' + '' + '' + '' + '' + '' + '' + '' + '' ) return tableheading def create_choose_units_form(selected_btc, selected_rel): choose_units_form = ( '' + '') choose_units_form = choose_units_form.replace( '" bondtable = "" for (bond_data, utxo_data), bond_value, conf_time in zip( fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times): if bond_value == -1 or conf_time == -1 or utxo_data == None: bond_value_str = "No data" conf_time_str = "No data" utxo_value_str = "No data" else: 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)) utxo_value_str = sat_to_unit(utxo_data["value"], html.unescape(btc_unit)) bondtable += ("" + elem(bond_data.maker_nick) + elem(bintohex(bond_data.utxo[0]) + ":" + str(bond_data.utxo[1])) + elem(bond_value_str) + elem((datetime.utcfromtimestamp(0) + timedelta(seconds=bond_data.locktime)).strftime("%Y-%m-%d")) + elem(utxo_value_str) + elem(conf_time_str) + elem(str(bond_data.cert_expiry*RETARGET_INTERVAL)) + elem(bintohex(btc.mk_freeze_script(bond_data.utxo_pub, bond_data.locktime))) + "" ) heading2 = (str(len(fidelity_bond_data)) + " fidelity bonds found with " + total_btc_committed_str + " " + btc_unit + " total locked up") choose_units_form = ( '' + '') choose_units_form = choose_units_form.replace( '
CounterpartyUTXOBond value / ' + btc_unit + '' + bond_exponent + 'LocktimeLocked coins / ' + btc_unit + 'Confirmation timeSignature expiry heightRedeem script
{e}
" + decodescript_tip) def create_sybil_resistance_page(self, btc_unit: str) -> Tuple[str, str]: if jm_single().bc_interface == None: return "", "Calculations unavailable, requires configured bitcoin node." (fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times) =\ get_fidelity_bond_data(self.taker) choose_units_form = ( '
' + '
') choose_units_form = choose_units_form.replace( '