mirror of
https://github.com/basicswap/basicswap.git
synced 2025-03-12 09:38:03 +00:00
add Haven
This commit is contained in:
parent
594845e312
commit
187ad9cb27
37 changed files with 6366 additions and 7 deletions
|
@ -14,6 +14,7 @@ from .util import (
|
|||
TemporaryError,
|
||||
)
|
||||
|
||||
XHV_COIN = 10 ** 12
|
||||
XMR_COIN = 10 ** 12
|
||||
|
||||
|
||||
|
@ -33,6 +34,7 @@ class Coins(IntEnum):
|
|||
FIRO = 13
|
||||
NAV = 14
|
||||
LTC_MWEB = 15
|
||||
XHV = 16
|
||||
|
||||
|
||||
chainparams = {
|
||||
|
@ -370,6 +372,30 @@ chainparams = {
|
|||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
},
|
||||
Coins.XHV: {
|
||||
'name': 'haven',
|
||||
'ticker': 'XHV',
|
||||
'client': 'xhv',
|
||||
'decimal_places': 12,
|
||||
'mainnet': {
|
||||
'rpcport': 17750,
|
||||
'walletrpcport': 17751,
|
||||
'min_amount': 100000,
|
||||
'max_amount': 10000 * XHV_COIN,
|
||||
},
|
||||
'testnet': {
|
||||
'rpcport': 27750,
|
||||
'walletrpcport': 27751,
|
||||
'min_amount': 100000,
|
||||
'max_amount': 10000 * XHV_COIN,
|
||||
},
|
||||
'regtest': {
|
||||
'rpcport': 17750,
|
||||
'walletrpcport': 17751,
|
||||
'min_amount': 100000,
|
||||
'max_amount': 10000 * XHV_COIN,
|
||||
}
|
||||
}
|
||||
}
|
||||
ticker_map = {}
|
||||
|
|
|
@ -33,6 +33,10 @@ NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix)
|
|||
NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix)
|
||||
NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
|
||||
|
||||
XHV_BINDIR = os.path.expanduser(os.getenv('XHV_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'haven')))
|
||||
XHVD = os.getenv('XHVD', 'havend' + bin_suffix)
|
||||
XHV_WALLET_RPC = os.getenv('XHV_WALLET_RPC', 'haven-wallet-rpc' + bin_suffix)
|
||||
|
||||
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
|
||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
||||
|
|
0
basicswap/contrib/HavenPy/__init__.py
Normal file
0
basicswap/contrib/HavenPy/__init__.py
Normal file
168
basicswap/contrib/HavenPy/base58.py
Normal file
168
basicswap/contrib/HavenPy/base58.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
# HavenPy - A python toolbox for Haven
|
||||
# Copyright (C) 2016 The MoneroPy Developers and The Haven Developers.
|
||||
#
|
||||
# MoneroPy is released under the BSD 3-Clause license. Use and redistribution of
|
||||
# this software is subject to the license terms in the LICENSE file found in the
|
||||
# top-level directory of this distribution.
|
||||
|
||||
__alphabet = [ord(s) for s in '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
||||
__b58base = 58
|
||||
__UINT64MAX = 2**64
|
||||
__encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]
|
||||
__fullBlockSize = 8
|
||||
__fullEncodedBlockSize = 11
|
||||
|
||||
def _hexToBin(hex):
|
||||
if len(hex) % 2 != 0:
|
||||
return "Hex string has invalid length!"
|
||||
return [int(hex[i*2:i*2+2], 16) for i in range(len(hex)//2)]
|
||||
|
||||
def _binToHex(bin):
|
||||
return "".join([("0" + hex(int(bin[i])).split('x')[1])[-2:] for i in range(len(bin))])
|
||||
|
||||
def _strToBin(a):
|
||||
return [ord(s) for s in a]
|
||||
|
||||
def _binToStr(bin):
|
||||
return ''.join([chr(bin[i]) for i in range(len(bin))])
|
||||
|
||||
def _uint8be_to_64(data):
|
||||
l_data = len(data)
|
||||
|
||||
if l_data < 1 or l_data > 8:
|
||||
return "Invalid input length"
|
||||
|
||||
res = 0
|
||||
switch = 9 - l_data
|
||||
for i in range(l_data):
|
||||
if switch == 1:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 2:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 3:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 4:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 5:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 6:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 7:
|
||||
res = res << 8 | data[i]
|
||||
elif switch == 8:
|
||||
res = res << 8 | data[i]
|
||||
else:
|
||||
return "Impossible condition"
|
||||
return res
|
||||
|
||||
def _uint64_to_8be(num, size):
|
||||
res = [0] * size;
|
||||
if size < 1 or size > 8:
|
||||
return "Invalid input length"
|
||||
|
||||
twopow8 = 2**8
|
||||
for i in range(size-1,-1,-1):
|
||||
res[i] = num % twopow8
|
||||
num = num // twopow8
|
||||
|
||||
return res
|
||||
|
||||
def encode_block(data, buf, index):
|
||||
l_data = len(data)
|
||||
|
||||
if l_data < 1 or l_data > __fullEncodedBlockSize:
|
||||
return "Invalid block length: " + str(l_data)
|
||||
|
||||
num = _uint8be_to_64(data)
|
||||
i = __encodedBlockSizes[l_data] - 1
|
||||
|
||||
while num > 0:
|
||||
remainder = num % __b58base
|
||||
num = num // __b58base
|
||||
buf[index+i] = __alphabet[remainder];
|
||||
i -= 1
|
||||
|
||||
return buf
|
||||
|
||||
def encode(hex):
|
||||
'''Encode hexadecimal string as base58 (ex: encoding a Monero address).'''
|
||||
data = _hexToBin(hex)
|
||||
l_data = len(data)
|
||||
|
||||
if l_data == 0:
|
||||
return ""
|
||||
|
||||
full_block_count = l_data // __fullBlockSize
|
||||
last_block_size = l_data % __fullBlockSize
|
||||
res_size = full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size]
|
||||
|
||||
res = [0] * res_size
|
||||
for i in range(res_size):
|
||||
res[i] = __alphabet[0]
|
||||
|
||||
for i in range(full_block_count):
|
||||
res = encode_block(data[(i*__fullBlockSize):(i*__fullBlockSize+__fullBlockSize)], res, i * __fullEncodedBlockSize)
|
||||
|
||||
if last_block_size > 0:
|
||||
res = encode_block(data[(full_block_count*__fullBlockSize):(full_block_count*__fullBlockSize+last_block_size)], res, full_block_count * __fullEncodedBlockSize)
|
||||
|
||||
return _binToStr(res)
|
||||
|
||||
def decode_block(data, buf, index):
|
||||
l_data = len(data)
|
||||
|
||||
if l_data < 1 or l_data > __fullEncodedBlockSize:
|
||||
return "Invalid block length: " + l_data
|
||||
|
||||
res_size = __encodedBlockSizes.index(l_data)
|
||||
if res_size <= 0:
|
||||
return "Invalid block size"
|
||||
|
||||
res_num = 0
|
||||
order = 1
|
||||
for i in range(l_data-1, -1, -1):
|
||||
digit = __alphabet.index(data[i])
|
||||
if digit < 0:
|
||||
return "Invalid symbol"
|
||||
|
||||
product = order * digit + res_num
|
||||
if product > __UINT64MAX:
|
||||
return "Overflow"
|
||||
|
||||
res_num = product
|
||||
order = order * __b58base
|
||||
|
||||
if res_size < __fullBlockSize and 2**(8 * res_size) <= res_num:
|
||||
return "Overflow 2"
|
||||
|
||||
tmp_buf = _uint64_to_8be(res_num, res_size)
|
||||
for i in range(len(tmp_buf)):
|
||||
buf[i+index] = tmp_buf[i]
|
||||
|
||||
return buf
|
||||
|
||||
def decode(enc):
|
||||
'''Decode a base58 string (ex: a Monero address) into hexidecimal form.'''
|
||||
enc = _strToBin(enc)
|
||||
l_enc = len(enc)
|
||||
|
||||
if l_enc == 0:
|
||||
return ""
|
||||
|
||||
full_block_count = l_enc // __fullEncodedBlockSize
|
||||
last_block_size = l_enc % __fullEncodedBlockSize
|
||||
last_block_decoded_size = __encodedBlockSizes.index(last_block_size)
|
||||
|
||||
if last_block_decoded_size < 0:
|
||||
return "Invalid encoded length"
|
||||
|
||||
data_size = full_block_count * __fullBlockSize + last_block_decoded_size
|
||||
|
||||
data = [0] * data_size
|
||||
for i in range(full_block_count):
|
||||
data = decode_block(enc[(i*__fullEncodedBlockSize):(i*__fullEncodedBlockSize+__fullEncodedBlockSize)], data, i * __fullBlockSize)
|
||||
|
||||
if last_block_size > 0:
|
||||
data = decode_block(enc[(full_block_count*__fullEncodedBlockSize):(full_block_count*__fullEncodedBlockSize+last_block_size)], data, full_block_count * __fullBlockSize)
|
||||
|
||||
return _binToHex(data)
|
|
@ -329,6 +329,8 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
coins = listAvailableCoins(swap_client, with_variants=False)
|
||||
with_xmr: bool = any(c[0] == Coins.XMR for c in coins)
|
||||
coins = [c for c in coins if c[0] != Coins.XMR]
|
||||
with_xhv: bool = any(c[0] == Coins.XHV for c in coins)
|
||||
coins = [c for c in coins if c[0] != Coins.XHV]
|
||||
|
||||
if any(c[0] == Coins.LTC for c in coins):
|
||||
coins.append((-5, 'Litecoin MWEB Wallet'))
|
||||
|
@ -336,6 +338,10 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
coins.append((-2, 'Monero'))
|
||||
coins.append((-3, 'Monero JSON'))
|
||||
coins.append((-4, 'Monero Wallet'))
|
||||
if with_xhv:
|
||||
coins.append((-2, 'Haven'))
|
||||
coins.append((-3, 'Haven JSON'))
|
||||
coins.append((-4, 'Haven Wallet'))
|
||||
|
||||
return self.render_template(template, {
|
||||
'messages': messages,
|
||||
|
|
575
basicswap/interface/xhv.py
Normal file
575
basicswap/interface/xhv.py
Normal file
|
@ -0,0 +1,575 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
import basicswap.contrib.ed25519_fast as edf
|
||||
import basicswap.ed25519_fast_util as edu
|
||||
import basicswap.util_xhv as xhv_util
|
||||
from coincurve.ed25519 import (
|
||||
ed25519_add,
|
||||
ed25519_get_pubkey,
|
||||
ed25519_scalar_add,
|
||||
)
|
||||
from coincurve.keys import PrivateKey
|
||||
from coincurve.dleag import (
|
||||
dleag_prove,
|
||||
dleag_verify,
|
||||
dleag_proof_len,
|
||||
verify_ed25519_point,
|
||||
)
|
||||
|
||||
from basicswap.interface import (
|
||||
Curves)
|
||||
from basicswap.util import (
|
||||
i2b, b2i, b2h,
|
||||
dumpj,
|
||||
ensure,
|
||||
make_int,
|
||||
TemporaryError)
|
||||
from basicswap.util.network import (
|
||||
is_private_ip_address)
|
||||
from basicswap.rpc_xhv import (
|
||||
make_xhv_rpc_func,
|
||||
make_xhv_rpc2_func)
|
||||
from basicswap.chainparams import XHV_COIN, CoinInterface, Coins
|
||||
|
||||
|
||||
class XHVInterface(CoinInterface):
|
||||
@staticmethod
|
||||
def curve_type():
|
||||
return Curves.ed25519
|
||||
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.XHV
|
||||
|
||||
@staticmethod
|
||||
def COIN():
|
||||
return XHV_COIN
|
||||
|
||||
@staticmethod
|
||||
def exp() -> int:
|
||||
return 12
|
||||
|
||||
@staticmethod
|
||||
def nbk() -> int:
|
||||
return 32
|
||||
|
||||
@staticmethod
|
||||
def nbK() -> int: # No. of bytes requires to encode a public key
|
||||
return 32
|
||||
|
||||
@staticmethod
|
||||
def depth_spendable() -> int:
|
||||
return 10
|
||||
|
||||
@staticmethod
|
||||
def xhv_swap_a_lock_spend_tx_vsize() -> int:
|
||||
raise ValueError('Not possible')
|
||||
|
||||
@staticmethod
|
||||
def xhv_swap_b_lock_spend_tx_vsize() -> int:
|
||||
# TODO: Estimate with ringsize
|
||||
return 1604
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super().__init__(network)
|
||||
|
||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
self._restore_height = coin_settings.get('restore_height', 0)
|
||||
self.setFeePriority(coin_settings.get('fee_priority', 2))
|
||||
self._sc = swap_client
|
||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self._wallet_password = None
|
||||
self._have_checked_seed = False
|
||||
|
||||
daemon_login = None
|
||||
if coin_settings.get('rpcuser', '') != '':
|
||||
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
|
||||
|
||||
rpchost = coin_settings.get('rpchost', '127.0.0.1')
|
||||
proxy_host = None
|
||||
proxy_port = None
|
||||
# Connect to the daemon over a proxy if not running locally
|
||||
if swap_client:
|
||||
chain_client_settings = swap_client.getChainClientSettings(self.coin_type())
|
||||
manage_daemon: bool = chain_client_settings['manage_daemon']
|
||||
if swap_client.use_tor_proxy:
|
||||
if manage_daemon is False:
|
||||
log_str: str = ''
|
||||
have_cc_tor_opt = 'use_tor' in chain_client_settings
|
||||
if have_cc_tor_opt and chain_client_settings['use_tor'] is False:
|
||||
log_str = ' bypassing proxy (use_tor false for XHV)'
|
||||
elif have_cc_tor_opt is False and is_private_ip_address(rpchost):
|
||||
log_str = ' bypassing proxy (private ip address)'
|
||||
else:
|
||||
proxy_host = swap_client.tor_proxy_host
|
||||
proxy_port = swap_client.tor_proxy_port
|
||||
log_str = f' through proxy at {proxy_host}'
|
||||
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.')
|
||||
else:
|
||||
self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.')
|
||||
elif manage_daemon is False:
|
||||
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.')
|
||||
|
||||
self._rpctimeout = coin_settings.get('rpctimeout', 60)
|
||||
self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120)
|
||||
self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600)
|
||||
|
||||
self.rpc = make_xhv_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node(j) ')
|
||||
self.rpc2 = make_xhv_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint
|
||||
self.rpc_wallet = make_xhv_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ')
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def setFeePriority(self, new_priority):
|
||||
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
|
||||
self._fee_priority = new_priority
|
||||
|
||||
def setWalletFilename(self, wallet_filename):
|
||||
self._wallet_filename = wallet_filename
|
||||
|
||||
def createWallet(self, params):
|
||||
if self._wallet_password is not None:
|
||||
params['password'] = self._wallet_password
|
||||
rv = self.rpc_wallet('generate_from_keys', params)
|
||||
self._log.info('generate_from_keys %s', dumpj(rv))
|
||||
|
||||
def openWallet(self, filename):
|
||||
params = {'filename': filename}
|
||||
if self._wallet_password is not None:
|
||||
params['password'] = self._wallet_password
|
||||
|
||||
try:
|
||||
# Can't reopen the same wallet in windows, !is_keys_file_locked()
|
||||
self.rpc_wallet('close_wallet')
|
||||
except Exception:
|
||||
pass
|
||||
self.rpc_wallet('open_wallet', params)
|
||||
|
||||
def initialiseWallet(self, key_view, key_spend, restore_height=None):
|
||||
with self._mx_wallet:
|
||||
try:
|
||||
self.openWallet(self._wallet_filename)
|
||||
# TODO: Check address
|
||||
return # Wallet exists
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
Kbv = self.getPubkey(key_view)
|
||||
Kbs = self.getPubkey(key_spend)
|
||||
address_b58 = xhv_util.encode_address(Kbv, Kbs)
|
||||
|
||||
params = {
|
||||
'filename': self._wallet_filename,
|
||||
'address': address_b58,
|
||||
'viewkey': b2h(key_view[::-1]),
|
||||
'spendkey': b2h(key_spend[::-1]),
|
||||
'restore_height': self._restore_height,
|
||||
}
|
||||
self.createWallet(params)
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
||||
def ensureWalletExists(self) -> None:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
self.rpc_wallet('get_languages')
|
||||
|
||||
def getDaemonVersion(self):
|
||||
return self.rpc_wallet('get_version')['version']
|
||||
|
||||
def getBlockchainInfo(self):
|
||||
get_height = self.rpc2('get_height', timeout=self._rpctimeout)
|
||||
rv = {
|
||||
'blocks': get_height['height'],
|
||||
'verificationprogress': 0.0,
|
||||
}
|
||||
|
||||
try:
|
||||
# get_block_count.block_count is how many blocks are in the longest chain known to the node.
|
||||
# get_block_count returns "Internal error" if bootstrap-daemon is active
|
||||
if get_height['untrusted'] is True:
|
||||
rv['bootstrapping'] = True
|
||||
get_info = self.rpc2('get_info', timeout=self._rpctimeout)
|
||||
if 'height_without_bootstrap' in get_info:
|
||||
rv['blocks'] = get_info['height_without_bootstrap']
|
||||
|
||||
rv['known_block_count'] = get_info['height']
|
||||
if rv['known_block_count'] > rv['blocks']:
|
||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
||||
else:
|
||||
rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count']
|
||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
||||
except Exception as e:
|
||||
self._log.warning('XHV get_block_count failed with: %s', str(e))
|
||||
rv['verificationprogress'] = 0.0
|
||||
|
||||
return rv
|
||||
|
||||
def getChainHeight(self):
|
||||
return self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
||||
|
||||
def getWalletInfo(self):
|
||||
with self._mx_wallet:
|
||||
try:
|
||||
self.openWallet(self._wallet_filename)
|
||||
except Exception as e:
|
||||
if 'Failed to open wallet' in str(e):
|
||||
rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0}
|
||||
return rv
|
||||
raise e
|
||||
|
||||
rv = {}
|
||||
self.rpc_wallet('refresh')
|
||||
balance_info = self.rpc_wallet('get_balance')
|
||||
|
||||
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
|
||||
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance'])
|
||||
rv['encrypted'] = False if self._wallet_password is None else True
|
||||
rv['locked'] = False
|
||||
return rv
|
||||
|
||||
def walletRestoreHeight(self):
|
||||
return self._restore_height
|
||||
|
||||
def getMainWalletAddress(self) -> str:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
return self.rpc_wallet('get_address')['address']
|
||||
|
||||
def getNewAddress(self, placeholder) -> str:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address']
|
||||
self.rpc_wallet('store')
|
||||
return new_address
|
||||
|
||||
def get_fee_rate(self, conf_target: int = 2):
|
||||
# fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest].
|
||||
fee_est = self.rpc('get_fee_estimate')
|
||||
if conf_target <= 1:
|
||||
conf_target = 1 # normal
|
||||
else:
|
||||
conf_target = 0 # slow
|
||||
fee_per_k_bytes = fee_est['fees'][conf_target] * 1000
|
||||
|
||||
return float(self.format_amount(fee_per_k_bytes)), 'get_fee_estimate'
|
||||
|
||||
def getNewSecretKey(self) -> bytes:
|
||||
# Note: Returned bytes are in big endian order
|
||||
return i2b(edu.get_secret())
|
||||
|
||||
def pubkey(self, key: bytes) -> bytes:
|
||||
return edf.scalarmult_B(key)
|
||||
|
||||
def encodeKey(self, vk: bytes) -> str:
|
||||
return vk[::-1].hex()
|
||||
|
||||
def decodeKey(self, k_hex: str) -> bytes:
|
||||
return bytes.fromhex(k_hex)[::-1]
|
||||
|
||||
def encodePubkey(self, pk: bytes) -> str:
|
||||
return edu.encodepoint(pk)
|
||||
|
||||
def decodePubkey(self, pke):
|
||||
return edf.decodepoint(pke)
|
||||
|
||||
def getPubkey(self, privkey):
|
||||
return ed25519_get_pubkey(privkey)
|
||||
|
||||
def getAddressFromKeys(self, key_view: bytes, key_spend: bytes) -> str:
|
||||
pk_view = self.getPubkey(key_view)
|
||||
pk_spend = self.getPubkey(key_spend)
|
||||
return xhv_util.encode_address(pk_view, pk_spend)
|
||||
|
||||
def verifyKey(self, k: int) -> bool:
|
||||
i = b2i(k)
|
||||
return (i < edf.l and i > 8)
|
||||
|
||||
def verifyPubkey(self, pubkey_bytes):
|
||||
# Calls ed25519_decode_check_point() in secp256k1
|
||||
# Checks for small order
|
||||
return verify_ed25519_point(pubkey_bytes)
|
||||
|
||||
def proveDLEAG(self, key: bytes) -> bytes:
|
||||
privkey = PrivateKey(key)
|
||||
return dleag_prove(privkey)
|
||||
|
||||
def verifyDLEAG(self, dleag_bytes: bytes) -> bool:
|
||||
return dleag_verify(dleag_bytes)
|
||||
|
||||
def lengthDLEAG(self) -> int:
|
||||
return dleag_proof_len()
|
||||
|
||||
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
|
||||
return ed25519_scalar_add(ka, kb)
|
||||
|
||||
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
|
||||
return ed25519_add(Ka, Kb)
|
||||
|
||||
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
||||
return xhv_util.encode_address(Kbv, Kbs)
|
||||
|
||||
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet('refresh')
|
||||
|
||||
Kbv = self.getPubkey(kbv)
|
||||
shared_addr = xhv_util.encode_address(Kbv, Kbs)
|
||||
|
||||
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
rv = self.rpc_wallet('transfer', params)
|
||||
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
|
||||
tx_hash = bytes.fromhex(rv['tx_hash'])
|
||||
|
||||
return tx_hash
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
with self._mx_wallet:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
address_b58 = xhv_util.encode_address(Kbv, Kbs)
|
||||
|
||||
kbv_le = kbv[::-1]
|
||||
params = {
|
||||
'restore_height': restore_height,
|
||||
'filename': address_b58,
|
||||
'address': address_b58,
|
||||
'viewkey': b2h(kbv_le),
|
||||
}
|
||||
|
||||
try:
|
||||
self.openWallet(address_b58)
|
||||
except Exception as e:
|
||||
self.createWallet(params)
|
||||
self.openWallet(address_b58)
|
||||
|
||||
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
|
||||
|
||||
'''
|
||||
# Debug
|
||||
try:
|
||||
current_height = self.rpc_wallet('get_height')['height']
|
||||
self._log.info('findTxB XHV current_height %d\nAddress: %s', current_height, address_b58)
|
||||
except Exception as e:
|
||||
self._log.info('rpc failed %s', str(e))
|
||||
current_height = None # If the transfer is available it will be deep enough
|
||||
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||
'''
|
||||
params = {'transfer_type': 'available'}
|
||||
transfers = self.rpc_wallet('incoming_transfers', params)
|
||||
rv = None
|
||||
if 'transfers' in transfers:
|
||||
for transfer in transfers['transfers']:
|
||||
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
||||
if not transfer['unlocked']:
|
||||
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
|
||||
unlock_time = full_tx['transfer']['unlock_time']
|
||||
if unlock_time != 0:
|
||||
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
|
||||
rv = -1
|
||||
continue
|
||||
if transfer['amount'] == cb_swap_value:
|
||||
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']}
|
||||
else:
|
||||
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash']))
|
||||
rv = -1
|
||||
return rv
|
||||
|
||||
def findTxnByHash(self, txid):
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
|
||||
|
||||
try:
|
||||
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
||||
self._log.info('findTxnByHash XHV current_height %d\nhash: %s', current_height, txid)
|
||||
except Exception as e:
|
||||
self._log.info('rpc failed %s', str(e))
|
||||
current_height = None # If the transfer is available it will be deep enough
|
||||
|
||||
params = {'transfer_type': 'available'}
|
||||
rv = self.rpc_wallet('incoming_transfers', params)
|
||||
if 'transfers' in rv:
|
||||
for transfer in rv['transfers']:
|
||||
if transfer['tx_hash'] == txid \
|
||||
and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed):
|
||||
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']}
|
||||
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
|
||||
'''
|
||||
Notes:
|
||||
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
|
||||
'''
|
||||
with self._mx_wallet:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
address_b58 = xhv_util.encode_address(Kbv, Kbs)
|
||||
|
||||
wallet_filename = address_b58 + '_spend'
|
||||
|
||||
params = {
|
||||
'filename': wallet_filename,
|
||||
'address': address_b58,
|
||||
'viewkey': b2h(kbv[::-1]),
|
||||
'spendkey': b2h(kbs[::-1]),
|
||||
'restore_height': restore_height,
|
||||
}
|
||||
|
||||
try:
|
||||
self.openWallet(wallet_filename)
|
||||
except Exception as e:
|
||||
self.createWallet(params)
|
||||
self.openWallet(wallet_filename)
|
||||
|
||||
self.rpc_wallet('refresh')
|
||||
rv = self.rpc_wallet('get_balance')
|
||||
if rv['balance'] < cb_swap_value:
|
||||
self._log.warning('Balance is too low, checking for existing spend.')
|
||||
txns = self.rpc_wallet('get_transfers', {'out': True})
|
||||
if 'out' in txns:
|
||||
txns = txns['out']
|
||||
if len(txns) > 0:
|
||||
txid = txns[0]['txid']
|
||||
self._log.warning(f'spendBLockTx detected spending tx: {txid}.')
|
||||
if txns[0]['address'] == address_b58:
|
||||
return bytes.fromhex(txid)
|
||||
|
||||
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
|
||||
|
||||
if not spend_actual_balance:
|
||||
raise TemporaryError('Invalid balance')
|
||||
|
||||
if spend_actual_balance and rv['balance'] != cb_swap_value:
|
||||
self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value))
|
||||
cb_swap_value = rv['balance']
|
||||
if rv['unlocked_balance'] < cb_swap_value:
|
||||
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock']))
|
||||
raise TemporaryError('Invalid unlocked_balance')
|
||||
|
||||
params = {'address': address_to}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
|
||||
rv = self.rpc_wallet('sweep_all', params)
|
||||
self._log.debug('sweep_all {}'.format(json.dumps(rv)))
|
||||
|
||||
return bytes.fromhex(rv['tx_hash_list'][0])
|
||||
|
||||
def withdrawCoin(self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False) -> str:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet('refresh')
|
||||
|
||||
if sweepall:
|
||||
balance = self.rpc_wallet('get_balance')
|
||||
if balance['balance'] != balance['unlocked_balance']:
|
||||
raise ValueError('Balance must be fully confirmed to use sweep all.')
|
||||
self._log.info('XHV {} sweep_all.'.format('estimate fee' if estimate_fee else 'withdraw'))
|
||||
self._log.debug('XHV balance: {}'.format(balance['balance']))
|
||||
params = {'address': addr_to, 'do_not_relay': estimate_fee}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
rv = self.rpc_wallet('sweep_all', params)
|
||||
if estimate_fee:
|
||||
return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])}
|
||||
return rv['tx_hash_list'][0]
|
||||
|
||||
value_sats: int = make_int(value, self.exp())
|
||||
params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
rv = self.rpc_wallet('transfer', params)
|
||||
if estimate_fee:
|
||||
return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']}
|
||||
return rv['tx_hash']
|
||||
|
||||
def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str:
|
||||
return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True)
|
||||
|
||||
def showLockTransfers(self, kbv, Kbs, restore_height):
|
||||
with self._mx_wallet:
|
||||
try:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
address_b58 = xhv_util.encode_address(Kbv, Kbs)
|
||||
wallet_file = address_b58 + '_spend'
|
||||
try:
|
||||
self.openWallet(wallet_file)
|
||||
except Exception:
|
||||
wallet_file = address_b58
|
||||
try:
|
||||
self.openWallet(wallet_file)
|
||||
except Exception:
|
||||
self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.')
|
||||
kbv_le = kbv[::-1]
|
||||
params = {
|
||||
'restore_height': restore_height,
|
||||
'filename': address_b58,
|
||||
'address': address_b58,
|
||||
'viewkey': b2h(kbv_le),
|
||||
}
|
||||
self.createWallet(params)
|
||||
self.openWallet(address_b58)
|
||||
|
||||
self.rpc_wallet('refresh')
|
||||
|
||||
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
|
||||
rv['filename'] = wallet_file
|
||||
return rv
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
||||
self.rpc_wallet('refresh')
|
||||
balance_info = self.rpc_wallet('get_balance')
|
||||
return balance_info['unlocked_balance']
|
||||
|
||||
def changeWalletPassword(self, old_password, new_password):
|
||||
self._log.info('changeWalletPassword - {}'.format(self.ticker()))
|
||||
orig_password = self._wallet_password
|
||||
if old_password != '':
|
||||
self._wallet_password = old_password
|
||||
try:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
|
||||
except Exception as e:
|
||||
self._wallet_password = orig_password
|
||||
raise e
|
||||
|
||||
def unlockWallet(self, password: str) -> None:
|
||||
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
||||
self._wallet_password = password
|
||||
|
||||
if not self._have_checked_seed:
|
||||
self._sc.checkWalletSeed(self.coin_type())
|
||||
|
||||
def lockWallet(self) -> None:
|
||||
self._log.info('lockWallet - {}'.format(self.ticker()))
|
||||
self._wallet_password = None
|
||||
|
||||
def isAddressMine(self, address):
|
||||
# TODO
|
||||
return True
|
||||
|
||||
def ensureFunds(self, amount: int) -> None:
|
||||
if self.getSpendableBalance() < amount:
|
||||
raise ValueError('Balance too low')
|
||||
|
||||
def getTransaction(self, txid: bytes):
|
||||
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
163
basicswap/protocols/xhv_swap_1.py
Normal file
163
basicswap/protocols/xhv_swap_1.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from sqlalchemy.orm import scoped_session
|
||||
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
)
|
||||
from basicswap.interface import Curves
|
||||
from basicswap.chainparams import (
|
||||
Coins,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
KeyTypes,
|
||||
SwapTypes,
|
||||
EventLogTypes,
|
||||
)
|
||||
from . import ProtocolInterface
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
OP_CHECKMULTISIG)
|
||||
|
||||
|
||||
def addLockRefundSigs(self, xhv_swap, ci):
|
||||
self.log.debug('Setting lock refund tx sigs')
|
||||
witness_stack = [
|
||||
b'',
|
||||
xhv_swap.al_lock_refund_tx_sig,
|
||||
xhv_swap.af_lock_refund_tx_sig,
|
||||
xhv_swap.a_lock_tx_script,
|
||||
]
|
||||
|
||||
signed_tx = ci.setTxSignature(xhv_swap.a_lock_refund_tx, witness_stack)
|
||||
ensure(signed_tx, 'setTxSignature failed')
|
||||
xhv_swap.a_lock_refund_tx = signed_tx
|
||||
|
||||
|
||||
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
|
||||
self.log.info('Manually recovering %s', bid_id.hex())
|
||||
# Manually recover txn if other key is known
|
||||
session = scoped_session(self.session_factory)
|
||||
try:
|
||||
bid, xhv_swap = self.getXhvBidFromSession(session, bid_id)
|
||||
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex()))
|
||||
ensure(xhv_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex()))
|
||||
offer, xhv_offer = self.getXhvOfferFromSession(session, bid.offer_id, sent=False)
|
||||
ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex()))
|
||||
ensure(xhv_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex()))
|
||||
ci_to = self.ci(offer.coin_to)
|
||||
|
||||
for_ed25519 = True if Coins(offer.coin_to) == Coins.XHV else False
|
||||
|
||||
try:
|
||||
decoded_key_half = ci_to.decodeKey(encoded_key)
|
||||
except Exception as e:
|
||||
raise ValueError('Failed to decode provided key-half: ', str(e))
|
||||
|
||||
if bid.was_sent:
|
||||
kbsl = decoded_key_half
|
||||
kbsf = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xhv_swap.contract_count, KeyTypes.KBSF, for_ed25519)
|
||||
else:
|
||||
kbsl = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xhv_swap.contract_count, KeyTypes.KBSL, for_ed25519)
|
||||
kbsf = decoded_key_half
|
||||
ensure(ci_to.verifyKey(kbsl), 'Invalid kbsl')
|
||||
ensure(ci_to.verifyKey(kbsf), 'Invalid kbsf')
|
||||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||
|
||||
if offer.coin_to == Coins.XHV:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
else:
|
||||
address_to = self.getCachedStealthAddressForCoin(offer.coin_to)
|
||||
|
||||
amount = bid.amount_to
|
||||
txid = ci_to.spendBLockTx(xhv_swap.b_lock_tx_id, address_to, xhv_swap.vkbv, vkbs, bid.amount_to, xhv_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True)
|
||||
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), session)
|
||||
session.commit()
|
||||
|
||||
return txid
|
||||
finally:
|
||||
session.close()
|
||||
session.remove()
|
||||
|
||||
|
||||
def getChainBSplitKey(swap_client, bid, xhv_swap, offer):
|
||||
reverse_bid: bool = offer.bid_reversed
|
||||
ci_follower = swap_client.ci(offer.coin_from if reverse_bid else offer.coin_to)
|
||||
|
||||
key_type = KeyTypes.KBSF if bid.was_sent else KeyTypes.KBSL
|
||||
return ci_follower.encodeKey(swap_client.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xhv_swap.contract_count, key_type, True if ci_follower.coin_type() == Coins.XHV else False))
|
||||
|
||||
|
||||
def getChainBRemoteSplitKey(swap_client, bid, xhv_swap, offer):
|
||||
reverse_bid: bool = offer.bid_reversed
|
||||
ci_leader = swap_client.ci(offer.coin_to if reverse_bid else offer.coin_from)
|
||||
ci_follower = swap_client.ci(offer.coin_from if reverse_bid else offer.coin_to)
|
||||
|
||||
if bid.was_sent:
|
||||
if xhv_swap.a_lock_refund_spend_tx:
|
||||
af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(xhv_swap.a_lock_refund_spend_tx)
|
||||
kbsl = ci_leader.recoverEncKey(xhv_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xhv_swap.pkasl)
|
||||
return ci_follower.encodeKey(kbsl)
|
||||
else:
|
||||
if xhv_swap.a_lock_spend_tx:
|
||||
al_lock_spend_tx_sig = ci_leader.extractLeaderSig(xhv_swap.a_lock_spend_tx)
|
||||
kbsf = ci_leader.recoverEncKey(xhv_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xhv_swap.pkasf)
|
||||
return ci_follower.encodeKey(kbsf)
|
||||
return None
|
||||
|
||||
|
||||
def setDLEAG(xhv_swap, ci_to, kbsf: bytes) -> None:
|
||||
if ci_to.curve_type() == Curves.ed25519:
|
||||
xhv_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf)
|
||||
xhv_swap.pkasf = xhv_swap.kbsf_dleag[0: 33]
|
||||
elif ci_to.curve_type() == Curves.secp256k1:
|
||||
for i in range(10):
|
||||
xhv_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap')
|
||||
pk_recovered: bytes = ci_to.verifySigAndRecover(xhv_swap.kbsf_dleag, 'proof kbsf owned for swap')
|
||||
if pk_recovered == xhv_swap.pkbsf:
|
||||
break
|
||||
# self.log.debug('kbsl recovered pubkey mismatch, retrying.')
|
||||
assert (pk_recovered == xhv_swap.pkbsf)
|
||||
xhv_swap.pkasf = xhv_swap.pkbsf
|
||||
else:
|
||||
raise ValueError('Unknown curve')
|
||||
|
||||
|
||||
class XhvSwapInterface(ProtocolInterface):
|
||||
swap_type = SwapTypes.XHV_SWAP
|
||||
|
||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes) -> CScript:
|
||||
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
|
||||
Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
|
||||
|
||||
return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)])
|
||||
|
||||
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||
addr_to = self.getMockAddrTo(ci)
|
||||
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
|
||||
|
||||
return bytes.fromhex(funded_tx)
|
||||
|
||||
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
||||
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
||||
real_txo_script = ci.getScriptDest(script)
|
||||
|
||||
found: int = 0
|
||||
ctx = ci.loadTx(mock_tx)
|
||||
for txo in ctx.vout:
|
||||
if txo.scriptPubKey == mock_txo_script:
|
||||
txo.scriptPubKey = real_txo_script
|
||||
found += 1
|
||||
|
||||
if found < 1:
|
||||
raise ValueError('Mocked output not found')
|
||||
if found > 1:
|
||||
raise ValueError('Too many mocked outputs found')
|
||||
ctx.nLockTime = 0
|
||||
|
||||
return ctx.serialize()
|
258
basicswap/rpc_xhv.py
Normal file
258
basicswap/rpc_xhv.py
Normal file
|
@ -0,0 +1,258 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import json
|
||||
import socks
|
||||
import time
|
||||
import urllib
|
||||
import hashlib
|
||||
from xmlrpc.client import (
|
||||
Fault,
|
||||
Transport,
|
||||
SafeTransport,
|
||||
)
|
||||
from sockshandler import SocksiPyConnection
|
||||
from .util import jsonDecimal
|
||||
|
||||
|
||||
class SocksTransport(Transport):
|
||||
|
||||
def set_proxy(self, proxy_host, proxy_port):
|
||||
self.proxy_host = proxy_host
|
||||
self.proxy_port = proxy_port
|
||||
|
||||
self.proxy_type = socks.PROXY_TYPE_SOCKS5
|
||||
self.proxy_rdns = True
|
||||
self.proxy_username = None
|
||||
self.proxy_password = None
|
||||
|
||||
def make_connection(self, host):
|
||||
# return an existing connection if possible. This allows
|
||||
# HTTP/1.1 keep-alive.
|
||||
if self._connection and host == self._connection[0]:
|
||||
return self._connection[1]
|
||||
# create a HTTP connection object from a host descriptor
|
||||
chost, self._extra_headers, x509 = self.get_host_info(host)
|
||||
self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost)
|
||||
return self._connection[1]
|
||||
|
||||
|
||||
class JsonrpcDigest():
|
||||
# __getattr__ complicates extending ServerProxy
|
||||
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
||||
allow_none=False, use_datetime=False, use_builtin_types=False,
|
||||
*, context=None):
|
||||
|
||||
parsed = urllib.parse.urlparse(uri)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise OSError('unsupported XML-RPC protocol')
|
||||
self.__host = parsed.netloc
|
||||
self.__handler = parsed.path
|
||||
|
||||
if transport is None:
|
||||
handler = SafeTransport if parsed.scheme == 'https' else Transport
|
||||
extra_kwargs = {}
|
||||
transport = handler(use_datetime=use_datetime,
|
||||
use_builtin_types=use_builtin_types,
|
||||
**extra_kwargs)
|
||||
self.__transport = transport
|
||||
|
||||
self.__encoding = encoding or 'utf-8'
|
||||
self.__verbose = verbose
|
||||
self.__allow_none = allow_none
|
||||
|
||||
self.__request_id = 0
|
||||
|
||||
def close(self):
|
||||
if self.__transport is not None:
|
||||
self.__transport.close()
|
||||
|
||||
def request_id(self):
|
||||
return self.__request_id
|
||||
|
||||
def post_request(self, method, params, timeout=None):
|
||||
try:
|
||||
connection = self.__transport.make_connection(self.__host)
|
||||
if timeout:
|
||||
connection.timeout = timeout
|
||||
headers = self.__transport._extra_headers[:]
|
||||
|
||||
connection.putrequest('POST', self.__handler)
|
||||
headers.append(('Content-Type', 'application/json'))
|
||||
headers.append(('User-Agent', 'jsonrpc'))
|
||||
self.__transport.send_headers(connection, headers)
|
||||
self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8'))
|
||||
self.__request_id += 1
|
||||
|
||||
resp = connection.getresponse()
|
||||
return resp.read()
|
||||
|
||||
except Fault:
|
||||
raise
|
||||
except Exception:
|
||||
self.__transport.close()
|
||||
raise
|
||||
|
||||
def json_request(self, request_body, username='', password='', timeout=None):
|
||||
try:
|
||||
connection = self.__transport.make_connection(self.__host)
|
||||
if timeout:
|
||||
connection.timeout = timeout
|
||||
|
||||
headers = self.__transport._extra_headers[:]
|
||||
|
||||
connection.putrequest('POST', self.__handler)
|
||||
headers.append(('Content-Type', 'application/json'))
|
||||
headers.append(('Connection', 'keep-alive'))
|
||||
self.__transport.send_headers(connection, headers)
|
||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
||||
resp = connection.getresponse()
|
||||
|
||||
if resp.status == 401:
|
||||
resp_headers = resp.getheaders()
|
||||
v = resp.read()
|
||||
|
||||
algorithm = ''
|
||||
realm = ''
|
||||
nonce = ''
|
||||
for h in resp_headers:
|
||||
if h[0] != 'WWW-authenticate':
|
||||
continue
|
||||
fields = h[1].split(',')
|
||||
for f in fields:
|
||||
key, value = f.split('=', 1)
|
||||
if key == 'algorithm' and value != 'MD5':
|
||||
break
|
||||
if key == 'realm':
|
||||
realm = value.strip('"')
|
||||
if key == 'nonce':
|
||||
nonce = value.strip('"')
|
||||
if realm != '' and nonce != '':
|
||||
break
|
||||
|
||||
if realm == '' or nonce == '':
|
||||
raise ValueError('Authenticate header not found.')
|
||||
|
||||
path = self.__handler
|
||||
HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest()
|
||||
|
||||
http_method = 'POST'
|
||||
HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest()
|
||||
|
||||
ncvalue = '{:08x}'.format(1)
|
||||
s = ncvalue.encode('utf-8')
|
||||
s += nonce.encode('utf-8')
|
||||
s += time.ctime().encode('utf-8')
|
||||
s += os.urandom(8)
|
||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||
|
||||
# MD5-SESS
|
||||
HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest()
|
||||
|
||||
respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest()
|
||||
|
||||
header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
|
||||
headers = self.__transport._extra_headers[:]
|
||||
headers.append(('Authorization', header_value))
|
||||
|
||||
connection.putrequest('POST', self.__handler)
|
||||
headers.append(('Content-Type', 'application/json'))
|
||||
headers.append(('Connection', 'keep-alive'))
|
||||
self.__transport.send_headers(connection, headers)
|
||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
||||
resp = connection.getresponse()
|
||||
|
||||
self.__request_id += 1
|
||||
return resp.read()
|
||||
|
||||
except Fault:
|
||||
raise
|
||||
except Exception:
|
||||
self.__transport.close()
|
||||
raise
|
||||
|
||||
|
||||
def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''):
|
||||
# auth is a tuple: (username, password)
|
||||
try:
|
||||
if rpc_host.count('://') > 0:
|
||||
url = '{}:{}/{}'.format(rpc_host, rpc_port, path)
|
||||
else:
|
||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
||||
|
||||
x = JsonrpcDigest(url, transport=transport)
|
||||
request_body = {
|
||||
'method': method,
|
||||
'params': params,
|
||||
'jsonrpc': '2.0',
|
||||
'id': x.request_id()
|
||||
}
|
||||
if auth:
|
||||
v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout)
|
||||
else:
|
||||
v = x.json_request(request_body, timeout=timeout)
|
||||
x.close()
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
||||
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError(tag + 'RPC error ' + str(r['error']))
|
||||
|
||||
return r['result']
|
||||
|
||||
|
||||
def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''):
|
||||
try:
|
||||
if rpc_host.count('://') > 0:
|
||||
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
||||
else:
|
||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
|
||||
|
||||
x = JsonrpcDigest(url, transport=transport)
|
||||
if auth:
|
||||
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
|
||||
else:
|
||||
v = x.json_request(params, timeout=timeout)
|
||||
x.close()
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
||||
port = port
|
||||
auth = auth
|
||||
host = host
|
||||
transport = None
|
||||
default_timeout = default_timeout
|
||||
tag = tag
|
||||
|
||||
if proxy_host:
|
||||
transport = SocksTransport()
|
||||
transport.set_proxy(proxy_host, proxy_port)
|
||||
|
||||
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
||||
nonlocal port, auth, host, transport, tag
|
||||
return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=tag)
|
||||
return rpc_func
|
||||
|
||||
|
||||
def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
||||
port = port
|
||||
auth = auth
|
||||
host = host
|
||||
transport = None
|
||||
default_timeout = default_timeout
|
||||
tag = tag
|
||||
|
||||
if proxy_host:
|
||||
transport = SocksTransport()
|
||||
transport.set_proxy(proxy_host, proxy_port)
|
||||
|
||||
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
||||
nonlocal port, auth, host, transport, tag
|
||||
return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag)
|
||||
return rpc_func
|
BIN
basicswap/static/images/coins/Haven-20.png
Normal file
BIN
basicswap/static/images/coins/Haven-20.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
BIN
basicswap/static/images/coins/Haven.png
Normal file
BIN
basicswap/static/images/coins/Haven.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8 KiB |
864
basicswap/templates/bid_xhv.html
Normal file
864
basicswap/templates/bid_xhv.html
Normal file
|
@ -0,0 +1,864 @@
|
|||
{% include 'header.html' %}
|
||||
{% from 'style.html' import input_arrow_down_svg %}
|
||||
|
||||
<div class="container mx-auto">
|
||||
<section class="p-5 mt-5">
|
||||
<div class="flex flex-wrap items-center -m-2">
|
||||
<div class="w-full md:w-1/2 p-2">
|
||||
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
||||
<li>
|
||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">
|
||||
<p>Home</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg width="6" height="15" viewBox="0 0 6 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.34 0.671999L2.076 14.1H0.732L3.984 0.671999H5.34Z" fill="#BBC3CF"></path>
|
||||
</svg>
|
||||
</li>
|
||||
<li>
|
||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="#">Bids</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg width="6" height="15" viewBox="0 0 6 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.34 0.671999L2.076 14.1H0.732L3.984 0.671999H5.34Z" fill="#BBC3CF"></path>
|
||||
</svg>
|
||||
</li>
|
||||
<li>
|
||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="{{ bid_id }}">BID ID: {{ bid_id }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="py-3">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-blue-500 rounded-md overflow-hidden">
|
||||
<img class="absolute z-10 left-4 top-4" src="/static/images/elements/dots-red.svg" alt="">
|
||||
<img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
|
||||
<img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
|
||||
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
||||
<div class="w-full md:w-1/2 p-3">
|
||||
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Bid {% if debug_mode == true %} (Debug: bid_xhv template) {% endif %}</h2>
|
||||
<p class="font-normal text-coolGray-200 dark:text-white">Bid ID: {{ bid_id }}</p>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto"> {% if refresh %} <a id="refresh" href="/bid/{{ bid_id }}" class="flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
||||
<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
|
||||
<g fill="#ffffff">
|
||||
<path fill="#ffffff" d="M12,3c1.989,0,3.873,0.65,5.43,1.833l-3.604,3.393l9.167,0.983L22.562,0l-3.655,3.442 C16.957,1.862,14.545,1,12,1C5.935,1,1,5.935,1,12h2C3,7.037,7.037,3,12,3z"></path>
|
||||
<path data-color="color-2" d="M12,21c-1.989,0-3.873-0.65-5.43-1.833l3.604-3.393l-9.167-0.983L1.438,24l3.655-3.442 C7.043,22.138,9.455,23,12,23c6.065,0,11-4.935,11-11h-2C21,16.963,16.963,21,12,21z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<span>Refresh {{ refresh }} seconds</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a id="refresh" href="/bid/{{ bid_id }}" class="flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
||||
<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
|
||||
<g fill="#ffffff">
|
||||
<path fill="#ffffff" d="M12,3c1.989,0,3.873,0.65,5.43,1.833l-3.604,3.393l9.167,0.983L22.562,0l-3.655,3.442 C16.957,1.862,14.545,1,12,1C5.935,1,1,5.935,1,12h2C3,7.037,7.037,3,12,3z"></path>
|
||||
<path data-color="color-2" d="M12,21c-1.989,0-3.873-0.65-5.43-1.833l3.604-3.393l-9.167-0.983L1.438,24l3.655-3.442 C7.043,22.138,9.455,23,12,23c6.065,0,11-4.935,11-11h-2C21,16.963,16.963,21,12,21z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<span>Refresh</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% include 'inc_messages.html' %}
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Details</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% if data.was_sent == 'True' %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Swap</td>
|
||||
<td class="py-3 px-6">
|
||||
<div class="content flex py-2">
|
||||
<span class="bold">{{ data.amt_to }} {{ data.ticker_to }}</span>
|
||||
<svg aria-hidden="true " class="w-5 h-5 ml-3 mr-3" fill="currentColor " viewBox="0 0 20 20 " xmlns="http://www.w3.org/2000/svg ">
|
||||
<path fill-rule="evenodd " d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z " clip-rule="evenodd "></path>
|
||||
</svg>
|
||||
<span class="text-xs bold">{{ data.amt_from }} {{ data.ticker_from }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Swap</td>
|
||||
<td class="py-3 px-6">
|
||||
<div class="content flex py-2">
|
||||
<span class="bold">{{ data.amt_from }} {{ data.ticker_from }}</span>
|
||||
<svg aria-hidden="true " class="w-5 h-5 ml-3 mr-3" fill="currentColor " viewBox="0 0 20 20 " xmlns="http://www.w3.org/2000/svg ">
|
||||
<path fill-rule="evenodd " d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z " clip-rule="evenodd "></path>
|
||||
</svg>
|
||||
<span class="bold">{{ data.amt_to }} {{ data.ticker_to }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Bid Rate</td>
|
||||
<td class="py-3 px-6">{{ data.bid_rate }}</td>
|
||||
</tr>
|
||||
{% if data.was_sent == 'True' %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">You Send</td>
|
||||
<td class="py-3 px-6">
|
||||
<span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
|
||||
<img class="h-7" src="/static/images/coins/{{ data.coin_to }}.png" alt="{{ data.coin_to }}">
|
||||
</span>{{ data.coin_to }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">You Get</td>
|
||||
<td class="py-3 px-6">
|
||||
<span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
|
||||
<img class="h-7" src="/static/images/coins/{{ data.coin_from }}.png" alt="{{ data.coin_from }}">
|
||||
</span>{{ data.coin_from }}
|
||||
</td>
|
||||
</tr>{% else %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">You Send</td>
|
||||
<td class="py-3 px-6">
|
||||
<span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
|
||||
<img class="h-7" src="/static/images/coins/{{ data.coin_from }}.png" alt="{{ data.coin_from }}">
|
||||
</span>
|
||||
{{ data.coin_from }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">You Get</td>
|
||||
<td class="py-3 px-6">
|
||||
<span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
|
||||
<img class="h-7" src="/static/images/coins/{{ data.coin_to }}.png" alt="{{ data.coin_to }}">
|
||||
</span>
|
||||
{{ data.coin_to }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Bid State</td>
|
||||
<td class="py-3 px-6">{{ data.bid_state }}</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">State Description </td>
|
||||
<td class="py-3 px-6">{{ data.state_description }}</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Offer</td>
|
||||
<td class="py-3 px-6">
|
||||
<a class="monospace bold select-all" href="/offer/{{ data.offer_id }}">{{ data.offer_id }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Address From</td>
|
||||
<td class="py-3 px-6">
|
||||
<a class="monospace bold select-all" href="/identity/{{ data.addr_from }}">{{ data.addr_from }}</a> {{ data.addr_from_label }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="flex items-center px-46 whitespace-nowrap">
|
||||
<svg alt="" class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#3B82F6" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="11"></circle>
|
||||
<polyline points=" 12,6 12,12 18,12 " stroke="#3B82F6"></polyline>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="py-3 pl-2 bold">
|
||||
<div>Created At</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-6">{{ data.created_at }}</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="flex items-center px-46 whitespace-nowrap">
|
||||
<svg alt="" class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="11"></circle>
|
||||
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="py-3 pl-2 bold">
|
||||
<div>Expired At</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-6">{{ data.expired_at }}</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Sent</td>
|
||||
<td class="py-3 px-6">{{ data.was_sent }}</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Received</td>
|
||||
<td class="py-3 px-6">{{ data.was_received }}</td>
|
||||
</tr>
|
||||
{% if data.coin_a_lock_refund_tx_est_final != 'None' %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">{{ data.ticker_from }} lock refund tx valid at</td>
|
||||
<td class="py-3 px-6">{{ data.coin_a_lock_refund_tx_est_final | formatts }}</td>
|
||||
</tr>
|
||||
{% if data.coin_a_lock_refund_swipe_tx_est_final != 'None' %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">{{ data.ticker_from }} lock refund tx swipeable at</td>
|
||||
<td class="py-3 px-6">{{ data.coin_a_lock_refund_swipe_tx_est_final | formatts }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">{{ data.ticker_from }} chain median time</td>
|
||||
<td class="py-3 px-6">{{ data.coin_a_last_median_time | formatts }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">Old states</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Set at time</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Old states</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for s in old_states %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="flex items-center whitespace-nowrap">
|
||||
<svg alt="" class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="11"></circle>
|
||||
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="py-3 pl-2 bold">
|
||||
<div>{{ s[0] | formatts }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-6">{{ s[1] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section> {% if data.events %} <section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">Events</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Time</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Events</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead> {% for e in data.events %} <tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="flex items-center px-46 whitespace-nowrap">
|
||||
<svg alt="" class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="11"></circle>
|
||||
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="py-3 pl-2 bold">
|
||||
<div>{{ e.at | formatts }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-6">{{ e.desc }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% else %}
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
{% if data.show_txns %}
|
||||
{% if data.xhv_b_shared_address or data.xhv_b_shared_viewkey or data.xhv_b_half_privatekey %}
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">View keys/Shared Address</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="table w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Type</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Output</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% if data.xhv_b_shared_address %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Shared Address:</td>
|
||||
<td class="py-3 px-6 monospace">{{ data.xhv_b_shared_address }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if data.xhv_b_shared_viewkey %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Shared View Key:</td>
|
||||
<td class="py-3 px-6 monospace">{{ data.xhv_b_shared_viewkey }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if data.xhv_b_half_privatekey %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Key Half (WARNING key data!):</td>
|
||||
<td class="py-3 px-6 monospace">{{ data.xhv_b_half_privatekey }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if data.xhv_b_half_privatekey_remote %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Remote Key Half:</td>
|
||||
<td class="py-3 px-6 monospace">{{ data.xhv_b_half_privatekey_remote }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">Transactions</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Tx Type</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Tx ID</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Blocks Deep</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for tx in data.txns %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">{{ tx.type }}</td>
|
||||
<td class="py-3 px-6 monospace">{{ tx.txid }}</td>
|
||||
<td class="py-3 px-6">{{ tx.confirms }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">More Information</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Setting</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6 bold">View Transaction:</td>
|
||||
<td class="py-3 px-6 bold">
|
||||
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||
<select class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="view_tx">
|
||||
{% if data.txns|length %} {% for tx in data.txns %}
|
||||
<option value="{{ tx.txid }}" {% if data.view_tx_ind==tx.txid %} selected{% endif %}>{{ tx.type }} {{ tx.txid }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="0">None exist yet</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="w-full md:w-0/12 mt-5">
|
||||
<div class="flex flex-wrap justify-end">
|
||||
<div class="w-full md:w-auto p-1.5 ml-2">
|
||||
<button name="view_tx_submit" value="View Tx" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
||||
<span>View Tx</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Info: Unimplemented
|
||||
<div class="w-full md:w-auto p-1.5 ml-2"><button name="view_lock_transfers" value="View Lock Wallet Transfers" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" ><span>View Lock Wallet Transfers</span></button></div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
{% if data.view_tx_hex %}
|
||||
<table class="w-full min-w-max text-sm mt-10">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">TX Hex:</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6">
|
||||
<textarea rows="5" class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0 monospace" readonly>{{ data.view_tx_hex }}</textarea>
|
||||
<textarea rows="20" class="mt-5 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0 monospace" id="tx_view" readonly>{{ data.view_tx_desc }}</textarea>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if data.lock_transfers %}
|
||||
<!--<label for="transfers_view" class="bold block mb-2 text-sm font-medium text-gray-900">Lock wallet transfers:</label>-->
|
||||
<textarea rows="20" class="mt-5 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0 monospace" id="transfers_view" readonly>{{ data.lock_transfers }}</textarea>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if data.chain_a_lock_tx_inputs %}
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">Chain A Lock TX Inputs</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">TXID</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Out</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Locked</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for txi in data.chain_a_lock_tx_inputs %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 monospace">{{ txi.txid }}</td>
|
||||
<td class="py-3 px-6 monospace">{{ txi.vout }}</td>
|
||||
<td class="py-3 px-6 bold">{% if txi.islocked %} true {% else %} false {% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if data.show_bidder_seq_diagram %}
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">Bidder Sequence Diagram</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<div class="overflow-x-auto items-center justify-center relative">
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="flex items-center justify-between text-white">
|
||||
{% if data.reverse_bid %}
|
||||
<img class="h-full py-2 pr-4 ml-8" src="/static/sequence_diagrams/ads.rev.bidder.xu.min.svg">
|
||||
{% else %}
|
||||
<img class="h-full py-2 pr-4 ml-8" src="/static/sequence_diagrams/ads.bidder.alt.xu.min.svg">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if data.show_offerer_seq_diagram %}
|
||||
<section class="p-6">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="w-full">
|
||||
<h4 class="font-semibold text-black dark:text-white text-2xl">Offerer Sequence Diagram</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<div class="overflow-x-auto items-center justify-center relative">
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="flex items-center justify-between text-white">
|
||||
{% if data.reverse_bid %}
|
||||
<img class="h-full py-2 pr-4 ml-8" src="/static/sequence_diagrams/ads.rev.offerer.xu.min.svg">
|
||||
{% else %}
|
||||
<img class="h-full py-2 pr-4 ml-8" src="/static/sequence_diagrams/ads.offerer.alt.xu.min.svg">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if edit_bid %}
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||
<table class="w-full min-w-max text-sm">
|
||||
<thead class="uppercase">
|
||||
<tr class="text-left">
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Option</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="p-0">
|
||||
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Settings</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6 bold">Change Bid State:</td>
|
||||
<td class="py-3 px-6">
|
||||
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||
<select class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="new_state">
|
||||
{% for s in data.bid_states %}
|
||||
<option value="{{ s[0] }}" {% if data.bid_state_ind==s[0] %} selected{% endif %}>{{ s[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% if data.debug_ui == true %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6 bold">Add Bid Action:</td>
|
||||
<td class="py-3 px-6">
|
||||
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||
<select class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="new_action">
|
||||
{% for a in data.bid_actions %}
|
||||
<option value="{{ a[0] }}">{{ a[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6 bold">Debug Option</td>
|
||||
<td class="py-3 px-6">
|
||||
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||
<select class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="debugind">
|
||||
<option{% if data.debug_ind=="-1" %} selected{% endif %} value="-1">None</option>
|
||||
{% for a in data.debug_options %}
|
||||
<option{% if data.debug_ind==a[0] %} selected{% endif %} value="{{ a[0] }}">{{ a[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6 bold">Sweep No-Script TX</td>
|
||||
<td class="py-3 px-6">
|
||||
<input class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="kbs_other" name="kbs_other">
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="flex flex-wrap justify-end">
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="edit_bid_cancel" value="Cancel" type="submit" class="lex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md shadow-button focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Cancel</button>
|
||||
</div>
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="edit_bid_submit" value="Submit" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Submit Edit</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<section>
|
||||
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
|
||||
<div class="pb-6 border-coolGray-100">
|
||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||
<div class="w-full pt-2">
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
<div class="flex flex-wrap justify-end">
|
||||
{% if data.show_bidder_seq_diagram %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="hide_bidder_seq_diagram" type="submit" value="Hide Bidder Sequence Diagram" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Hide Bidder Sequence Diagram</button>
|
||||
</div> {% else %} <div class="w-full md:w-auto p-1.5">
|
||||
<button name="show_bidder_seq_diagram" type="submit" value="Show Bidder Sequence Diagram" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Show Bidder Sequence Diagram</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.show_offerer_seq_diagram %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="hide_offerer_seq_diagram" type="submit" value="Hide Offerer Sequence Diagram" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Hide Offerer Sequence Diagram</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="show_offerer_seq_diagram" type="submit" value="Show Offerer Sequence Diagram" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Show Offerer Sequence Diagram</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.show_txns %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="hide_txns" type="submit" value="Hide Info" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Hide More info</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="show_txns" type="submit" value="Show More Info" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Show More Info </button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="edit_bid" type="submit" value="Edit Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Edit Bid</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.can_abandon == true %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="abandon_bid" type="submit" value="Abandon Bid" onclick="return confirmPopup();" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md focus:ring-0 focus:outline-none">Abandon Bid</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.was_received == 'True' and not edit_bid and data.can_accept_bid %}
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<button name="accept_bid" value="Accept Bid" type="submit" onclick='return confirmPopup("Accept");' class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">Accept Bid</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
function confirmPopup(name) {
|
||||
return confirm(name + " Bid - Are you sure?");
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
</html>
|
|
@ -128,6 +128,40 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="xhv-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white price-container">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Haven.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Haven">
|
||||
<p class="ml-2 text-black text-sm dark:text-white">
|
||||
Haven (XHV)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="xhv-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="xhv-price-usd-value"></span>
|
||||
</p>
|
||||
<div class="flex items-center text-sm">
|
||||
<div class="w-auto">
|
||||
<div id="xhv-price-change-container" class="w-auto p-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2">
|
||||
<span class="bold mr-2">VOL:</span>
|
||||
<div id="xhv-volume-24h">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2">
|
||||
<span class="bold mr-2">BTC:</span>
|
||||
<span id="xhv-price-btc">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="part-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
|
@ -366,7 +400,7 @@
|
|||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
const api_key = '{{chart_api_key}}';
|
||||
const coins = ['BTC', 'PART', 'XMR', 'LTC', 'FIRO', 'DASH', 'PIVX', 'DOGE', 'ETH'];
|
||||
const coins = ['BTC', 'PART', 'XMR', 'XHV', 'LTC', 'FIRO', 'DASH', 'PIVX', 'DOGE', 'ETH'];
|
||||
coins.forEach(coin => {
|
||||
fetch(`https://min-api.cryptocompare.com/data/pricemultifull?fsyms=${coin}&tsyms=USD,BTC&api_key=${api_key}`)
|
||||
.then(response => {
|
||||
|
@ -451,7 +485,7 @@ function negativePriceChangeHTML(value) {
|
|||
}
|
||||
|
||||
function setActiveContainer(containerId) {
|
||||
const containerIds = ['btc-container', 'xmr-container', 'part-container', 'pivx-container', 'firo-container', 'dash-container', 'ltc-container', 'doge-container', 'eth-container'];
|
||||
const containerIds = ['btc-container', 'xmr-container', 'xhv-container', 'part-container', 'pivx-container', 'firo-container', 'dash-container', 'ltc-container', 'doge-container', 'eth-container'];
|
||||
const activeClass = 'active-container';
|
||||
containerIds.forEach(id => {
|
||||
const container = document.getElementById(id);
|
||||
|
@ -472,6 +506,10 @@ document.getElementById('xmr-container').addEventListener('click', () => {
|
|||
setActiveContainer('xmr-container');
|
||||
updateChart('XMR');
|
||||
});
|
||||
document.getElementById('xhv-container').addEventListener('click', () => {
|
||||
setActiveContainer('xhv-container');
|
||||
updateChart('XHV');
|
||||
});
|
||||
document.getElementById('part-container').addEventListener('click', () => {
|
||||
setActiveContainer('part-container');
|
||||
updateChart('PART');
|
||||
|
@ -1075,6 +1113,7 @@ const coinNameToSymbol = {
|
|||
'Particl Blind': 'PART',
|
||||
'Particl Anon': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Haven': 'XHV',
|
||||
'Litecoin': 'LTC',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
|
|
|
@ -541,6 +541,7 @@ const coinNameToSymbol = {
|
|||
'Particl Blind': 'PART',
|
||||
'Particl Anon': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Haven': 'XHV',
|
||||
'Litecoin': 'LTC',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
|
|
|
@ -200,6 +200,7 @@ const coinNameToSymbol = {
|
|||
'Particl Blind': 'PART',
|
||||
'Particl Anon': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Haven': 'XHV',
|
||||
'Litecoin': 'LTC',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
|
|
|
@ -55,7 +55,7 @@ def page_settings(self, url_split, post_string):
|
|||
for name, c in swap_client.settings['chainclients'].items():
|
||||
if have_data_entry(form_data, 'apply_' + name):
|
||||
data = {'lookups': get_data_entry(form_data, 'lookups_' + name)}
|
||||
if name == 'monero':
|
||||
if name in ['haven', 'monero']:
|
||||
data['fee_priority'] = int(get_data_entry(form_data, 'fee_priority_' + name))
|
||||
data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False
|
||||
data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name)
|
||||
|
@ -102,7 +102,7 @@ def page_settings(self, url_split, post_string):
|
|||
'manage_daemon': c.get('manage_daemon', 'Unknown'),
|
||||
'connection_type': c.get('connection_type', 'Unknown'),
|
||||
})
|
||||
if name == 'monero':
|
||||
if name in ['haven', 'monero']:
|
||||
chains_formatted[-1]['fee_priority'] = c.get('fee_priority', 0)
|
||||
chains_formatted[-1]['manage_wallet_daemon'] = c.get('manage_wallet_daemon', 'Unknown')
|
||||
chains_formatted[-1]['rpchost'] = c.get('rpchost', 'localhost')
|
||||
|
|
17
basicswap/util_xhv.py
Normal file
17
basicswap/util_xhv.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import basicswap.contrib.Keccak as Keccak
|
||||
from .contrib.HavenPy.base58 import encode as xhv_b58encode
|
||||
|
||||
|
||||
def cn_fast_hash(s):
|
||||
k = Keccak.Keccak()
|
||||
return k.Keccak((len(s) * 8, s.hex()), 1088, 512, 32 * 8, False).lower() # r = bitrate = 1088, c = capacity, n = output length in bits
|
||||
|
||||
|
||||
def encode_address(view_point, spend_point, version=18):
|
||||
buf = bytes((version,)) + spend_point + view_point
|
||||
h = cn_fast_hash(buf)
|
||||
buf = buf + bytes.fromhex(h[0: 8])
|
||||
|
||||
return xhv_b58encode(buf.hex())
|
16
docker/production/compose-fragments/1_haven-wallet.yml
Normal file
16
docker/production/compose-fragments/1_haven-wallet.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
haven_wallet:
|
||||
image: i_haven_wallet
|
||||
build:
|
||||
context: haven_wallet
|
||||
dockerfile: Dockerfile
|
||||
container_name: haven_wallet
|
||||
volumes:
|
||||
- ${DATA_PATH}/haven_wallet:/data
|
||||
expose:
|
||||
- ${BASE_XHV_WALLET_PORT}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
16
docker/production/compose-fragments/8_haven-daemon.yml
Normal file
16
docker/production/compose-fragments/8_haven-daemon.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
haven_daemon:
|
||||
image: i_haven_daemon
|
||||
build:
|
||||
context: haven_daemon
|
||||
dockerfile: Dockerfile
|
||||
container_name: haven_daemon
|
||||
volumes:
|
||||
- ${DATA_PATH}/haven_daemon:/data
|
||||
expose:
|
||||
- ${BASE_XHV_RPC_PORT}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
|
@ -8,6 +8,8 @@
|
|||
- ${DATA_PATH}/swapclient:/data/swapclient
|
||||
- ${DATA_PATH}/monero_daemon:/data/monero_daemon
|
||||
- ${DATA_PATH}/monero_wallet:/data/monero_wallet
|
||||
- ${DATA_PATH}/haven_daemon:/data/haven_daemon
|
||||
- ${DATA_PATH}/haven_wallet:/data/haven_wallet
|
||||
- ${DATA_PATH}/particl:/data/particl
|
||||
- ${DATA_PATH}/bitcoin:/data/bitcoin
|
||||
- ${DATA_PATH}/litecoin:/data/litecoin
|
||||
|
@ -45,6 +47,16 @@
|
|||
- XMR_WALLET_RPC_USER
|
||||
- XMR_WALLET_RPC_PWD
|
||||
- DEFAULT_XMR_RESTORE_HEIGHT
|
||||
- XHV_DATA_DIR
|
||||
- XHV_RPC_HOST
|
||||
- BASE_XHV_RPC_PORT
|
||||
- BASE_XHV_ZMQ_PORT
|
||||
- XHV_WALLETS_DIR
|
||||
- XHV_WALLET_RPC_HOST
|
||||
- BASE_XHV_WALLET_PORT
|
||||
- XHV_WALLET_RPC_USER
|
||||
- XHV_WALLET_RPC_PWD
|
||||
- DEFAULT_XHV_RESTORE_HEIGHT
|
||||
- PIVX_DATA_DIR
|
||||
- PIVX_RPC_HOST
|
||||
- PIVX_RPC_PORT
|
||||
|
|
|
@ -30,12 +30,22 @@ XMR_DATA_DIR=/data/monero_daemon
|
|||
XMR_RPC_HOST=monero_daemon
|
||||
BASE_XMR_RPC_PORT=29798
|
||||
|
||||
XMR_WALLETS_DIR=/data/monero_wallet
|
||||
XMR_WALLET_RPC_HOST=monero_wallet
|
||||
BASE_XMR_WALLET_PORT=29998
|
||||
XMR_WALLETS_DIR=/data/haven_wallet
|
||||
XMR_WALLET_RPC_HOST=haven_wallet
|
||||
BASE_XMR_WALLET_PORT=29958
|
||||
XMR_WALLET_RPC_USER=xmr_wallet_user
|
||||
XMR_WALLET_RPC_PWD=xmr_wallet_pwd
|
||||
|
||||
XHV_DATA_DIR=/data/haven_daemon
|
||||
XHV_RPC_HOST=haven_daemon
|
||||
BASE_XHV_RPC_PORT=29758
|
||||
|
||||
XHV_WALLETS_DIR=/data/haven_wallet
|
||||
XHV_WALLET_RPC_HOST=haven_wallet
|
||||
BASE_XHV_WALLET_PORT=29958
|
||||
XHV_WALLET_RPC_USER=xhv_wallet_user
|
||||
XHV_WALLET_RPC_PWD=xhv_wallet_pwd
|
||||
|
||||
PIVX_DATA_DIR=/data/pivx
|
||||
PIVX_RPC_HOST=pivx_core
|
||||
PIVX_RPC_PORT=51473
|
||||
|
|
24
docker/production/haven_daemon/Dockerfile
Normal file
24
docker/production/haven_daemon/Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
|||
FROM i_swapclient as install_stage
|
||||
|
||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=haven --withoutcoins=particl
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
COPY --from=install_stage /coin_bin .
|
||||
|
||||
ENV HAVEN_DATA /data
|
||||
|
||||
RUN groupadd -r haven && useradd -r -m -g haven haven \
|
||||
&& apt-get update \
|
||||
&& apt-get install -qq --no-install-recommends gosu \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p "$HAVEN_DATA" \
|
||||
&& chown -R haven:haven "$HAVEN_DATA" \
|
||||
&& ln -sfn "$HAVEN_DATA" /home/haven/.haven \
|
||||
&& chown -h haven:haven /home/haven/.haven
|
||||
VOLUME $HAVEN_DATA
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
CMD ["/haven/havend", "--non-interactive", "--config-file=/home/haven/.haven/havend.conf", "--confirm-external-bind"]
|
11
docker/production/haven_daemon/entrypoint.sh
Executable file
11
docker/production/haven_daemon/entrypoint.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ "$1" == "havend" ]]; then
|
||||
mkdir -p "$HAVEN_DATA"
|
||||
|
||||
chown -h haven:haven /home/haven/.haven
|
||||
exec gosu haven "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
16
docker/production/haven_wallet/Dockerfile
Normal file
16
docker/production/haven_wallet/Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
|||
FROM i_haven_daemon
|
||||
|
||||
ENV HAVEN_DATA /data
|
||||
|
||||
RUN groupadd -r haven_wallet && useradd -r -m -g haven_wallet haven_wallet \
|
||||
&& apt-get update \
|
||||
&& apt-get install -qq --no-install-recommends gosu \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p "$HAVEN_DATA" \
|
||||
&& chown -R haven_wallet:haven_wallet "$HAVEN_DATA"
|
||||
VOLUME $HAVEN_DATA
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
CMD ["/haven/haven-wallet-rpc", "--non-interactive", "--config-file=/data/haven_wallet.conf", "--confirm-external-bind"]
|
11
docker/production/haven_wallet/entrypoint.sh
Executable file
11
docker/production/haven_wallet/entrypoint.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ "$1" == "haven-wallet-rpc" ]]; then
|
||||
mkdir -p "$HAVEN_DATA"
|
||||
|
||||
chown -h haven_wallet:haven_wallet /data
|
||||
exec gosu haven_wallet "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
|
@ -66,6 +66,15 @@ def main():
|
|||
for line in fp_in:
|
||||
fp.write(line)
|
||||
continue
|
||||
if coin_name == 'haven':
|
||||
with open(os.path.join(fragments_dir, '1_haven-wallet.yml'), 'rb') as fp_in:
|
||||
for line in fp_in:
|
||||
fp.write(line)
|
||||
fpp.write(line)
|
||||
with open(os.path.join(fragments_dir, '8_haven-daemon.yml'), 'rb') as fp_in:
|
||||
for line in fp_in:
|
||||
fp.write(line)
|
||||
continue
|
||||
with open(os.path.join(fragments_dir, f'1_{coin_name}.yml'), 'rb') as fp_in:
|
||||
for line in fp_in:
|
||||
fp.write(line)
|
||||
|
|
419
tests/basicswap/common_xhv.py
Normal file
419
tests/basicswap/common_xhv.py
Normal file
|
@ -0,0 +1,419 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2022 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import signal
|
||||
import logging
|
||||
import unittest
|
||||
import threading
|
||||
import multiprocessing
|
||||
from io import StringIO
|
||||
from urllib.request import urlopen
|
||||
from unittest.mock import patch
|
||||
|
||||
from basicswap.rpc_xhv import (
|
||||
callrpc_xhv,
|
||||
)
|
||||
from tests.basicswap.mnemonics import mnemonics
|
||||
from tests.basicswap.util import (
|
||||
waitForServer,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
BASE_PORT, BASE_RPC_PORT,
|
||||
BTC_BASE_PORT, BTC_BASE_RPC_PORT, BTC_BASE_TOR_PORT,
|
||||
LTC_BASE_PORT, LTC_BASE_RPC_PORT,
|
||||
PIVX_BASE_PORT,
|
||||
)
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
|
||||
import basicswap.config as cfg
|
||||
import bin.basicswap_run as runSystem
|
||||
|
||||
TEST_PATH = os.path.expanduser(os.getenv('TEST_PATH', '~/test_basicswap1'))
|
||||
|
||||
PARTICL_PORT_BASE = int(os.getenv('PARTICL_PORT_BASE', BASE_PORT))
|
||||
PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT))
|
||||
|
||||
BITCOIN_PORT_BASE = int(os.getenv('BITCOIN_PORT_BASE', BTC_BASE_PORT))
|
||||
BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT))
|
||||
BITCOIN_TOR_PORT_BASE = int(os.getenv('BITCOIN_TOR_PORT_BASE', BTC_BASE_TOR_PORT))
|
||||
|
||||
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
|
||||
|
||||
FIRO_BASE_PORT = 34832
|
||||
FIRO_BASE_RPC_PORT = 35832
|
||||
FIRO_RPC_PORT_BASE = int(os.getenv('FIRO_RPC_PORT_BASE', FIRO_BASE_RPC_PORT))
|
||||
|
||||
|
||||
XHV_BASE_P2P_PORT = 17752
|
||||
XHV_BASE_RPC_PORT = 29758
|
||||
XHV_BASE_WALLET_RPC_PORT = 29958
|
||||
|
||||
EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
|
||||
|
||||
|
||||
def waitForBidState(delay_event, port, bid_id, state_str, wait_for=60):
|
||||
for i in range(wait_for):
|
||||
if delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
bid = json.loads(urlopen('http://127.0.0.1:12700/json/bids/{}'.format(bid_id)).read())
|
||||
if bid['bid_state'] == state_str:
|
||||
return
|
||||
delay_event.wait(1)
|
||||
raise ValueError('waitForBidState failed')
|
||||
|
||||
|
||||
def updateThread(xhv_addr, delay_event, xhv_auth):
|
||||
while not delay_event.is_set():
|
||||
try:
|
||||
callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': xhv_addr, 'amount_of_blocks': 1}, auth=xhv_auth)
|
||||
except Exception as e:
|
||||
print('updateThread error', str(e))
|
||||
delay_event.wait(2)
|
||||
|
||||
|
||||
def recursive_update_dict(base, new_vals):
|
||||
for key, value in new_vals.items():
|
||||
if key in base and isinstance(value, dict):
|
||||
recursive_update_dict(base[key], value)
|
||||
else:
|
||||
base[key] = value
|
||||
|
||||
|
||||
def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None, num_nodes=3, use_rpcauth=False, extra_settings={}, port_ofs=0):
|
||||
config_path = os.path.join(datadir_path, cfg.CONFIG_FILENAME)
|
||||
|
||||
os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE)
|
||||
os.environ['BTC_RPC_PORT'] = str(BITCOIN_RPC_PORT_BASE)
|
||||
os.environ['LTC_RPC_PORT'] = str(LITECOIN_RPC_PORT_BASE)
|
||||
os.environ['FIRO_RPC_PORT'] = str(FIRO_RPC_PORT_BASE)
|
||||
|
||||
os.environ['XHV_RPC_USER'] = 'xhv_user'
|
||||
os.environ['XHV_RPC_PWD'] = 'xhv_pwd'
|
||||
|
||||
import bin.basicswap_prepare as prepareSystem
|
||||
# Hack: Reload module to set env vars as the basicswap_prepare module is initialised if imported from elsewhere earlier
|
||||
from importlib import reload
|
||||
prepareSystem = reload(prepareSystem)
|
||||
|
||||
testargs = [
|
||||
'basicswap-prepare',
|
||||
f'-datadir="{datadir_path}"',
|
||||
f'-bindir="{bins_path}"',
|
||||
f'-portoffset={(node_id + port_ofs)}',
|
||||
'-regtest',
|
||||
f'-withcoins={with_coins}',
|
||||
'-noextractover',
|
||||
'-xhvrestoreheight=0']
|
||||
if mnemonic_in:
|
||||
testargs.append(f'-particl_mnemonic="{mnemonic_in}"')
|
||||
|
||||
keysdirpath = os.getenv('PGP_KEYS_DIR_PATH', None)
|
||||
if keysdirpath is not None:
|
||||
testargs.append('-keysdirpath="' + os.path.expanduser(keysdirpath) + '"')
|
||||
with patch.object(sys, 'argv', testargs), patch('sys.stdout', new=StringIO()) as mocked_stdout:
|
||||
prepareSystem.main()
|
||||
lines = mocked_stdout.getvalue().split('\n')
|
||||
mnemonic_out = lines[-4]
|
||||
|
||||
with open(config_path) as fs:
|
||||
settings = json.load(fs)
|
||||
|
||||
with open(os.path.join(datadir_path, 'particl', 'particl.conf'), 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(os.path.join(datadir_path, 'particl', 'particl.conf'), 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('staking'):
|
||||
fp.write(line)
|
||||
fp.write('port={}\n'.format(PARTICL_PORT_BASE + node_id + port_ofs))
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('dnsseed=0\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('upnp=0\n')
|
||||
fp.write('minstakeinterval=5\n')
|
||||
fp.write('stakethreadconddelayms=2000\n')
|
||||
fp.write('smsgsregtestadjust=0\n')
|
||||
if use_rpcauth:
|
||||
salt = generate_salt(16)
|
||||
rpc_user = 'test_part_' + str(node_id)
|
||||
rpc_pass = 'test_part_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['particl']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['particl']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(PARTICL_PORT_BASE + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('part{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
coins_array = with_coins.split(',')
|
||||
|
||||
if 'bitcoin' in coins_array:
|
||||
# Pruned nodes don't provide blocks
|
||||
with open(os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf'), 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf'), 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('prune'):
|
||||
fp.write(line)
|
||||
fp.write('port={}\n'.format(BITCOIN_PORT_BASE + node_id + port_ofs))
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
# listenonion=0 does not stop the node from trying to bind to the tor port
|
||||
# https://github.com/bitcoin/bitcoin/issues/22726
|
||||
fp.write('bind=127.0.0.1:{}=onion\n'.format(BITCOIN_TOR_PORT_BASE + node_id + port_ofs))
|
||||
fp.write('dnsseed=0\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('upnp=0\n')
|
||||
if use_rpcauth:
|
||||
salt = generate_salt(16)
|
||||
rpc_user = 'test_btc_' + str(node_id)
|
||||
rpc_pass = 'test_btc_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['bitcoin']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['bitcoin']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(BITCOIN_PORT_BASE + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('btc{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if 'litecoin' in coins_array:
|
||||
# Pruned nodes don't provide blocks
|
||||
with open(os.path.join(datadir_path, 'litecoin', 'litecoin.conf'), 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(os.path.join(datadir_path, 'litecoin', 'litecoin.conf'), 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('prune'):
|
||||
fp.write(line)
|
||||
fp.write('port={}\n'.format(LTC_BASE_PORT + node_id + port_ofs))
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('dnsseed=0\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('upnp=0\n')
|
||||
if use_rpcauth:
|
||||
salt = generate_salt(16)
|
||||
rpc_user = 'test_ltc_' + str(node_id)
|
||||
rpc_pass = 'test_ltc_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['litecoin']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['litecoin']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(LTC_BASE_PORT + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if 'pivx' in coins_array:
|
||||
# Pruned nodes don't provide blocks
|
||||
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('prune'):
|
||||
fp.write(line)
|
||||
fp.write('port={}\n'.format(PIVX_BASE_PORT + node_id + port_ofs))
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('dnsseed=0\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('upnp=0\n')
|
||||
if use_rpcauth:
|
||||
salt = generate_salt(16)
|
||||
rpc_user = 'test_pivx_' + str(node_id)
|
||||
rpc_pass = 'test_pivx_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['pivx']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['pivx']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(PIVX_BASE_PORT + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('pivx{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if 'firo' in coins_array:
|
||||
# Pruned nodes don't provide blocks
|
||||
with open(os.path.join(datadir_path, 'firo', 'firo.conf'), 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
with open(os.path.join(datadir_path, 'firo', 'firo.conf'), 'w') as fp:
|
||||
for line in lines:
|
||||
if not line.startswith('prune'):
|
||||
fp.write(line)
|
||||
fp.write('port={}\n'.format(FIRO_BASE_PORT + node_id + port_ofs))
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('dnsseed=0\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('upnp=0\n')
|
||||
if use_rpcauth:
|
||||
salt = generate_salt(16)
|
||||
rpc_user = 'test_firo_' + str(node_id)
|
||||
rpc_pass = 'test_firo_pwd_' + str(node_id)
|
||||
fp.write('rpcauth={}:{}${}\n'.format(rpc_user, salt, password_to_hmac(salt, rpc_pass)))
|
||||
settings['chainclients']['firo']['rpcuser'] = rpc_user
|
||||
settings['chainclients']['firo']['rpcpassword'] = rpc_pass
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('connect=127.0.0.1:{}\n'.format(FIRO_BASE_PORT + ip + port_ofs))
|
||||
for opt in EXTRA_CONFIG_JSON.get('firo{}'.format(node_id), []):
|
||||
fp.write(opt + '\n')
|
||||
|
||||
if 'haven' in coins_array:
|
||||
with open(os.path.join(datadir_path, 'haven', 'havend.conf'), 'a') as fp:
|
||||
fp.write('p2p-bind-ip=127.0.0.1\n')
|
||||
fp.write('p2p-bind-port={}\n'.format(XHV_BASE_P2P_PORT + node_id + port_ofs))
|
||||
for ip in range(num_nodes):
|
||||
if ip != node_id:
|
||||
fp.write('add-exclusive-node=127.0.0.1:{}\n'.format(XHV_BASE_P2P_PORT + ip + port_ofs))
|
||||
|
||||
with open(config_path) as fs:
|
||||
settings = json.load(fs)
|
||||
|
||||
settings['min_delay_event'] = 1
|
||||
settings['max_delay_event'] = 4
|
||||
settings['min_delay_event_short'] = 1
|
||||
settings['max_delay_event_short'] = 4
|
||||
settings['min_delay_retry'] = 10
|
||||
settings['max_delay_retry'] = 20
|
||||
|
||||
settings['check_progress_seconds'] = 5
|
||||
settings['check_watched_seconds'] = 5
|
||||
settings['check_expired_seconds'] = 60
|
||||
settings['check_events_seconds'] = 5
|
||||
settings['check_xhv_swaps_seconds'] = 5
|
||||
|
||||
recursive_update_dict(settings, extra_settings)
|
||||
|
||||
extra_config = EXTRA_CONFIG_JSON.get('sc{}'.format(node_id), {})
|
||||
recursive_update_dict(settings, extra_config)
|
||||
|
||||
with open(config_path, 'w') as fp:
|
||||
json.dump(settings, fp, indent=4)
|
||||
|
||||
return mnemonic_out
|
||||
|
||||
|
||||
def prepare_nodes(num_nodes, extra_coins, use_rpcauth=False, extra_settings={}, port_ofs=0):
|
||||
bins_path = os.path.join(TEST_PATH, 'bin')
|
||||
for i in range(num_nodes):
|
||||
logging.info('Preparing node: %d.', i)
|
||||
client_path = os.path.join(TEST_PATH, 'client{}'.format(i))
|
||||
try:
|
||||
shutil.rmtree(client_path)
|
||||
except Exception as ex:
|
||||
logging.warning('setUpClass %s', str(ex))
|
||||
|
||||
run_prepare(i, client_path, bins_path, extra_coins, mnemonics[i] if i < len(mnemonics) else None,
|
||||
num_nodes=num_nodes, use_rpcauth=use_rpcauth, extra_settings=extra_settings, port_ofs=port_ofs)
|
||||
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
def setUpClass(cls):
|
||||
super(TestBase, cls).setUpClass()
|
||||
|
||||
cls.delay_event = threading.Event()
|
||||
signal.signal(signal.SIGINT, lambda signal, frame: cls.signal_handler(cls, signal, frame))
|
||||
|
||||
def signal_handler(self, sig, frame):
|
||||
logging.info('signal {} detected.'.format(sig))
|
||||
self.delay_event.set()
|
||||
|
||||
def wait_seconds(self, seconds):
|
||||
self.delay_event.wait(seconds)
|
||||
if self.delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
|
||||
def wait_for_particl_height(self, http_port, num_blocks=3):
|
||||
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||
logging.info('Waiting for Particl chain height %d', num_blocks)
|
||||
for i in range(60):
|
||||
if self.delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
try:
|
||||
wallets = json.loads(urlopen(f'http://127.0.0.1:{http_port}/json/wallets').read())
|
||||
particl_blocks = wallets['PART']['blocks']
|
||||
print('particl_blocks', particl_blocks)
|
||||
if particl_blocks >= num_blocks:
|
||||
return
|
||||
except Exception as e:
|
||||
print('Error reading wallets', str(e))
|
||||
|
||||
self.delay_event.wait(1)
|
||||
raise ValueError(f'wait_for_particl_height failed http_port: {http_port}')
|
||||
|
||||
|
||||
class XhvTestBase(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(XhvTestBase, cls).setUpClass(cls)
|
||||
|
||||
cls.update_thread = None
|
||||
cls.processes = []
|
||||
|
||||
prepare_nodes(3, 'haven')
|
||||
|
||||
def run_thread(self, client_id):
|
||||
client_path = os.path.join(TEST_PATH, 'client{}'.format(client_id))
|
||||
testargs = ['basicswap-run', '-datadir=' + client_path, '-regtest']
|
||||
with patch.object(sys, 'argv', testargs):
|
||||
runSystem.main()
|
||||
|
||||
def start_processes(self):
|
||||
self.delay_event.clear()
|
||||
|
||||
for i in range(3):
|
||||
self.processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
|
||||
self.processes[-1].start()
|
||||
|
||||
waitForServer(self.delay_event, 12701)
|
||||
|
||||
def waitForMainAddress():
|
||||
for i in range(20):
|
||||
if self.delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
try:
|
||||
wallets = json.loads(urlopen('http://127.0.0.1:12701/json/wallets').read())
|
||||
return wallets['XHV']['main_address']
|
||||
except Exception as e:
|
||||
print('Waiting for main address {}'.format(str(e)))
|
||||
self.delay_event.wait(1)
|
||||
raise ValueError('waitForMainAddress timedout')
|
||||
xhv_addr1 = waitForMainAddress()
|
||||
|
||||
num_blocks = 100
|
||||
|
||||
xhv_auth = None
|
||||
if os.getenv('XHV_RPC_USER', '') != '':
|
||||
xhv_auth = (os.getenv('XHV_RPC_USER', ''), os.getenv('XHV_RPC_PWD', ''))
|
||||
|
||||
if callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'get_block_count', auth=xhv_auth)['count'] < num_blocks:
|
||||
logging.info('Mining {} Haven blocks to {}.'.format(num_blocks, xhv_addr1))
|
||||
callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': xhv_addr1, 'amount_of_blocks': num_blocks}, auth=xhv_auth)
|
||||
logging.info('XHV blocks: %d', callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'get_block_count', auth=xhv_auth)['count'])
|
||||
|
||||
self.update_thread = threading.Thread(target=updateThread, args=(xhv_addr1, self.delay_event, xhv_auth))
|
||||
self.update_thread.start()
|
||||
|
||||
self.wait_for_particl_height(12701, num_blocks=3)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info('Stopping test')
|
||||
cls.delay_event.set()
|
||||
if cls.update_thread:
|
||||
cls.update_thread.join()
|
||||
for p in cls.processes:
|
||||
p.terminate()
|
||||
for p in cls.processes:
|
||||
p.join()
|
||||
cls.update_thread = None
|
||||
cls.processes = []
|
252
tests/basicswap/extended/test_xhv_persistent.py
Normal file
252
tests/basicswap/extended/test_xhv_persistent.py
Normal file
|
@ -0,0 +1,252 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021-2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
"""
|
||||
export RESET_TEST=true
|
||||
export TEST_PATH=/tmp/test_persistent
|
||||
mkdir -p ${TEST_PATH}/bin
|
||||
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
|
||||
export PYTHONPATH=$(pwd)
|
||||
export XHV_RPC_USER=xhv_user
|
||||
export XHV_RPC_PWD=xhv_pwd
|
||||
python tests/basicswap/extended/test_xhv_persistent.py
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import signal
|
||||
import logging
|
||||
import unittest
|
||||
import threading
|
||||
import multiprocessing
|
||||
from unittest.mock import patch
|
||||
|
||||
from basicswap.rpc_xhv import (
|
||||
callrpc_xhv,
|
||||
)
|
||||
from basicswap.rpc import (
|
||||
callrpc,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
BASE_RPC_PORT,
|
||||
BTC_BASE_RPC_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
)
|
||||
from tests.basicswap.util import (
|
||||
make_boolean,
|
||||
read_json_api,
|
||||
waitForServer,
|
||||
)
|
||||
from tests.basicswap.common_xhv import (
|
||||
prepare_nodes,
|
||||
XHV_BASE_RPC_PORT,
|
||||
)
|
||||
import bin.basicswap_run as runSystem
|
||||
|
||||
|
||||
test_path = os.path.expanduser(os.getenv('TEST_PATH', '/tmp/test_persistent'))
|
||||
RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'false'))
|
||||
|
||||
PORT_OFS = int(os.getenv('PORT_OFS', 1))
|
||||
UI_PORT = 12700 + PORT_OFS
|
||||
|
||||
PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT))
|
||||
BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT))
|
||||
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
|
||||
XHV_BASE_RPC_PORT = int(os.getenv('XHV_BASE_RPC_PORT', XHV_BASE_RPC_PORT))
|
||||
TEST_COINS_LIST = os.getenv('TEST_COINS_LIST', 'bitcoin,monero')
|
||||
|
||||
NUM_NODES = int(os.getenv('NUM_NODES', 3))
|
||||
EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
|
||||
def callpartrpc(node_id, method, params=[], wallet=None, base_rpc_port=PARTICL_RPC_PORT_BASE + PORT_OFS):
|
||||
auth = 'test_part_{0}:test_part_pwd_{0}'.format(node_id)
|
||||
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
||||
|
||||
|
||||
def callbtcrpc(node_id, method, params=[], wallet=None, base_rpc_port=BITCOIN_RPC_PORT_BASE + PORT_OFS):
|
||||
auth = 'test_btc_{0}:test_btc_pwd_{0}'.format(node_id)
|
||||
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
||||
|
||||
|
||||
def callltcrpc(node_id, method, params=[], wallet=None, base_rpc_port=LITECOIN_RPC_PORT_BASE + PORT_OFS):
|
||||
auth = 'test_ltc_{0}:test_ltc_pwd_{0}'.format(node_id)
|
||||
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
||||
|
||||
|
||||
def updateThread(cls):
|
||||
while not cls.delay_event.is_set():
|
||||
try:
|
||||
if cls.btc_addr is not None:
|
||||
callbtcrpc(0, 'generatetoaddress', [1, cls.btc_addr])
|
||||
except Exception as e:
|
||||
print('updateThread error', str(e))
|
||||
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
|
||||
|
||||
|
||||
def updateThreadXhv(cls):
|
||||
xhv_auth = None
|
||||
if os.getenv('XHV_RPC_USER', '') != '':
|
||||
xhv_auth = (os.getenv('XHV_RPC_USER', ''), os.getenv('XHV_RPC_PWD', ''))
|
||||
|
||||
while not cls.delay_event.is_set():
|
||||
try:
|
||||
if cls.xhv_addr is not None:
|
||||
callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xhv_addr, 'amount_of_blocks': 1}, auth=xhv_auth)
|
||||
except Exception as e:
|
||||
print('updateThreadXhv error', str(e))
|
||||
cls.delay_event.wait(random.randrange(cls.xhv_update_min, cls.xhv_update_max))
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
cls.update_min = int(os.getenv('UPDATE_THREAD_MIN_WAIT', '1'))
|
||||
cls.update_max = cls.update_min * 4
|
||||
|
||||
cls.xhv_update_min = int(os.getenv('XHV_UPDATE_THREAD_MIN_WAIT', '1'))
|
||||
cls.xhv_update_max = cls.xhv_update_min * 4
|
||||
|
||||
cls.delay_event = threading.Event()
|
||||
cls.update_thread = None
|
||||
cls.update_thread_xhv = None
|
||||
cls.processes = []
|
||||
cls.btc_addr = None
|
||||
cls.xhv_addr = None
|
||||
|
||||
random.seed(time.time())
|
||||
|
||||
if os.path.exists(test_path) and not RESET_TEST:
|
||||
logging.info(f'Continuing with existing directory: {test_path}')
|
||||
else:
|
||||
logging.info('Preparing %d nodes.', NUM_NODES)
|
||||
prepare_nodes(NUM_NODES, TEST_COINS_LIST, True, {'min_sequence_lock_seconds': 60}, PORT_OFS)
|
||||
|
||||
signal.signal(signal.SIGINT, lambda signal, frame: cls.signal_handler(cls, signal, frame))
|
||||
|
||||
def signal_handler(self, sig, frame):
|
||||
logging.info('signal {} detected.'.format(sig))
|
||||
self.delay_event.set()
|
||||
|
||||
def run_thread(self, client_id):
|
||||
client_path = os.path.join(test_path, 'client{}'.format(client_id))
|
||||
testargs = ['basicswap-run', '-datadir=' + client_path, '-regtest']
|
||||
with patch.object(sys, 'argv', testargs):
|
||||
runSystem.main()
|
||||
|
||||
def start_processes(self):
|
||||
self.delay_event.clear()
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
self.processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
|
||||
self.processes[-1].start()
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
waitForServer(self.delay_event, UI_PORT + i)
|
||||
|
||||
wallets = read_json_api(UI_PORT + 1, 'wallets')
|
||||
|
||||
xhv_auth = None
|
||||
if os.getenv('XHV_RPC_USER', '') != '':
|
||||
xhv_auth = (os.getenv('XHV_RPC_USER', ''), os.getenv('XHV_RPC_PWD', ''))
|
||||
|
||||
self.xhv_addr = wallets['XHV']['main_address']
|
||||
num_blocks = 100
|
||||
if callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'get_block_count', auth=xhv_auth)['count'] < num_blocks:
|
||||
logging.info('Mining {} Monero blocks to {}.'.format(num_blocks, self.xhv_addr))
|
||||
callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': self.xhv_addr, 'amount_of_blocks': num_blocks}, auth=xhv_auth)
|
||||
logging.info('XHV blocks: %d', callrpc_xhv(XHV_BASE_RPC_PORT + 1, 'get_block_count', auth=xhv_auth)['count'])
|
||||
|
||||
self.btc_addr = callbtcrpc(0, 'getnewaddress', ['mining_addr', 'bech32'])
|
||||
num_blocks: int = 500 # Mine enough to activate segwit
|
||||
if callbtcrpc(0, 'getblockcount') < num_blocks:
|
||||
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, self.btc_addr)
|
||||
callbtcrpc(0, 'generatetoaddress', [num_blocks, self.btc_addr])
|
||||
logging.info('BTC blocks: %d', callbtcrpc(0, 'getblockcount'))
|
||||
|
||||
if 'litecoin' in TEST_COINS_LIST:
|
||||
self.ltc_addr = callltcrpc(0, 'getnewaddress', ['mining_addr'], wallet='wallet.dat')
|
||||
num_blocks: int = 431
|
||||
have_blocks: int = callltcrpc(0, 'getblockcount')
|
||||
if have_blocks < 500:
|
||||
logging.info('Mining %d Litecoin blocks to %s', num_blocks, self.ltc_addr)
|
||||
callltcrpc(0, 'generatetoaddress', [num_blocks - have_blocks, self.ltc_addr], wallet='wallet.dat')
|
||||
|
||||
# https://github.com/litecoin-project/litecoin/issues/807
|
||||
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
|
||||
mweb_addr = callltcrpc(0, 'getnewaddress', ['mweb_addr', 'mweb'], wallet='mweb')
|
||||
callltcrpc(0, 'sendtoaddress', [mweb_addr, 1.0], wallet='wallet.dat')
|
||||
num_blocks = 69
|
||||
|
||||
have_blocks: int = callltcrpc(0, 'getblockcount')
|
||||
callltcrpc(0, 'generatetoaddress', [500 - have_blocks, self.ltc_addr], wallet='wallet.dat')
|
||||
|
||||
# Lower output split threshold for more stakeable outputs
|
||||
for i in range(NUM_NODES):
|
||||
callpartrpc(i, 'walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
|
||||
self.update_thread = threading.Thread(target=updateThread, args=(self,))
|
||||
self.update_thread.start()
|
||||
|
||||
self.update_thread_xhv = threading.Thread(target=updateThreadXhv, args=(self,))
|
||||
self.update_thread_xhv.start()
|
||||
|
||||
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||
num_blocks = 3
|
||||
logging.info('Waiting for Particl chain height %d', num_blocks)
|
||||
for i in range(60):
|
||||
if self.delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
particl_blocks = callpartrpc(0, 'getblockcount')
|
||||
print('particl_blocks', particl_blocks)
|
||||
if particl_blocks >= num_blocks:
|
||||
break
|
||||
self.delay_event.wait(1)
|
||||
logging.info('PART blocks: %d', callpartrpc(0, 'getblockcount'))
|
||||
assert particl_blocks >= num_blocks
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info('Stopping test')
|
||||
cls.delay_event.set()
|
||||
if cls.update_thread:
|
||||
cls.update_thread.join()
|
||||
if cls.update_thread_xhv:
|
||||
cls.update_thread_xhv.join()
|
||||
for p in cls.processes:
|
||||
p.terminate()
|
||||
for p in cls.processes:
|
||||
p.join()
|
||||
cls.update_thread = None
|
||||
cls.update_thread_xhv = None
|
||||
cls.processes = []
|
||||
|
||||
def test_persistent(self):
|
||||
|
||||
self.start_processes()
|
||||
|
||||
waitForServer(self.delay_event, UI_PORT + 0)
|
||||
waitForServer(self.delay_event, UI_PORT + 1)
|
||||
|
||||
while not self.delay_event.is_set():
|
||||
logging.info('Looping indefinitely, ctrl+c to exit.')
|
||||
self.delay_event.wait(10)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,2 +1,3 @@
|
|||
python tests/basicswap/extended/test_xmr_persistent.py
|
||||
python tests/basicswap/extended/test_xhv_persistent.py
|
||||
python tests/basicswap/selenium/test_wallets.py
|
||||
|
|
|
@ -88,6 +88,7 @@ def test_offer(driver):
|
|||
|
||||
print('Test Passed!')
|
||||
|
||||
# didn't do test_offer_for_xhv
|
||||
|
||||
def run_tests():
|
||||
driver = get_driver()
|
||||
|
|
|
@ -136,6 +136,40 @@ def test_settings(driver):
|
|||
settings = json.load(fs)
|
||||
assert (len(settings['chainclients']['monero']['remote_daemon_urls']) == 0)
|
||||
|
||||
# Apply XHV settings with blank nodes list
|
||||
driver.find_element(By.ID, 'coins-tab').click()
|
||||
btn_apply_haven = wait.until(EC.element_to_be_clickable((By.NAME, 'apply_haven')))
|
||||
el = driver.find_element(By.NAME, 'remotedaemonurls_haven')
|
||||
el.clear()
|
||||
btn_apply_haven.click()
|
||||
time.sleep(1)
|
||||
|
||||
with open(settings_path_0) as fs:
|
||||
settings = json.load(fs)
|
||||
assert (len(settings['chainclients']['haven']['remote_daemon_urls']) == 0)
|
||||
|
||||
btn_apply_haven = wait.until(EC.element_to_be_clickable((By.NAME, 'apply_haven')))
|
||||
el = driver.find_element(By.NAME, 'remotedaemonurls_haven')
|
||||
el.clear()
|
||||
el.send_keys('remote.haven.miner.rocks:17750\nsg.haven.miner.rocks:30031')
|
||||
btn_apply_haven.click()
|
||||
time.sleep(1)
|
||||
|
||||
with open(settings_path_0) as fs:
|
||||
settings = json.load(fs)
|
||||
remotedaemonurls = settings['chainclients']['haven']['remote_daemon_urls']
|
||||
assert (len(remotedaemonurls) == 2)
|
||||
|
||||
btn_apply_haven = wait.until(EC.element_to_be_clickable((By.NAME, 'apply_haven')))
|
||||
el = driver.find_element(By.NAME, 'remotedaemonurls_haven')
|
||||
el.clear()
|
||||
btn_apply_haven.click()
|
||||
time.sleep(1)
|
||||
|
||||
with open(settings_path_0) as fs:
|
||||
settings = json.load(fs)
|
||||
assert (len(settings['chainclients']['haven']['remote_daemon_urls']) == 0)
|
||||
|
||||
print('Test Passed!')
|
||||
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ def test_swap_dir(driver):
|
|||
|
||||
print('Test Passed!')
|
||||
|
||||
# didn't do test_swap_dir_for_xhv
|
||||
|
||||
def run_tests():
|
||||
driver = get_driver()
|
||||
|
|
1267
tests/basicswap/test_btc_xhv.py
Normal file
1267
tests/basicswap/test_btc_xhv.py
Normal file
File diff suppressed because it is too large
Load diff
246
tests/basicswap/test_ltc_xhv.py
Normal file
246
tests/basicswap/test_ltc_xhv.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021-2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import random
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
from basicswap.basicswap import (
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
)
|
||||
from basicswap.util import (
|
||||
COIN,
|
||||
)
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
wait_for_bid,
|
||||
wait_for_offer,
|
||||
wait_for_in_progress,
|
||||
TEST_HTTP_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
)
|
||||
from .test_btc_xhv import BasicSwapTest, test_delay_event
|
||||
from .test_xhv import pause_event
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class TestLTC(BasicSwapTest):
|
||||
__test__ = True
|
||||
test_coin_from = Coins.LTC
|
||||
start_ltc_nodes = True
|
||||
base_rpc_port = LTC_BASE_RPC_PORT
|
||||
|
||||
def mineBlock(self, num_blocks=1):
|
||||
self.callnoderpc('generatetoaddress', [num_blocks, self.ltc_addr])
|
||||
|
||||
def check_softfork_active(self, feature_name):
|
||||
deploymentinfo = self.callnoderpc('getblockchaininfo')
|
||||
assert (deploymentinfo['softforks'][feature_name]['active'] is True)
|
||||
|
||||
def test_001_nested_segwit(self):
|
||||
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
|
||||
logging.info('Skipped')
|
||||
|
||||
def test_002_native_segwit(self):
|
||||
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name))
|
||||
|
||||
ci = self.swap_clients[0].ci(self.test_coin_from)
|
||||
addr_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'bech32'])
|
||||
addr_info = ci.rpc_wallet('getaddressinfo', [addr_segwit, ])
|
||||
assert addr_info['iswitness'] is True
|
||||
|
||||
txid = ci.rpc_wallet('sendtoaddress', [addr_segwit, 1.0])
|
||||
assert len(txid) == 64
|
||||
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex']
|
||||
tx = ci.rpc('decoderawtransaction', [tx_wallet, ])
|
||||
|
||||
self.mineBlock()
|
||||
ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
|
||||
assert (len(ro['unspents']) == 1)
|
||||
assert (ro['unspents'][0]['txid'] == txid)
|
||||
|
||||
prevout_n = -1
|
||||
for txo in tx['vout']:
|
||||
if addr_segwit in txo['scriptPubKey']['addresses']:
|
||||
prevout_n = txo['n']
|
||||
break
|
||||
assert prevout_n > -1
|
||||
|
||||
tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
|
||||
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex']
|
||||
tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ])
|
||||
tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ])
|
||||
assert tx_funded_decoded['txid'] == tx_signed_decoded['txid']
|
||||
|
||||
def test_007_hdwallet(self):
|
||||
logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name))
|
||||
|
||||
test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b'
|
||||
test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed))
|
||||
new_wallet_name = random.randbytes(10).hex()
|
||||
self.callnoderpc('createwallet', [new_wallet_name])
|
||||
self.callnoderpc('sethdseed', [True, test_wif], wallet=new_wallet_name)
|
||||
addr = self.callnoderpc('getnewaddress', wallet=new_wallet_name)
|
||||
self.callnoderpc('unloadwallet', [new_wallet_name])
|
||||
assert (addr == 'rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la')
|
||||
|
||||
def test_20_btc_coin(self):
|
||||
logging.info('---------- Test BTC to {}'.format(self.test_coin_from.name))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.BTC, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
def test_21_mweb(self):
|
||||
logging.info('---------- Test MWEB {}'.format(self.test_coin_from.name))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
ci0 = swap_clients[0].ci(self.test_coin_from)
|
||||
ci1 = swap_clients[1].ci(self.test_coin_from)
|
||||
|
||||
mweb_addr_0 = ci0.rpc_wallet('getnewaddress', ['mweb addr test 0', 'mweb'])
|
||||
mweb_addr_1 = ci1.rpc_wallet('getnewaddress', ['mweb addr test 1', 'mweb'])
|
||||
|
||||
addr_info0 = ci0.rpc_wallet('getaddressinfo', [mweb_addr_0,])
|
||||
assert (addr_info0['ismweb'] is True)
|
||||
|
||||
addr_info1 = ci1.rpc_wallet('getaddressinfo', [mweb_addr_1,])
|
||||
assert (addr_info1['ismweb'] is True)
|
||||
|
||||
trusted_before = ci0.rpc_wallet('getbalances')['mine']['trusted']
|
||||
ci0.rpc_wallet('sendtoaddress', [mweb_addr_0, 10.0])
|
||||
assert (trusted_before - float(ci0.rpc_wallet('getbalances')['mine']['trusted']) < 0.1)
|
||||
|
||||
try:
|
||||
pause_event.clear() # Stop mining
|
||||
ci0.rpc_wallet('sendtoaddress', [mweb_addr_1, 10.0])
|
||||
|
||||
found_unconfirmed: bool = False
|
||||
for i in range(20):
|
||||
test_delay_event.wait(1)
|
||||
ltc_wallet = read_json_api(TEST_HTTP_PORT + 1, 'wallets/ltc')
|
||||
if float(ltc_wallet['unconfirmed']) == 10.0:
|
||||
found_unconfirmed = True
|
||||
break
|
||||
finally:
|
||||
pause_event.set()
|
||||
assert (found_unconfirmed)
|
||||
|
||||
self.mineBlock()
|
||||
|
||||
txns = ci0.rpc_wallet('listtransactions')
|
||||
|
||||
utxos = ci0.rpc_wallet('listunspent')
|
||||
balances = ci0.rpc_wallet('getbalances')
|
||||
wi = ci0.rpc_wallet('getwalletinfo')
|
||||
|
||||
txid = ci0.rpc_wallet('sendtoaddress', [mweb_addr_1, 10.0])
|
||||
|
||||
self.mineBlock()
|
||||
|
||||
txns = ci1.rpc_wallet('listtransactions')
|
||||
|
||||
utxos = ci1.rpc_wallet('listunspent')
|
||||
balances = ci1.rpc_wallet('getbalances')
|
||||
wi = ci1.rpc_wallet('getwalletinfo')
|
||||
|
||||
mweb_tx = None
|
||||
for utxo in utxos:
|
||||
if utxo.get('address', '') == mweb_addr_1:
|
||||
mweb_tx = utxo
|
||||
assert (mweb_tx is not None)
|
||||
|
||||
tx = ci1.rpc_wallet('gettransaction', [mweb_tx['txid'],])
|
||||
|
||||
blockhash = tx['blockhash']
|
||||
block = ci1.rpc('getblock', [blockhash, 3])
|
||||
block = ci1.rpc('getblock', [blockhash, 0])
|
||||
|
||||
# TODO
|
||||
|
||||
def test_22_mweb_balance(self):
|
||||
logging.info('---------- Test MWEB balance {}'.format(self.test_coin_from.name))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB)
|
||||
mweb_addr_0 = ci_mweb.getNewAddress()
|
||||
addr_info0 = ci_mweb.rpc_wallet('getaddressinfo', [mweb_addr_0,])
|
||||
assert (addr_info0['ismweb'] is True)
|
||||
|
||||
ltc_addr = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/nextdepositaddr')
|
||||
ltc_mweb_addr = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc_mweb/nextdepositaddr')
|
||||
ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/newmwebaddress')
|
||||
|
||||
assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_addr,])['ismweb'] is False)
|
||||
assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_mweb_addr,])['ismweb'] is True)
|
||||
assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_mweb_addr2,])['ismweb'] is True)
|
||||
|
||||
post_json = {
|
||||
'value': 10,
|
||||
'address': ltc_mweb_addr,
|
||||
'subfee': False,
|
||||
}
|
||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json)
|
||||
assert (len(json_rv['txid']) == 64)
|
||||
|
||||
self.mineBlock()
|
||||
|
||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc', post_json)
|
||||
assert (json_rv['mweb_balance'] == 10.0)
|
||||
mweb_address = json_rv['mweb_address']
|
||||
|
||||
post_json = {
|
||||
'value': 11,
|
||||
'address': mweb_address,
|
||||
'subfee': False,
|
||||
}
|
||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json)
|
||||
assert (len(json_rv['txid']) == 64)
|
||||
|
||||
self.mineBlock()
|
||||
|
||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc_mweb', post_json)
|
||||
assert (json_rv['mweb_balance'] == 21.0)
|
||||
assert (json_rv['mweb_address'] == mweb_address)
|
||||
ltc_address = json_rv['deposit_address']
|
||||
|
||||
# Check that spending the mweb balance takes from the correct wallet
|
||||
post_json = {
|
||||
'value': 1,
|
||||
'address': ltc_address,
|
||||
'subfee': False,
|
||||
'type_from': 'mweb',
|
||||
}
|
||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json)
|
||||
assert (len(json_rv['txid']) == 64)
|
||||
|
||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc', post_json)
|
||||
assert (json_rv['mweb_balance'] <= 20.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
1588
tests/basicswap/test_xhv.py
Normal file
1588
tests/basicswap/test_xhv.py
Normal file
File diff suppressed because it is too large
Load diff
155
tests/basicswap/test_xhv_bids_offline.py
Normal file
155
tests/basicswap/test_xhv_bids_offline.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021-2022 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
"""
|
||||
export TEST_PATH=/tmp/test_basicswap
|
||||
mkdir -p ${TEST_PATH}/bin
|
||||
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
|
||||
export PYTHONPATH=$(pwd)
|
||||
python tests/basicswap/test_xhv_bids_offline.py
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import unittest
|
||||
import multiprocessing
|
||||
from urllib import parse
|
||||
from urllib.request import urlopen
|
||||
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
waitForServer,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
waitForNumOffers,
|
||||
waitForNumBids,
|
||||
)
|
||||
from tests.basicswap.common_xhv import (
|
||||
XhvTestBase,
|
||||
waitForBidState,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
|
||||
class Test(XhvTestBase):
|
||||
|
||||
def test_bids_offline(self):
|
||||
# Start multiple bids while offering node is offline
|
||||
|
||||
self.start_processes()
|
||||
|
||||
waitForServer(self.delay_event, 12700)
|
||||
waitForServer(self.delay_event, 12701)
|
||||
wallets1 = read_json_api(12701, 'wallets')
|
||||
assert (float(wallets1['XHV']['balance']) > 0.0)
|
||||
|
||||
offer_data = {
|
||||
'addr_from': -1,
|
||||
'coin_from': 'PART',
|
||||
'coin_to': 'XHV',
|
||||
'amt_from': 1,
|
||||
'amt_to': 1,
|
||||
'lockhrs': 24,
|
||||
'automation_strat_id': 1}
|
||||
rv = json.loads(urlopen('http://127.0.0.1:12700/json/offers/new', data=parse.urlencode(offer_data).encode()).read())
|
||||
offer0_id = rv['offer_id']
|
||||
|
||||
offer_data['amt_from'] = '2'
|
||||
rv = json.loads(urlopen('http://127.0.0.1:12700/json/offers/new', data=parse.urlencode(offer_data).encode()).read())
|
||||
offer1_id = rv['offer_id']
|
||||
|
||||
summary = read_json_api(12700)
|
||||
assert (summary['num_sent_offers'] > 1)
|
||||
|
||||
logger.info('Waiting for offer')
|
||||
waitForNumOffers(self.delay_event, 12701, 2)
|
||||
|
||||
logger.info('Stopping node 0')
|
||||
c0 = self.processes[0]
|
||||
c0.terminate()
|
||||
c0.join()
|
||||
|
||||
offers = json.loads(urlopen('http://127.0.0.1:12701/json/offers/{}'.format(offer0_id)).read())
|
||||
assert (len(offers) == 1)
|
||||
offer0 = offers[0]
|
||||
|
||||
post_data = {
|
||||
'coin_from': 'PART'
|
||||
}
|
||||
test_post_offers = json.loads(urlopen('http://127.0.0.1:12701/json/offers', data=parse.urlencode(post_data).encode()).read())
|
||||
assert (len(test_post_offers) == 2)
|
||||
post_data['coin_from'] = '2'
|
||||
test_post_offers = json.loads(urlopen('http://127.0.0.1:12701/json/offers', data=parse.urlencode(post_data).encode()).read())
|
||||
assert (len(test_post_offers) == 0)
|
||||
|
||||
bid_data = {
|
||||
'offer_id': offer0_id,
|
||||
'amount_from': offer0['amount_from']}
|
||||
|
||||
bid0_id = json.loads(urlopen('http://127.0.0.1:12701/json/bids/new', data=parse.urlencode(bid_data).encode()).read())['bid_id']
|
||||
|
||||
offers = json.loads(urlopen('http://127.0.0.1:12701/json/offers/{}'.format(offer1_id)).read())
|
||||
assert (len(offers) == 1)
|
||||
offer1 = offers[0]
|
||||
|
||||
bid_data = {
|
||||
'offer_id': offer1_id,
|
||||
'amount_from': offer1['amount_from']}
|
||||
|
||||
bid1_id = json.loads(urlopen('http://127.0.0.1:12701/json/bids/new', data=parse.urlencode(bid_data).encode()).read())['bid_id']
|
||||
|
||||
logger.info('Delaying for 5 seconds.')
|
||||
self.delay_event.wait(5)
|
||||
|
||||
logger.info('Starting node 0')
|
||||
self.processes[0] = multiprocessing.Process(target=self.run_thread, args=(0,))
|
||||
self.processes[0].start()
|
||||
|
||||
waitForServer(self.delay_event, 12700)
|
||||
waitForNumBids(self.delay_event, 12700, 2)
|
||||
|
||||
waitForBidState(self.delay_event, 12700, bid0_id, 'Received')
|
||||
waitForBidState(self.delay_event, 12700, bid1_id, 'Received')
|
||||
|
||||
# Manually accept on top of auto-accept for extra chaos
|
||||
data = parse.urlencode({
|
||||
'accept': True
|
||||
}).encode()
|
||||
try:
|
||||
rv = json.loads(urlopen('http://127.0.0.1:12700/json/bids/{}'.format(bid0_id), data=data).read())
|
||||
assert rv['bid_state'] == 'Accepted'
|
||||
except Exception as e:
|
||||
print('Accept bid failed', str(e), rv)
|
||||
try:
|
||||
rv = json.loads(urlopen('http://127.0.0.1:12700/json/bids/{}'.format(bid1_id), data=data).read())
|
||||
assert (rv['bid_state'] == 'Accepted')
|
||||
except Exception as e:
|
||||
print('Accept bid failed', str(e), rv)
|
||||
|
||||
logger.info('Completing swap')
|
||||
for i in range(240):
|
||||
if self.delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
self.delay_event.wait(4)
|
||||
|
||||
rv0 = read_json_api(12700, 'bids/{}'.format(bid0_id))
|
||||
rv1 = read_json_api(12700, 'bids/{}'.format(bid1_id))
|
||||
if rv0['bid_state'] == 'Completed' and rv1['bid_state'] == 'Completed':
|
||||
break
|
||||
assert rv0['bid_state'] == 'Completed'
|
||||
assert rv1['bid_state'] == 'Completed'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
147
tests/basicswap/test_xhv_reload.py
Normal file
147
tests/basicswap/test_xhv_reload.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2022 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
"""
|
||||
export TEST_PATH=/tmp/test_basicswap
|
||||
mkdir -p ${TEST_PATH}/bin
|
||||
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
|
||||
export PYTHONPATH=$(pwd)
|
||||
python tests/basicswap/test_xhv_reload.py
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import unittest
|
||||
import multiprocessing
|
||||
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
post_json_api,
|
||||
waitForServer,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
waitForNumOffers,
|
||||
waitForNumBids,
|
||||
waitForNumSwapping,
|
||||
)
|
||||
from tests.basicswap.common_xhv import (
|
||||
XhvTestBase,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
|
||||
class Test(XhvTestBase):
|
||||
|
||||
def test_reload(self):
|
||||
self.start_processes()
|
||||
|
||||
waitForServer(self.delay_event, 12700)
|
||||
waitForServer(self.delay_event, 12701)
|
||||
wallets1 = read_json_api(12701, 'wallets')
|
||||
assert (float(wallets1['XHV']['balance']) > 0.0)
|
||||
|
||||
data = {
|
||||
'addr_from': '-1',
|
||||
'coin_from': 'part',
|
||||
'coin_to': 'xhv',
|
||||
'amt_from': '1',
|
||||
'amt_to': '1',
|
||||
'lockhrs': '24'}
|
||||
|
||||
offer_id = post_json_api(12700, 'offers/new', data)['offer_id']
|
||||
summary = read_json_api(12700)
|
||||
assert (summary['num_sent_offers'] == 1)
|
||||
|
||||
logger.info('Waiting for offer')
|
||||
waitForNumOffers(self.delay_event, 12701, 1)
|
||||
|
||||
offers = read_json_api(12701, 'offers')
|
||||
offer = offers[0]
|
||||
|
||||
data = {
|
||||
'offer_id': offer['offer_id'],
|
||||
'amount_from': offer['amount_from']}
|
||||
|
||||
data['valid_for_seconds'] = 24 * 60 * 60 + 1
|
||||
bid = post_json_api(12701, 'bids/new', data)
|
||||
assert (bid['error'] == 'Bid TTL too high')
|
||||
del data['valid_for_seconds']
|
||||
data['validmins'] = 24 * 60 + 1
|
||||
bid = post_json_api(12701, 'bids/new', data)
|
||||
assert (bid['error'] == 'Bid TTL too high')
|
||||
|
||||
del data['validmins']
|
||||
data['valid_for_seconds'] = 10
|
||||
bid = post_json_api(12701, 'bids/new', data)
|
||||
assert (bid['error'] == 'Bid TTL too low')
|
||||
del data['valid_for_seconds']
|
||||
data['validmins'] = 1
|
||||
bid = post_json_api(12701, 'bids/new', data)
|
||||
assert (bid['error'] == 'Bid TTL too low')
|
||||
|
||||
data['validmins'] = 60
|
||||
bid_id = post_json_api(12701, 'bids/new', data)
|
||||
|
||||
waitForNumBids(self.delay_event, 12700, 1)
|
||||
|
||||
for i in range(10):
|
||||
bids = read_json_api(12700, 'bids')
|
||||
bid = bids[0]
|
||||
if bid['bid_state'] == 'Received':
|
||||
break
|
||||
self.delay_event.wait(1)
|
||||
assert (bid['expire_at'] == bid['created_at'] + data['validmins'] * 60)
|
||||
|
||||
data = {
|
||||
'accept': True
|
||||
}
|
||||
rv = post_json_api(12700, 'bids/{}'.format(bid['bid_id']), data)
|
||||
assert (rv['bid_state'] == 'Accepted')
|
||||
|
||||
waitForNumSwapping(self.delay_event, 12701, 1)
|
||||
|
||||
logger.info('Restarting client')
|
||||
c1 = self.processes[1]
|
||||
c1.terminate()
|
||||
c1.join()
|
||||
self.processes[1] = multiprocessing.Process(target=self.run_thread, args=(1,))
|
||||
self.processes[1].start()
|
||||
|
||||
waitForServer(self.delay_event, 12701)
|
||||
rv = read_json_api(12701)
|
||||
assert (rv['num_swapping'] == 1)
|
||||
|
||||
rv = read_json_api(12700, 'revokeoffer/{}'.format(offer_id))
|
||||
assert (rv['revoked_offer'] == offer_id)
|
||||
|
||||
logger.info('Completing swap')
|
||||
for i in range(240):
|
||||
if self.delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
self.delay_event.wait(4)
|
||||
|
||||
rv = read_json_api(12700, 'bids/{}'.format(bid['bid_id']))
|
||||
if rv['bid_state'] == 'Completed':
|
||||
break
|
||||
assert (rv['bid_state'] == 'Completed')
|
||||
|
||||
# Ensure offer was revoked
|
||||
summary = read_json_api(12700)
|
||||
assert (summary['num_network_offers'] == 0)
|
||||
|
||||
# Wait for bid to be removed from in-progress
|
||||
waitForNumBids(self.delay_event, 12700, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
1
tox.ini
1
tox.ini
|
@ -10,6 +10,7 @@ passenv =
|
|||
PARTICL_BINDIR
|
||||
BITCOIN_BINDIR
|
||||
LITECOIN_BINDIR
|
||||
XHV_BINDIR
|
||||
XMR_BINDIR
|
||||
TEST_PREPARE_PATH
|
||||
TEST_RELOAD_PATH
|
||||
|
|
Loading…
Reference in a new issue