Compare commits
104 Commits
veil
...
mweb_uncon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e7d36e84 | ||
|
|
6e4feb33d7 | ||
|
|
1f810fab6b | ||
|
|
7bf5f7ddfa | ||
|
|
d7da532111 | ||
|
|
fa35102794 | ||
|
|
da8c3e0237 | ||
|
|
ec29c9889c | ||
|
|
14298d022a | ||
|
|
5ceaab57d1 | ||
|
|
a5c3534c19 | ||
|
|
3b6f72c084 | ||
|
|
7c0ea36e37 | ||
|
|
14577f7741 | ||
|
|
dbf3f8f034 | ||
|
|
b85d234a0b | ||
|
|
1bfb271b87 | ||
|
|
f9bc5d46af | ||
|
|
fab89a42f3 | ||
|
|
3e14a784f3 | ||
|
|
e4f196411a | ||
|
|
8318961f0b | ||
|
|
7c9504e0cd | ||
|
|
98f3a52daa | ||
|
|
3276f9abd4 | ||
|
|
041ab18288 | ||
|
|
d57366c0b2 | ||
|
|
1ec1764012 | ||
|
|
28fc4817c0 | ||
|
|
2d1bd87b41 | ||
|
|
9ee6669179 | ||
|
|
30a5ea1652 | ||
|
|
53ceae718b | ||
|
|
1af9f64020 | ||
|
|
cfd2151c1a | ||
|
|
237d12afa0 | ||
|
|
a0cdd8cec9 | ||
|
|
1754650e75 | ||
|
|
3b8b512003 | ||
|
|
81649dcf9b | ||
|
|
f5d4b8dc0d | ||
|
|
bcfd63037a | ||
|
|
ddf3734f9d | ||
|
|
8c07ee5108 | ||
|
|
6ad5880ba4 | ||
|
|
0ff0a13a67 | ||
|
|
ff7e8fe0aa | ||
|
|
1068694990 | ||
|
|
66d1abd888 | ||
|
|
671e626551 | ||
|
|
192aff221e | ||
|
|
03fdf44220 | ||
|
|
38fa498b0b | ||
|
|
7547587d4e | ||
|
|
fd0772f893 | ||
|
|
0a12625290 | ||
|
|
d6ed5ba24c | ||
|
|
fb48797298 | ||
|
|
5bec1c31da | ||
|
|
3d3fcbde0b | ||
|
|
61845a7a84 | ||
|
|
65c93eaee6 | ||
|
|
6a26f72bca | ||
|
|
0a9db22828 | ||
|
|
08f0156b75 | ||
|
|
15bf9b2187 | ||
|
|
258b730c41 | ||
|
|
b409fe9f0e | ||
|
|
01bb3870b6 | ||
|
|
9efb244952 | ||
|
|
0be5a4fca7 | ||
|
|
a4c79fb7aa | ||
|
|
5b6f447692 | ||
|
|
28baa597cc | ||
|
|
d4a6ad7d6f | ||
|
|
ce578f8025 | ||
|
|
c387bfec71 | ||
|
|
d668a2f342 | ||
|
|
8e17ee5939 | ||
|
|
3b55d17a26 | ||
|
|
fd0bf9ed73 | ||
|
|
e6c7c4d9bb | ||
|
|
7053d7ee4b | ||
|
|
22cd3cf9f1 | ||
|
|
05e6edd5df | ||
|
|
9a1b7db2dc | ||
|
|
2a2f1ca3b6 | ||
|
|
db0e85d37c | ||
|
|
ebcd7738e5 | ||
|
|
4d8d421de6 | ||
|
|
23330c20bc | ||
|
|
22e005728a | ||
|
|
f2b69e5498 | ||
|
|
d617ab1d6b | ||
|
|
e7ae290eb5 | ||
|
|
20b405a944 | ||
|
|
c01b4a3d70 | ||
|
|
7caac8a8eb | ||
|
|
a17129999c | ||
|
|
5775ac5931 | ||
|
|
0b963bffde | ||
|
|
45e49848b1 | ||
|
|
5c23983c8e | ||
|
|
68ff57ebdc |
@@ -24,7 +24,7 @@ test_task:
|
|||||||
- apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
|
- apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
|
||||||
- pip install tox pytest
|
- pip install tox pytest
|
||||||
- python3 setup.py install
|
- python3 setup.py install
|
||||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.1.zip
|
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
||||||
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
||||||
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
||||||
- cd coincurve-anonswap
|
- cd coincurve-anonswap
|
||||||
@@ -47,3 +47,4 @@ test_task:
|
|||||||
- pytest tests/basicswap/test_other.py
|
- pytest tests/basicswap/test_other.py
|
||||||
- pytest tests/basicswap/test_run.py
|
- pytest tests/basicswap/test_run.py
|
||||||
- pytest tests/basicswap/test_reload.py
|
- pytest tests/basicswap/test_reload.py
|
||||||
|
- pytest tests/basicswap/test_btc_xmr.py -k 'test_01_a or test_01_b or test_02_a or test_02_b'
|
||||||
|
|||||||
3
.gitignore
vendored
@@ -9,3 +9,6 @@ __pycache__
|
|||||||
.tox
|
.tox
|
||||||
.eggs
|
.eggs
|
||||||
*~
|
*~
|
||||||
|
|
||||||
|
# geckodriver.log
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ before_install:
|
|||||||
install:
|
install:
|
||||||
- travis_retry pip install tox pytest
|
- travis_retry pip install tox pytest
|
||||||
before_script:
|
before_script:
|
||||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.1.zip
|
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
||||||
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
||||||
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
||||||
- cd coincurve-anonswap
|
- cd coincurve-anonswap
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN wget -O protobuf_src.tar.gz https://github.com/protocolbuffers/protobuf/rele
|
|||||||
make -j$(nproc) install && \
|
make -j$(nproc) install && \
|
||||||
ldconfig
|
ldconfig
|
||||||
|
|
||||||
ARG COINCURVE_VERSION=v0.1
|
ARG COINCURVE_VERSION=v0.2
|
||||||
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
||||||
unzip coincurve-anonswap.zip && \
|
unzip coincurve-anonswap.zip && \
|
||||||
mv ./coincurve-anonswap_$COINCURVE_VERSION ./coincurve-anonswap && \
|
mv ./coincurve-anonswap_$COINCURVE_VERSION ./coincurve-anonswap && \
|
||||||
|
|||||||
2
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
Copyright (c) 2019 tecnovert
|
Copyright (c) 2019-2024 tecnovert
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
name = "basicswap"
|
name = "basicswap"
|
||||||
|
|
||||||
__version__ = "0.11.66"
|
__version__ = "0.12.7"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2023 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import shlex
|
import shlex
|
||||||
import socks
|
import socks
|
||||||
|
import random
|
||||||
import socket
|
import socket
|
||||||
import urllib
|
import urllib
|
||||||
import logging
|
import logging
|
||||||
@@ -37,7 +38,6 @@ class BaseApp:
|
|||||||
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
|
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
|
||||||
self.log_name = log_name
|
self.log_name = log_name
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.is_running = True
|
|
||||||
self.fail_code = 0
|
self.fail_code = 0
|
||||||
self.mock_time_offset = 0
|
self.mock_time_offset = 0
|
||||||
|
|
||||||
@@ -49,6 +49,8 @@ class BaseApp:
|
|||||||
self.mxDB = threading.RLock()
|
self.mxDB = threading.RLock()
|
||||||
self.debug = self.settings.get('debug', False)
|
self.debug = self.settings.get('debug', False)
|
||||||
self.delay_event = threading.Event()
|
self.delay_event = threading.Event()
|
||||||
|
self.chainstate_delay_event = threading.Event()
|
||||||
|
|
||||||
self._network = None
|
self._network = None
|
||||||
self.prepareLogging()
|
self.prepareLogging()
|
||||||
self.log.info('Network: {}'.format(self.chain))
|
self.log.info('Network: {}'.format(self.chain))
|
||||||
@@ -65,7 +67,7 @@ class BaseApp:
|
|||||||
def stopRunning(self, with_code=0):
|
def stopRunning(self, with_code=0):
|
||||||
self.fail_code = with_code
|
self.fail_code = with_code
|
||||||
with self.mxDB:
|
with self.mxDB:
|
||||||
self.is_running = False
|
self.chainstate_delay_event.set()
|
||||||
self.delay_event.set()
|
self.delay_event.set()
|
||||||
|
|
||||||
def prepareLogging(self):
|
def prepareLogging(self):
|
||||||
@@ -75,10 +77,10 @@ class BaseApp:
|
|||||||
# Remove any existing handlers
|
# Remove any existing handlers
|
||||||
self.log.handlers = []
|
self.log.handlers = []
|
||||||
|
|
||||||
formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s')
|
formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S')
|
||||||
stream_stdout = logging.StreamHandler()
|
stream_stdout = logging.StreamHandler()
|
||||||
if self.log_name != 'BasicSwap':
|
if self.log_name != 'BasicSwap':
|
||||||
stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s'))
|
stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S'))
|
||||||
else:
|
else:
|
||||||
stream_stdout.setFormatter(formatter)
|
stream_stdout.setFormatter(formatter)
|
||||||
stream_fp = logging.StreamHandler(self.fp)
|
stream_fp = logging.StreamHandler(self.fp)
|
||||||
@@ -197,3 +199,28 @@ class BaseApp:
|
|||||||
def setMockTimeOffset(self, new_offset: int) -> None:
|
def setMockTimeOffset(self, new_offset: int) -> None:
|
||||||
self.log.warning(f'Setting mocktime to {new_offset}')
|
self.log.warning(f'Setting mocktime to {new_offset}')
|
||||||
self.mock_time_offset = new_offset
|
self.mock_time_offset = new_offset
|
||||||
|
|
||||||
|
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
||||||
|
value: int = self.settings.get(name, default_v)
|
||||||
|
if value < min_v:
|
||||||
|
self.log.warning(f'Setting {name} to {min_v}')
|
||||||
|
value = min_v
|
||||||
|
if value > max_v:
|
||||||
|
self.log.warning(f'Setting {name} to {max_v}')
|
||||||
|
value = max_v
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_delay_event_seconds(self):
|
||||||
|
if self.min_delay_event == self.max_delay_event:
|
||||||
|
return self.min_delay_event
|
||||||
|
return random.randrange(self.min_delay_event, self.max_delay_event)
|
||||||
|
|
||||||
|
def get_short_delay_event_seconds(self):
|
||||||
|
if self.min_delay_event_short == self.max_delay_event_short:
|
||||||
|
return self.min_delay_event_short
|
||||||
|
return random.randrange(self.min_delay_event_short, self.max_delay_event_short)
|
||||||
|
|
||||||
|
def get_delay_retry_seconds(self):
|
||||||
|
if self.min_delay_retry == self.max_delay_retry:
|
||||||
|
return self.min_delay_retry
|
||||||
|
return random.randrange(self.min_delay_retry, self.max_delay_retry)
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ class EventLogTypes(IntEnum):
|
|||||||
PTX_PUBLISHED = auto()
|
PTX_PUBLISHED = auto()
|
||||||
PTX_REDEEM_PUBLISHED = auto()
|
PTX_REDEEM_PUBLISHED = auto()
|
||||||
PTX_REFUND_PUBLISHED = auto()
|
PTX_REFUND_PUBLISHED = auto()
|
||||||
|
LOCK_TX_B_IN_MEMPOOL = auto()
|
||||||
|
|
||||||
|
|
||||||
class XmrSplitMsgTypes(IntEnum):
|
class XmrSplitMsgTypes(IntEnum):
|
||||||
@@ -198,6 +199,7 @@ class DebugTypes(IntEnum):
|
|||||||
SKIP_LOCK_TX_REFUND = auto()
|
SKIP_LOCK_TX_REFUND = auto()
|
||||||
SEND_LOCKED_XMR = auto()
|
SEND_LOCKED_XMR = auto()
|
||||||
B_LOCK_TX_MISSED_SEND = auto()
|
B_LOCK_TX_MISSED_SEND = auto()
|
||||||
|
DUPLICATE_ACTIONS = auto()
|
||||||
|
|
||||||
|
|
||||||
class NotificationTypes(IntEnum):
|
class NotificationTypes(IntEnum):
|
||||||
@@ -390,6 +392,8 @@ def describeEventEntry(event_type, event_msg):
|
|||||||
return 'Lock tx B seen in chain'
|
return 'Lock tx B seen in chain'
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
|
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
|
||||||
return 'Lock tx B confirmed in chain'
|
return 'Lock tx B confirmed in chain'
|
||||||
|
if event_type == EventLogTypes.LOCK_TX_B_IN_MEMPOOL:
|
||||||
|
return 'Lock tx B seen in mempool'
|
||||||
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
|
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
|
||||||
return 'Debug tweak applied ' + event_msg
|
return 'Debug tweak applied ' + event_msg
|
||||||
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
|
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
|
||||||
@@ -446,14 +450,14 @@ def getVoutByAddress(txjs, p2sh):
|
|||||||
raise ValueError('Address output not found in txn')
|
raise ValueError('Address output not found in txn')
|
||||||
|
|
||||||
|
|
||||||
def getVoutByP2WSH(txjs, p2wsh_hex):
|
def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int:
|
||||||
for o in txjs['vout']:
|
for o in txjs['vout']:
|
||||||
try:
|
try:
|
||||||
if p2wsh_hex == o['scriptPubKey']['hex']:
|
if scriptPubKey_hex == o['scriptPubKey']['hex']:
|
||||||
return o['n']
|
return o['n']
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise ValueError('P2WSH output not found in txn')
|
raise ValueError('scriptPubKey output not found in txn')
|
||||||
|
|
||||||
|
|
||||||
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'):
|
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'):
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class Coins(IntEnum):
|
|||||||
PIVX = 11
|
PIVX = 11
|
||||||
DASH = 12
|
DASH = 12
|
||||||
FIRO = 13
|
FIRO = 13
|
||||||
|
NAV = 14
|
||||||
|
LTC_MWEB = 15
|
||||||
|
|
||||||
|
|
||||||
chainparams = {
|
chainparams = {
|
||||||
@@ -218,6 +220,7 @@ chainparams = {
|
|||||||
'message_magic': 'DarkNet Signed Message:\n',
|
'message_magic': 'DarkNet Signed Message:\n',
|
||||||
'blocks_target': 60 * 1,
|
'blocks_target': 60 * 1,
|
||||||
'decimal_places': 8,
|
'decimal_places': 8,
|
||||||
|
'has_cltv': True,
|
||||||
'has_csv': False,
|
'has_csv': False,
|
||||||
'has_segwit': False,
|
'has_segwit': False,
|
||||||
'use_ticker_as_name': True,
|
'use_ticker_as_name': True,
|
||||||
@@ -295,7 +298,8 @@ chainparams = {
|
|||||||
'message_magic': 'Zcoin Signed Message:\n',
|
'message_magic': 'Zcoin Signed Message:\n',
|
||||||
'blocks_target': 60 * 10,
|
'blocks_target': 60 * 10,
|
||||||
'decimal_places': 8,
|
'decimal_places': 8,
|
||||||
'has_csv': True,
|
'has_cltv': False,
|
||||||
|
'has_csv': False,
|
||||||
'has_segwit': False,
|
'has_segwit': False,
|
||||||
'mainnet': {
|
'mainnet': {
|
||||||
'rpcport': 8888,
|
'rpcport': 8888,
|
||||||
@@ -327,6 +331,45 @@ chainparams = {
|
|||||||
'min_amount': 1000,
|
'min_amount': 1000,
|
||||||
'max_amount': 100000 * COIN,
|
'max_amount': 100000 * COIN,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Coins.NAV: {
|
||||||
|
'name': 'navcoin',
|
||||||
|
'ticker': 'NAV',
|
||||||
|
'message_magic': 'Navcoin Signed Message:\n',
|
||||||
|
'blocks_target': 30,
|
||||||
|
'decimal_places': 8,
|
||||||
|
'has_csv': True,
|
||||||
|
'has_segwit': True,
|
||||||
|
'mainnet': {
|
||||||
|
'rpcport': 44444,
|
||||||
|
'pubkey_address': 53,
|
||||||
|
'script_address': 85,
|
||||||
|
'key_prefix': 150,
|
||||||
|
'hrp': '',
|
||||||
|
'bip44': 130,
|
||||||
|
'min_amount': 1000,
|
||||||
|
'max_amount': 100000 * COIN,
|
||||||
|
},
|
||||||
|
'testnet': {
|
||||||
|
'rpcport': 44445,
|
||||||
|
'pubkey_address': 111,
|
||||||
|
'script_address': 196,
|
||||||
|
'key_prefix': 239,
|
||||||
|
'hrp': '',
|
||||||
|
'bip44': 1,
|
||||||
|
'min_amount': 1000,
|
||||||
|
'max_amount': 100000 * COIN,
|
||||||
|
},
|
||||||
|
'regtest': {
|
||||||
|
'rpcport': 44446,
|
||||||
|
'pubkey_address': 111,
|
||||||
|
'script_address': 196,
|
||||||
|
'key_prefix': 239,
|
||||||
|
'hrp': '',
|
||||||
|
'bip44': 1,
|
||||||
|
'min_amount': 1000,
|
||||||
|
'max_amount': 100000 * COIN,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ticker_map = {}
|
ticker_map = {}
|
||||||
@@ -422,4 +465,8 @@ class CoinInterface:
|
|||||||
return True
|
return True
|
||||||
if 'daemon is busy' in str_error:
|
if 'daemon is busy' in str_error:
|
||||||
return True
|
return True
|
||||||
|
if 'timed out' in str_error:
|
||||||
|
return True
|
||||||
|
if 'request-sent' in str_error:
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2022 tecnovert
|
# Copyright (c) 2019-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -36,18 +36,3 @@ NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
|
|||||||
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
|
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
|
||||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
||||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
||||||
|
|
||||||
PIVX_BINDIR = os.path.expanduser(os.getenv('PIVX_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'pivx')))
|
|
||||||
PIVXD = os.getenv('PIVXD', 'pivxd' + bin_suffix)
|
|
||||||
PIVX_CLI = os.getenv('PIVX_CLI', 'pivx-cli' + bin_suffix)
|
|
||||||
PIVX_TX = os.getenv('PIVX_TX', 'pivx-tx' + bin_suffix)
|
|
||||||
|
|
||||||
DASH_BINDIR = os.path.expanduser(os.getenv('DASH_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'dash')))
|
|
||||||
DASHD = os.getenv('DASHD', 'dashd' + bin_suffix)
|
|
||||||
DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix)
|
|
||||||
DASH_TX = os.getenv('DASH_TX', 'dash-tx' + bin_suffix)
|
|
||||||
|
|
||||||
FIRO_BINDIR = os.path.expanduser(os.getenv('FIRO_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'firo')))
|
|
||||||
FIROD = os.getenv('FIROD', 'firod' + bin_suffix)
|
|
||||||
FIRO_CLI = os.getenv('FIRO_CLI', 'firo-cli' + bin_suffix)
|
|
||||||
FIRO_TX = os.getenv('FIRO_TX', 'firo-tx' + bin_suffix)
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from enum import IntEnum, auto
|
|||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
|
||||||
CURRENT_DB_VERSION = 21
|
CURRENT_DB_VERSION = 22
|
||||||
CURRENT_DB_DATA_VERSION = 4
|
CURRENT_DB_DATA_VERSION = 4
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ class Offer(Base):
|
|||||||
|
|
||||||
proof_address = sa.Column(sa.String)
|
proof_address = sa.Column(sa.String)
|
||||||
proof_signature = sa.Column(sa.LargeBinary)
|
proof_signature = sa.Column(sa.LargeBinary)
|
||||||
|
proof_utxos = sa.Column(sa.LargeBinary)
|
||||||
pkhash_seller = sa.Column(sa.LargeBinary)
|
pkhash_seller = sa.Column(sa.LargeBinary)
|
||||||
secret_hash = sa.Column(sa.LargeBinary)
|
secret_hash = sa.Column(sa.LargeBinary)
|
||||||
|
|
||||||
@@ -115,6 +116,7 @@ class Bid(Base):
|
|||||||
expire_at = sa.Column(sa.BigInteger)
|
expire_at = sa.Column(sa.BigInteger)
|
||||||
bid_addr = sa.Column(sa.String)
|
bid_addr = sa.Column(sa.String)
|
||||||
proof_address = sa.Column(sa.String)
|
proof_address = sa.Column(sa.String)
|
||||||
|
proof_utxos = sa.Column(sa.LargeBinary)
|
||||||
withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO
|
withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO
|
||||||
|
|
||||||
recovered_secret = sa.Column(sa.LargeBinary)
|
recovered_secret = sa.Column(sa.LargeBinary)
|
||||||
|
|||||||
@@ -293,6 +293,10 @@ def upgradeDatabase(self, db_version):
|
|||||||
msg_id BLOB,
|
msg_id BLOB,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))''')
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER')
|
session.execute('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER')
|
||||||
|
elif current_version == 21:
|
||||||
|
db_version += 1
|
||||||
|
session.execute('ALTER TABLE offers ADD COLUMN proof_utxos BLOB')
|
||||||
|
session.execute('ALTER TABLE bids ADD COLUMN proof_utxos BLOB')
|
||||||
|
|
||||||
if current_version != db_version:
|
if current_version != db_version:
|
||||||
self.db_version = db_version
|
self.db_version = db_version
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2023 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -280,6 +280,8 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
coin_id = int(get_data_entry(form_data, 'coin_type'))
|
coin_id = int(get_data_entry(form_data, 'coin_type'))
|
||||||
if coin_id in (-2, -3, -4):
|
if coin_id in (-2, -3, -4):
|
||||||
coin_type = Coins(Coins.XMR)
|
coin_type = Coins(Coins.XMR)
|
||||||
|
elif coin_id in (-5,):
|
||||||
|
coin_type = Coins(Coins.LTC)
|
||||||
else:
|
else:
|
||||||
coin_type = Coins(coin_id)
|
coin_type = Coins(coin_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -295,20 +297,26 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
method = arr[0]
|
method = arr[0]
|
||||||
params = json.loads(arr[1]) if len(arr) > 1 else []
|
params = json.loads(arr[1]) if len(arr) > 1 else []
|
||||||
if coin_id == -4:
|
if coin_id == -4:
|
||||||
rv = ci.rpc_wallet_cb(method, params)
|
rv = ci.rpc_wallet(method, params)
|
||||||
elif coin_id == -3:
|
elif coin_id == -3:
|
||||||
rv = ci.rpc_cb(method, params)
|
rv = ci.rpc(method, params)
|
||||||
elif coin_id == -2:
|
elif coin_id == -2:
|
||||||
if params == []:
|
if params == []:
|
||||||
params = None
|
params = None
|
||||||
rv = ci.rpc_cb2(method, params)
|
rv = ci.rpc2(method, params)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown XMR RPC variant')
|
raise ValueError('Unknown XMR RPC variant')
|
||||||
result = json.dumps(rv, indent=4)
|
result = json.dumps(rv, indent=4)
|
||||||
else:
|
else:
|
||||||
if call_type == 'http':
|
if call_type == 'http':
|
||||||
method, params = parse_cmd(cmd, type_map)
|
method, params = parse_cmd(cmd, type_map)
|
||||||
result = cmd + '\n' + swap_client.ci(coin_type).rpc_callback(method, params)
|
if coin_id == -5:
|
||||||
|
rv = swap_client.ci(coin_type).rpc_wallet_mweb(method, params)
|
||||||
|
else:
|
||||||
|
rv = swap_client.ci(coin_type).rpc_wallet(method, params)
|
||||||
|
if not isinstance(rv, str):
|
||||||
|
rv = json.dumps(rv, indent=4)
|
||||||
|
result = cmd + '\n' + rv
|
||||||
else:
|
else:
|
||||||
result = cmd + '\n' + swap_client.callcoincli(coin_type, cmd)
|
result = cmd + '\n' + swap_client.callcoincli(coin_type, cmd)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -319,10 +327,15 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
template = env.get_template('rpc.html')
|
template = env.get_template('rpc.html')
|
||||||
|
|
||||||
coins = listAvailableCoins(swap_client, with_variants=False)
|
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]
|
coins = [c for c in coins if c[0] != Coins.XMR]
|
||||||
coins.append((-2, 'Monero'))
|
|
||||||
coins.append((-3, 'Monero JSON'))
|
if any(c[0] == Coins.LTC for c in coins):
|
||||||
coins.append((-4, 'Monero Wallet'))
|
coins.append((-5, 'Litecoin MWEB Wallet'))
|
||||||
|
if with_xmr:
|
||||||
|
coins.append((-2, 'Monero'))
|
||||||
|
coins.append((-3, 'Monero JSON'))
|
||||||
|
coins.append((-4, 'Monero Wallet'))
|
||||||
|
|
||||||
return self.render_template(template, {
|
return self.render_template(template, {
|
||||||
'messages': messages,
|
'messages': messages,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -55,7 +55,6 @@ from basicswap.contrib.test_framework.messages import (
|
|||||||
CTxIn,
|
CTxIn,
|
||||||
CTxInWitness,
|
CTxInWitness,
|
||||||
CTxOut,
|
CTxOut,
|
||||||
FromHex,
|
|
||||||
uint256_from_str)
|
uint256_from_str)
|
||||||
|
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
@@ -196,7 +195,9 @@ class BTCInterface(CoinInterface):
|
|||||||
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
||||||
self._rpcport = coin_settings['rpcport']
|
self._rpcport = coin_settings['rpcport']
|
||||||
self._rpcauth = coin_settings['rpcauth']
|
self._rpcauth = coin_settings['rpcauth']
|
||||||
self.rpc_callback = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
|
self._rpc_wallet = 'wallet.dat'
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
||||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||||
self.setConfTarget(coin_settings['conf_target'])
|
self.setConfTarget(coin_settings['conf_target'])
|
||||||
self._use_segwit = coin_settings['use_segwit']
|
self._use_segwit = coin_settings['use_segwit']
|
||||||
@@ -205,10 +206,31 @@ class BTCInterface(CoinInterface):
|
|||||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||||
self._expect_seedid_hex = None
|
self._expect_seedid_hex = None
|
||||||
|
|
||||||
|
def checkWallets(self) -> int:
|
||||||
|
wallets = self.rpc('listwallets')
|
||||||
|
|
||||||
|
# Wallet name is "" for some LTC and PART installs on older cores
|
||||||
|
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
||||||
|
self._log.debug('Changing {} wallet name.'.format(self.ticker()))
|
||||||
|
for wallet_name in wallets:
|
||||||
|
# Skip over other expected wallets
|
||||||
|
if wallet_name in ('mweb', ):
|
||||||
|
continue
|
||||||
|
self._rpc_wallet = wallet_name
|
||||||
|
self._log.info('Switched {} wallet name to {}.'.format(self.ticker(), self._rpc_wallet))
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
||||||
|
break
|
||||||
|
|
||||||
|
return len(wallets)
|
||||||
|
|
||||||
def using_segwit(self) -> bool:
|
def using_segwit(self) -> bool:
|
||||||
# Using btc native segwit
|
# Using btc native segwit
|
||||||
return self._use_segwit
|
return self._use_segwit
|
||||||
|
|
||||||
|
def use_p2shp2wsh(self) -> bool:
|
||||||
|
# p2sh-p2wsh
|
||||||
|
return False
|
||||||
|
|
||||||
def get_connection_type(self):
|
def get_connection_type(self):
|
||||||
return self._connection_type
|
return self._connection_type
|
||||||
|
|
||||||
@@ -236,34 +258,34 @@ class BTCInterface(CoinInterface):
|
|||||||
self._conf_target = new_conf_target
|
self._conf_target = new_conf_target
|
||||||
|
|
||||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||||
self.rpc_callback('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||||
|
|
||||||
def getDaemonVersion(self):
|
def getDaemonVersion(self):
|
||||||
return self.rpc_callback('getnetworkinfo')['version']
|
return self.rpc('getnetworkinfo')['version']
|
||||||
|
|
||||||
def getBlockchainInfo(self):
|
def getBlockchainInfo(self):
|
||||||
return self.rpc_callback('getblockchaininfo')
|
return self.rpc('getblockchaininfo')
|
||||||
|
|
||||||
def getChainHeight(self) -> int:
|
def getChainHeight(self) -> int:
|
||||||
return self.rpc_callback('getblockcount')
|
return self.rpc('getblockcount')
|
||||||
|
|
||||||
def getMempoolTx(self, txid):
|
def getMempoolTx(self, txid):
|
||||||
return self.rpc_callback('getrawtransaction', [txid.hex()])
|
return self.rpc('getrawtransaction', [txid.hex()])
|
||||||
|
|
||||||
def getBlockHeaderFromHeight(self, height):
|
def getBlockHeaderFromHeight(self, height):
|
||||||
block_hash = self.rpc_callback('getblockhash', [height])
|
block_hash = self.rpc('getblockhash', [height])
|
||||||
return self.rpc_callback('getblockheader', [block_hash])
|
return self.rpc('getblockheader', [block_hash])
|
||||||
|
|
||||||
def getBlockHeader(self, block_hash):
|
def getBlockHeader(self, block_hash):
|
||||||
return self.rpc_callback('getblockheader', [block_hash])
|
return self.rpc('getblockheader', [block_hash])
|
||||||
|
|
||||||
def getBlockHeaderAt(self, time: int, block_after=False):
|
def getBlockHeaderAt(self, time: int, block_after=False):
|
||||||
blockchaininfo = self.rpc_callback('getblockchaininfo')
|
blockchaininfo = self.rpc('getblockchaininfo')
|
||||||
last_block_header = self.rpc_callback('getblockheader', [blockchaininfo['bestblockhash']])
|
last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']])
|
||||||
|
|
||||||
max_tries = 5000
|
max_tries = 5000
|
||||||
for i in range(max_tries):
|
for i in range(max_tries):
|
||||||
prev_block_header = self.rpc_callback('getblock', [last_block_header['previousblockhash']])
|
prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
|
||||||
if prev_block_header['time'] <= time:
|
if prev_block_header['time'] <= time:
|
||||||
return last_block_header if block_after else prev_block_header
|
return last_block_header if block_after else prev_block_header
|
||||||
|
|
||||||
@@ -272,22 +294,22 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
def initialiseWallet(self, key_bytes: bytes) -> None:
|
def initialiseWallet(self, key_bytes: bytes) -> None:
|
||||||
key_wif = self.encodeKey(key_bytes)
|
key_wif = self.encodeKey(key_bytes)
|
||||||
|
self.rpc_wallet('sethdseed', [True, key_wif])
|
||||||
self.rpc_callback('sethdseed', [True, key_wif])
|
|
||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
rv = self.rpc_callback('getwalletinfo')
|
rv = self.rpc_wallet('getwalletinfo')
|
||||||
rv['encrypted'] = 'unlocked_until' in rv
|
rv['encrypted'] = 'unlocked_until' in rv
|
||||||
rv['locked'] = rv.get('unlocked_until', 1) <= 0
|
rv['locked'] = rv.get('unlocked_until', 1) <= 0
|
||||||
|
rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent'))
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def walletRestoreHeight(self) -> int:
|
def walletRestoreHeight(self) -> int:
|
||||||
return self._restore_height
|
return self._restore_height
|
||||||
|
|
||||||
def getWalletRestoreHeight(self) -> int:
|
def getWalletRestoreHeight(self) -> int:
|
||||||
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
|
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
||||||
|
|
||||||
blockchaininfo = self.rpc_callback('getblockchaininfo')
|
blockchaininfo = self.getBlockchainInfo()
|
||||||
best_block = blockchaininfo['bestblockhash']
|
best_block = blockchaininfo['bestblockhash']
|
||||||
|
|
||||||
chain_synced = round(blockchaininfo['verificationprogress'], 3)
|
chain_synced = round(blockchaininfo['verificationprogress'], 3)
|
||||||
@@ -309,7 +331,7 @@ class BTCInterface(CoinInterface):
|
|||||||
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
|
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
|
||||||
|
|
||||||
def getWalletSeedID(self) -> str:
|
def getWalletSeedID(self) -> str:
|
||||||
wi = self.rpc_callback('getwalletinfo')
|
wi = self.rpc_wallet('getwalletinfo')
|
||||||
return 'Not found' if 'hdseedid' not in wi else wi['hdseedid']
|
return 'Not found' if 'hdseedid' not in wi else wi['hdseedid']
|
||||||
|
|
||||||
def checkExpectedSeed(self, expect_seedid) -> bool:
|
def checkExpectedSeed(self, expect_seedid) -> bool:
|
||||||
@@ -320,11 +342,11 @@ class BTCInterface(CoinInterface):
|
|||||||
args = [label]
|
args = [label]
|
||||||
if use_segwit:
|
if use_segwit:
|
||||||
args.append('bech32')
|
args.append('bech32')
|
||||||
return self.rpc_callback('getnewaddress', args)
|
return self.rpc_wallet('getnewaddress', args)
|
||||||
|
|
||||||
def isValidAddress(self, address: str) -> bool:
|
def isValidAddress(self, address: str) -> bool:
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('validateaddress', [address])
|
rv = self.rpc_wallet('validateaddress', [address])
|
||||||
if rv['isvalid'] is True:
|
if rv['isvalid'] is True:
|
||||||
return True
|
return True
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -344,29 +366,45 @@ class BTCInterface(CoinInterface):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||||
if not or_watch_only:
|
if not or_watch_only:
|
||||||
return addr_info['ismine']
|
return addr_info['ismine']
|
||||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||||
|
|
||||||
def checkAddressMine(self, address: str) -> None:
|
def checkAddressMine(self, address: str) -> None:
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||||
ensure(addr_info['ismine'], 'ismine is false')
|
ensure(addr_info['ismine'], 'ismine is false')
|
||||||
if self.sc._restrict_unknown_seed_wallets:
|
if self.sc._restrict_unknown_seed_wallets:
|
||||||
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
|
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
|
||||||
|
|
||||||
def get_fee_rate(self, conf_target: int = 2):
|
def get_fee_rate(self, conf_target: int = 2) -> (float, str):
|
||||||
try:
|
chain_client_settings = self._sc.getChainClientSettings(self.coin_type()) # basicswap.json
|
||||||
fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate']
|
override_feerate = chain_client_settings.get('override_feerate', None)
|
||||||
assert (fee_rate > 0.0), 'Non positive feerate'
|
if override_feerate:
|
||||||
return fee_rate, 'estimatesmartfee'
|
self._log.debug('Fee rate override used for %s: %f', self.coin_name(), override_feerate)
|
||||||
except Exception:
|
return override_feerate, 'override_feerate'
|
||||||
|
|
||||||
|
min_relay_fee = chain_client_settings.get('min_relay_fee', None)
|
||||||
|
|
||||||
|
def try_get_fee_rate(self, conf_target):
|
||||||
try:
|
try:
|
||||||
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
|
fee_rate: float = self.rpc_wallet('estimatesmartfee', [conf_target])['feerate']
|
||||||
assert (fee_rate > 0.0), 'Non positive feerate'
|
assert (fee_rate > 0.0), 'Negative feerate'
|
||||||
return fee_rate, 'paytxfee'
|
return fee_rate, 'estimatesmartfee'
|
||||||
except Exception:
|
except Exception:
|
||||||
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
try:
|
||||||
|
fee_rate: float = self.rpc_wallet('getwalletinfo')['paytxfee']
|
||||||
|
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||||
|
return fee_rate, 'paytxfee'
|
||||||
|
except Exception:
|
||||||
|
fee_rate: float = self.rpc('getnetworkinfo')['relayfee']
|
||||||
|
return fee_rate, 'relayfee'
|
||||||
|
|
||||||
|
fee_rate, rate_src = try_get_fee_rate(self, conf_target)
|
||||||
|
if min_relay_fee and min_relay_fee > fee_rate:
|
||||||
|
self._log.warning('Feerate {} ({}) is below min relay fee {} for {}'.format(self.format_amount(fee_rate, True, 1), rate_src, self.format_amount(min_relay_fee, True, 1), self.coin_name()))
|
||||||
|
return min_relay_fee, 'min_relay_fee'
|
||||||
|
return fee_rate, rate_src
|
||||||
|
|
||||||
def isSegwitAddress(self, address: str) -> bool:
|
def isSegwitAddress(self, address: str) -> bool:
|
||||||
return address.startswith(self.chainparams_network()['hrp'] + '1')
|
return address.startswith(self.chainparams_network()['hrp'] + '1')
|
||||||
@@ -450,7 +488,7 @@ class BTCInterface(CoinInterface):
|
|||||||
def decodePubkey(self, pke):
|
def decodePubkey(self, pke):
|
||||||
return CPKToPoint(pke)
|
return CPKToPoint(pke)
|
||||||
|
|
||||||
def decodeKey(self, k):
|
def decodeKey(self, k: str) -> bytes:
|
||||||
return decodeWif(k)
|
return decodeWif(k)
|
||||||
|
|
||||||
def sumKeys(self, ka, kb):
|
def sumKeys(self, ka, kb):
|
||||||
@@ -461,8 +499,15 @@ class BTCInterface(CoinInterface):
|
|||||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
||||||
|
# p2wpkh
|
||||||
return CScript([OP_0, pkh])
|
return CScript([OP_0, pkh])
|
||||||
|
|
||||||
|
def loadTx(self, tx_bytes: bytes) -> CTransaction:
|
||||||
|
# Load tx from bytes to internal representation
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.deserialize(BytesIO(tx_bytes))
|
||||||
|
return tx
|
||||||
|
|
||||||
def extractScriptLockScriptValues(self, script_bytes: bytes):
|
def extractScriptLockScriptValues(self, script_bytes: bytes):
|
||||||
script_len = len(script_bytes)
|
script_len = len(script_bytes)
|
||||||
ensure(script_len == 71, 'Bad script length')
|
ensure(script_len == 71, 'Bad script length')
|
||||||
@@ -536,7 +581,7 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
|
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
|
||||||
tx_lock = CTransaction()
|
tx_lock = CTransaction()
|
||||||
tx_lock = FromHex(tx_lock, tx_lock_bytes.hex())
|
tx_lock = self.loadTx(tx_lock_bytes)
|
||||||
|
|
||||||
output_script = self.getScriptDest(script_lock)
|
output_script = self.getScriptDest(script_lock)
|
||||||
locked_n = findOutput(tx_lock, output_script)
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
@@ -710,7 +755,7 @@ class BTCInterface(CoinInterface):
|
|||||||
add_bytes = 0
|
add_bytes = 0
|
||||||
add_witness_bytes = getCompactSizeLen(len(tx.vin))
|
add_witness_bytes = getCompactSizeLen(len(tx.vin))
|
||||||
for pi in tx.vin:
|
for pi in tx.vin:
|
||||||
ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True])
|
ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||||
prevout = ptx['vout'][pi.prevout.n]
|
prevout = ptx['vout'][pi.prevout.n]
|
||||||
inputs_value += make_int(prevout['value'])
|
inputs_value += make_int(prevout['value'])
|
||||||
|
|
||||||
@@ -900,7 +945,7 @@ class BTCInterface(CoinInterface):
|
|||||||
def decryptOtVES(self, k, esig):
|
def decryptOtVES(self, k, esig):
|
||||||
return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
|
return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
|
||||||
|
|
||||||
def verifyTxSig(self, tx_bytes, sig, K, input_n, prevout_script, prevout_value):
|
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||||
|
|
||||||
@@ -918,13 +963,13 @@ class BTCInterface(CoinInterface):
|
|||||||
'lockUnspents': True,
|
'lockUnspents': True,
|
||||||
'feeRate': feerate_str,
|
'feeRate': feerate_str,
|
||||||
}
|
}
|
||||||
rv = self.rpc_callback('fundrawtransaction', [tx.hex(), options])
|
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def listInputs(self, tx_bytes):
|
def listInputs(self, tx_bytes):
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
|
|
||||||
all_locked = self.rpc_callback('listlockunspent')
|
all_locked = self.rpc_wallet('listlockunspent')
|
||||||
inputs = []
|
inputs = []
|
||||||
for pi in tx.vin:
|
for pi in tx.vin:
|
||||||
txid_hex = i2h(pi.prevout.hash)
|
txid_hex = i2h(pi.prevout.hash)
|
||||||
@@ -938,29 +983,23 @@ class BTCInterface(CoinInterface):
|
|||||||
inputs = []
|
inputs = []
|
||||||
for pi in tx.vin:
|
for pi in tx.vin:
|
||||||
inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n})
|
inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n})
|
||||||
self.rpc_callback('lockunspent', [True, inputs])
|
self.rpc_wallet('lockunspent', [True, inputs])
|
||||||
|
|
||||||
def signTxWithWallet(self, tx: bytes) -> bytes:
|
def signTxWithWallet(self, tx: bytes) -> bytes:
|
||||||
rv = self.rpc_callback('signrawtransactionwithwallet', [tx.hex()])
|
rv = self.rpc_wallet('signrawtransactionwithwallet', [tx.hex()])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc_callback('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
|
rv = self.rpc('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def publishTx(self, tx: bytes):
|
def publishTx(self, tx: bytes):
|
||||||
return self.rpc_callback('sendrawtransaction', [tx.hex()])
|
return self.rpc('sendrawtransaction', [tx.hex()])
|
||||||
|
|
||||||
def encodeTx(self, tx) -> bytes:
|
def encodeTx(self, tx) -> bytes:
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def loadTx(self, tx_bytes: bytes) -> CTransaction:
|
|
||||||
# Load tx from bytes to internal representation
|
|
||||||
tx = CTransaction()
|
|
||||||
tx.deserialize(BytesIO(tx_bytes))
|
|
||||||
return tx
|
|
||||||
|
|
||||||
def getTxid(self, tx) -> bytes:
|
def getTxid(self, tx) -> bytes:
|
||||||
if isinstance(tx, str):
|
if isinstance(tx, str):
|
||||||
tx = bytes.fromhex(tx)
|
tx = bytes.fromhex(tx)
|
||||||
@@ -984,22 +1023,34 @@ class BTCInterface(CoinInterface):
|
|||||||
def getScriptScriptSig(self, script: bytes) -> bytes:
|
def getScriptScriptSig(self, script: bytes) -> bytes:
|
||||||
return bytes()
|
return bytes()
|
||||||
|
|
||||||
|
def getP2SHP2WSHDest(self, script):
|
||||||
|
script_hash = hashlib.sha256(script).digest()
|
||||||
|
assert len(script_hash) == 32
|
||||||
|
p2wsh_hash = hash160(CScript([OP_0, script_hash]))
|
||||||
|
assert len(p2wsh_hash) == 20
|
||||||
|
return CScript([OP_HASH160, p2wsh_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def getP2SHP2WSHScriptSig(self, script):
|
||||||
|
script_hash = hashlib.sha256(script).digest()
|
||||||
|
assert len(script_hash) == 32
|
||||||
|
return CScript([CScript([OP_0, script_hash, ]), ])
|
||||||
|
|
||||||
def getPkDest(self, K: bytes) -> bytearray:
|
def getPkDest(self, K: bytes) -> bytearray:
|
||||||
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
|
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
|
||||||
|
|
||||||
def scanTxOutset(self, dest):
|
def scanTxOutset(self, dest):
|
||||||
return self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
return self.rpc('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
||||||
|
|
||||||
def getTransaction(self, txid: bytes):
|
def getTransaction(self, txid: bytes):
|
||||||
try:
|
try:
|
||||||
return bytes.fromhex(self.rpc_callback('getrawtransaction', [txid.hex()]))
|
return bytes.fromhex(self.rpc('getrawtransaction', [txid.hex()]))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# TODO: filter errors
|
# TODO: filter errors
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getWalletTransaction(self, txid: bytes):
|
def getWalletTransaction(self, txid: bytes):
|
||||||
try:
|
try:
|
||||||
return bytes.fromhex(self.rpc_callback('gettransaction', [txid.hex()]))
|
return bytes.fromhex(self.rpc('gettransaction', [txid.hex()]))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# TODO: filter errors
|
# TODO: filter errors
|
||||||
return None
|
return None
|
||||||
@@ -1085,7 +1136,7 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||||
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
|
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||||
|
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
@@ -1114,10 +1165,10 @@ class BTCInterface(CoinInterface):
|
|||||||
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
|
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
|
||||||
|
|
||||||
def importWatchOnlyAddress(self, address: str, label: str):
|
def importWatchOnlyAddress(self, address: str, label: str):
|
||||||
self.rpc_callback('importaddress', [address, label, False])
|
self.rpc_wallet('importaddress', [address, label, False])
|
||||||
|
|
||||||
def isWatchOnlyAddress(self, address: str):
|
def isWatchOnlyAddress(self, address: str):
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||||
return addr_info['iswatchonly']
|
return addr_info['iswatchonly']
|
||||||
|
|
||||||
def getSCLockScriptAddress(self, lock_script):
|
def getSCLockScriptAddress(self, lock_script):
|
||||||
@@ -1131,11 +1182,11 @@ class BTCInterface(CoinInterface):
|
|||||||
self.importWatchOnlyAddress(dest_address, 'bid')
|
self.importWatchOnlyAddress(dest_address, 'bid')
|
||||||
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
||||||
self.rpc_callback('rescanblockchain', [rescan_from])
|
self.rpc_wallet('rescanblockchain', [rescan_from])
|
||||||
|
|
||||||
return_txid = True if txid is None else False
|
return_txid = True if txid is None else False
|
||||||
if txid is None:
|
if txid is None:
|
||||||
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
|
txns = self.rpc_wallet('listunspent', [0, 9999999, [dest_address, ]])
|
||||||
|
|
||||||
for tx in txns:
|
for tx in txns:
|
||||||
if self.make_int(tx['amount']) == bid_amount:
|
if self.make_int(tx['amount']) == bid_amount:
|
||||||
@@ -1146,11 +1197,11 @@ class BTCInterface(CoinInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx = self.rpc_callback('gettransaction', [txid.hex()])
|
tx = self.rpc_wallet('gettransaction', [txid.hex()])
|
||||||
|
|
||||||
block_height = 0
|
block_height = 0
|
||||||
if 'blockhash' in tx:
|
if 'blockhash' in tx:
|
||||||
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
|
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
||||||
block_height = block_header['height']
|
block_height = block_header['height']
|
||||||
|
|
||||||
rv = {
|
rv = {
|
||||||
@@ -1162,7 +1213,7 @@ class BTCInterface(CoinInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if find_index:
|
if find_index:
|
||||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
|
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
||||||
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||||
|
|
||||||
if return_txid:
|
if return_txid:
|
||||||
@@ -1172,7 +1223,7 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
def getOutput(self, txid, dest_script, expect_value, xmr_swap=None):
|
def getOutput(self, txid, dest_script, expect_value, xmr_swap=None):
|
||||||
# TODO: Use getrawtransaction if txindex is active
|
# TODO: Use getrawtransaction if txindex is active
|
||||||
utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
|
utxos = self.rpc('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
|
||||||
if 'height' in utxos: # chain_height not returned by v18 codebase
|
if 'height' in utxos: # chain_height not returned by v18 codebase
|
||||||
chain_height = utxos['height']
|
chain_height = utxos['height']
|
||||||
else:
|
else:
|
||||||
@@ -1195,7 +1246,7 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||||
return self.rpc_callback('sendtoaddress', params)
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
def signCompact(self, k, message):
|
def signCompact(self, k, message):
|
||||||
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
||||||
@@ -1288,10 +1339,10 @@ class BTCInterface(CoinInterface):
|
|||||||
return length
|
return length
|
||||||
|
|
||||||
def describeTx(self, tx_hex: str):
|
def describeTx(self, tx_hex: str):
|
||||||
return self.rpc_callback('decoderawtransaction', [tx_hex])
|
return self.rpc('decoderawtransaction', [tx_hex])
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getbalances')['mine']['trusted'])
|
return self.make_int(self.rpc_wallet('getbalances')['mine']['trusted'])
|
||||||
|
|
||||||
def createUTXO(self, value_sats: int):
|
def createUTXO(self, value_sats: int):
|
||||||
# Create a new address and send value_sats to it
|
# Create a new address and send value_sats to it
|
||||||
@@ -1304,7 +1355,7 @@ class BTCInterface(CoinInterface):
|
|||||||
return self.withdrawCoin(self.format_amount(value_sats), address, False), address
|
return self.withdrawCoin(self.format_amount(value_sats), address, False), address
|
||||||
|
|
||||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': lock_unspents,
|
'lockUnspents': lock_unspents,
|
||||||
@@ -1312,30 +1363,44 @@ class BTCInterface(CoinInterface):
|
|||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options['subtractFeeFromOutputs'] = [0,]
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
return self.rpc_wallet('fundrawtransaction', [txn, options])['hex']
|
||||||
|
|
||||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
return self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
|
return self.rpc_wallet('signrawtransactionwithwallet', [txn_funded])['hex']
|
||||||
|
|
||||||
def getBlockWithTxns(self, block_hash: str):
|
def getBlockWithTxns(self, block_hash: str):
|
||||||
return self.rpc_callback('getblock', [block_hash, 2])
|
return self.rpc('getblock', [block_hash, 2])
|
||||||
|
|
||||||
def getUnspentsByAddr(self):
|
def getUnspentsByAddr(self):
|
||||||
unspent_addr = dict()
|
unspent_addr = dict()
|
||||||
unspent = self.rpc_callback('listunspent')
|
unspent = self.rpc_wallet('listunspent')
|
||||||
for u in unspent:
|
for u in unspent:
|
||||||
if u['spendable'] is not True:
|
if u['spendable'] is not True:
|
||||||
continue
|
continue
|
||||||
|
if 'address' not in u:
|
||||||
|
continue
|
||||||
|
if 'desc' in u:
|
||||||
|
desc = u['desc']
|
||||||
|
if self.using_segwit:
|
||||||
|
if self.use_p2shp2wsh():
|
||||||
|
if not desc.startswith('sh(wpkh'):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if not desc.startswith('wpkh'):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if not desc.startswith('pkh'):
|
||||||
|
continue
|
||||||
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
|
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
|
||||||
return unspent_addr
|
return unspent_addr
|
||||||
|
|
||||||
def getUTXOBalance(self, address: str):
|
def getUTXOBalance(self, address: str):
|
||||||
num_blocks = self.rpc_callback('getblockcount')
|
num_blocks = self.rpc('getblockcount')
|
||||||
|
|
||||||
sum_unspent = 0
|
sum_unspent = 0
|
||||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||||
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
|
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
|
||||||
self._log.debug('[rm] scantxoutset end')
|
self._log.debug('[rm] scantxoutset end')
|
||||||
for o in ro['unspents']:
|
for o in ro['unspents']:
|
||||||
sum_unspent += self.make_int(o['amount'])
|
sum_unspent += self.make_int(o['amount'])
|
||||||
@@ -1361,11 +1426,22 @@ class BTCInterface(CoinInterface):
|
|||||||
sign_for_addr = self.pkh_to_address(pkh)
|
sign_for_addr = self.pkh_to_address(pkh)
|
||||||
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
||||||
|
|
||||||
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
|
signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
|
||||||
|
|
||||||
return (sign_for_addr, signature)
|
prove_utxos = [] # TODO: Send specific utxos
|
||||||
|
return (sign_for_addr, signature, prove_utxos)
|
||||||
|
|
||||||
def verifyProofOfFunds(self, address, signature, extra_commit_bytes):
|
def decodeProofUtxos(self, msg_utxos):
|
||||||
|
proof_utxos = []
|
||||||
|
if len(msg_utxos) > 0:
|
||||||
|
num_utxos = len(msg_utxos) // 34
|
||||||
|
p: int = 0
|
||||||
|
for i in range(num_utxos):
|
||||||
|
proof_utxos.append((msg_utxos[p: p + 32], int.from_bytes(msg_utxos[p + 32: p + 34], 'big')))
|
||||||
|
p += 34
|
||||||
|
return proof_utxos
|
||||||
|
|
||||||
|
def verifyProofOfFunds(self, address, signature, utxos, extra_commit_bytes):
|
||||||
passed = self.verifyMessage(address, address + '_swap_proof_' + extra_commit_bytes.hex(), signature)
|
passed = self.verifyMessage(address, address + '_swap_proof_' + extra_commit_bytes.hex(), signature)
|
||||||
ensure(passed is True, 'Proof of funds signature invalid')
|
ensure(passed is True, 'Proof of funds signature invalid')
|
||||||
|
|
||||||
@@ -1375,17 +1451,17 @@ class BTCInterface(CoinInterface):
|
|||||||
return self.getUTXOBalance(address)
|
return self.getUTXOBalance(address)
|
||||||
|
|
||||||
def isWalletEncrypted(self) -> bool:
|
def isWalletEncrypted(self) -> bool:
|
||||||
wallet_info = self.rpc_callback('getwalletinfo')
|
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||||
return 'unlocked_until' in wallet_info
|
return 'unlocked_until' in wallet_info
|
||||||
|
|
||||||
def isWalletLocked(self) -> bool:
|
def isWalletLocked(self) -> bool:
|
||||||
wallet_info = self.rpc_callback('getwalletinfo')
|
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||||
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
|
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def isWalletEncryptedLocked(self):
|
def isWalletEncryptedLocked(self):
|
||||||
wallet_info = self.rpc_callback('getwalletinfo')
|
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||||
encrypted = 'unlocked_until' in wallet_info
|
encrypted = 'unlocked_until' in wallet_info
|
||||||
locked = encrypted and wallet_info['unlocked_until'] <= 0
|
locked = encrypted and wallet_info['unlocked_until'] <= 0
|
||||||
return encrypted, locked
|
return encrypted, locked
|
||||||
@@ -1395,8 +1471,8 @@ class BTCInterface(CoinInterface):
|
|||||||
if old_password == '':
|
if old_password == '':
|
||||||
if self.isWalletEncrypted():
|
if self.isWalletEncrypted():
|
||||||
raise ValueError('Old password must be set')
|
raise ValueError('Old password must be set')
|
||||||
return self.rpc_callback('encryptwallet', [new_password])
|
return self.rpc_wallet('encryptwallet', [new_password])
|
||||||
self.rpc_callback('walletpassphrasechange', [old_password, new_password])
|
self.rpc_wallet('walletpassphrasechange', [old_password, new_password])
|
||||||
|
|
||||||
def unlockWallet(self, password: str):
|
def unlockWallet(self, password: str):
|
||||||
if password == '':
|
if password == '':
|
||||||
@@ -1406,21 +1482,20 @@ class BTCInterface(CoinInterface):
|
|||||||
if self.coin_type() == Coins.BTC:
|
if self.coin_type() == Coins.BTC:
|
||||||
# Recreate wallet if none found
|
# Recreate wallet if none found
|
||||||
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
|
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
|
||||||
wallets = self.rpc_callback('listwallets')
|
wallets = self.rpc('listwallets')
|
||||||
if len(wallets) < 1:
|
if len(wallets) < 1:
|
||||||
self._log.info('Creating wallet.dat for {}.'.format(self.coin_name()))
|
self._log.info('Creating wallet.dat for {}.'.format(self.coin_name()))
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||||
self.rpc_callback('createwallet', ['wallet.dat', False, True, '', False, False])
|
self.rpc('createwallet', ['wallet.dat', False, True, '', False, False])
|
||||||
self.rpc_callback('encryptwallet', [password])
|
self.rpc_wallet('encryptwallet', [password])
|
||||||
|
|
||||||
# Max timeout value, ~3 years
|
# Max timeout value, ~3 years
|
||||||
self.rpc_callback('walletpassphrase', [password, 100000000])
|
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||||
|
|
||||||
self._sc.checkWalletSeed(self.coin_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|
||||||
def lockWallet(self):
|
def lockWallet(self):
|
||||||
self._log.info('lockWallet - {}'.format(self.ticker()))
|
self._log.info('lockWallet - {}'.format(self.ticker()))
|
||||||
self.rpc_callback('walletlock')
|
self.rpc_wallet('walletlock')
|
||||||
|
|
||||||
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
|
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
|
||||||
script_hash = hash160(script)
|
script_hash = hash160(script)
|
||||||
@@ -1433,7 +1508,7 @@ class BTCInterface(CoinInterface):
|
|||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
rv = self.rpc_wallet('gettransaction', [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
return None
|
return None
|
||||||
@@ -1468,6 +1543,14 @@ class BTCInterface(CoinInterface):
|
|||||||
if self.getSpendableBalance() < amount:
|
if self.getSpendableBalance() < amount:
|
||||||
raise ValueError('Balance too low')
|
raise ValueError('Balance too low')
|
||||||
|
|
||||||
|
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
|
||||||
|
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes
|
||||||
|
if self.using_segwit():
|
||||||
|
tx_vsize += 143 if redeem else 134
|
||||||
|
else:
|
||||||
|
tx_vsize += 323 if redeem else 287
|
||||||
|
return tx_vsize
|
||||||
|
|
||||||
|
|
||||||
def testBTCInterface():
|
def testBTCInterface():
|
||||||
print('TODO: testBTCInterface')
|
print('TODO: testBTCInterface')
|
||||||
|
|||||||
191
basicswap/interface/contrib/firo_test_framework/authproxy.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
Copyright (c) 2011 Jeff Garzik
|
||||||
|
|
||||||
|
AuthServiceProxy has the following improvements over python-jsonrpc's
|
||||||
|
ServiceProxy class:
|
||||||
|
|
||||||
|
- HTTP connections persist for the life of the AuthServiceProxy object
|
||||||
|
(if server supports HTTP/1.1)
|
||||||
|
- sends protocol 'version', per JSON-RPC 1.1
|
||||||
|
- sends proper, incrementing 'id'
|
||||||
|
- sends Basic HTTP authentication headers
|
||||||
|
- parses all JSON numbers that look like floats as Decimal
|
||||||
|
- uses standard Python json lib
|
||||||
|
|
||||||
|
Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
|
||||||
|
|
||||||
|
Copyright (c) 2007 Jan-Klaas Kollhof
|
||||||
|
|
||||||
|
This file is part of jsonrpc.
|
||||||
|
|
||||||
|
jsonrpc is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This software is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this software; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import http.client as httplib
|
||||||
|
except ImportError:
|
||||||
|
import httplib
|
||||||
|
import base64
|
||||||
|
import decimal
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
try:
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
except ImportError:
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
USER_AGENT = "AuthServiceProxy/0.1"
|
||||||
|
|
||||||
|
HTTP_TIMEOUT = 30
|
||||||
|
|
||||||
|
log = logging.getLogger("BitcoinRPC")
|
||||||
|
|
||||||
|
class JSONRPCException(Exception):
|
||||||
|
def __init__(self, rpc_error):
|
||||||
|
try:
|
||||||
|
errmsg = '%(message)s (%(code)i)' % rpc_error
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
errmsg = ''
|
||||||
|
Exception.__init__(self, errmsg)
|
||||||
|
self.error = rpc_error
|
||||||
|
|
||||||
|
|
||||||
|
def EncodeDecimal(o):
|
||||||
|
if isinstance(o, decimal.Decimal):
|
||||||
|
return str(o)
|
||||||
|
raise TypeError(repr(o) + " is not JSON serializable")
|
||||||
|
|
||||||
|
class AuthServiceProxy(object):
|
||||||
|
__id_count = 0
|
||||||
|
|
||||||
|
# ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
|
||||||
|
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
|
||||||
|
self.__service_url = service_url
|
||||||
|
self._service_name = service_name
|
||||||
|
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
|
||||||
|
self.__url = urlparse.urlparse(service_url)
|
||||||
|
if self.__url.port is None:
|
||||||
|
port = 80
|
||||||
|
else:
|
||||||
|
port = self.__url.port
|
||||||
|
(user, passwd) = (self.__url.username, self.__url.password)
|
||||||
|
try:
|
||||||
|
user = user.encode('utf8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
passwd = passwd.encode('utf8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
authpair = user + b':' + passwd
|
||||||
|
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
|
||||||
|
|
||||||
|
if connection:
|
||||||
|
# Callables re-use the connection of the original proxy
|
||||||
|
self.__conn = connection
|
||||||
|
elif self.__url.scheme == 'https':
|
||||||
|
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
|
||||||
|
timeout=timeout)
|
||||||
|
else:
|
||||||
|
self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
|
||||||
|
timeout=timeout)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name.startswith('__') and name.endswith('__'):
|
||||||
|
# Python internal stuff
|
||||||
|
raise AttributeError
|
||||||
|
if self._service_name is not None:
|
||||||
|
name = "%s.%s" % (self._service_name, name)
|
||||||
|
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
|
||||||
|
|
||||||
|
def _request(self, method, path, postdata):
|
||||||
|
'''
|
||||||
|
Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
|
||||||
|
This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
|
||||||
|
'''
|
||||||
|
headers = {'Host': self.__url.hostname,
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'Authorization': self.__auth_header,
|
||||||
|
'Content-type': 'application/json'}
|
||||||
|
try:
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
except httplib.BadStatusLine as e:
|
||||||
|
if e.line == "''": # if connection was closed, try again
|
||||||
|
self.__conn.close()
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
except (BrokenPipeError,ConnectionResetError):
|
||||||
|
# Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset
|
||||||
|
# ConnectionResetError happens on FreeBSD with Python 3.4
|
||||||
|
self.__conn.close()
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
|
||||||
|
def __call__(self, *args, **argsn):
|
||||||
|
AuthServiceProxy.__id_count += 1
|
||||||
|
|
||||||
|
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name,
|
||||||
|
json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||||
|
if args and argsn:
|
||||||
|
raise ValueError('Cannot handle both named and positional arguments')
|
||||||
|
postdata = json.dumps({'version': '1.1',
|
||||||
|
'method': self._service_name,
|
||||||
|
'params': args or argsn,
|
||||||
|
'id': AuthServiceProxy.__id_count}, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||||
|
response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||||
|
if response['error'] is not None:
|
||||||
|
raise JSONRPCException(response['error'])
|
||||||
|
elif 'result' not in response:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -343, 'message': 'missing JSON-RPC result'})
|
||||||
|
else:
|
||||||
|
return response['result']
|
||||||
|
|
||||||
|
def _batch(self, rpc_call_list):
|
||||||
|
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||||
|
log.debug("--> "+postdata)
|
||||||
|
return self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||||
|
|
||||||
|
def _get_response(self):
|
||||||
|
try:
|
||||||
|
http_response = self.__conn.getresponse()
|
||||||
|
except socket.timeout as e:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -344,
|
||||||
|
'message': '%r RPC took longer than %f seconds. Consider '
|
||||||
|
'using larger timeout for calls that take '
|
||||||
|
'longer to return.' % (self._service_name,
|
||||||
|
self.__conn.timeout)})
|
||||||
|
if http_response is None:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'missing HTTP response from server'})
|
||||||
|
|
||||||
|
content_type = http_response.getheader('Content-Type')
|
||||||
|
if content_type != 'application/json':
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
|
||||||
|
|
||||||
|
responsedata = http_response.read().decode('utf8')
|
||||||
|
response = json.loads(responsedata, parse_float=decimal.Decimal)
|
||||||
|
if "error" in response and response["error"] is None:
|
||||||
|
log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||||
|
else:
|
||||||
|
log.debug("<-- "+responsedata)
|
||||||
|
return response
|
||||||
101
basicswap/interface/contrib/firo_test_framework/bignum.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# bignum.py
|
||||||
|
#
|
||||||
|
# This file is copied from python-bitcoinlib.
|
||||||
|
#
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Bignum routines"""
|
||||||
|
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
# generic big endian MPI format
|
||||||
|
|
||||||
|
def bn_bytes(v, have_ext=False):
|
||||||
|
ext = 0
|
||||||
|
if have_ext:
|
||||||
|
ext = 1
|
||||||
|
return ((v.bit_length()+7)//8) + ext
|
||||||
|
|
||||||
|
def bn2bin(v):
|
||||||
|
s = bytearray()
|
||||||
|
i = bn_bytes(v)
|
||||||
|
while i > 0:
|
||||||
|
s.append((v >> ((i-1) * 8)) & 0xff)
|
||||||
|
i -= 1
|
||||||
|
return s
|
||||||
|
|
||||||
|
def bin2bn(s):
|
||||||
|
l = 0
|
||||||
|
for ch in s:
|
||||||
|
l = (l << 8) | ch
|
||||||
|
return l
|
||||||
|
|
||||||
|
def bn2mpi(v):
|
||||||
|
have_ext = False
|
||||||
|
if v.bit_length() > 0:
|
||||||
|
have_ext = (v.bit_length() & 0x07) == 0
|
||||||
|
|
||||||
|
neg = False
|
||||||
|
if v < 0:
|
||||||
|
neg = True
|
||||||
|
v = -v
|
||||||
|
|
||||||
|
s = struct.pack(b">I", bn_bytes(v, have_ext))
|
||||||
|
ext = bytearray()
|
||||||
|
if have_ext:
|
||||||
|
ext.append(0)
|
||||||
|
v_bin = bn2bin(v)
|
||||||
|
if neg:
|
||||||
|
if have_ext:
|
||||||
|
ext[0] |= 0x80
|
||||||
|
else:
|
||||||
|
v_bin[0] |= 0x80
|
||||||
|
return s + ext + v_bin
|
||||||
|
|
||||||
|
def mpi2bn(s):
|
||||||
|
if len(s) < 4:
|
||||||
|
return None
|
||||||
|
s_size = bytes(s[:4])
|
||||||
|
v_len = struct.unpack(b">I", s_size)[0]
|
||||||
|
if len(s) != (v_len + 4):
|
||||||
|
return None
|
||||||
|
if v_len == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
v_str = bytearray(s[4:])
|
||||||
|
neg = False
|
||||||
|
i = v_str[0]
|
||||||
|
if i & 0x80:
|
||||||
|
neg = True
|
||||||
|
i &= ~0x80
|
||||||
|
v_str[0] = i
|
||||||
|
|
||||||
|
v = bin2bn(v_str)
|
||||||
|
|
||||||
|
if neg:
|
||||||
|
return -v
|
||||||
|
return v
|
||||||
|
|
||||||
|
# bitcoin-specific little endian format, with implicit size
|
||||||
|
def mpi2vch(s):
|
||||||
|
r = s[4:] # strip size
|
||||||
|
r = r[::-1] # reverse string, converting BE->LE
|
||||||
|
return r
|
||||||
|
|
||||||
|
def bn2vch(v):
|
||||||
|
return bytes(mpi2vch(bn2mpi(v)))
|
||||||
|
|
||||||
|
def vch2mpi(s):
|
||||||
|
r = struct.pack(b">I", len(s)) # size
|
||||||
|
r += s[::-1] # reverse string, converting LE->BE
|
||||||
|
return r
|
||||||
|
|
||||||
|
def vch2bn(s):
|
||||||
|
return mpi2bn(vch2mpi(s))
|
||||||
|
|
||||||
106
basicswap/interface/contrib/firo_test_framework/coverage.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2015-2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module contains utilities for doing coverage analysis on the RPC
|
||||||
|
interface.
|
||||||
|
|
||||||
|
It provides a way to track which RPC commands are exercised during
|
||||||
|
testing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
REFERENCE_FILENAME = 'rpc_interface.txt'
|
||||||
|
|
||||||
|
|
||||||
|
class AuthServiceProxyWrapper(object):
|
||||||
|
"""
|
||||||
|
An object that wraps AuthServiceProxy to record specific RPC calls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
|
||||||
|
"""
|
||||||
|
Kwargs:
|
||||||
|
auth_service_proxy_instance (AuthServiceProxy): the instance
|
||||||
|
being wrapped.
|
||||||
|
coverage_logfile (str): if specified, write each service_name
|
||||||
|
out to a file when called.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.auth_service_proxy_instance = auth_service_proxy_instance
|
||||||
|
self.coverage_logfile = coverage_logfile
|
||||||
|
|
||||||
|
def __getattr__(self, *args, **kwargs):
|
||||||
|
return_val = self.auth_service_proxy_instance.__getattr__(
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Delegates to AuthServiceProxy, then writes the particular RPC method
|
||||||
|
called to a file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
|
||||||
|
rpc_method = self.auth_service_proxy_instance._service_name
|
||||||
|
|
||||||
|
if self.coverage_logfile:
|
||||||
|
with open(self.coverage_logfile, 'a+', encoding='utf8') as f:
|
||||||
|
f.write("%s\n" % rpc_method)
|
||||||
|
|
||||||
|
return return_val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self.auth_service_proxy_instance.url
|
||||||
|
|
||||||
|
|
||||||
|
def get_filename(dirname, n_node):
|
||||||
|
"""
|
||||||
|
Get a filename unique to the test process ID and node.
|
||||||
|
|
||||||
|
This file will contain a list of RPC commands covered.
|
||||||
|
"""
|
||||||
|
pid = str(os.getpid())
|
||||||
|
return os.path.join(
|
||||||
|
dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
|
||||||
|
|
||||||
|
|
||||||
|
def write_all_rpc_commands(dirname, node):
|
||||||
|
"""
|
||||||
|
Write out a list of all RPC functions available in `bitcoin-cli` for
|
||||||
|
coverage comparison. This will only happen once per coverage
|
||||||
|
directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dirname (str): temporary test dir
|
||||||
|
node (AuthServiceProxy): client
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool. if the RPC interface file was written.
|
||||||
|
|
||||||
|
"""
|
||||||
|
filename = os.path.join(dirname, REFERENCE_FILENAME)
|
||||||
|
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return False
|
||||||
|
|
||||||
|
help_output = node.help().split('\n')
|
||||||
|
commands = set()
|
||||||
|
|
||||||
|
for line in help_output:
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Ignore blanks and headers
|
||||||
|
if line and not line.startswith('='):
|
||||||
|
commands.add("%s\n" % line.split()[0])
|
||||||
|
|
||||||
|
with open(filename, 'w', encoding='utf8') as f:
|
||||||
|
f.writelines(list(commands))
|
||||||
|
|
||||||
|
return True
|
||||||
1904
basicswap/interface/contrib/firo_test_framework/mininode.py
Normal file
943
basicswap/interface/contrib/firo_test_framework/script.py
Normal file
@@ -0,0 +1,943 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2015-2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#
|
||||||
|
# script.py
|
||||||
|
#
|
||||||
|
# This file is modified from python-bitcoinlib.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Scripts
|
||||||
|
|
||||||
|
Functionality to build scripts, as well as SignatureHash().
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from .mininode import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string
|
||||||
|
from binascii import hexlify
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
import sys
|
||||||
|
bchr = chr
|
||||||
|
bord = ord
|
||||||
|
if sys.version > '3':
|
||||||
|
long = int
|
||||||
|
bchr = lambda x: bytes([x])
|
||||||
|
bord = lambda x: x
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from .bignum import bn2vch
|
||||||
|
|
||||||
|
MAX_SCRIPT_SIZE = 10000
|
||||||
|
MAX_SCRIPT_ELEMENT_SIZE = 520
|
||||||
|
MAX_SCRIPT_OPCODES = 201
|
||||||
|
|
||||||
|
OPCODE_NAMES = {}
|
||||||
|
|
||||||
|
_opcode_instances = []
|
||||||
|
class CScriptOp(int):
|
||||||
|
"""A single script opcode"""
|
||||||
|
__slots__ = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_pushdata(d):
|
||||||
|
"""Encode a PUSHDATA op, returning bytes"""
|
||||||
|
if len(d) < 0x4c:
|
||||||
|
return b'' + bchr(len(d)) + d # OP_PUSHDATA
|
||||||
|
elif len(d) <= 0xff:
|
||||||
|
return b'\x4c' + bchr(len(d)) + d # OP_PUSHDATA1
|
||||||
|
elif len(d) <= 0xffff:
|
||||||
|
return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2
|
||||||
|
elif len(d) <= 0xffffffff:
|
||||||
|
return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4
|
||||||
|
else:
|
||||||
|
raise ValueError("Data too long to encode in a PUSHDATA op")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_n(n):
|
||||||
|
"""Encode a small integer op, returning an opcode"""
|
||||||
|
if not (0 <= n <= 16):
|
||||||
|
raise ValueError('Integer must be in range 0 <= n <= 16, got %d' % n)
|
||||||
|
|
||||||
|
if n == 0:
|
||||||
|
return OP_0
|
||||||
|
else:
|
||||||
|
return CScriptOp(OP_1 + n-1)
|
||||||
|
|
||||||
|
def decode_op_n(self):
|
||||||
|
"""Decode a small integer opcode, returning an integer"""
|
||||||
|
if self == OP_0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not (self == OP_0 or OP_1 <= self <= OP_16):
|
||||||
|
raise ValueError('op %r is not an OP_N' % self)
|
||||||
|
|
||||||
|
return int(self - OP_1+1)
|
||||||
|
|
||||||
|
def is_small_int(self):
|
||||||
|
"""Return true if the op pushes a small integer to the stack"""
|
||||||
|
if 0x51 <= self <= 0x60 or self == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self in OPCODE_NAMES:
|
||||||
|
return OPCODE_NAMES[self]
|
||||||
|
else:
|
||||||
|
return 'CScriptOp(0x%x)' % self
|
||||||
|
|
||||||
|
def __new__(cls, n):
|
||||||
|
try:
|
||||||
|
return _opcode_instances[n]
|
||||||
|
except IndexError:
|
||||||
|
assert len(_opcode_instances) == n
|
||||||
|
_opcode_instances.append(super(CScriptOp, cls).__new__(cls, n))
|
||||||
|
return _opcode_instances[n]
|
||||||
|
|
||||||
|
# Populate opcode instance table
|
||||||
|
for n in range(0xff+1):
|
||||||
|
CScriptOp(n)
|
||||||
|
|
||||||
|
|
||||||
|
# push value
|
||||||
|
OP_0 = CScriptOp(0x00)
|
||||||
|
OP_FALSE = OP_0
|
||||||
|
OP_PUSHDATA1 = CScriptOp(0x4c)
|
||||||
|
OP_PUSHDATA2 = CScriptOp(0x4d)
|
||||||
|
OP_PUSHDATA4 = CScriptOp(0x4e)
|
||||||
|
OP_1NEGATE = CScriptOp(0x4f)
|
||||||
|
OP_RESERVED = CScriptOp(0x50)
|
||||||
|
OP_1 = CScriptOp(0x51)
|
||||||
|
OP_TRUE=OP_1
|
||||||
|
OP_2 = CScriptOp(0x52)
|
||||||
|
OP_3 = CScriptOp(0x53)
|
||||||
|
OP_4 = CScriptOp(0x54)
|
||||||
|
OP_5 = CScriptOp(0x55)
|
||||||
|
OP_6 = CScriptOp(0x56)
|
||||||
|
OP_7 = CScriptOp(0x57)
|
||||||
|
OP_8 = CScriptOp(0x58)
|
||||||
|
OP_9 = CScriptOp(0x59)
|
||||||
|
OP_10 = CScriptOp(0x5a)
|
||||||
|
OP_11 = CScriptOp(0x5b)
|
||||||
|
OP_12 = CScriptOp(0x5c)
|
||||||
|
OP_13 = CScriptOp(0x5d)
|
||||||
|
OP_14 = CScriptOp(0x5e)
|
||||||
|
OP_15 = CScriptOp(0x5f)
|
||||||
|
OP_16 = CScriptOp(0x60)
|
||||||
|
|
||||||
|
# control
|
||||||
|
OP_NOP = CScriptOp(0x61)
|
||||||
|
OP_VER = CScriptOp(0x62)
|
||||||
|
OP_IF = CScriptOp(0x63)
|
||||||
|
OP_NOTIF = CScriptOp(0x64)
|
||||||
|
OP_VERIF = CScriptOp(0x65)
|
||||||
|
OP_VERNOTIF = CScriptOp(0x66)
|
||||||
|
OP_ELSE = CScriptOp(0x67)
|
||||||
|
OP_ENDIF = CScriptOp(0x68)
|
||||||
|
OP_VERIFY = CScriptOp(0x69)
|
||||||
|
OP_RETURN = CScriptOp(0x6a)
|
||||||
|
|
||||||
|
# stack ops
|
||||||
|
OP_TOALTSTACK = CScriptOp(0x6b)
|
||||||
|
OP_FROMALTSTACK = CScriptOp(0x6c)
|
||||||
|
OP_2DROP = CScriptOp(0x6d)
|
||||||
|
OP_2DUP = CScriptOp(0x6e)
|
||||||
|
OP_3DUP = CScriptOp(0x6f)
|
||||||
|
OP_2OVER = CScriptOp(0x70)
|
||||||
|
OP_2ROT = CScriptOp(0x71)
|
||||||
|
OP_2SWAP = CScriptOp(0x72)
|
||||||
|
OP_IFDUP = CScriptOp(0x73)
|
||||||
|
OP_DEPTH = CScriptOp(0x74)
|
||||||
|
OP_DROP = CScriptOp(0x75)
|
||||||
|
OP_DUP = CScriptOp(0x76)
|
||||||
|
OP_NIP = CScriptOp(0x77)
|
||||||
|
OP_OVER = CScriptOp(0x78)
|
||||||
|
OP_PICK = CScriptOp(0x79)
|
||||||
|
OP_ROLL = CScriptOp(0x7a)
|
||||||
|
OP_ROT = CScriptOp(0x7b)
|
||||||
|
OP_SWAP = CScriptOp(0x7c)
|
||||||
|
OP_TUCK = CScriptOp(0x7d)
|
||||||
|
|
||||||
|
# splice ops
|
||||||
|
OP_CAT = CScriptOp(0x7e)
|
||||||
|
OP_SUBSTR = CScriptOp(0x7f)
|
||||||
|
OP_LEFT = CScriptOp(0x80)
|
||||||
|
OP_RIGHT = CScriptOp(0x81)
|
||||||
|
OP_SIZE = CScriptOp(0x82)
|
||||||
|
|
||||||
|
# bit logic
|
||||||
|
OP_INVERT = CScriptOp(0x83)
|
||||||
|
OP_AND = CScriptOp(0x84)
|
||||||
|
OP_OR = CScriptOp(0x85)
|
||||||
|
OP_XOR = CScriptOp(0x86)
|
||||||
|
OP_EQUAL = CScriptOp(0x87)
|
||||||
|
OP_EQUALVERIFY = CScriptOp(0x88)
|
||||||
|
OP_RESERVED1 = CScriptOp(0x89)
|
||||||
|
OP_RESERVED2 = CScriptOp(0x8a)
|
||||||
|
|
||||||
|
# numeric
|
||||||
|
OP_1ADD = CScriptOp(0x8b)
|
||||||
|
OP_1SUB = CScriptOp(0x8c)
|
||||||
|
OP_2MUL = CScriptOp(0x8d)
|
||||||
|
OP_2DIV = CScriptOp(0x8e)
|
||||||
|
OP_NEGATE = CScriptOp(0x8f)
|
||||||
|
OP_ABS = CScriptOp(0x90)
|
||||||
|
OP_NOT = CScriptOp(0x91)
|
||||||
|
OP_0NOTEQUAL = CScriptOp(0x92)
|
||||||
|
|
||||||
|
OP_ADD = CScriptOp(0x93)
|
||||||
|
OP_SUB = CScriptOp(0x94)
|
||||||
|
OP_MUL = CScriptOp(0x95)
|
||||||
|
OP_DIV = CScriptOp(0x96)
|
||||||
|
OP_MOD = CScriptOp(0x97)
|
||||||
|
OP_LSHIFT = CScriptOp(0x98)
|
||||||
|
OP_RSHIFT = CScriptOp(0x99)
|
||||||
|
|
||||||
|
OP_BOOLAND = CScriptOp(0x9a)
|
||||||
|
OP_BOOLOR = CScriptOp(0x9b)
|
||||||
|
OP_NUMEQUAL = CScriptOp(0x9c)
|
||||||
|
OP_NUMEQUALVERIFY = CScriptOp(0x9d)
|
||||||
|
OP_NUMNOTEQUAL = CScriptOp(0x9e)
|
||||||
|
OP_LESSTHAN = CScriptOp(0x9f)
|
||||||
|
OP_GREATERTHAN = CScriptOp(0xa0)
|
||||||
|
OP_LESSTHANOREQUAL = CScriptOp(0xa1)
|
||||||
|
OP_GREATERTHANOREQUAL = CScriptOp(0xa2)
|
||||||
|
OP_MIN = CScriptOp(0xa3)
|
||||||
|
OP_MAX = CScriptOp(0xa4)
|
||||||
|
|
||||||
|
OP_WITHIN = CScriptOp(0xa5)
|
||||||
|
|
||||||
|
# crypto
|
||||||
|
OP_RIPEMD160 = CScriptOp(0xa6)
|
||||||
|
OP_SHA1 = CScriptOp(0xa7)
|
||||||
|
OP_SHA256 = CScriptOp(0xa8)
|
||||||
|
OP_HASH160 = CScriptOp(0xa9)
|
||||||
|
OP_HASH256 = CScriptOp(0xaa)
|
||||||
|
OP_CODESEPARATOR = CScriptOp(0xab)
|
||||||
|
OP_CHECKSIG = CScriptOp(0xac)
|
||||||
|
OP_CHECKSIGVERIFY = CScriptOp(0xad)
|
||||||
|
OP_CHECKMULTISIG = CScriptOp(0xae)
|
||||||
|
OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
|
||||||
|
|
||||||
|
# expansion
|
||||||
|
OP_NOP1 = CScriptOp(0xb0)
|
||||||
|
OP_CHECKLOCKTIMEVERIFY = CScriptOp(0xb1)
|
||||||
|
OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
|
||||||
|
OP_NOP4 = CScriptOp(0xb3)
|
||||||
|
OP_NOP5 = CScriptOp(0xb4)
|
||||||
|
OP_NOP6 = CScriptOp(0xb5)
|
||||||
|
OP_NOP7 = CScriptOp(0xb6)
|
||||||
|
OP_NOP8 = CScriptOp(0xb7)
|
||||||
|
OP_NOP9 = CScriptOp(0xb8)
|
||||||
|
OP_NOP10 = CScriptOp(0xb9)
|
||||||
|
|
||||||
|
# template matching params
|
||||||
|
OP_SMALLINTEGER = CScriptOp(0xfa)
|
||||||
|
OP_PUBKEYS = CScriptOp(0xfb)
|
||||||
|
OP_PUBKEYHASH = CScriptOp(0xfd)
|
||||||
|
OP_PUBKEY = CScriptOp(0xfe)
|
||||||
|
|
||||||
|
OP_INVALIDOPCODE = CScriptOp(0xff)
|
||||||
|
|
||||||
|
VALID_OPCODES = {
|
||||||
|
OP_1NEGATE,
|
||||||
|
OP_RESERVED,
|
||||||
|
OP_1,
|
||||||
|
OP_2,
|
||||||
|
OP_3,
|
||||||
|
OP_4,
|
||||||
|
OP_5,
|
||||||
|
OP_6,
|
||||||
|
OP_7,
|
||||||
|
OP_8,
|
||||||
|
OP_9,
|
||||||
|
OP_10,
|
||||||
|
OP_11,
|
||||||
|
OP_12,
|
||||||
|
OP_13,
|
||||||
|
OP_14,
|
||||||
|
OP_15,
|
||||||
|
OP_16,
|
||||||
|
|
||||||
|
OP_NOP,
|
||||||
|
OP_VER,
|
||||||
|
OP_IF,
|
||||||
|
OP_NOTIF,
|
||||||
|
OP_VERIF,
|
||||||
|
OP_VERNOTIF,
|
||||||
|
OP_ELSE,
|
||||||
|
OP_ENDIF,
|
||||||
|
OP_VERIFY,
|
||||||
|
OP_RETURN,
|
||||||
|
|
||||||
|
OP_TOALTSTACK,
|
||||||
|
OP_FROMALTSTACK,
|
||||||
|
OP_2DROP,
|
||||||
|
OP_2DUP,
|
||||||
|
OP_3DUP,
|
||||||
|
OP_2OVER,
|
||||||
|
OP_2ROT,
|
||||||
|
OP_2SWAP,
|
||||||
|
OP_IFDUP,
|
||||||
|
OP_DEPTH,
|
||||||
|
OP_DROP,
|
||||||
|
OP_DUP,
|
||||||
|
OP_NIP,
|
||||||
|
OP_OVER,
|
||||||
|
OP_PICK,
|
||||||
|
OP_ROLL,
|
||||||
|
OP_ROT,
|
||||||
|
OP_SWAP,
|
||||||
|
OP_TUCK,
|
||||||
|
|
||||||
|
OP_CAT,
|
||||||
|
OP_SUBSTR,
|
||||||
|
OP_LEFT,
|
||||||
|
OP_RIGHT,
|
||||||
|
OP_SIZE,
|
||||||
|
|
||||||
|
OP_INVERT,
|
||||||
|
OP_AND,
|
||||||
|
OP_OR,
|
||||||
|
OP_XOR,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_RESERVED1,
|
||||||
|
OP_RESERVED2,
|
||||||
|
|
||||||
|
OP_1ADD,
|
||||||
|
OP_1SUB,
|
||||||
|
OP_2MUL,
|
||||||
|
OP_2DIV,
|
||||||
|
OP_NEGATE,
|
||||||
|
OP_ABS,
|
||||||
|
OP_NOT,
|
||||||
|
OP_0NOTEQUAL,
|
||||||
|
|
||||||
|
OP_ADD,
|
||||||
|
OP_SUB,
|
||||||
|
OP_MUL,
|
||||||
|
OP_DIV,
|
||||||
|
OP_MOD,
|
||||||
|
OP_LSHIFT,
|
||||||
|
OP_RSHIFT,
|
||||||
|
|
||||||
|
OP_BOOLAND,
|
||||||
|
OP_BOOLOR,
|
||||||
|
OP_NUMEQUAL,
|
||||||
|
OP_NUMEQUALVERIFY,
|
||||||
|
OP_NUMNOTEQUAL,
|
||||||
|
OP_LESSTHAN,
|
||||||
|
OP_GREATERTHAN,
|
||||||
|
OP_LESSTHANOREQUAL,
|
||||||
|
OP_GREATERTHANOREQUAL,
|
||||||
|
OP_MIN,
|
||||||
|
OP_MAX,
|
||||||
|
|
||||||
|
OP_WITHIN,
|
||||||
|
|
||||||
|
OP_RIPEMD160,
|
||||||
|
OP_SHA1,
|
||||||
|
OP_SHA256,
|
||||||
|
OP_HASH160,
|
||||||
|
OP_HASH256,
|
||||||
|
OP_CODESEPARATOR,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_CHECKSIGVERIFY,
|
||||||
|
OP_CHECKMULTISIG,
|
||||||
|
OP_CHECKMULTISIGVERIFY,
|
||||||
|
|
||||||
|
OP_NOP1,
|
||||||
|
OP_CHECKLOCKTIMEVERIFY,
|
||||||
|
OP_CHECKSEQUENCEVERIFY,
|
||||||
|
OP_NOP4,
|
||||||
|
OP_NOP5,
|
||||||
|
OP_NOP6,
|
||||||
|
OP_NOP7,
|
||||||
|
OP_NOP8,
|
||||||
|
OP_NOP9,
|
||||||
|
OP_NOP10,
|
||||||
|
|
||||||
|
OP_SMALLINTEGER,
|
||||||
|
OP_PUBKEYS,
|
||||||
|
OP_PUBKEYHASH,
|
||||||
|
OP_PUBKEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
OPCODE_NAMES.update({
|
||||||
|
OP_0 : 'OP_0',
|
||||||
|
OP_PUSHDATA1 : 'OP_PUSHDATA1',
|
||||||
|
OP_PUSHDATA2 : 'OP_PUSHDATA2',
|
||||||
|
OP_PUSHDATA4 : 'OP_PUSHDATA4',
|
||||||
|
OP_1NEGATE : 'OP_1NEGATE',
|
||||||
|
OP_RESERVED : 'OP_RESERVED',
|
||||||
|
OP_1 : 'OP_1',
|
||||||
|
OP_2 : 'OP_2',
|
||||||
|
OP_3 : 'OP_3',
|
||||||
|
OP_4 : 'OP_4',
|
||||||
|
OP_5 : 'OP_5',
|
||||||
|
OP_6 : 'OP_6',
|
||||||
|
OP_7 : 'OP_7',
|
||||||
|
OP_8 : 'OP_8',
|
||||||
|
OP_9 : 'OP_9',
|
||||||
|
OP_10 : 'OP_10',
|
||||||
|
OP_11 : 'OP_11',
|
||||||
|
OP_12 : 'OP_12',
|
||||||
|
OP_13 : 'OP_13',
|
||||||
|
OP_14 : 'OP_14',
|
||||||
|
OP_15 : 'OP_15',
|
||||||
|
OP_16 : 'OP_16',
|
||||||
|
OP_NOP : 'OP_NOP',
|
||||||
|
OP_VER : 'OP_VER',
|
||||||
|
OP_IF : 'OP_IF',
|
||||||
|
OP_NOTIF : 'OP_NOTIF',
|
||||||
|
OP_VERIF : 'OP_VERIF',
|
||||||
|
OP_VERNOTIF : 'OP_VERNOTIF',
|
||||||
|
OP_ELSE : 'OP_ELSE',
|
||||||
|
OP_ENDIF : 'OP_ENDIF',
|
||||||
|
OP_VERIFY : 'OP_VERIFY',
|
||||||
|
OP_RETURN : 'OP_RETURN',
|
||||||
|
OP_TOALTSTACK : 'OP_TOALTSTACK',
|
||||||
|
OP_FROMALTSTACK : 'OP_FROMALTSTACK',
|
||||||
|
OP_2DROP : 'OP_2DROP',
|
||||||
|
OP_2DUP : 'OP_2DUP',
|
||||||
|
OP_3DUP : 'OP_3DUP',
|
||||||
|
OP_2OVER : 'OP_2OVER',
|
||||||
|
OP_2ROT : 'OP_2ROT',
|
||||||
|
OP_2SWAP : 'OP_2SWAP',
|
||||||
|
OP_IFDUP : 'OP_IFDUP',
|
||||||
|
OP_DEPTH : 'OP_DEPTH',
|
||||||
|
OP_DROP : 'OP_DROP',
|
||||||
|
OP_DUP : 'OP_DUP',
|
||||||
|
OP_NIP : 'OP_NIP',
|
||||||
|
OP_OVER : 'OP_OVER',
|
||||||
|
OP_PICK : 'OP_PICK',
|
||||||
|
OP_ROLL : 'OP_ROLL',
|
||||||
|
OP_ROT : 'OP_ROT',
|
||||||
|
OP_SWAP : 'OP_SWAP',
|
||||||
|
OP_TUCK : 'OP_TUCK',
|
||||||
|
OP_CAT : 'OP_CAT',
|
||||||
|
OP_SUBSTR : 'OP_SUBSTR',
|
||||||
|
OP_LEFT : 'OP_LEFT',
|
||||||
|
OP_RIGHT : 'OP_RIGHT',
|
||||||
|
OP_SIZE : 'OP_SIZE',
|
||||||
|
OP_INVERT : 'OP_INVERT',
|
||||||
|
OP_AND : 'OP_AND',
|
||||||
|
OP_OR : 'OP_OR',
|
||||||
|
OP_XOR : 'OP_XOR',
|
||||||
|
OP_EQUAL : 'OP_EQUAL',
|
||||||
|
OP_EQUALVERIFY : 'OP_EQUALVERIFY',
|
||||||
|
OP_RESERVED1 : 'OP_RESERVED1',
|
||||||
|
OP_RESERVED2 : 'OP_RESERVED2',
|
||||||
|
OP_1ADD : 'OP_1ADD',
|
||||||
|
OP_1SUB : 'OP_1SUB',
|
||||||
|
OP_2MUL : 'OP_2MUL',
|
||||||
|
OP_2DIV : 'OP_2DIV',
|
||||||
|
OP_NEGATE : 'OP_NEGATE',
|
||||||
|
OP_ABS : 'OP_ABS',
|
||||||
|
OP_NOT : 'OP_NOT',
|
||||||
|
OP_0NOTEQUAL : 'OP_0NOTEQUAL',
|
||||||
|
OP_ADD : 'OP_ADD',
|
||||||
|
OP_SUB : 'OP_SUB',
|
||||||
|
OP_MUL : 'OP_MUL',
|
||||||
|
OP_DIV : 'OP_DIV',
|
||||||
|
OP_MOD : 'OP_MOD',
|
||||||
|
OP_LSHIFT : 'OP_LSHIFT',
|
||||||
|
OP_RSHIFT : 'OP_RSHIFT',
|
||||||
|
OP_BOOLAND : 'OP_BOOLAND',
|
||||||
|
OP_BOOLOR : 'OP_BOOLOR',
|
||||||
|
OP_NUMEQUAL : 'OP_NUMEQUAL',
|
||||||
|
OP_NUMEQUALVERIFY : 'OP_NUMEQUALVERIFY',
|
||||||
|
OP_NUMNOTEQUAL : 'OP_NUMNOTEQUAL',
|
||||||
|
OP_LESSTHAN : 'OP_LESSTHAN',
|
||||||
|
OP_GREATERTHAN : 'OP_GREATERTHAN',
|
||||||
|
OP_LESSTHANOREQUAL : 'OP_LESSTHANOREQUAL',
|
||||||
|
OP_GREATERTHANOREQUAL : 'OP_GREATERTHANOREQUAL',
|
||||||
|
OP_MIN : 'OP_MIN',
|
||||||
|
OP_MAX : 'OP_MAX',
|
||||||
|
OP_WITHIN : 'OP_WITHIN',
|
||||||
|
OP_RIPEMD160 : 'OP_RIPEMD160',
|
||||||
|
OP_SHA1 : 'OP_SHA1',
|
||||||
|
OP_SHA256 : 'OP_SHA256',
|
||||||
|
OP_HASH160 : 'OP_HASH160',
|
||||||
|
OP_HASH256 : 'OP_HASH256',
|
||||||
|
OP_CODESEPARATOR : 'OP_CODESEPARATOR',
|
||||||
|
OP_CHECKSIG : 'OP_CHECKSIG',
|
||||||
|
OP_CHECKSIGVERIFY : 'OP_CHECKSIGVERIFY',
|
||||||
|
OP_CHECKMULTISIG : 'OP_CHECKMULTISIG',
|
||||||
|
OP_CHECKMULTISIGVERIFY : 'OP_CHECKMULTISIGVERIFY',
|
||||||
|
OP_NOP1 : 'OP_NOP1',
|
||||||
|
OP_CHECKLOCKTIMEVERIFY : 'OP_CHECKLOCKTIMEVERIFY',
|
||||||
|
OP_CHECKSEQUENCEVERIFY : 'OP_CHECKSEQUENCEVERIFY',
|
||||||
|
OP_NOP4 : 'OP_NOP4',
|
||||||
|
OP_NOP5 : 'OP_NOP5',
|
||||||
|
OP_NOP6 : 'OP_NOP6',
|
||||||
|
OP_NOP7 : 'OP_NOP7',
|
||||||
|
OP_NOP8 : 'OP_NOP8',
|
||||||
|
OP_NOP9 : 'OP_NOP9',
|
||||||
|
OP_NOP10 : 'OP_NOP10',
|
||||||
|
OP_SMALLINTEGER : 'OP_SMALLINTEGER',
|
||||||
|
OP_PUBKEYS : 'OP_PUBKEYS',
|
||||||
|
OP_PUBKEYHASH : 'OP_PUBKEYHASH',
|
||||||
|
OP_PUBKEY : 'OP_PUBKEY',
|
||||||
|
OP_INVALIDOPCODE : 'OP_INVALIDOPCODE',
|
||||||
|
})
|
||||||
|
|
||||||
|
OPCODES_BY_NAME = {
|
||||||
|
'OP_0' : OP_0,
|
||||||
|
'OP_PUSHDATA1' : OP_PUSHDATA1,
|
||||||
|
'OP_PUSHDATA2' : OP_PUSHDATA2,
|
||||||
|
'OP_PUSHDATA4' : OP_PUSHDATA4,
|
||||||
|
'OP_1NEGATE' : OP_1NEGATE,
|
||||||
|
'OP_RESERVED' : OP_RESERVED,
|
||||||
|
'OP_1' : OP_1,
|
||||||
|
'OP_2' : OP_2,
|
||||||
|
'OP_3' : OP_3,
|
||||||
|
'OP_4' : OP_4,
|
||||||
|
'OP_5' : OP_5,
|
||||||
|
'OP_6' : OP_6,
|
||||||
|
'OP_7' : OP_7,
|
||||||
|
'OP_8' : OP_8,
|
||||||
|
'OP_9' : OP_9,
|
||||||
|
'OP_10' : OP_10,
|
||||||
|
'OP_11' : OP_11,
|
||||||
|
'OP_12' : OP_12,
|
||||||
|
'OP_13' : OP_13,
|
||||||
|
'OP_14' : OP_14,
|
||||||
|
'OP_15' : OP_15,
|
||||||
|
'OP_16' : OP_16,
|
||||||
|
'OP_NOP' : OP_NOP,
|
||||||
|
'OP_VER' : OP_VER,
|
||||||
|
'OP_IF' : OP_IF,
|
||||||
|
'OP_NOTIF' : OP_NOTIF,
|
||||||
|
'OP_VERIF' : OP_VERIF,
|
||||||
|
'OP_VERNOTIF' : OP_VERNOTIF,
|
||||||
|
'OP_ELSE' : OP_ELSE,
|
||||||
|
'OP_ENDIF' : OP_ENDIF,
|
||||||
|
'OP_VERIFY' : OP_VERIFY,
|
||||||
|
'OP_RETURN' : OP_RETURN,
|
||||||
|
'OP_TOALTSTACK' : OP_TOALTSTACK,
|
||||||
|
'OP_FROMALTSTACK' : OP_FROMALTSTACK,
|
||||||
|
'OP_2DROP' : OP_2DROP,
|
||||||
|
'OP_2DUP' : OP_2DUP,
|
||||||
|
'OP_3DUP' : OP_3DUP,
|
||||||
|
'OP_2OVER' : OP_2OVER,
|
||||||
|
'OP_2ROT' : OP_2ROT,
|
||||||
|
'OP_2SWAP' : OP_2SWAP,
|
||||||
|
'OP_IFDUP' : OP_IFDUP,
|
||||||
|
'OP_DEPTH' : OP_DEPTH,
|
||||||
|
'OP_DROP' : OP_DROP,
|
||||||
|
'OP_DUP' : OP_DUP,
|
||||||
|
'OP_NIP' : OP_NIP,
|
||||||
|
'OP_OVER' : OP_OVER,
|
||||||
|
'OP_PICK' : OP_PICK,
|
||||||
|
'OP_ROLL' : OP_ROLL,
|
||||||
|
'OP_ROT' : OP_ROT,
|
||||||
|
'OP_SWAP' : OP_SWAP,
|
||||||
|
'OP_TUCK' : OP_TUCK,
|
||||||
|
'OP_CAT' : OP_CAT,
|
||||||
|
'OP_SUBSTR' : OP_SUBSTR,
|
||||||
|
'OP_LEFT' : OP_LEFT,
|
||||||
|
'OP_RIGHT' : OP_RIGHT,
|
||||||
|
'OP_SIZE' : OP_SIZE,
|
||||||
|
'OP_INVERT' : OP_INVERT,
|
||||||
|
'OP_AND' : OP_AND,
|
||||||
|
'OP_OR' : OP_OR,
|
||||||
|
'OP_XOR' : OP_XOR,
|
||||||
|
'OP_EQUAL' : OP_EQUAL,
|
||||||
|
'OP_EQUALVERIFY' : OP_EQUALVERIFY,
|
||||||
|
'OP_RESERVED1' : OP_RESERVED1,
|
||||||
|
'OP_RESERVED2' : OP_RESERVED2,
|
||||||
|
'OP_1ADD' : OP_1ADD,
|
||||||
|
'OP_1SUB' : OP_1SUB,
|
||||||
|
'OP_2MUL' : OP_2MUL,
|
||||||
|
'OP_2DIV' : OP_2DIV,
|
||||||
|
'OP_NEGATE' : OP_NEGATE,
|
||||||
|
'OP_ABS' : OP_ABS,
|
||||||
|
'OP_NOT' : OP_NOT,
|
||||||
|
'OP_0NOTEQUAL' : OP_0NOTEQUAL,
|
||||||
|
'OP_ADD' : OP_ADD,
|
||||||
|
'OP_SUB' : OP_SUB,
|
||||||
|
'OP_MUL' : OP_MUL,
|
||||||
|
'OP_DIV' : OP_DIV,
|
||||||
|
'OP_MOD' : OP_MOD,
|
||||||
|
'OP_LSHIFT' : OP_LSHIFT,
|
||||||
|
'OP_RSHIFT' : OP_RSHIFT,
|
||||||
|
'OP_BOOLAND' : OP_BOOLAND,
|
||||||
|
'OP_BOOLOR' : OP_BOOLOR,
|
||||||
|
'OP_NUMEQUAL' : OP_NUMEQUAL,
|
||||||
|
'OP_NUMEQUALVERIFY' : OP_NUMEQUALVERIFY,
|
||||||
|
'OP_NUMNOTEQUAL' : OP_NUMNOTEQUAL,
|
||||||
|
'OP_LESSTHAN' : OP_LESSTHAN,
|
||||||
|
'OP_GREATERTHAN' : OP_GREATERTHAN,
|
||||||
|
'OP_LESSTHANOREQUAL' : OP_LESSTHANOREQUAL,
|
||||||
|
'OP_GREATERTHANOREQUAL' : OP_GREATERTHANOREQUAL,
|
||||||
|
'OP_MIN' : OP_MIN,
|
||||||
|
'OP_MAX' : OP_MAX,
|
||||||
|
'OP_WITHIN' : OP_WITHIN,
|
||||||
|
'OP_RIPEMD160' : OP_RIPEMD160,
|
||||||
|
'OP_SHA1' : OP_SHA1,
|
||||||
|
'OP_SHA256' : OP_SHA256,
|
||||||
|
'OP_HASH160' : OP_HASH160,
|
||||||
|
'OP_HASH256' : OP_HASH256,
|
||||||
|
'OP_CODESEPARATOR' : OP_CODESEPARATOR,
|
||||||
|
'OP_CHECKSIG' : OP_CHECKSIG,
|
||||||
|
'OP_CHECKSIGVERIFY' : OP_CHECKSIGVERIFY,
|
||||||
|
'OP_CHECKMULTISIG' : OP_CHECKMULTISIG,
|
||||||
|
'OP_CHECKMULTISIGVERIFY' : OP_CHECKMULTISIGVERIFY,
|
||||||
|
'OP_NOP1' : OP_NOP1,
|
||||||
|
'OP_CHECKLOCKTIMEVERIFY' : OP_CHECKLOCKTIMEVERIFY,
|
||||||
|
'OP_CHECKSEQUENCEVERIFY' : OP_CHECKSEQUENCEVERIFY,
|
||||||
|
'OP_NOP4' : OP_NOP4,
|
||||||
|
'OP_NOP5' : OP_NOP5,
|
||||||
|
'OP_NOP6' : OP_NOP6,
|
||||||
|
'OP_NOP7' : OP_NOP7,
|
||||||
|
'OP_NOP8' : OP_NOP8,
|
||||||
|
'OP_NOP9' : OP_NOP9,
|
||||||
|
'OP_NOP10' : OP_NOP10,
|
||||||
|
'OP_SMALLINTEGER' : OP_SMALLINTEGER,
|
||||||
|
'OP_PUBKEYS' : OP_PUBKEYS,
|
||||||
|
'OP_PUBKEYHASH' : OP_PUBKEYHASH,
|
||||||
|
'OP_PUBKEY' : OP_PUBKEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CScriptInvalidError(Exception):
|
||||||
|
"""Base class for CScript exceptions"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CScriptTruncatedPushDataError(CScriptInvalidError):
|
||||||
|
"""Invalid pushdata due to truncation"""
|
||||||
|
def __init__(self, msg, data):
|
||||||
|
self.data = data
|
||||||
|
super(CScriptTruncatedPushDataError, self).__init__(msg)
|
||||||
|
|
||||||
|
# This is used, eg, for blockchain heights in coinbase scripts (bip34)
|
||||||
|
class CScriptNum(object):
|
||||||
|
def __init__(self, d=0):
|
||||||
|
self.value = d
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode(obj):
|
||||||
|
r = bytearray(0)
|
||||||
|
if obj.value == 0:
|
||||||
|
return bytes(r)
|
||||||
|
neg = obj.value < 0
|
||||||
|
absvalue = -obj.value if neg else obj.value
|
||||||
|
while (absvalue):
|
||||||
|
r.append(absvalue & 0xff)
|
||||||
|
absvalue >>= 8
|
||||||
|
if r[-1] & 0x80:
|
||||||
|
r.append(0x80 if neg else 0)
|
||||||
|
elif neg:
|
||||||
|
r[-1] |= 0x80
|
||||||
|
return bytes(bchr(len(r)) + r)
|
||||||
|
|
||||||
|
|
||||||
|
class CScript(bytes):
|
||||||
|
"""Serialized script
|
||||||
|
|
||||||
|
A bytes subclass, so you can use this directly whenever bytes are accepted.
|
||||||
|
Note that this means that indexing does *not* work - you'll get an index by
|
||||||
|
byte rather than opcode. This format was chosen for efficiency so that the
|
||||||
|
general case would not require creating a lot of little CScriptOP objects.
|
||||||
|
|
||||||
|
iter(script) however does iterate by opcode.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def __coerce_instance(cls, other):
|
||||||
|
# Coerce other into bytes
|
||||||
|
if isinstance(other, CScriptOp):
|
||||||
|
other = bchr(other)
|
||||||
|
elif isinstance(other, CScriptNum):
|
||||||
|
if (other.value == 0):
|
||||||
|
other = bchr(CScriptOp(OP_0))
|
||||||
|
else:
|
||||||
|
other = CScriptNum.encode(other)
|
||||||
|
elif isinstance(other, int):
|
||||||
|
if 0 <= other <= 16:
|
||||||
|
other = bytes(bchr(CScriptOp.encode_op_n(other)))
|
||||||
|
elif other == -1:
|
||||||
|
other = bytes(bchr(OP_1NEGATE))
|
||||||
|
else:
|
||||||
|
other = CScriptOp.encode_op_pushdata(bn2vch(other))
|
||||||
|
elif isinstance(other, (bytes, bytearray)):
|
||||||
|
other = CScriptOp.encode_op_pushdata(other)
|
||||||
|
return other
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
# Do the coercion outside of the try block so that errors in it are
|
||||||
|
# noticed.
|
||||||
|
other = self.__coerce_instance(other)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# bytes.__add__ always returns bytes instances unfortunately
|
||||||
|
return CScript(super(CScript, self).__add__(other))
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError('Can not add a %r instance to a CScript' % other.__class__)
|
||||||
|
|
||||||
|
def join(self, iterable):
|
||||||
|
# join makes no sense for a CScript()
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __new__(cls, value=b''):
|
||||||
|
if isinstance(value, bytes) or isinstance(value, bytearray):
|
||||||
|
return super(CScript, cls).__new__(cls, value)
|
||||||
|
else:
|
||||||
|
def coerce_iterable(iterable):
|
||||||
|
for instance in iterable:
|
||||||
|
yield cls.__coerce_instance(instance)
|
||||||
|
# Annoyingly on both python2 and python3 bytes.join() always
|
||||||
|
# returns a bytes instance even when subclassed.
|
||||||
|
return super(CScript, cls).__new__(cls, b''.join(coerce_iterable(value)))
|
||||||
|
|
||||||
|
def raw_iter(self):
|
||||||
|
"""Raw iteration
|
||||||
|
|
||||||
|
Yields tuples of (opcode, data, sop_idx) so that the different possible
|
||||||
|
PUSHDATA encodings can be accurately distinguished, as well as
|
||||||
|
determining the exact opcode byte indexes. (sop_idx)
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
while i < len(self):
|
||||||
|
sop_idx = i
|
||||||
|
opcode = bord(self[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if opcode > OP_PUSHDATA4:
|
||||||
|
yield (opcode, None, sop_idx)
|
||||||
|
else:
|
||||||
|
datasize = None
|
||||||
|
pushdata_type = None
|
||||||
|
if opcode < OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA(%d)' % opcode
|
||||||
|
datasize = opcode
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA1'
|
||||||
|
if i >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA1: missing data length')
|
||||||
|
datasize = bord(self[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA2:
|
||||||
|
pushdata_type = 'PUSHDATA2'
|
||||||
|
if i + 1 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA2: missing data length')
|
||||||
|
datasize = bord(self[i]) + (bord(self[i+1]) << 8)
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA4:
|
||||||
|
pushdata_type = 'PUSHDATA4'
|
||||||
|
if i + 3 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA4: missing data length')
|
||||||
|
datasize = bord(self[i]) + (bord(self[i+1]) << 8) + (bord(self[i+2]) << 16) + (bord(self[i+3]) << 24)
|
||||||
|
i += 4
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert False # shouldn't happen
|
||||||
|
|
||||||
|
|
||||||
|
data = bytes(self[i:i+datasize])
|
||||||
|
|
||||||
|
# Check for truncation
|
||||||
|
if len(data) < datasize:
|
||||||
|
raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data)
|
||||||
|
|
||||||
|
i += datasize
|
||||||
|
|
||||||
|
yield (opcode, data, sop_idx)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""'Cooked' iteration
|
||||||
|
|
||||||
|
Returns either a CScriptOP instance, an integer, or bytes, as
|
||||||
|
appropriate.
|
||||||
|
|
||||||
|
See raw_iter() if you need to distinguish the different possible
|
||||||
|
PUSHDATA encodings.
|
||||||
|
"""
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if data is not None:
|
||||||
|
yield data
|
||||||
|
else:
|
||||||
|
opcode = CScriptOp(opcode)
|
||||||
|
|
||||||
|
if opcode.is_small_int():
|
||||||
|
yield opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
yield CScriptOp(opcode)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
# For Python3 compatibility add b before strings so testcases don't
|
||||||
|
# need to change
|
||||||
|
def _repr(o):
|
||||||
|
if isinstance(o, bytes):
|
||||||
|
return b"x('%s')" % hexlify(o).decode('ascii')
|
||||||
|
else:
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
ops = []
|
||||||
|
i = iter(self)
|
||||||
|
while True:
|
||||||
|
op = None
|
||||||
|
try:
|
||||||
|
op = _repr(next(i))
|
||||||
|
except CScriptTruncatedPushDataError as err:
|
||||||
|
op = '%s...<ERROR: %s>' % (_repr(err.data), err)
|
||||||
|
break
|
||||||
|
except CScriptInvalidError as err:
|
||||||
|
op = '<ERROR: %s>' % err
|
||||||
|
break
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
if op is not None:
|
||||||
|
ops.append(op)
|
||||||
|
|
||||||
|
return "CScript([%s])" % ', '.join(ops)
|
||||||
|
|
||||||
|
def GetSigOpCount(self, fAccurate):
|
||||||
|
"""Get the SigOp count.
|
||||||
|
|
||||||
|
fAccurate - Accurately count CHECKMULTISIG, see BIP16 for details.
|
||||||
|
|
||||||
|
Note that this is consensus-critical.
|
||||||
|
"""
|
||||||
|
n = 0
|
||||||
|
lastOpcode = OP_INVALIDOPCODE
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY):
|
||||||
|
n += 1
|
||||||
|
elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
|
||||||
|
if fAccurate and (OP_1 <= lastOpcode <= OP_16):
|
||||||
|
n += opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
n += 20
|
||||||
|
lastOpcode = opcode
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
SIGHASH_ALL = 1
|
||||||
|
SIGHASH_NONE = 2
|
||||||
|
SIGHASH_SINGLE = 3
|
||||||
|
SIGHASH_ANYONECANPAY = 0x80
|
||||||
|
|
||||||
|
def FindAndDelete(script, sig):
|
||||||
|
"""Consensus critical, see FindAndDelete() in Satoshi codebase"""
|
||||||
|
r = b''
|
||||||
|
last_sop_idx = sop_idx = 0
|
||||||
|
skip = True
|
||||||
|
for (opcode, data, sop_idx) in script.raw_iter():
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:sop_idx]
|
||||||
|
last_sop_idx = sop_idx
|
||||||
|
if script[sop_idx:sop_idx + len(sig)] == sig:
|
||||||
|
skip = True
|
||||||
|
else:
|
||||||
|
skip = False
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:]
|
||||||
|
return CScript(r)
|
||||||
|
|
||||||
|
|
||||||
|
def SignatureHash(script, txTo, inIdx, hashtype):
|
||||||
|
"""Consensus-correct SignatureHash
|
||||||
|
|
||||||
|
Returns (hash, err) to precisely match the consensus-critical behavior of
|
||||||
|
the SIGHASH_SINGLE bug. (inIdx is *not* checked for validity)
|
||||||
|
"""
|
||||||
|
HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
|
||||||
|
if inIdx >= len(txTo.vin):
|
||||||
|
return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin)))
|
||||||
|
txtmp = CTransaction(txTo)
|
||||||
|
|
||||||
|
for txin in txtmp.vin:
|
||||||
|
txin.scriptSig = b''
|
||||||
|
txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR]))
|
||||||
|
|
||||||
|
if (hashtype & 0x1f) == SIGHASH_NONE:
|
||||||
|
txtmp.vout = []
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
elif (hashtype & 0x1f) == SIGHASH_SINGLE:
|
||||||
|
outIdx = inIdx
|
||||||
|
if outIdx >= len(txtmp.vout):
|
||||||
|
return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout)))
|
||||||
|
|
||||||
|
tmp = txtmp.vout[outIdx]
|
||||||
|
txtmp.vout = []
|
||||||
|
for i in range(outIdx):
|
||||||
|
txtmp.vout.append(CTxOut(-1))
|
||||||
|
txtmp.vout.append(tmp)
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
if hashtype & SIGHASH_ANYONECANPAY:
|
||||||
|
tmp = txtmp.vin[inIdx]
|
||||||
|
txtmp.vin = []
|
||||||
|
txtmp.vin.append(tmp)
|
||||||
|
|
||||||
|
s = txtmp.serialize()
|
||||||
|
s += struct.pack(b"<I", hashtype)
|
||||||
|
|
||||||
|
hash = hash256(s)
|
||||||
|
|
||||||
|
return (hash, None)
|
||||||
|
|
||||||
|
# TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided.
|
||||||
|
# Performance optimization probably not necessary for python tests, however.
|
||||||
|
# Note that this corresponds to sigversion == 1 in EvalScript, which is used
|
||||||
|
# for version 0 witnesses.
|
||||||
|
def SegwitVersion1SignatureHash(script, txTo, inIdx, hashtype, amount):
|
||||||
|
|
||||||
|
hashPrevouts = 0
|
||||||
|
hashSequence = 0
|
||||||
|
hashOutputs = 0
|
||||||
|
|
||||||
|
if not (hashtype & SIGHASH_ANYONECANPAY):
|
||||||
|
serialize_prevouts = bytes()
|
||||||
|
for i in txTo.vin:
|
||||||
|
serialize_prevouts += i.prevout.serialize()
|
||||||
|
hashPrevouts = uint256_from_str(hash256(serialize_prevouts))
|
||||||
|
|
||||||
|
if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
|
||||||
|
serialize_sequence = bytes()
|
||||||
|
for i in txTo.vin:
|
||||||
|
serialize_sequence += struct.pack("<I", i.nSequence)
|
||||||
|
hashSequence = uint256_from_str(hash256(serialize_sequence))
|
||||||
|
|
||||||
|
if ((hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
|
||||||
|
serialize_outputs = bytes()
|
||||||
|
for o in txTo.vout:
|
||||||
|
serialize_outputs += o.serialize()
|
||||||
|
hashOutputs = uint256_from_str(hash256(serialize_outputs))
|
||||||
|
elif ((hashtype & 0x1f) == SIGHASH_SINGLE and inIdx < len(txTo.vout)):
|
||||||
|
serialize_outputs = txTo.vout[inIdx].serialize()
|
||||||
|
hashOutputs = uint256_from_str(hash256(serialize_outputs))
|
||||||
|
|
||||||
|
ss = bytes()
|
||||||
|
ss += struct.pack("<i", txTo.nVersion)
|
||||||
|
ss += ser_uint256(hashPrevouts)
|
||||||
|
ss += ser_uint256(hashSequence)
|
||||||
|
ss += txTo.vin[inIdx].prevout.serialize()
|
||||||
|
ss += ser_string(script)
|
||||||
|
ss += struct.pack("<q", amount)
|
||||||
|
ss += struct.pack("<I", txTo.vin[inIdx].nSequence)
|
||||||
|
ss += ser_uint256(hashOutputs)
|
||||||
|
ss += struct.pack("<i", txTo.nLockTime)
|
||||||
|
ss += struct.pack("<I", hashtype)
|
||||||
|
|
||||||
|
return hash256(ss)
|
||||||
64
basicswap/interface/contrib/firo_test_framework/siphash.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#
|
||||||
|
# siphash.py - Specialized SipHash-2-4 implementations
|
||||||
|
#
|
||||||
|
# This implements SipHash-2-4 for 256-bit integers.
|
||||||
|
|
||||||
|
def rotl64(n, b):
|
||||||
|
return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b
|
||||||
|
|
||||||
|
def siphash_round(v0, v1, v2, v3):
|
||||||
|
v0 = (v0 + v1) & ((1 << 64) - 1)
|
||||||
|
v1 = rotl64(v1, 13)
|
||||||
|
v1 ^= v0
|
||||||
|
v0 = rotl64(v0, 32)
|
||||||
|
v2 = (v2 + v3) & ((1 << 64) - 1)
|
||||||
|
v3 = rotl64(v3, 16)
|
||||||
|
v3 ^= v2
|
||||||
|
v0 = (v0 + v3) & ((1 << 64) - 1)
|
||||||
|
v3 = rotl64(v3, 21)
|
||||||
|
v3 ^= v0
|
||||||
|
v2 = (v2 + v1) & ((1 << 64) - 1)
|
||||||
|
v1 = rotl64(v1, 17)
|
||||||
|
v1 ^= v2
|
||||||
|
v2 = rotl64(v2, 32)
|
||||||
|
return (v0, v1, v2, v3)
|
||||||
|
|
||||||
|
def siphash256(k0, k1, h):
|
||||||
|
n0 = h & ((1 << 64) - 1)
|
||||||
|
n1 = (h >> 64) & ((1 << 64) - 1)
|
||||||
|
n2 = (h >> 128) & ((1 << 64) - 1)
|
||||||
|
n3 = (h >> 192) & ((1 << 64) - 1)
|
||||||
|
v0 = 0x736f6d6570736575 ^ k0
|
||||||
|
v1 = 0x646f72616e646f6d ^ k1
|
||||||
|
v2 = 0x6c7967656e657261 ^ k0
|
||||||
|
v3 = 0x7465646279746573 ^ k1 ^ n0
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n0
|
||||||
|
v3 ^= n1
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n1
|
||||||
|
v3 ^= n2
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n2
|
||||||
|
v3 ^= n3
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n3
|
||||||
|
v3 ^= 0x2000000000000000
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= 0x2000000000000000
|
||||||
|
v2 ^= 0xFF
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
return v0 ^ v1 ^ v2 ^ v3
|
||||||
841
basicswap/interface/contrib/firo_test_framework/util.py
Normal file
@@ -0,0 +1,841 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2014-2016 The Bitcoin Core developers
|
||||||
|
# Copyright (c) 2014-2017 The Dash Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Helpful routines for regression testing
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
from base64 import b64encode
|
||||||
|
from decimal import Decimal, ROUND_DOWN
|
||||||
|
import json
|
||||||
|
import http.client
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from . import coverage
|
||||||
|
from .authproxy import AuthServiceProxy, JSONRPCException
|
||||||
|
|
||||||
|
COVERAGE_DIR = None
|
||||||
|
|
||||||
|
logger = logging.getLogger("TestFramework.utils")
|
||||||
|
# The maximum number of nodes a single test can spawn
|
||||||
|
MAX_NODES = 15
|
||||||
|
# Don't assign rpc or p2p ports lower than this
|
||||||
|
PORT_MIN = 11000
|
||||||
|
# The number of ports to "reserve" for p2p and rpc, each
|
||||||
|
PORT_RANGE = 5000
|
||||||
|
|
||||||
|
BITCOIND_PROC_WAIT_TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
|
class PortSeed:
|
||||||
|
# Must be initialized with a unique integer for each process
|
||||||
|
n = None
|
||||||
|
|
||||||
|
#Set Mocktime default to OFF.
|
||||||
|
#MOCKTIME is only needed for scripts that use the
|
||||||
|
#cached version of the blockchain. If the cached
|
||||||
|
#version of the blockchain is used without MOCKTIME
|
||||||
|
#then the mempools will not sync due to IBD.
|
||||||
|
MOCKTIME = 0
|
||||||
|
|
||||||
|
def enable_mocktime():
|
||||||
|
#For backwared compatibility of the python scripts
|
||||||
|
#with previous versions of the cache, set MOCKTIME
|
||||||
|
#to Jan 1, 2014 + (201 * 10 * 60)
|
||||||
|
global MOCKTIME
|
||||||
|
MOCKTIME = 1414776313 + (201 * 10 * 60)
|
||||||
|
|
||||||
|
def set_mocktime(t):
|
||||||
|
global MOCKTIME
|
||||||
|
MOCKTIME = t
|
||||||
|
|
||||||
|
def disable_mocktime():
|
||||||
|
global MOCKTIME
|
||||||
|
MOCKTIME = 0
|
||||||
|
|
||||||
|
def get_mocktime():
|
||||||
|
return MOCKTIME
|
||||||
|
|
||||||
|
def enable_coverage(dirname):
|
||||||
|
"""Maintain a log of which RPC calls are made during testing."""
|
||||||
|
global COVERAGE_DIR
|
||||||
|
COVERAGE_DIR = dirname
|
||||||
|
|
||||||
|
|
||||||
|
def get_rpc_proxy(url, node_number, timeout=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
url (str): URL of the RPC server to call
|
||||||
|
node_number (int): the node number (or id) that this calls to
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
timeout (int): HTTP timeout in seconds
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AuthServiceProxy. convenience object for making RPC calls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
proxy_kwargs = {}
|
||||||
|
if timeout is not None:
|
||||||
|
proxy_kwargs['timeout'] = timeout
|
||||||
|
|
||||||
|
proxy = AuthServiceProxy(url, **proxy_kwargs)
|
||||||
|
proxy.url = url # store URL on proxy for info
|
||||||
|
|
||||||
|
coverage_logfile = coverage.get_filename(
|
||||||
|
COVERAGE_DIR, node_number) if COVERAGE_DIR else None
|
||||||
|
|
||||||
|
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
|
||||||
|
|
||||||
|
def get_evoznsync_status(node):
|
||||||
|
result = node.evoznsync("status")
|
||||||
|
return result['IsSynced']
|
||||||
|
|
||||||
|
def wait_to_sync(node, fast_znsync=False):
|
||||||
|
tm = 0
|
||||||
|
synced = False
|
||||||
|
while tm < 30:
|
||||||
|
synced = get_evoznsync_status(node)
|
||||||
|
if synced:
|
||||||
|
return
|
||||||
|
time.sleep(0.2)
|
||||||
|
if fast_znsync:
|
||||||
|
# skip mnsync states
|
||||||
|
node.evoznsync("next")
|
||||||
|
tm += 0.2
|
||||||
|
assert(synced)
|
||||||
|
|
||||||
|
def p2p_port(n):
|
||||||
|
assert(n <= MAX_NODES)
|
||||||
|
return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
|
def rpc_port(n):
|
||||||
|
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
|
def check_json_precision():
|
||||||
|
"""Make sure json library being used does not lose precision converting BTC values"""
|
||||||
|
n = Decimal("20000000.00000003")
|
||||||
|
satoshis = int(json.loads(json.dumps(float(n)))*1.0e8)
|
||||||
|
if satoshis != 2000000000000003:
|
||||||
|
raise RuntimeError("JSON encode/decode loses precision")
|
||||||
|
|
||||||
|
def count_bytes(hex_string):
|
||||||
|
return len(bytearray.fromhex(hex_string))
|
||||||
|
|
||||||
|
def bytes_to_hex_str(byte_str):
|
||||||
|
return hexlify(byte_str).decode('ascii')
|
||||||
|
|
||||||
|
def hex_str_to_bytes(hex_str):
|
||||||
|
return unhexlify(hex_str.encode('ascii'))
|
||||||
|
|
||||||
|
def str_to_b64str(string):
|
||||||
|
return b64encode(string.encode('utf-8')).decode('ascii')
|
||||||
|
|
||||||
|
def sync_blocks(rpc_connections, *, wait=1, timeout=60):
|
||||||
|
"""
|
||||||
|
Wait until everybody has the same tip.
|
||||||
|
|
||||||
|
sync_blocks needs to be called with an rpc_connections set that has least
|
||||||
|
one node already synced to the latest, stable tip, otherwise there's a
|
||||||
|
chance it might return before all nodes are stably synced.
|
||||||
|
"""
|
||||||
|
# Use getblockcount() instead of waitforblockheight() to determine the
|
||||||
|
# initial max height because the two RPCs look at different internal global
|
||||||
|
# variables (chainActive vs latestBlock) and the former gets updated
|
||||||
|
# earlier.
|
||||||
|
maxheight = max(x.getblockcount() for x in rpc_connections)
|
||||||
|
start_time = cur_time = time.time()
|
||||||
|
while cur_time <= start_time + timeout:
|
||||||
|
tips = [r.waitforblockheight(maxheight, int(wait * 1000)) for r in rpc_connections]
|
||||||
|
if all(t["height"] == maxheight for t in tips):
|
||||||
|
if all(t["hash"] == tips[0]["hash"] for t in tips):
|
||||||
|
return
|
||||||
|
raise AssertionError("Block sync failed, mismatched block hashes:{}".format(
|
||||||
|
"".join("\n {!r}".format(tip) for tip in tips)))
|
||||||
|
|
||||||
|
time.sleep(wait)
|
||||||
|
cur_time = time.time()
|
||||||
|
raise AssertionError("Block sync to height {} timed out:{}".format(
|
||||||
|
maxheight, "".join("\n {!r}".format(tip) for tip in tips)))
|
||||||
|
|
||||||
|
def sync_znodes(rpc_connections, *, timeout=60):
|
||||||
|
"""
|
||||||
|
Waits until every node has their znsync status is synced.
|
||||||
|
"""
|
||||||
|
start_time = cur_time = time.time()
|
||||||
|
while cur_time <= start_time + timeout:
|
||||||
|
statuses = [r.znsync("status") for r in rpc_connections]
|
||||||
|
if all(stat["IsSynced"] == True for stat in statuses):
|
||||||
|
return
|
||||||
|
cur_time = time.time()
|
||||||
|
raise AssertionError("Znode sync failed.")
|
||||||
|
|
||||||
|
def sync_chain(rpc_connections, *, wait=1, timeout=60):
|
||||||
|
"""
|
||||||
|
Wait until everybody has the same best block
|
||||||
|
"""
|
||||||
|
while timeout > 0:
|
||||||
|
best_hash = [x.getbestblockhash() for x in rpc_connections]
|
||||||
|
if best_hash == [best_hash[0]]*len(best_hash):
|
||||||
|
return
|
||||||
|
time.sleep(wait)
|
||||||
|
timeout -= wait
|
||||||
|
raise AssertionError("Chain sync failed: Best block hashes don't match")
|
||||||
|
|
||||||
|
def sync_mempools(rpc_connections, *, wait=1, timeout=60):
|
||||||
|
"""
|
||||||
|
Wait until everybody has the same transactions in their memory
|
||||||
|
pools
|
||||||
|
"""
|
||||||
|
while timeout > 0:
|
||||||
|
pool = set(rpc_connections[0].getrawmempool())
|
||||||
|
num_match = 1
|
||||||
|
for i in range(1, len(rpc_connections)):
|
||||||
|
if set(rpc_connections[i].getrawmempool()) == pool:
|
||||||
|
num_match = num_match+1
|
||||||
|
if num_match == len(rpc_connections):
|
||||||
|
return
|
||||||
|
time.sleep(wait)
|
||||||
|
timeout -= wait
|
||||||
|
raise AssertionError("Mempool sync failed")
|
||||||
|
|
||||||
|
def sync_znodes(rpc_connections, fast_mnsync=False):
|
||||||
|
for node in rpc_connections:
|
||||||
|
wait_to_sync(node, fast_mnsync)
|
||||||
|
|
||||||
|
bitcoind_processes = {}
|
||||||
|
|
||||||
|
def initialize_datadir(dirname, n):
|
||||||
|
datadir = os.path.join(dirname, "node"+str(n))
|
||||||
|
if not os.path.isdir(datadir):
|
||||||
|
os.makedirs(datadir)
|
||||||
|
rpc_u, rpc_p = rpc_auth_pair(n)
|
||||||
|
with open(os.path.join(datadir, "firo.conf"), 'w', encoding='utf8') as f:
|
||||||
|
f.write("regtest=1\n")
|
||||||
|
f.write("rpcuser=" + rpc_u + "\n")
|
||||||
|
f.write("rpcpassword=" + rpc_p + "\n")
|
||||||
|
f.write("port="+str(p2p_port(n))+"\n")
|
||||||
|
f.write("rpcport="+str(rpc_port(n))+"\n")
|
||||||
|
f.write("listenonion=0\n")
|
||||||
|
return datadir
|
||||||
|
|
||||||
|
def rpc_auth_pair(n):
|
||||||
|
return 'rpcuser💻' + str(n), 'rpcpass🔑' + str(n)
|
||||||
|
|
||||||
|
def rpc_url(i, rpchost=None):
|
||||||
|
rpc_u, rpc_p = rpc_auth_pair(i)
|
||||||
|
host = '127.0.0.1'
|
||||||
|
port = rpc_port(i)
|
||||||
|
if rpchost:
|
||||||
|
parts = rpchost.split(':')
|
||||||
|
if len(parts) == 2:
|
||||||
|
host, port = parts
|
||||||
|
else:
|
||||||
|
host = rpchost
|
||||||
|
return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port))
|
||||||
|
|
||||||
|
def wait_for_bitcoind_start(process, url, i):
|
||||||
|
'''
|
||||||
|
Wait for firod to start. This means that RPC is accessible and fully initialized.
|
||||||
|
Raise an exception if firod exits during initialization.
|
||||||
|
'''
|
||||||
|
while True:
|
||||||
|
if process.poll() is not None:
|
||||||
|
raise Exception('firod exited with status %i during initialization' % process.returncode)
|
||||||
|
try:
|
||||||
|
rpc = get_rpc_proxy(url, i)
|
||||||
|
blocks = rpc.getblockcount()
|
||||||
|
break # break out of loop on success
|
||||||
|
except IOError as e:
|
||||||
|
if e.errno != errno.ECONNREFUSED: # Port not yet open?
|
||||||
|
raise # unknown IO error
|
||||||
|
except JSONRPCException as e: # Initialization phase
|
||||||
|
if e.error['code'] != -28: # RPC in warmup?
|
||||||
|
raise # unknown JSON RPC exception
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
def initialize_chain(test_dir, num_nodes, cachedir):
|
||||||
|
"""
|
||||||
|
Create a cache of a 200-block-long chain (with wallet) for MAX_NODES
|
||||||
|
Afterward, create num_nodes copies from the cache
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert num_nodes <= MAX_NODES
|
||||||
|
create_cache = False
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
if not os.path.isdir(os.path.join(cachedir, 'node'+str(i))):
|
||||||
|
create_cache = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if create_cache:
|
||||||
|
|
||||||
|
#find and delete old cache directories if any exist
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
if os.path.isdir(os.path.join(cachedir,"node"+str(i))):
|
||||||
|
shutil.rmtree(os.path.join(cachedir,"node"+str(i)))
|
||||||
|
|
||||||
|
# Create cache directories, run bitcoinds:
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
datadir=initialize_datadir(cachedir, i)
|
||||||
|
args = [ os.getenv("FIROD", "firod"), "-server", "-keypool=1", "-datadir="+datadir, "-discover=0" ]
|
||||||
|
if i > 0:
|
||||||
|
args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
|
||||||
|
bitcoind_processes[i] = subprocess.Popen(args)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print("initialize_chain: bitcoind started, waiting for RPC to come up")
|
||||||
|
wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print("initialize_chain: RPC successfully started")
|
||||||
|
|
||||||
|
rpcs = []
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
try:
|
||||||
|
rpcs.append(get_rpc_proxy(rpc_url(i), i))
|
||||||
|
except:
|
||||||
|
sys.stderr.write("Error connecting to "+url+"\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create a 200-block-long chain; each of the 4 first nodes
|
||||||
|
# gets 25 mature blocks and 25 immature.
|
||||||
|
# Note: To preserve compatibility with older versions of
|
||||||
|
# initialize_chain, only 4 nodes will generate coins.
|
||||||
|
#
|
||||||
|
# blocks are created with timestamps 10 minutes apart
|
||||||
|
# starting from 2010 minutes in the past
|
||||||
|
enable_mocktime()
|
||||||
|
block_time = get_mocktime() - (201 * 10 * 60)
|
||||||
|
for i in range(2):
|
||||||
|
for peer in range(4):
|
||||||
|
for j in range(25):
|
||||||
|
set_node_times(rpcs, block_time)
|
||||||
|
rpcs[peer].generate(1)
|
||||||
|
block_time += 10*60
|
||||||
|
# Must sync before next peer starts generating blocks
|
||||||
|
sync_blocks(rpcs)
|
||||||
|
|
||||||
|
# Shut them down, and clean up cache directories:
|
||||||
|
stop_nodes(rpcs)
|
||||||
|
disable_mocktime()
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
try:
|
||||||
|
os.remove(log_filename(cachedir, i, "debug.log"))
|
||||||
|
os.remove(log_filename(cachedir, i, "db.log"))
|
||||||
|
os.remove(log_filename(cachedir, i, "peers.dat"))
|
||||||
|
os.remove(log_filename(cachedir, i, "fee_estimates.dat"))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for i in range(num_nodes):
|
||||||
|
from_dir = os.path.join(cachedir, "node"+str(i))
|
||||||
|
to_dir = os.path.join(test_dir, "node"+str(i))
|
||||||
|
if from_dir != to_dir:
|
||||||
|
shutil.copytree(from_dir, to_dir)
|
||||||
|
initialize_datadir(test_dir, i) # Overwrite port/rpcport in bitcoin.conf
|
||||||
|
|
||||||
|
def initialize_chain_clean(test_dir, num_nodes):
|
||||||
|
"""
|
||||||
|
Create an empty blockchain and num_nodes wallets.
|
||||||
|
Useful if a test case wants complete control over initialization.
|
||||||
|
"""
|
||||||
|
for i in range(num_nodes):
|
||||||
|
datadir=initialize_datadir(test_dir, i)
|
||||||
|
|
||||||
|
|
||||||
|
def _rpchost_to_args(rpchost):
|
||||||
|
'''Convert optional IP:port spec to rpcconnect/rpcport args'''
|
||||||
|
if rpchost is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
match = re.match('(\[[0-9a-fA-f:]+\]|[^:]+)(?::([0-9]+))?$', rpchost)
|
||||||
|
if not match:
|
||||||
|
raise ValueError('Invalid RPC host spec ' + rpchost)
|
||||||
|
|
||||||
|
rpcconnect = match.group(1)
|
||||||
|
rpcport = match.group(2)
|
||||||
|
|
||||||
|
if rpcconnect.startswith('['): # remove IPv6 [...] wrapping
|
||||||
|
rpcconnect = rpcconnect[1:-1]
|
||||||
|
|
||||||
|
rv = ['-rpcconnect=' + rpcconnect]
|
||||||
|
if rpcport:
|
||||||
|
rv += ['-rpcport=' + rpcport]
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, redirect_stderr=False, stderr=None):
|
||||||
|
"""
|
||||||
|
Start a bitcoind and return RPC connection to it
|
||||||
|
"""
|
||||||
|
datadir = os.path.join(dirname, "node"+str(i))
|
||||||
|
if binary is None:
|
||||||
|
binary = os.getenv("FIROD", "firod")
|
||||||
|
args = [ binary, "-datadir="+datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-dandelion=0", "-usemnemonic=0", "-mocktime="+str(get_mocktime()) ]
|
||||||
|
#Useful args for debugging
|
||||||
|
# "screen", "--",
|
||||||
|
# "gdb", "-x", "/tmp/gdb_run", "--args",
|
||||||
|
|
||||||
|
# Don't try auto backups (they fail a lot when running tests)
|
||||||
|
args += [ "-createwalletbackups=0" ]
|
||||||
|
if extra_args is not None: args.extend(extra_args)
|
||||||
|
# Allow to redirect stderr to stdout in case we expect some non-critical warnings/errors printed to stderr
|
||||||
|
# Otherwise the whole test would be considered to be failed in such cases
|
||||||
|
if redirect_stderr:
|
||||||
|
stderr = sys.stdout
|
||||||
|
bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr)
|
||||||
|
logger.debug("start_node: firod started, waiting for RPC to come up")
|
||||||
|
url = rpc_url(i, rpchost)
|
||||||
|
wait_for_bitcoind_start(bitcoind_processes[i], url, i)
|
||||||
|
logger.debug("start_node: RPC successfully started")
|
||||||
|
proxy = get_rpc_proxy(url, i, timeout=timewait)
|
||||||
|
|
||||||
|
if COVERAGE_DIR:
|
||||||
|
coverage.write_all_rpc_commands(COVERAGE_DIR, proxy)
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None):
|
||||||
|
"""
|
||||||
|
Start multiple bitcoinds, return RPC connections to them
|
||||||
|
"""
|
||||||
|
if extra_args is None: extra_args = [ None for _ in range(num_nodes) ]
|
||||||
|
if binary is None: binary = [ None for _ in range(num_nodes) ]
|
||||||
|
rpcs = []
|
||||||
|
try:
|
||||||
|
for i in range(num_nodes):
|
||||||
|
rpcs.append(start_node(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i]))
|
||||||
|
except: # If one node failed to start, stop the others
|
||||||
|
stop_nodes(rpcs)
|
||||||
|
raise
|
||||||
|
return rpcs
|
||||||
|
|
||||||
|
def copy_datadir(from_node, to_node, dirname):
|
||||||
|
from_datadir = os.path.join(dirname, "node"+str(from_node), "regtest")
|
||||||
|
to_datadir = os.path.join(dirname, "node"+str(to_node), "regtest")
|
||||||
|
|
||||||
|
dirs = ["blocks", "chainstate", "evodb", "llmq"]
|
||||||
|
for d in dirs:
|
||||||
|
try:
|
||||||
|
src = os.path.join(from_datadir, d)
|
||||||
|
dst = os.path.join(to_datadir, d)
|
||||||
|
shutil.copytree(src, dst)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
def log_filename(dirname, n_node, logname):
|
||||||
|
return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
|
||||||
|
|
||||||
|
def wait_node(i):
|
||||||
|
return_code = bitcoind_processes[i].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
|
||||||
|
assert_equal(return_code, 0)
|
||||||
|
del bitcoind_processes[i]
|
||||||
|
|
||||||
|
def stop_node(node, i, wait=True):
|
||||||
|
logger.debug("Stopping node %d" % i)
|
||||||
|
try:
|
||||||
|
node.stop()
|
||||||
|
except http.client.CannotSendRequest as e:
|
||||||
|
logger.exception("Unable to stop node")
|
||||||
|
if wait:
|
||||||
|
wait_node(i)
|
||||||
|
|
||||||
|
def stop_nodes(nodes, fast=True):
|
||||||
|
for i, node in enumerate(nodes):
|
||||||
|
stop_node(node, i, not fast)
|
||||||
|
if fast:
|
||||||
|
for i, node in enumerate(nodes):
|
||||||
|
wait_node(i)
|
||||||
|
assert not bitcoind_processes.values() # All connections must be gone now
|
||||||
|
|
||||||
|
def set_node_times(nodes, t):
|
||||||
|
for node in nodes:
|
||||||
|
node.setmocktime(t)
|
||||||
|
|
||||||
|
def connect_nodes(from_connection, node_num):
|
||||||
|
# NOTE: In next line p2p_port(0) was replaced by rpc_port(0).
|
||||||
|
ip_port = "127.0.0.1:"+str(p2p_port(node_num))
|
||||||
|
from_connection.addnode(ip_port, "onetry")
|
||||||
|
# poll until version handshake complete to avoid race conditions
|
||||||
|
# with transaction relaying
|
||||||
|
while any(peer['version'] == 0 for peer in from_connection.getpeerinfo()):
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def connect_nodes_bi(nodes, a, b):
|
||||||
|
connect_nodes(nodes[a], b)
|
||||||
|
connect_nodes(nodes[b], a)
|
||||||
|
|
||||||
|
def isolate_node(node, timeout=5):
|
||||||
|
node.setnetworkactive(False)
|
||||||
|
st = time.time()
|
||||||
|
while time.time() < st + timeout:
|
||||||
|
if node.getconnectioncount() == 0:
|
||||||
|
return
|
||||||
|
time.sleep(0.5)
|
||||||
|
raise AssertionError("disconnect_node timed out")
|
||||||
|
|
||||||
|
def reconnect_isolated_node(node, node_num):
|
||||||
|
node.setnetworkactive(True)
|
||||||
|
connect_nodes(node, node_num)
|
||||||
|
def find_output(node, txid, amount):
|
||||||
|
"""
|
||||||
|
Return index to output of txid with value amount
|
||||||
|
Raises exception if there is none.
|
||||||
|
"""
|
||||||
|
txdata = node.getrawtransaction(txid, 1)
|
||||||
|
for i in range(len(txdata["vout"])):
|
||||||
|
if txdata["vout"][i]["value"] == amount:
|
||||||
|
return i
|
||||||
|
raise RuntimeError("find_output txid %s : %s not found"%(txid,str(amount)))
|
||||||
|
|
||||||
|
|
||||||
|
def gather_inputs(from_node, amount_needed, confirmations_required=1):
|
||||||
|
"""
|
||||||
|
Return a random set of unspent txouts that are enough to pay amount_needed
|
||||||
|
"""
|
||||||
|
assert(confirmations_required >=0)
|
||||||
|
utxo = from_node.listunspent(confirmations_required)
|
||||||
|
random.shuffle(utxo)
|
||||||
|
inputs = []
|
||||||
|
total_in = Decimal("0.00000000")
|
||||||
|
while total_in < amount_needed and len(utxo) > 0:
|
||||||
|
t = utxo.pop()
|
||||||
|
total_in += t["amount"]
|
||||||
|
inputs.append({ "txid" : t["txid"], "vout" : t["vout"], "address" : t["address"] } )
|
||||||
|
if total_in < amount_needed:
|
||||||
|
raise RuntimeError("Insufficient funds: need %d, have %d"%(amount_needed, total_in))
|
||||||
|
return (total_in, inputs)
|
||||||
|
|
||||||
|
def make_change(from_node, amount_in, amount_out, fee):
|
||||||
|
"""
|
||||||
|
Create change output(s), return them
|
||||||
|
"""
|
||||||
|
outputs = {}
|
||||||
|
amount = amount_out+fee
|
||||||
|
change = amount_in - amount
|
||||||
|
if change > amount*2:
|
||||||
|
# Create an extra change output to break up big inputs
|
||||||
|
change_address = from_node.getnewaddress()
|
||||||
|
# Split change in two, being careful of rounding:
|
||||||
|
outputs[change_address] = Decimal(change/2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||||
|
change = amount_in - amount - outputs[change_address]
|
||||||
|
if change > 0:
|
||||||
|
outputs[from_node.getnewaddress()] = change
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
def send_zeropri_transaction(from_node, to_node, amount, fee):
|
||||||
|
"""
|
||||||
|
Create&broadcast a zero-priority transaction.
|
||||||
|
Returns (txid, hex-encoded-txdata)
|
||||||
|
Ensures transaction is zero-priority by first creating a send-to-self,
|
||||||
|
then using its output
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a send-to-self with confirmed inputs:
|
||||||
|
self_address = from_node.getnewaddress()
|
||||||
|
(total_in, inputs) = gather_inputs(from_node, amount+fee*2)
|
||||||
|
outputs = make_change(from_node, total_in, amount+fee, fee)
|
||||||
|
outputs[self_address] = float(amount+fee)
|
||||||
|
|
||||||
|
self_rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
self_signresult = from_node.signrawtransaction(self_rawtx)
|
||||||
|
self_txid = from_node.sendrawtransaction(self_signresult["hex"], True)
|
||||||
|
|
||||||
|
vout = find_output(from_node, self_txid, amount+fee)
|
||||||
|
# Now immediately spend the output to create a 1-input, 1-output
|
||||||
|
# zero-priority transaction:
|
||||||
|
inputs = [ { "txid" : self_txid, "vout" : vout } ]
|
||||||
|
outputs = { to_node.getnewaddress() : float(amount) }
|
||||||
|
|
||||||
|
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = from_node.signrawtransaction(rawtx)
|
||||||
|
txid = from_node.sendrawtransaction(signresult["hex"], True)
|
||||||
|
|
||||||
|
return (txid, signresult["hex"])
|
||||||
|
|
||||||
|
def random_zeropri_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||||
|
"""
|
||||||
|
Create a random zero-priority transaction.
|
||||||
|
Returns (txid, hex-encoded-transaction-data, fee)
|
||||||
|
"""
|
||||||
|
from_node = random.choice(nodes)
|
||||||
|
to_node = random.choice(nodes)
|
||||||
|
fee = min_fee + fee_increment*random.randint(0,fee_variants)
|
||||||
|
(txid, txhex) = send_zeropri_transaction(from_node, to_node, amount, fee)
|
||||||
|
return (txid, txhex, fee)
|
||||||
|
|
||||||
|
def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||||
|
"""
|
||||||
|
Create a random transaction.
|
||||||
|
Returns (txid, hex-encoded-transaction-data, fee)
|
||||||
|
"""
|
||||||
|
from_node = random.choice(nodes)
|
||||||
|
to_node = random.choice(nodes)
|
||||||
|
fee = min_fee + fee_increment*random.randint(0,fee_variants)
|
||||||
|
|
||||||
|
(total_in, inputs) = gather_inputs(from_node, amount+fee)
|
||||||
|
outputs = make_change(from_node, total_in, amount, fee)
|
||||||
|
outputs[to_node.getnewaddress()] = float(amount)
|
||||||
|
|
||||||
|
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = from_node.signrawtransaction(rawtx)
|
||||||
|
txid = from_node.sendrawtransaction(signresult["hex"], True)
|
||||||
|
|
||||||
|
return (txid, signresult["hex"], fee)
|
||||||
|
|
||||||
|
def assert_fee_amount(fee, tx_size, fee_per_kB):
|
||||||
|
"""Assert the fee was in range"""
|
||||||
|
target_fee = tx_size * fee_per_kB / 1000
|
||||||
|
if fee < target_fee:
|
||||||
|
raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)"%(str(fee), str(target_fee)))
|
||||||
|
# allow the wallet's estimation to be at most 2 bytes off
|
||||||
|
if fee > (tx_size + 2) * fee_per_kB / 1000:
|
||||||
|
raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)"%(str(fee), str(target_fee)))
|
||||||
|
|
||||||
|
def assert_equal(thing1, thing2, *args):
|
||||||
|
if thing1 != thing2 or any(thing1 != arg for arg in args):
|
||||||
|
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
|
||||||
|
|
||||||
|
def assert_greater_than(thing1, thing2):
|
||||||
|
if thing1 <= thing2:
|
||||||
|
raise AssertionError("%s <= %s"%(str(thing1),str(thing2)))
|
||||||
|
|
||||||
|
def assert_greater_than_or_equal(thing1, thing2):
|
||||||
|
if thing1 < thing2:
|
||||||
|
raise AssertionError("%s < %s"%(str(thing1),str(thing2)))
|
||||||
|
|
||||||
|
def assert_raises(exc, fun, *args, **kwds):
|
||||||
|
assert_raises_message(exc, None, fun, *args, **kwds)
|
||||||
|
|
||||||
|
def assert_raises_message(exc, message, fun, *args, **kwds):
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except exc as e:
|
||||||
|
if message is not None and message not in e.error['message']:
|
||||||
|
raise AssertionError("Expected substring not found:"+e.error['message'])
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Unexpected exception raised: "+type(e).__name__)
|
||||||
|
else:
|
||||||
|
raise AssertionError("No exception raised")
|
||||||
|
|
||||||
|
def assert_raises_jsonrpc(code, message, fun, *args, **kwds):
|
||||||
|
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
|
||||||
|
|
||||||
|
Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
|
||||||
|
and verifies that the error code and message are as expected. Throws AssertionError if
|
||||||
|
no JSONRPCException was returned or if the error code/message are not as expected.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
code (int), optional: the error code returned by the RPC call (defined
|
||||||
|
in src/rpc/protocol.h). Set to None if checking the error code is not required.
|
||||||
|
message (string), optional: [a substring of] the error string returned by the
|
||||||
|
RPC call. Set to None if checking the error string is not required
|
||||||
|
fun (function): the function to call. This should be the name of an RPC.
|
||||||
|
args*: positional arguments for the function.
|
||||||
|
kwds**: named arguments for the function.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except JSONRPCException as e:
|
||||||
|
# JSONRPCException was thrown as expected. Check the code and message values are correct.
|
||||||
|
if (code is not None) and (code != e.error["code"]):
|
||||||
|
raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
|
||||||
|
if (message is not None) and (message not in e.error['message']):
|
||||||
|
raise AssertionError("Expected substring not found:"+e.error['message'])
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Unexpected exception raised: "+type(e).__name__)
|
||||||
|
else:
|
||||||
|
raise AssertionError("No exception raised")
|
||||||
|
|
||||||
|
def assert_is_hex_string(string):
|
||||||
|
try:
|
||||||
|
int(string, 16)
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError(
|
||||||
|
"Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
|
||||||
|
|
||||||
|
def assert_is_hash_string(string, length=64):
|
||||||
|
if not isinstance(string, str):
|
||||||
|
raise AssertionError("Expected a string, got type %r" % type(string))
|
||||||
|
elif length and len(string) != length:
|
||||||
|
raise AssertionError(
|
||||||
|
"String of length %d expected; got %d" % (length, len(string)))
|
||||||
|
elif not re.match('[abcdef0-9]+$', string):
|
||||||
|
raise AssertionError(
|
||||||
|
"String %r contains invalid characters for a hash." % string)
|
||||||
|
|
||||||
|
def assert_array_result(object_array, to_match, expected, should_not_find = False):
|
||||||
|
"""
|
||||||
|
Pass in array of JSON objects, a dictionary with key/value pairs
|
||||||
|
to match against, and another dictionary with expected key/value
|
||||||
|
pairs.
|
||||||
|
If the should_not_find flag is true, to_match should not be found
|
||||||
|
in object_array
|
||||||
|
"""
|
||||||
|
if should_not_find == True:
|
||||||
|
assert_equal(expected, { })
|
||||||
|
num_matched = 0
|
||||||
|
for item in object_array:
|
||||||
|
all_match = True
|
||||||
|
for key,value in to_match.items():
|
||||||
|
if item[key] != value:
|
||||||
|
all_match = False
|
||||||
|
if not all_match:
|
||||||
|
continue
|
||||||
|
elif should_not_find == True:
|
||||||
|
num_matched = num_matched+1
|
||||||
|
for key,value in expected.items():
|
||||||
|
if item[key] != value:
|
||||||
|
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
|
||||||
|
num_matched = num_matched+1
|
||||||
|
if num_matched == 0 and should_not_find != True:
|
||||||
|
raise AssertionError("No objects matched %s"%(str(to_match)))
|
||||||
|
if num_matched > 0 and should_not_find == True:
|
||||||
|
raise AssertionError("Objects were found %s"%(str(to_match)))
|
||||||
|
|
||||||
|
def satoshi_round(amount):
|
||||||
|
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||||
|
|
||||||
|
# Helper to create at least "count" utxos
|
||||||
|
# Pass in a fee that is sufficient for relay and mining new transactions.
|
||||||
|
def create_confirmed_utxos(fee, node, count):
|
||||||
|
node.generate(int(0.5*count)+101)
|
||||||
|
utxos = node.listunspent()
|
||||||
|
iterations = count - len(utxos)
|
||||||
|
addr1 = node.getnewaddress()
|
||||||
|
addr2 = node.getnewaddress()
|
||||||
|
if iterations <= 0:
|
||||||
|
return utxos
|
||||||
|
for i in range(iterations):
|
||||||
|
t = utxos.pop()
|
||||||
|
inputs = []
|
||||||
|
inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
|
||||||
|
outputs = {}
|
||||||
|
send_value = t['amount'] - fee
|
||||||
|
outputs[addr1] = satoshi_round(send_value/2)
|
||||||
|
outputs[addr2] = satoshi_round(send_value/2)
|
||||||
|
raw_tx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signed_tx = node.signrawtransaction(raw_tx)["hex"]
|
||||||
|
txid = node.sendrawtransaction(signed_tx)
|
||||||
|
|
||||||
|
while (node.getmempoolinfo()['size'] > 0):
|
||||||
|
node.generate(1)
|
||||||
|
|
||||||
|
utxos = node.listunspent()
|
||||||
|
assert(len(utxos) >= count)
|
||||||
|
return utxos
|
||||||
|
|
||||||
|
# Create large OP_RETURN txouts that can be appended to a transaction
|
||||||
|
# to make it large (helper for constructing large transactions).
|
||||||
|
def gen_return_txouts():
|
||||||
|
# Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
|
||||||
|
# So we have big transactions (and therefore can't fit very many into each block)
|
||||||
|
# create one script_pubkey
|
||||||
|
script_pubkey = "6a4d0200" #OP_RETURN OP_PUSH2 512 bytes
|
||||||
|
for i in range (512):
|
||||||
|
script_pubkey = script_pubkey + "01"
|
||||||
|
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
|
||||||
|
txouts = "81"
|
||||||
|
for k in range(128):
|
||||||
|
# add txout value
|
||||||
|
txouts = txouts + "0000000000000000"
|
||||||
|
# add length of script_pubkey
|
||||||
|
txouts = txouts + "fd0402"
|
||||||
|
# add script_pubkey
|
||||||
|
txouts = txouts + script_pubkey
|
||||||
|
return txouts
|
||||||
|
|
||||||
|
def create_tx(node, coinbase, to_address, amount):
|
||||||
|
inputs = [{ "txid" : coinbase, "vout" : 0}]
|
||||||
|
outputs = { to_address : amount }
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = node.signrawtransaction(rawtx)
|
||||||
|
assert_equal(signresult["complete"], True)
|
||||||
|
return signresult["hex"]
|
||||||
|
|
||||||
|
def create_tx_multi_input(node, inputs, outputs):
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = node.signrawtransaction(rawtx)
|
||||||
|
assert_equal(signresult["complete"], True)
|
||||||
|
return signresult["hex"]
|
||||||
|
|
||||||
|
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
|
||||||
|
# transaction to make it large. See gen_return_txouts() above.
|
||||||
|
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
|
||||||
|
addr = node.getnewaddress()
|
||||||
|
txids = []
|
||||||
|
for _ in range(num):
|
||||||
|
t = utxos.pop()
|
||||||
|
inputs=[{ "txid" : t["txid"], "vout" : t["vout"]}]
|
||||||
|
outputs = {}
|
||||||
|
change = t['amount'] - fee
|
||||||
|
outputs[addr] = satoshi_round(change)
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
newtx = rawtx[0:92]
|
||||||
|
newtx = newtx + txouts
|
||||||
|
newtx = newtx + rawtx[94:]
|
||||||
|
signresult = node.signrawtransaction(newtx, None, None, "NONE")
|
||||||
|
txid = node.sendrawtransaction(signresult["hex"], True)
|
||||||
|
txids.append(txid)
|
||||||
|
return txids
|
||||||
|
|
||||||
|
def mine_large_block(node, utxos=None):
|
||||||
|
# generate a 66k transaction,
|
||||||
|
# and 14 of them is close to the 1MB block limit
|
||||||
|
num = 14
|
||||||
|
txouts = gen_return_txouts()
|
||||||
|
utxos = utxos if utxos is not None else []
|
||||||
|
if len(utxos) < num:
|
||||||
|
utxos.clear()
|
||||||
|
utxos.extend(node.listunspent())
|
||||||
|
fee = 100 * node.getnetworkinfo()["relayfee"]
|
||||||
|
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
|
||||||
|
node.generate(1)
|
||||||
|
|
||||||
|
def get_bip9_status(node, key):
|
||||||
|
info = node.getblockchaininfo()
|
||||||
|
return info['bip9_softforks'][key]
|
||||||
|
|
||||||
|
def dumpprivkey_otac(node, address):
|
||||||
|
import re
|
||||||
|
error_text = ''
|
||||||
|
try:
|
||||||
|
return node.dumpprivkey(address)
|
||||||
|
except JSONRPCException as e:
|
||||||
|
error_text = e.error
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
otac_match = re.search("Your one time authorization code is: ([a-zA-Z0-9]+)", error_text['message'])
|
||||||
|
if not otac_match:
|
||||||
|
raise JSONRPCException(error_text)
|
||||||
|
return node.dumpprivkey(address, otac_match.groups()[0])
|
||||||
|
|
||||||
|
def get_znsync_status(node):
|
||||||
|
result = node.znsync("status")
|
||||||
|
return result['IsSynced']
|
||||||
|
|
||||||
|
def wait_to_sync_znodes(node, fast_znsync=False):
|
||||||
|
while True:
|
||||||
|
synced = get_znsync_status(node)
|
||||||
|
if synced:
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
if fast_znsync:
|
||||||
|
# skip mnsync states
|
||||||
|
node.znsync("next")
|
||||||
|
|
||||||
|
def get_full_balance(node):
|
||||||
|
wallet_info = node.getwalletinfo()
|
||||||
|
return wallet_info["balance"] + wallet_info["immature_balance"] + wallet_info["unconfirmed_balance"]
|
||||||
0
basicswap/interface/contrib/nav_test_framework/__init__.py
Executable file
175
basicswap/interface/contrib/nav_test_framework/authproxy.py
Executable file
@@ -0,0 +1,175 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
Copyright 2011 Jeff Garzik
|
||||||
|
|
||||||
|
AuthServiceProxy has the following improvements over python-jsonrpc's
|
||||||
|
ServiceProxy class:
|
||||||
|
|
||||||
|
- HTTP connections persist for the life of the AuthServiceProxy object
|
||||||
|
(if server supports HTTP/1.1)
|
||||||
|
- sends protocol 'version', per JSON-RPC 1.1
|
||||||
|
- sends proper, incrementing 'id'
|
||||||
|
- sends Basic HTTP authentication headers
|
||||||
|
- parses all JSON numbers that look like floats as Decimal
|
||||||
|
- uses standard Python json lib
|
||||||
|
|
||||||
|
Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
|
||||||
|
|
||||||
|
Copyright (c) 2007 Jan-Klaas Kollhof
|
||||||
|
|
||||||
|
This file is part of jsonrpc.
|
||||||
|
|
||||||
|
jsonrpc is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This software is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this software; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import http.client as httplib
|
||||||
|
except ImportError:
|
||||||
|
import httplib
|
||||||
|
import base64
|
||||||
|
import decimal
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
try:
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
except ImportError:
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
USER_AGENT = "AuthServiceProxy/0.1"
|
||||||
|
|
||||||
|
HTTP_TIMEOUT = 30
|
||||||
|
|
||||||
|
log = logging.getLogger("NavcoinRPC")
|
||||||
|
|
||||||
|
class JSONRPCException(Exception):
|
||||||
|
def __init__(self, rpc_error):
|
||||||
|
Exception.__init__(self)
|
||||||
|
self.error = rpc_error
|
||||||
|
|
||||||
|
|
||||||
|
def EncodeDecimal(o):
|
||||||
|
if isinstance(o, decimal.Decimal):
|
||||||
|
return str(o)
|
||||||
|
raise TypeError(repr(o) + " is not JSON serializable")
|
||||||
|
|
||||||
|
class AuthServiceProxy(object):
|
||||||
|
__id_count = 0
|
||||||
|
|
||||||
|
# ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
|
||||||
|
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
|
||||||
|
self.__service_url = service_url
|
||||||
|
self._service_name = service_name
|
||||||
|
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
|
||||||
|
self.__url = urlparse.urlparse(service_url)
|
||||||
|
if self.__url.port is None:
|
||||||
|
port = 80
|
||||||
|
else:
|
||||||
|
port = self.__url.port
|
||||||
|
(user, passwd) = (self.__url.username, self.__url.password)
|
||||||
|
try:
|
||||||
|
user = user.encode('utf8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
passwd = passwd.encode('utf8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
authpair = user + b':' + passwd
|
||||||
|
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
|
||||||
|
|
||||||
|
if connection:
|
||||||
|
# Callables re-use the connection of the original proxy
|
||||||
|
self.__conn = connection
|
||||||
|
elif self.__url.scheme == 'https':
|
||||||
|
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
|
||||||
|
timeout=timeout)
|
||||||
|
else:
|
||||||
|
self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
|
||||||
|
timeout=timeout)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name.startswith('__') and name.endswith('__'):
|
||||||
|
# Python internal stuff
|
||||||
|
raise AttributeError
|
||||||
|
if self._service_name is not None:
|
||||||
|
name = "%s.%s" % (self._service_name, name)
|
||||||
|
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
|
||||||
|
|
||||||
|
def _request(self, method, path, postdata):
|
||||||
|
'''
|
||||||
|
Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
|
||||||
|
This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
|
||||||
|
'''
|
||||||
|
headers = {'Host': self.__url.hostname,
|
||||||
|
'User-Agent': USER_AGENT,
|
||||||
|
'Authorization': self.__auth_header,
|
||||||
|
'Content-type': 'application/json'}
|
||||||
|
try:
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
except httplib.BadStatusLine as e:
|
||||||
|
if e.line == "''": # if connection was closed, try again
|
||||||
|
self.__conn.close()
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
except BrokenPipeError:
|
||||||
|
# Python 3.5+ raises this instead of BadStatusLine when the connection was reset
|
||||||
|
self.__conn.close()
|
||||||
|
self.__conn.request(method, path, postdata, headers)
|
||||||
|
return self._get_response()
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
AuthServiceProxy.__id_count += 1
|
||||||
|
|
||||||
|
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name,
|
||||||
|
json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||||
|
postdata = json.dumps({'version': '1.1',
|
||||||
|
'method': self._service_name,
|
||||||
|
'params': args,
|
||||||
|
'id': AuthServiceProxy.__id_count}, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||||
|
response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||||
|
if response['error'] is not None:
|
||||||
|
raise JSONRPCException(response['error'])
|
||||||
|
elif 'result' not in response:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -343, 'message': 'missing JSON-RPC result'})
|
||||||
|
else:
|
||||||
|
return response['result']
|
||||||
|
|
||||||
|
def _batch(self, rpc_call_list):
|
||||||
|
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
|
||||||
|
log.debug("--> "+postdata)
|
||||||
|
return self._request('POST', self.__url.path, postdata.encode('utf-8'))
|
||||||
|
|
||||||
|
def _get_response(self):
|
||||||
|
http_response = self.__conn.getresponse()
|
||||||
|
if http_response is None:
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'missing HTTP response from server'})
|
||||||
|
|
||||||
|
content_type = http_response.getheader('Content-Type')
|
||||||
|
if content_type != 'application/json':
|
||||||
|
raise JSONRPCException({
|
||||||
|
'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
|
||||||
|
|
||||||
|
responsedata = http_response.read().decode('utf8')
|
||||||
|
response = json.loads(responsedata, parse_float=decimal.Decimal)
|
||||||
|
if "error" in response and response["error"] is None:
|
||||||
|
log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
|
||||||
|
else:
|
||||||
|
log.debug("<-- "+responsedata)
|
||||||
|
return response
|
||||||
101
basicswap/interface/contrib/nav_test_framework/bignum.py
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# bignum.py
|
||||||
|
#
|
||||||
|
# This file is copied from python-navcoinlib.
|
||||||
|
#
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Bignum routines"""
|
||||||
|
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
# generic big endian MPI format
|
||||||
|
|
||||||
|
def bn_bytes(v, have_ext=False):
|
||||||
|
ext = 0
|
||||||
|
if have_ext:
|
||||||
|
ext = 1
|
||||||
|
return ((v.bit_length()+7)//8) + ext
|
||||||
|
|
||||||
|
def bn2bin(v):
|
||||||
|
s = bytearray()
|
||||||
|
i = bn_bytes(v)
|
||||||
|
while i > 0:
|
||||||
|
s.append((v >> ((i-1) * 8)) & 0xff)
|
||||||
|
i -= 1
|
||||||
|
return s
|
||||||
|
|
||||||
|
def bin2bn(s):
|
||||||
|
l = 0
|
||||||
|
for ch in s:
|
||||||
|
l = (l << 8) | ch
|
||||||
|
return l
|
||||||
|
|
||||||
|
def bn2mpi(v):
|
||||||
|
have_ext = False
|
||||||
|
if v.bit_length() > 0:
|
||||||
|
have_ext = (v.bit_length() & 0x07) == 0
|
||||||
|
|
||||||
|
neg = False
|
||||||
|
if v < 0:
|
||||||
|
neg = True
|
||||||
|
v = -v
|
||||||
|
|
||||||
|
s = struct.pack(b">I", bn_bytes(v, have_ext))
|
||||||
|
ext = bytearray()
|
||||||
|
if have_ext:
|
||||||
|
ext.append(0)
|
||||||
|
v_bin = bn2bin(v)
|
||||||
|
if neg:
|
||||||
|
if have_ext:
|
||||||
|
ext[0] |= 0x80
|
||||||
|
else:
|
||||||
|
v_bin[0] |= 0x80
|
||||||
|
return s + ext + v_bin
|
||||||
|
|
||||||
|
def mpi2bn(s):
|
||||||
|
if len(s) < 4:
|
||||||
|
return None
|
||||||
|
s_size = bytes(s[:4])
|
||||||
|
v_len = struct.unpack(b">I", s_size)[0]
|
||||||
|
if len(s) != (v_len + 4):
|
||||||
|
return None
|
||||||
|
if v_len == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
v_str = bytearray(s[4:])
|
||||||
|
neg = False
|
||||||
|
i = v_str[0]
|
||||||
|
if i & 0x80:
|
||||||
|
neg = True
|
||||||
|
i &= ~0x80
|
||||||
|
v_str[0] = i
|
||||||
|
|
||||||
|
v = bin2bn(v_str)
|
||||||
|
|
||||||
|
if neg:
|
||||||
|
return -v
|
||||||
|
return v
|
||||||
|
|
||||||
|
# navcoin-specific little endian format, with implicit size
|
||||||
|
def mpi2vch(s):
|
||||||
|
r = s[4:] # strip size
|
||||||
|
r = r[::-1] # reverse string, converting BE->LE
|
||||||
|
return r
|
||||||
|
|
||||||
|
def bn2vch(v):
|
||||||
|
return bytes(mpi2vch(bn2mpi(v)))
|
||||||
|
|
||||||
|
def vch2mpi(s):
|
||||||
|
r = struct.pack(b">I", len(s)) # size
|
||||||
|
r += s[::-1] # reverse string, converting LE->BE
|
||||||
|
return r
|
||||||
|
|
||||||
|
def vch2bn(s):
|
||||||
|
return mpi2bn(vch2mpi(s))
|
||||||
|
|
||||||
106
basicswap/interface/contrib/nav_test_framework/coverage.py
Executable file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2015-2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module contains utilities for doing coverage analysis on the RPC
|
||||||
|
interface.
|
||||||
|
|
||||||
|
It provides a way to track which RPC commands are exercised during
|
||||||
|
testing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
REFERENCE_FILENAME = 'rpc_interface.txt'
|
||||||
|
|
||||||
|
|
||||||
|
class AuthServiceProxyWrapper(object):
|
||||||
|
"""
|
||||||
|
An object that wraps AuthServiceProxy to record specific RPC calls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
|
||||||
|
"""
|
||||||
|
Kwargs:
|
||||||
|
auth_service_proxy_instance (AuthServiceProxy): the instance
|
||||||
|
being wrapped.
|
||||||
|
coverage_logfile (str): if specified, write each service_name
|
||||||
|
out to a file when called.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.auth_service_proxy_instance = auth_service_proxy_instance
|
||||||
|
self.coverage_logfile = coverage_logfile
|
||||||
|
|
||||||
|
def __getattr__(self, *args, **kwargs):
|
||||||
|
return_val = self.auth_service_proxy_instance.__getattr__(
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Delegates to AuthServiceProxy, then writes the particular RPC method
|
||||||
|
called to a file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
|
||||||
|
rpc_method = self.auth_service_proxy_instance._service_name
|
||||||
|
|
||||||
|
if self.coverage_logfile:
|
||||||
|
with open(self.coverage_logfile, 'a+') as f:
|
||||||
|
f.write("%s\n" % rpc_method)
|
||||||
|
|
||||||
|
return return_val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self.auth_service_proxy_instance.url
|
||||||
|
|
||||||
|
|
||||||
|
def get_filename(dirname, n_node):
|
||||||
|
"""
|
||||||
|
Get a filename unique to the test process ID and node.
|
||||||
|
|
||||||
|
This file will contain a list of RPC commands covered.
|
||||||
|
"""
|
||||||
|
pid = str(os.getpid())
|
||||||
|
return os.path.join(
|
||||||
|
dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
|
||||||
|
|
||||||
|
|
||||||
|
def write_all_rpc_commands(dirname, node):
|
||||||
|
"""
|
||||||
|
Write out a list of all RPC functions available in `navcoin-cli` for
|
||||||
|
coverage comparison. This will only happen once per coverage
|
||||||
|
directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dirname (str): temporary test dir
|
||||||
|
node (AuthServiceProxy): client
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool. if the RPC interface file was written.
|
||||||
|
|
||||||
|
"""
|
||||||
|
filename = os.path.join(dirname, REFERENCE_FILENAME)
|
||||||
|
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return False
|
||||||
|
|
||||||
|
help_output = node.help().split('\n')
|
||||||
|
commands = set()
|
||||||
|
|
||||||
|
for line in help_output:
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Ignore blanks and headers
|
||||||
|
if line and not line.startswith('='):
|
||||||
|
commands.add("%s\n" % line.split()[0])
|
||||||
|
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.writelines(list(commands))
|
||||||
|
|
||||||
|
return True
|
||||||
1495
basicswap/interface/contrib/nav_test_framework/mininode.py
Executable file
943
basicswap/interface/contrib/nav_test_framework/script.py
Executable file
@@ -0,0 +1,943 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2015-2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#
|
||||||
|
# script.py
|
||||||
|
#
|
||||||
|
# This file is modified from python-navcoinlib.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Scripts
|
||||||
|
|
||||||
|
Functionality to build scripts, as well as SignatureHash().
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from .mininode import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string
|
||||||
|
from binascii import hexlify
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
import sys
|
||||||
|
bchr = chr
|
||||||
|
bord = ord
|
||||||
|
if sys.version > '3':
|
||||||
|
long = int
|
||||||
|
bchr = lambda x: bytes([x])
|
||||||
|
bord = lambda x: x
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from .bignum import bn2vch
|
||||||
|
|
||||||
|
MAX_SCRIPT_SIZE = 10000
|
||||||
|
MAX_SCRIPT_ELEMENT_SIZE = 520
|
||||||
|
MAX_SCRIPT_OPCODES = 201
|
||||||
|
|
||||||
|
OPCODE_NAMES = {}
|
||||||
|
|
||||||
|
_opcode_instances = []
|
||||||
|
class CScriptOp(int):
|
||||||
|
"""A single script opcode"""
|
||||||
|
__slots__ = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_pushdata(d):
|
||||||
|
"""Encode a PUSHDATA op, returning bytes"""
|
||||||
|
if len(d) < 0x4c:
|
||||||
|
return b'' + bchr(len(d)) + d # OP_PUSHDATA
|
||||||
|
elif len(d) <= 0xff:
|
||||||
|
return b'\x4c' + bchr(len(d)) + d # OP_PUSHDATA1
|
||||||
|
elif len(d) <= 0xffff:
|
||||||
|
return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2
|
||||||
|
elif len(d) <= 0xffffffff:
|
||||||
|
return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4
|
||||||
|
else:
|
||||||
|
raise ValueError("Data too long to encode in a PUSHDATA op")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_n(n):
|
||||||
|
"""Encode a small integer op, returning an opcode"""
|
||||||
|
if not (0 <= n <= 16):
|
||||||
|
raise ValueError('Integer must be in range 0 <= n <= 16, got %d' % n)
|
||||||
|
|
||||||
|
if n == 0:
|
||||||
|
return OP_0
|
||||||
|
else:
|
||||||
|
return CScriptOp(OP_1 + n-1)
|
||||||
|
|
||||||
|
def decode_op_n(self):
|
||||||
|
"""Decode a small integer opcode, returning an integer"""
|
||||||
|
if self == OP_0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not (self == OP_0 or OP_1 <= self <= OP_16):
|
||||||
|
raise ValueError('op %r is not an OP_N' % self)
|
||||||
|
|
||||||
|
return int(self - OP_1+1)
|
||||||
|
|
||||||
|
def is_small_int(self):
|
||||||
|
"""Return true if the op pushes a small integer to the stack"""
|
||||||
|
if 0x51 <= self <= 0x60 or self == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self in OPCODE_NAMES:
|
||||||
|
return OPCODE_NAMES[self]
|
||||||
|
else:
|
||||||
|
return 'CScriptOp(0x%x)' % self
|
||||||
|
|
||||||
|
def __new__(cls, n):
|
||||||
|
try:
|
||||||
|
return _opcode_instances[n]
|
||||||
|
except IndexError:
|
||||||
|
assert len(_opcode_instances) == n
|
||||||
|
_opcode_instances.append(super(CScriptOp, cls).__new__(cls, n))
|
||||||
|
return _opcode_instances[n]
|
||||||
|
|
||||||
|
# Populate opcode instance table
|
||||||
|
for n in range(0xff+1):
|
||||||
|
CScriptOp(n)
|
||||||
|
|
||||||
|
|
||||||
|
# push value
|
||||||
|
OP_0 = CScriptOp(0x00)
|
||||||
|
OP_FALSE = OP_0
|
||||||
|
OP_PUSHDATA1 = CScriptOp(0x4c)
|
||||||
|
OP_PUSHDATA2 = CScriptOp(0x4d)
|
||||||
|
OP_PUSHDATA4 = CScriptOp(0x4e)
|
||||||
|
OP_1NEGATE = CScriptOp(0x4f)
|
||||||
|
OP_RESERVED = CScriptOp(0x50)
|
||||||
|
OP_1 = CScriptOp(0x51)
|
||||||
|
OP_TRUE=OP_1
|
||||||
|
OP_2 = CScriptOp(0x52)
|
||||||
|
OP_3 = CScriptOp(0x53)
|
||||||
|
OP_4 = CScriptOp(0x54)
|
||||||
|
OP_5 = CScriptOp(0x55)
|
||||||
|
OP_6 = CScriptOp(0x56)
|
||||||
|
OP_7 = CScriptOp(0x57)
|
||||||
|
OP_8 = CScriptOp(0x58)
|
||||||
|
OP_9 = CScriptOp(0x59)
|
||||||
|
OP_10 = CScriptOp(0x5a)
|
||||||
|
OP_11 = CScriptOp(0x5b)
|
||||||
|
OP_12 = CScriptOp(0x5c)
|
||||||
|
OP_13 = CScriptOp(0x5d)
|
||||||
|
OP_14 = CScriptOp(0x5e)
|
||||||
|
OP_15 = CScriptOp(0x5f)
|
||||||
|
OP_16 = CScriptOp(0x60)
|
||||||
|
|
||||||
|
# control
|
||||||
|
OP_NOP = CScriptOp(0x61)
|
||||||
|
OP_VER = CScriptOp(0x62)
|
||||||
|
OP_IF = CScriptOp(0x63)
|
||||||
|
OP_NOTIF = CScriptOp(0x64)
|
||||||
|
OP_VERIF = CScriptOp(0x65)
|
||||||
|
OP_VERNOTIF = CScriptOp(0x66)
|
||||||
|
OP_ELSE = CScriptOp(0x67)
|
||||||
|
OP_ENDIF = CScriptOp(0x68)
|
||||||
|
OP_VERIFY = CScriptOp(0x69)
|
||||||
|
OP_RETURN = CScriptOp(0x6a)
|
||||||
|
|
||||||
|
# stack ops
|
||||||
|
OP_TOALTSTACK = CScriptOp(0x6b)
|
||||||
|
OP_FROMALTSTACK = CScriptOp(0x6c)
|
||||||
|
OP_2DROP = CScriptOp(0x6d)
|
||||||
|
OP_2DUP = CScriptOp(0x6e)
|
||||||
|
OP_3DUP = CScriptOp(0x6f)
|
||||||
|
OP_2OVER = CScriptOp(0x70)
|
||||||
|
OP_2ROT = CScriptOp(0x71)
|
||||||
|
OP_2SWAP = CScriptOp(0x72)
|
||||||
|
OP_IFDUP = CScriptOp(0x73)
|
||||||
|
OP_DEPTH = CScriptOp(0x74)
|
||||||
|
OP_DROP = CScriptOp(0x75)
|
||||||
|
OP_DUP = CScriptOp(0x76)
|
||||||
|
OP_NIP = CScriptOp(0x77)
|
||||||
|
OP_OVER = CScriptOp(0x78)
|
||||||
|
OP_PICK = CScriptOp(0x79)
|
||||||
|
OP_ROLL = CScriptOp(0x7a)
|
||||||
|
OP_ROT = CScriptOp(0x7b)
|
||||||
|
OP_SWAP = CScriptOp(0x7c)
|
||||||
|
OP_TUCK = CScriptOp(0x7d)
|
||||||
|
|
||||||
|
# splice ops
|
||||||
|
OP_CAT = CScriptOp(0x7e)
|
||||||
|
OP_SUBSTR = CScriptOp(0x7f)
|
||||||
|
OP_LEFT = CScriptOp(0x80)
|
||||||
|
OP_RIGHT = CScriptOp(0x81)
|
||||||
|
OP_SIZE = CScriptOp(0x82)
|
||||||
|
|
||||||
|
# bit logic
|
||||||
|
OP_INVERT = CScriptOp(0x83)
|
||||||
|
OP_AND = CScriptOp(0x84)
|
||||||
|
OP_OR = CScriptOp(0x85)
|
||||||
|
OP_XOR = CScriptOp(0x86)
|
||||||
|
OP_EQUAL = CScriptOp(0x87)
|
||||||
|
OP_EQUALVERIFY = CScriptOp(0x88)
|
||||||
|
OP_RESERVED1 = CScriptOp(0x89)
|
||||||
|
OP_RESERVED2 = CScriptOp(0x8a)
|
||||||
|
|
||||||
|
# numeric
|
||||||
|
OP_1ADD = CScriptOp(0x8b)
|
||||||
|
OP_1SUB = CScriptOp(0x8c)
|
||||||
|
OP_2MUL = CScriptOp(0x8d)
|
||||||
|
OP_2DIV = CScriptOp(0x8e)
|
||||||
|
OP_NEGATE = CScriptOp(0x8f)
|
||||||
|
OP_ABS = CScriptOp(0x90)
|
||||||
|
OP_NOT = CScriptOp(0x91)
|
||||||
|
OP_0NOTEQUAL = CScriptOp(0x92)
|
||||||
|
|
||||||
|
OP_ADD = CScriptOp(0x93)
|
||||||
|
OP_SUB = CScriptOp(0x94)
|
||||||
|
OP_MUL = CScriptOp(0x95)
|
||||||
|
OP_DIV = CScriptOp(0x96)
|
||||||
|
OP_MOD = CScriptOp(0x97)
|
||||||
|
OP_LSHIFT = CScriptOp(0x98)
|
||||||
|
OP_RSHIFT = CScriptOp(0x99)
|
||||||
|
|
||||||
|
OP_BOOLAND = CScriptOp(0x9a)
|
||||||
|
OP_BOOLOR = CScriptOp(0x9b)
|
||||||
|
OP_NUMEQUAL = CScriptOp(0x9c)
|
||||||
|
OP_NUMEQUALVERIFY = CScriptOp(0x9d)
|
||||||
|
OP_NUMNOTEQUAL = CScriptOp(0x9e)
|
||||||
|
OP_LESSTHAN = CScriptOp(0x9f)
|
||||||
|
OP_GREATERTHAN = CScriptOp(0xa0)
|
||||||
|
OP_LESSTHANOREQUAL = CScriptOp(0xa1)
|
||||||
|
OP_GREATERTHANOREQUAL = CScriptOp(0xa2)
|
||||||
|
OP_MIN = CScriptOp(0xa3)
|
||||||
|
OP_MAX = CScriptOp(0xa4)
|
||||||
|
|
||||||
|
OP_WITHIN = CScriptOp(0xa5)
|
||||||
|
|
||||||
|
# crypto
|
||||||
|
OP_RIPEMD160 = CScriptOp(0xa6)
|
||||||
|
OP_SHA1 = CScriptOp(0xa7)
|
||||||
|
OP_SHA256 = CScriptOp(0xa8)
|
||||||
|
OP_HASH160 = CScriptOp(0xa9)
|
||||||
|
OP_HASH256 = CScriptOp(0xaa)
|
||||||
|
OP_CODESEPARATOR = CScriptOp(0xab)
|
||||||
|
OP_CHECKSIG = CScriptOp(0xac)
|
||||||
|
OP_CHECKSIGVERIFY = CScriptOp(0xad)
|
||||||
|
OP_CHECKMULTISIG = CScriptOp(0xae)
|
||||||
|
OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
|
||||||
|
|
||||||
|
# expansion
|
||||||
|
OP_NOP1 = CScriptOp(0xb0)
|
||||||
|
OP_CHECKLOCKTIMEVERIFY = CScriptOp(0xb1)
|
||||||
|
OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
|
||||||
|
OP_NOP4 = CScriptOp(0xb3)
|
||||||
|
OP_NOP5 = CScriptOp(0xb4)
|
||||||
|
OP_NOP6 = CScriptOp(0xb5)
|
||||||
|
OP_NOP7 = CScriptOp(0xb6)
|
||||||
|
OP_NOP8 = CScriptOp(0xb7)
|
||||||
|
OP_NOP9 = CScriptOp(0xb8)
|
||||||
|
OP_NOP10 = CScriptOp(0xb9)
|
||||||
|
|
||||||
|
# template matching params
|
||||||
|
OP_SMALLINTEGER = CScriptOp(0xfa)
|
||||||
|
OP_PUBKEYS = CScriptOp(0xfb)
|
||||||
|
OP_PUBKEYHASH = CScriptOp(0xfd)
|
||||||
|
OP_PUBKEY = CScriptOp(0xfe)
|
||||||
|
|
||||||
|
OP_INVALIDOPCODE = CScriptOp(0xff)
|
||||||
|
|
||||||
|
VALID_OPCODES = {
|
||||||
|
OP_1NEGATE,
|
||||||
|
OP_RESERVED,
|
||||||
|
OP_1,
|
||||||
|
OP_2,
|
||||||
|
OP_3,
|
||||||
|
OP_4,
|
||||||
|
OP_5,
|
||||||
|
OP_6,
|
||||||
|
OP_7,
|
||||||
|
OP_8,
|
||||||
|
OP_9,
|
||||||
|
OP_10,
|
||||||
|
OP_11,
|
||||||
|
OP_12,
|
||||||
|
OP_13,
|
||||||
|
OP_14,
|
||||||
|
OP_15,
|
||||||
|
OP_16,
|
||||||
|
|
||||||
|
OP_NOP,
|
||||||
|
OP_VER,
|
||||||
|
OP_IF,
|
||||||
|
OP_NOTIF,
|
||||||
|
OP_VERIF,
|
||||||
|
OP_VERNOTIF,
|
||||||
|
OP_ELSE,
|
||||||
|
OP_ENDIF,
|
||||||
|
OP_VERIFY,
|
||||||
|
OP_RETURN,
|
||||||
|
|
||||||
|
OP_TOALTSTACK,
|
||||||
|
OP_FROMALTSTACK,
|
||||||
|
OP_2DROP,
|
||||||
|
OP_2DUP,
|
||||||
|
OP_3DUP,
|
||||||
|
OP_2OVER,
|
||||||
|
OP_2ROT,
|
||||||
|
OP_2SWAP,
|
||||||
|
OP_IFDUP,
|
||||||
|
OP_DEPTH,
|
||||||
|
OP_DROP,
|
||||||
|
OP_DUP,
|
||||||
|
OP_NIP,
|
||||||
|
OP_OVER,
|
||||||
|
OP_PICK,
|
||||||
|
OP_ROLL,
|
||||||
|
OP_ROT,
|
||||||
|
OP_SWAP,
|
||||||
|
OP_TUCK,
|
||||||
|
|
||||||
|
OP_CAT,
|
||||||
|
OP_SUBSTR,
|
||||||
|
OP_LEFT,
|
||||||
|
OP_RIGHT,
|
||||||
|
OP_SIZE,
|
||||||
|
|
||||||
|
OP_INVERT,
|
||||||
|
OP_AND,
|
||||||
|
OP_OR,
|
||||||
|
OP_XOR,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_RESERVED1,
|
||||||
|
OP_RESERVED2,
|
||||||
|
|
||||||
|
OP_1ADD,
|
||||||
|
OP_1SUB,
|
||||||
|
OP_2MUL,
|
||||||
|
OP_2DIV,
|
||||||
|
OP_NEGATE,
|
||||||
|
OP_ABS,
|
||||||
|
OP_NOT,
|
||||||
|
OP_0NOTEQUAL,
|
||||||
|
|
||||||
|
OP_ADD,
|
||||||
|
OP_SUB,
|
||||||
|
OP_MUL,
|
||||||
|
OP_DIV,
|
||||||
|
OP_MOD,
|
||||||
|
OP_LSHIFT,
|
||||||
|
OP_RSHIFT,
|
||||||
|
|
||||||
|
OP_BOOLAND,
|
||||||
|
OP_BOOLOR,
|
||||||
|
OP_NUMEQUAL,
|
||||||
|
OP_NUMEQUALVERIFY,
|
||||||
|
OP_NUMNOTEQUAL,
|
||||||
|
OP_LESSTHAN,
|
||||||
|
OP_GREATERTHAN,
|
||||||
|
OP_LESSTHANOREQUAL,
|
||||||
|
OP_GREATERTHANOREQUAL,
|
||||||
|
OP_MIN,
|
||||||
|
OP_MAX,
|
||||||
|
|
||||||
|
OP_WITHIN,
|
||||||
|
|
||||||
|
OP_RIPEMD160,
|
||||||
|
OP_SHA1,
|
||||||
|
OP_SHA256,
|
||||||
|
OP_HASH160,
|
||||||
|
OP_HASH256,
|
||||||
|
OP_CODESEPARATOR,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_CHECKSIGVERIFY,
|
||||||
|
OP_CHECKMULTISIG,
|
||||||
|
OP_CHECKMULTISIGVERIFY,
|
||||||
|
|
||||||
|
OP_NOP1,
|
||||||
|
OP_CHECKLOCKTIMEVERIFY,
|
||||||
|
OP_CHECKSEQUENCEVERIFY,
|
||||||
|
OP_NOP4,
|
||||||
|
OP_NOP5,
|
||||||
|
OP_NOP6,
|
||||||
|
OP_NOP7,
|
||||||
|
OP_NOP8,
|
||||||
|
OP_NOP9,
|
||||||
|
OP_NOP10,
|
||||||
|
|
||||||
|
OP_SMALLINTEGER,
|
||||||
|
OP_PUBKEYS,
|
||||||
|
OP_PUBKEYHASH,
|
||||||
|
OP_PUBKEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
OPCODE_NAMES.update({
|
||||||
|
OP_0 : 'OP_0',
|
||||||
|
OP_PUSHDATA1 : 'OP_PUSHDATA1',
|
||||||
|
OP_PUSHDATA2 : 'OP_PUSHDATA2',
|
||||||
|
OP_PUSHDATA4 : 'OP_PUSHDATA4',
|
||||||
|
OP_1NEGATE : 'OP_1NEGATE',
|
||||||
|
OP_RESERVED : 'OP_RESERVED',
|
||||||
|
OP_1 : 'OP_1',
|
||||||
|
OP_2 : 'OP_2',
|
||||||
|
OP_3 : 'OP_3',
|
||||||
|
OP_4 : 'OP_4',
|
||||||
|
OP_5 : 'OP_5',
|
||||||
|
OP_6 : 'OP_6',
|
||||||
|
OP_7 : 'OP_7',
|
||||||
|
OP_8 : 'OP_8',
|
||||||
|
OP_9 : 'OP_9',
|
||||||
|
OP_10 : 'OP_10',
|
||||||
|
OP_11 : 'OP_11',
|
||||||
|
OP_12 : 'OP_12',
|
||||||
|
OP_13 : 'OP_13',
|
||||||
|
OP_14 : 'OP_14',
|
||||||
|
OP_15 : 'OP_15',
|
||||||
|
OP_16 : 'OP_16',
|
||||||
|
OP_NOP : 'OP_NOP',
|
||||||
|
OP_VER : 'OP_VER',
|
||||||
|
OP_IF : 'OP_IF',
|
||||||
|
OP_NOTIF : 'OP_NOTIF',
|
||||||
|
OP_VERIF : 'OP_VERIF',
|
||||||
|
OP_VERNOTIF : 'OP_VERNOTIF',
|
||||||
|
OP_ELSE : 'OP_ELSE',
|
||||||
|
OP_ENDIF : 'OP_ENDIF',
|
||||||
|
OP_VERIFY : 'OP_VERIFY',
|
||||||
|
OP_RETURN : 'OP_RETURN',
|
||||||
|
OP_TOALTSTACK : 'OP_TOALTSTACK',
|
||||||
|
OP_FROMALTSTACK : 'OP_FROMALTSTACK',
|
||||||
|
OP_2DROP : 'OP_2DROP',
|
||||||
|
OP_2DUP : 'OP_2DUP',
|
||||||
|
OP_3DUP : 'OP_3DUP',
|
||||||
|
OP_2OVER : 'OP_2OVER',
|
||||||
|
OP_2ROT : 'OP_2ROT',
|
||||||
|
OP_2SWAP : 'OP_2SWAP',
|
||||||
|
OP_IFDUP : 'OP_IFDUP',
|
||||||
|
OP_DEPTH : 'OP_DEPTH',
|
||||||
|
OP_DROP : 'OP_DROP',
|
||||||
|
OP_DUP : 'OP_DUP',
|
||||||
|
OP_NIP : 'OP_NIP',
|
||||||
|
OP_OVER : 'OP_OVER',
|
||||||
|
OP_PICK : 'OP_PICK',
|
||||||
|
OP_ROLL : 'OP_ROLL',
|
||||||
|
OP_ROT : 'OP_ROT',
|
||||||
|
OP_SWAP : 'OP_SWAP',
|
||||||
|
OP_TUCK : 'OP_TUCK',
|
||||||
|
OP_CAT : 'OP_CAT',
|
||||||
|
OP_SUBSTR : 'OP_SUBSTR',
|
||||||
|
OP_LEFT : 'OP_LEFT',
|
||||||
|
OP_RIGHT : 'OP_RIGHT',
|
||||||
|
OP_SIZE : 'OP_SIZE',
|
||||||
|
OP_INVERT : 'OP_INVERT',
|
||||||
|
OP_AND : 'OP_AND',
|
||||||
|
OP_OR : 'OP_OR',
|
||||||
|
OP_XOR : 'OP_XOR',
|
||||||
|
OP_EQUAL : 'OP_EQUAL',
|
||||||
|
OP_EQUALVERIFY : 'OP_EQUALVERIFY',
|
||||||
|
OP_RESERVED1 : 'OP_RESERVED1',
|
||||||
|
OP_RESERVED2 : 'OP_RESERVED2',
|
||||||
|
OP_1ADD : 'OP_1ADD',
|
||||||
|
OP_1SUB : 'OP_1SUB',
|
||||||
|
OP_2MUL : 'OP_2MUL',
|
||||||
|
OP_2DIV : 'OP_2DIV',
|
||||||
|
OP_NEGATE : 'OP_NEGATE',
|
||||||
|
OP_ABS : 'OP_ABS',
|
||||||
|
OP_NOT : 'OP_NOT',
|
||||||
|
OP_0NOTEQUAL : 'OP_0NOTEQUAL',
|
||||||
|
OP_ADD : 'OP_ADD',
|
||||||
|
OP_SUB : 'OP_SUB',
|
||||||
|
OP_MUL : 'OP_MUL',
|
||||||
|
OP_DIV : 'OP_DIV',
|
||||||
|
OP_MOD : 'OP_MOD',
|
||||||
|
OP_LSHIFT : 'OP_LSHIFT',
|
||||||
|
OP_RSHIFT : 'OP_RSHIFT',
|
||||||
|
OP_BOOLAND : 'OP_BOOLAND',
|
||||||
|
OP_BOOLOR : 'OP_BOOLOR',
|
||||||
|
OP_NUMEQUAL : 'OP_NUMEQUAL',
|
||||||
|
OP_NUMEQUALVERIFY : 'OP_NUMEQUALVERIFY',
|
||||||
|
OP_NUMNOTEQUAL : 'OP_NUMNOTEQUAL',
|
||||||
|
OP_LESSTHAN : 'OP_LESSTHAN',
|
||||||
|
OP_GREATERTHAN : 'OP_GREATERTHAN',
|
||||||
|
OP_LESSTHANOREQUAL : 'OP_LESSTHANOREQUAL',
|
||||||
|
OP_GREATERTHANOREQUAL : 'OP_GREATERTHANOREQUAL',
|
||||||
|
OP_MIN : 'OP_MIN',
|
||||||
|
OP_MAX : 'OP_MAX',
|
||||||
|
OP_WITHIN : 'OP_WITHIN',
|
||||||
|
OP_RIPEMD160 : 'OP_RIPEMD160',
|
||||||
|
OP_SHA1 : 'OP_SHA1',
|
||||||
|
OP_SHA256 : 'OP_SHA256',
|
||||||
|
OP_HASH160 : 'OP_HASH160',
|
||||||
|
OP_HASH256 : 'OP_HASH256',
|
||||||
|
OP_CODESEPARATOR : 'OP_CODESEPARATOR',
|
||||||
|
OP_CHECKSIG : 'OP_CHECKSIG',
|
||||||
|
OP_CHECKSIGVERIFY : 'OP_CHECKSIGVERIFY',
|
||||||
|
OP_CHECKMULTISIG : 'OP_CHECKMULTISIG',
|
||||||
|
OP_CHECKMULTISIGVERIFY : 'OP_CHECKMULTISIGVERIFY',
|
||||||
|
OP_NOP1 : 'OP_NOP1',
|
||||||
|
OP_CHECKLOCKTIMEVERIFY : 'OP_CHECKLOCKTIMEVERIFY',
|
||||||
|
OP_CHECKSEQUENCEVERIFY : 'OP_CHECKSEQUENCEVERIFY',
|
||||||
|
OP_NOP4 : 'OP_NOP4',
|
||||||
|
OP_NOP5 : 'OP_NOP5',
|
||||||
|
OP_NOP6 : 'OP_NOP6',
|
||||||
|
OP_NOP7 : 'OP_NOP7',
|
||||||
|
OP_NOP8 : 'OP_NOP8',
|
||||||
|
OP_NOP9 : 'OP_NOP9',
|
||||||
|
OP_NOP10 : 'OP_NOP10',
|
||||||
|
OP_SMALLINTEGER : 'OP_SMALLINTEGER',
|
||||||
|
OP_PUBKEYS : 'OP_PUBKEYS',
|
||||||
|
OP_PUBKEYHASH : 'OP_PUBKEYHASH',
|
||||||
|
OP_PUBKEY : 'OP_PUBKEY',
|
||||||
|
OP_INVALIDOPCODE : 'OP_INVALIDOPCODE',
|
||||||
|
})
|
||||||
|
|
||||||
|
OPCODES_BY_NAME = {
|
||||||
|
'OP_0' : OP_0,
|
||||||
|
'OP_PUSHDATA1' : OP_PUSHDATA1,
|
||||||
|
'OP_PUSHDATA2' : OP_PUSHDATA2,
|
||||||
|
'OP_PUSHDATA4' : OP_PUSHDATA4,
|
||||||
|
'OP_1NEGATE' : OP_1NEGATE,
|
||||||
|
'OP_RESERVED' : OP_RESERVED,
|
||||||
|
'OP_1' : OP_1,
|
||||||
|
'OP_2' : OP_2,
|
||||||
|
'OP_3' : OP_3,
|
||||||
|
'OP_4' : OP_4,
|
||||||
|
'OP_5' : OP_5,
|
||||||
|
'OP_6' : OP_6,
|
||||||
|
'OP_7' : OP_7,
|
||||||
|
'OP_8' : OP_8,
|
||||||
|
'OP_9' : OP_9,
|
||||||
|
'OP_10' : OP_10,
|
||||||
|
'OP_11' : OP_11,
|
||||||
|
'OP_12' : OP_12,
|
||||||
|
'OP_13' : OP_13,
|
||||||
|
'OP_14' : OP_14,
|
||||||
|
'OP_15' : OP_15,
|
||||||
|
'OP_16' : OP_16,
|
||||||
|
'OP_NOP' : OP_NOP,
|
||||||
|
'OP_VER' : OP_VER,
|
||||||
|
'OP_IF' : OP_IF,
|
||||||
|
'OP_NOTIF' : OP_NOTIF,
|
||||||
|
'OP_VERIF' : OP_VERIF,
|
||||||
|
'OP_VERNOTIF' : OP_VERNOTIF,
|
||||||
|
'OP_ELSE' : OP_ELSE,
|
||||||
|
'OP_ENDIF' : OP_ENDIF,
|
||||||
|
'OP_VERIFY' : OP_VERIFY,
|
||||||
|
'OP_RETURN' : OP_RETURN,
|
||||||
|
'OP_TOALTSTACK' : OP_TOALTSTACK,
|
||||||
|
'OP_FROMALTSTACK' : OP_FROMALTSTACK,
|
||||||
|
'OP_2DROP' : OP_2DROP,
|
||||||
|
'OP_2DUP' : OP_2DUP,
|
||||||
|
'OP_3DUP' : OP_3DUP,
|
||||||
|
'OP_2OVER' : OP_2OVER,
|
||||||
|
'OP_2ROT' : OP_2ROT,
|
||||||
|
'OP_2SWAP' : OP_2SWAP,
|
||||||
|
'OP_IFDUP' : OP_IFDUP,
|
||||||
|
'OP_DEPTH' : OP_DEPTH,
|
||||||
|
'OP_DROP' : OP_DROP,
|
||||||
|
'OP_DUP' : OP_DUP,
|
||||||
|
'OP_NIP' : OP_NIP,
|
||||||
|
'OP_OVER' : OP_OVER,
|
||||||
|
'OP_PICK' : OP_PICK,
|
||||||
|
'OP_ROLL' : OP_ROLL,
|
||||||
|
'OP_ROT' : OP_ROT,
|
||||||
|
'OP_SWAP' : OP_SWAP,
|
||||||
|
'OP_TUCK' : OP_TUCK,
|
||||||
|
'OP_CAT' : OP_CAT,
|
||||||
|
'OP_SUBSTR' : OP_SUBSTR,
|
||||||
|
'OP_LEFT' : OP_LEFT,
|
||||||
|
'OP_RIGHT' : OP_RIGHT,
|
||||||
|
'OP_SIZE' : OP_SIZE,
|
||||||
|
'OP_INVERT' : OP_INVERT,
|
||||||
|
'OP_AND' : OP_AND,
|
||||||
|
'OP_OR' : OP_OR,
|
||||||
|
'OP_XOR' : OP_XOR,
|
||||||
|
'OP_EQUAL' : OP_EQUAL,
|
||||||
|
'OP_EQUALVERIFY' : OP_EQUALVERIFY,
|
||||||
|
'OP_RESERVED1' : OP_RESERVED1,
|
||||||
|
'OP_RESERVED2' : OP_RESERVED2,
|
||||||
|
'OP_1ADD' : OP_1ADD,
|
||||||
|
'OP_1SUB' : OP_1SUB,
|
||||||
|
'OP_2MUL' : OP_2MUL,
|
||||||
|
'OP_2DIV' : OP_2DIV,
|
||||||
|
'OP_NEGATE' : OP_NEGATE,
|
||||||
|
'OP_ABS' : OP_ABS,
|
||||||
|
'OP_NOT' : OP_NOT,
|
||||||
|
'OP_0NOTEQUAL' : OP_0NOTEQUAL,
|
||||||
|
'OP_ADD' : OP_ADD,
|
||||||
|
'OP_SUB' : OP_SUB,
|
||||||
|
'OP_MUL' : OP_MUL,
|
||||||
|
'OP_DIV' : OP_DIV,
|
||||||
|
'OP_MOD' : OP_MOD,
|
||||||
|
'OP_LSHIFT' : OP_LSHIFT,
|
||||||
|
'OP_RSHIFT' : OP_RSHIFT,
|
||||||
|
'OP_BOOLAND' : OP_BOOLAND,
|
||||||
|
'OP_BOOLOR' : OP_BOOLOR,
|
||||||
|
'OP_NUMEQUAL' : OP_NUMEQUAL,
|
||||||
|
'OP_NUMEQUALVERIFY' : OP_NUMEQUALVERIFY,
|
||||||
|
'OP_NUMNOTEQUAL' : OP_NUMNOTEQUAL,
|
||||||
|
'OP_LESSTHAN' : OP_LESSTHAN,
|
||||||
|
'OP_GREATERTHAN' : OP_GREATERTHAN,
|
||||||
|
'OP_LESSTHANOREQUAL' : OP_LESSTHANOREQUAL,
|
||||||
|
'OP_GREATERTHANOREQUAL' : OP_GREATERTHANOREQUAL,
|
||||||
|
'OP_MIN' : OP_MIN,
|
||||||
|
'OP_MAX' : OP_MAX,
|
||||||
|
'OP_WITHIN' : OP_WITHIN,
|
||||||
|
'OP_RIPEMD160' : OP_RIPEMD160,
|
||||||
|
'OP_SHA1' : OP_SHA1,
|
||||||
|
'OP_SHA256' : OP_SHA256,
|
||||||
|
'OP_HASH160' : OP_HASH160,
|
||||||
|
'OP_HASH256' : OP_HASH256,
|
||||||
|
'OP_CODESEPARATOR' : OP_CODESEPARATOR,
|
||||||
|
'OP_CHECKSIG' : OP_CHECKSIG,
|
||||||
|
'OP_CHECKSIGVERIFY' : OP_CHECKSIGVERIFY,
|
||||||
|
'OP_CHECKMULTISIG' : OP_CHECKMULTISIG,
|
||||||
|
'OP_CHECKMULTISIGVERIFY' : OP_CHECKMULTISIGVERIFY,
|
||||||
|
'OP_NOP1' : OP_NOP1,
|
||||||
|
'OP_CHECKLOCKTIMEVERIFY' : OP_CHECKLOCKTIMEVERIFY,
|
||||||
|
'OP_CHECKSEQUENCEVERIFY' : OP_CHECKSEQUENCEVERIFY,
|
||||||
|
'OP_NOP4' : OP_NOP4,
|
||||||
|
'OP_NOP5' : OP_NOP5,
|
||||||
|
'OP_NOP6' : OP_NOP6,
|
||||||
|
'OP_NOP7' : OP_NOP7,
|
||||||
|
'OP_NOP8' : OP_NOP8,
|
||||||
|
'OP_NOP9' : OP_NOP9,
|
||||||
|
'OP_NOP10' : OP_NOP10,
|
||||||
|
'OP_SMALLINTEGER' : OP_SMALLINTEGER,
|
||||||
|
'OP_PUBKEYS' : OP_PUBKEYS,
|
||||||
|
'OP_PUBKEYHASH' : OP_PUBKEYHASH,
|
||||||
|
'OP_PUBKEY' : OP_PUBKEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CScriptInvalidError(Exception):
|
||||||
|
"""Base class for CScript exceptions"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CScriptTruncatedPushDataError(CScriptInvalidError):
|
||||||
|
"""Invalid pushdata due to truncation"""
|
||||||
|
def __init__(self, msg, data):
|
||||||
|
self.data = data
|
||||||
|
super(CScriptTruncatedPushDataError, self).__init__(msg)
|
||||||
|
|
||||||
|
# This is used, eg, for blockchain heights in coinbase scripts (bip34)
|
||||||
|
class CScriptNum(object):
|
||||||
|
def __init__(self, d=0):
|
||||||
|
self.value = d
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode(obj):
|
||||||
|
r = bytearray(0)
|
||||||
|
if obj.value == 0:
|
||||||
|
return bytes(r)
|
||||||
|
neg = obj.value < 0
|
||||||
|
absvalue = -obj.value if neg else obj.value
|
||||||
|
while (absvalue):
|
||||||
|
r.append(absvalue & 0xff)
|
||||||
|
absvalue >>= 8
|
||||||
|
if r[-1] & 0x80:
|
||||||
|
r.append(0x80 if neg else 0)
|
||||||
|
elif neg:
|
||||||
|
r[-1] |= 0x80
|
||||||
|
return bytes(bchr(len(r)) + r)
|
||||||
|
|
||||||
|
|
||||||
|
class CScript(bytes):
|
||||||
|
"""Serialized script
|
||||||
|
|
||||||
|
A bytes subclass, so you can use this directly whenever bytes are accepted.
|
||||||
|
Note that this means that indexing does *not* work - you'll get an index by
|
||||||
|
byte rather than opcode. This format was chosen for efficiency so that the
|
||||||
|
general case would not require creating a lot of little CScriptOP objects.
|
||||||
|
|
||||||
|
iter(script) however does iterate by opcode.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def __coerce_instance(cls, other):
|
||||||
|
# Coerce other into bytes
|
||||||
|
if isinstance(other, CScriptOp):
|
||||||
|
other = bchr(other)
|
||||||
|
elif isinstance(other, CScriptNum):
|
||||||
|
if (other.value == 0):
|
||||||
|
other = bchr(CScriptOp(OP_0))
|
||||||
|
else:
|
||||||
|
other = CScriptNum.encode(other)
|
||||||
|
elif isinstance(other, int):
|
||||||
|
if 0 <= other <= 16:
|
||||||
|
other = bytes(bchr(CScriptOp.encode_op_n(other)))
|
||||||
|
elif other == -1:
|
||||||
|
other = bytes(bchr(OP_1NEGATE))
|
||||||
|
else:
|
||||||
|
other = CScriptOp.encode_op_pushdata(bn2vch(other))
|
||||||
|
elif isinstance(other, (bytes, bytearray)):
|
||||||
|
other = CScriptOp.encode_op_pushdata(other)
|
||||||
|
return other
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
# Do the coercion outside of the try block so that errors in it are
|
||||||
|
# noticed.
|
||||||
|
other = self.__coerce_instance(other)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# bytes.__add__ always returns bytes instances unfortunately
|
||||||
|
return CScript(super(CScript, self).__add__(other))
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError('Can not add a %r instance to a CScript' % other.__class__)
|
||||||
|
|
||||||
|
def join(self, iterable):
|
||||||
|
# join makes no sense for a CScript()
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __new__(cls, value=b''):
|
||||||
|
if isinstance(value, bytes) or isinstance(value, bytearray):
|
||||||
|
return super(CScript, cls).__new__(cls, value)
|
||||||
|
else:
|
||||||
|
def coerce_iterable(iterable):
|
||||||
|
for instance in iterable:
|
||||||
|
yield cls.__coerce_instance(instance)
|
||||||
|
# Annoyingly on both python2 and python3 bytes.join() always
|
||||||
|
# returns a bytes instance even when subclassed.
|
||||||
|
return super(CScript, cls).__new__(cls, b''.join(coerce_iterable(value)))
|
||||||
|
|
||||||
|
def raw_iter(self):
|
||||||
|
"""Raw iteration
|
||||||
|
|
||||||
|
Yields tuples of (opcode, data, sop_idx) so that the different possible
|
||||||
|
PUSHDATA encodings can be accurately distinguished, as well as
|
||||||
|
determining the exact opcode byte indexes. (sop_idx)
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
while i < len(self):
|
||||||
|
sop_idx = i
|
||||||
|
opcode = bord(self[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if opcode > OP_PUSHDATA4:
|
||||||
|
yield (opcode, None, sop_idx)
|
||||||
|
else:
|
||||||
|
datasize = None
|
||||||
|
pushdata_type = None
|
||||||
|
if opcode < OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA(%d)' % opcode
|
||||||
|
datasize = opcode
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA1'
|
||||||
|
if i >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA1: missing data length')
|
||||||
|
datasize = bord(self[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA2:
|
||||||
|
pushdata_type = 'PUSHDATA2'
|
||||||
|
if i + 1 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA2: missing data length')
|
||||||
|
datasize = bord(self[i]) + (bord(self[i+1]) << 8)
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA4:
|
||||||
|
pushdata_type = 'PUSHDATA4'
|
||||||
|
if i + 3 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA4: missing data length')
|
||||||
|
datasize = bord(self[i]) + (bord(self[i+1]) << 8) + (bord(self[i+2]) << 16) + (bord(self[i+3]) << 24)
|
||||||
|
i += 4
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert False # shouldn't happen
|
||||||
|
|
||||||
|
|
||||||
|
data = bytes(self[i:i+datasize])
|
||||||
|
|
||||||
|
# Check for truncation
|
||||||
|
if len(data) < datasize:
|
||||||
|
raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data)
|
||||||
|
|
||||||
|
i += datasize
|
||||||
|
|
||||||
|
yield (opcode, data, sop_idx)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""'Cooked' iteration
|
||||||
|
|
||||||
|
Returns either a CScriptOP instance, an integer, or bytes, as
|
||||||
|
appropriate.
|
||||||
|
|
||||||
|
See raw_iter() if you need to distinguish the different possible
|
||||||
|
PUSHDATA encodings.
|
||||||
|
"""
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if data is not None:
|
||||||
|
yield data
|
||||||
|
else:
|
||||||
|
opcode = CScriptOp(opcode)
|
||||||
|
|
||||||
|
if opcode.is_small_int():
|
||||||
|
yield opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
yield CScriptOp(opcode)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
# For Python3 compatibility add b before strings so testcases don't
|
||||||
|
# need to change
|
||||||
|
def _repr(o):
|
||||||
|
if isinstance(o, bytes):
|
||||||
|
return b"x('%s')" % hexlify(o).decode('ascii')
|
||||||
|
else:
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
ops = []
|
||||||
|
i = iter(self)
|
||||||
|
while True:
|
||||||
|
op = None
|
||||||
|
try:
|
||||||
|
op = _repr(next(i))
|
||||||
|
except CScriptTruncatedPushDataError as err:
|
||||||
|
op = '%s...<ERROR: %s>' % (_repr(err.data), err)
|
||||||
|
break
|
||||||
|
except CScriptInvalidError as err:
|
||||||
|
op = '<ERROR: %s>' % err
|
||||||
|
break
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
if op is not None:
|
||||||
|
ops.append(op)
|
||||||
|
|
||||||
|
return "CScript([%s])" % ', '.join(ops)
|
||||||
|
|
||||||
|
def GetSigOpCount(self, fAccurate):
|
||||||
|
"""Get the SigOp count.
|
||||||
|
|
||||||
|
fAccurate - Accurately count CHECKMULTISIG, see BIP16 for details.
|
||||||
|
|
||||||
|
Note that this is consensus-critical.
|
||||||
|
"""
|
||||||
|
n = 0
|
||||||
|
lastOpcode = OP_INVALIDOPCODE
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY):
|
||||||
|
n += 1
|
||||||
|
elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
|
||||||
|
if fAccurate and (OP_1 <= lastOpcode <= OP_16):
|
||||||
|
n += opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
n += 20
|
||||||
|
lastOpcode = opcode
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
SIGHASH_ALL = 1
|
||||||
|
SIGHASH_NONE = 2
|
||||||
|
SIGHASH_SINGLE = 3
|
||||||
|
SIGHASH_ANYONECANPAY = 0x80
|
||||||
|
|
||||||
|
def FindAndDelete(script, sig):
|
||||||
|
"""Consensus critical, see FindAndDelete() in Satoshi codebase"""
|
||||||
|
r = b''
|
||||||
|
last_sop_idx = sop_idx = 0
|
||||||
|
skip = True
|
||||||
|
for (opcode, data, sop_idx) in script.raw_iter():
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:sop_idx]
|
||||||
|
last_sop_idx = sop_idx
|
||||||
|
if script[sop_idx:sop_idx + len(sig)] == sig:
|
||||||
|
skip = True
|
||||||
|
else:
|
||||||
|
skip = False
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:]
|
||||||
|
return CScript(r)
|
||||||
|
|
||||||
|
|
||||||
|
def SignatureHash(script, txTo, inIdx, hashtype):
|
||||||
|
"""Consensus-correct SignatureHash
|
||||||
|
|
||||||
|
Returns (hash, err) to precisely match the consensus-critical behavior of
|
||||||
|
the SIGHASH_SINGLE bug. (inIdx is *not* checked for validity)
|
||||||
|
"""
|
||||||
|
HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
|
||||||
|
if inIdx >= len(txTo.vin):
|
||||||
|
return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin)))
|
||||||
|
txtmp = CTransaction(txTo)
|
||||||
|
|
||||||
|
for txin in txtmp.vin:
|
||||||
|
txin.scriptSig = b''
|
||||||
|
txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR]))
|
||||||
|
|
||||||
|
if (hashtype & 0x1f) == SIGHASH_NONE:
|
||||||
|
txtmp.vout = []
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
elif (hashtype & 0x1f) == SIGHASH_SINGLE:
|
||||||
|
outIdx = inIdx
|
||||||
|
if outIdx >= len(txtmp.vout):
|
||||||
|
return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout)))
|
||||||
|
|
||||||
|
tmp = txtmp.vout[outIdx]
|
||||||
|
txtmp.vout = []
|
||||||
|
for i in range(outIdx):
|
||||||
|
txtmp.vout.append(CTxOut())
|
||||||
|
txtmp.vout.append(tmp)
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
if hashtype & SIGHASH_ANYONECANPAY:
|
||||||
|
tmp = txtmp.vin[inIdx]
|
||||||
|
txtmp.vin = []
|
||||||
|
txtmp.vin.append(tmp)
|
||||||
|
|
||||||
|
s = txtmp.serialize()
|
||||||
|
s += struct.pack(b"<I", hashtype)
|
||||||
|
|
||||||
|
hash = hash256(s)
|
||||||
|
|
||||||
|
return (hash, None)
|
||||||
|
|
||||||
|
# TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided.
|
||||||
|
# Performance optimization probably not necessary for python tests, however.
|
||||||
|
# Note that this corresponds to sigversion == 1 in EvalScript, which is used
|
||||||
|
# for version 0 witnesses.
|
||||||
|
def SegwitVersion1SignatureHash(script, txTo, inIdx, hashtype, amount):
|
||||||
|
|
||||||
|
hashPrevouts = 0
|
||||||
|
hashSequence = 0
|
||||||
|
hashOutputs = 0
|
||||||
|
|
||||||
|
if not (hashtype & SIGHASH_ANYONECANPAY):
|
||||||
|
serialize_prevouts = bytes()
|
||||||
|
for i in txTo.vin:
|
||||||
|
serialize_prevouts += i.prevout.serialize()
|
||||||
|
hashPrevouts = uint256_from_str(hash256(serialize_prevouts))
|
||||||
|
|
||||||
|
if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
|
||||||
|
serialize_sequence = bytes()
|
||||||
|
for i in txTo.vin:
|
||||||
|
serialize_sequence += struct.pack("<I", i.nSequence)
|
||||||
|
hashSequence = uint256_from_str(hash256(serialize_sequence))
|
||||||
|
|
||||||
|
if ((hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
|
||||||
|
serialize_outputs = bytes()
|
||||||
|
for o in txTo.vout:
|
||||||
|
serialize_outputs += o.serialize()
|
||||||
|
hashOutputs = uint256_from_str(hash256(serialize_outputs))
|
||||||
|
elif ((hashtype & 0x1f) == SIGHASH_SINGLE and inIdx < len(txTo.vout)):
|
||||||
|
serialize_outputs = txTo.vout[inIdx].serialize()
|
||||||
|
hashOutputs = uint256_from_str(hash256(serialize_outputs))
|
||||||
|
|
||||||
|
ss = bytes()
|
||||||
|
ss += struct.pack("<i", txTo.nVersion)
|
||||||
|
ss += ser_uint256(hashPrevouts)
|
||||||
|
ss += ser_uint256(hashSequence)
|
||||||
|
ss += txTo.vin[inIdx].prevout.serialize()
|
||||||
|
ss += ser_string(script)
|
||||||
|
ss += struct.pack("<q", amount)
|
||||||
|
ss += struct.pack("<I", txTo.vin[inIdx].nSequence)
|
||||||
|
ss += ser_uint256(hashOutputs)
|
||||||
|
ss += struct.pack("<i", txTo.nLockTime)
|
||||||
|
ss += struct.pack("<I", hashtype)
|
||||||
|
|
||||||
|
return hash256(ss)
|
||||||
63
basicswap/interface/contrib/nav_test_framework/siphash.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2016-2018 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Specialized SipHash-2-4 implementations.
|
||||||
|
|
||||||
|
This implements SipHash-2-4 for 256-bit integers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def rotl64(n, b):
|
||||||
|
return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b
|
||||||
|
|
||||||
|
def siphash_round(v0, v1, v2, v3):
|
||||||
|
v0 = (v0 + v1) & ((1 << 64) - 1)
|
||||||
|
v1 = rotl64(v1, 13)
|
||||||
|
v1 ^= v0
|
||||||
|
v0 = rotl64(v0, 32)
|
||||||
|
v2 = (v2 + v3) & ((1 << 64) - 1)
|
||||||
|
v3 = rotl64(v3, 16)
|
||||||
|
v3 ^= v2
|
||||||
|
v0 = (v0 + v3) & ((1 << 64) - 1)
|
||||||
|
v3 = rotl64(v3, 21)
|
||||||
|
v3 ^= v0
|
||||||
|
v2 = (v2 + v1) & ((1 << 64) - 1)
|
||||||
|
v1 = rotl64(v1, 17)
|
||||||
|
v1 ^= v2
|
||||||
|
v2 = rotl64(v2, 32)
|
||||||
|
return (v0, v1, v2, v3)
|
||||||
|
|
||||||
|
def siphash256(k0, k1, h):
|
||||||
|
n0 = h & ((1 << 64) - 1)
|
||||||
|
n1 = (h >> 64) & ((1 << 64) - 1)
|
||||||
|
n2 = (h >> 128) & ((1 << 64) - 1)
|
||||||
|
n3 = (h >> 192) & ((1 << 64) - 1)
|
||||||
|
v0 = 0x736f6d6570736575 ^ k0
|
||||||
|
v1 = 0x646f72616e646f6d ^ k1
|
||||||
|
v2 = 0x6c7967656e657261 ^ k0
|
||||||
|
v3 = 0x7465646279746573 ^ k1 ^ n0
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n0
|
||||||
|
v3 ^= n1
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n1
|
||||||
|
v3 ^= n2
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n2
|
||||||
|
v3 ^= n3
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= n3
|
||||||
|
v3 ^= 0x2000000000000000
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0 ^= 0x2000000000000000
|
||||||
|
v2 ^= 0xFF
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
|
||||||
|
return v0 ^ v1 ^ v2 ^ v3
|
||||||
700
basicswap/interface/contrib/nav_test_framework/util.py
Executable file
@@ -0,0 +1,700 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2014-2016 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Helpful routines for regression testing
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
from base64 import b64encode
|
||||||
|
from decimal import Decimal, ROUND_DOWN
|
||||||
|
import json
|
||||||
|
import http.client
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import errno
|
||||||
|
|
||||||
|
from . import coverage
|
||||||
|
from .authproxy import AuthServiceProxy, JSONRPCException
|
||||||
|
|
||||||
|
COVERAGE_DIR = None
|
||||||
|
|
||||||
|
# The maximum number of nodes a single test can spawn
|
||||||
|
MAX_NODES = 8
|
||||||
|
# Don't assign rpc or p2p ports lower than this
|
||||||
|
PORT_MIN = 11000
|
||||||
|
# The number of ports to "reserve" for p2p and rpc, each
|
||||||
|
PORT_RANGE = 5000
|
||||||
|
|
||||||
|
NAVCOIND_PROC_WAIT_TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
|
class PortSeed:
|
||||||
|
# Must be initialized with a unique integer for each process
|
||||||
|
n = None
|
||||||
|
|
||||||
|
#Set Mocktime default to OFF.
|
||||||
|
#MOCKTIME is only needed for scripts that use the
|
||||||
|
#cached version of the blockchain. If the cached
|
||||||
|
#version of the blockchain is used without MOCKTIME
|
||||||
|
#then the mempools will not sync due to IBD.
|
||||||
|
MOCKTIME = 0
|
||||||
|
|
||||||
|
def enable_mocktime():
|
||||||
|
#For backwared compatibility of the python scripts
|
||||||
|
#with previous versions of the cache, set MOCKTIME
|
||||||
|
#to Jan 1, 2014 + (201 * 10 * 60)
|
||||||
|
global MOCKTIME
|
||||||
|
MOCKTIME = 1388534400 + (201 * 10 * 60)
|
||||||
|
|
||||||
|
def disable_mocktime():
|
||||||
|
global MOCKTIME
|
||||||
|
MOCKTIME = 0
|
||||||
|
|
||||||
|
def get_mocktime():
|
||||||
|
return MOCKTIME
|
||||||
|
|
||||||
|
def enable_coverage(dirname):
|
||||||
|
"""Maintain a log of which RPC calls are made during testing."""
|
||||||
|
global COVERAGE_DIR
|
||||||
|
COVERAGE_DIR = dirname
|
||||||
|
|
||||||
|
def get_rpc_proxy(url, node_number, timeout=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
url (str): URL of the RPC server to call
|
||||||
|
node_number (int): the node number (or id) that this calls to
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
timeout (int): HTTP timeout in seconds
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AuthServiceProxy. convenience object for making RPC calls.
|
||||||
|
|
||||||
|
"""
|
||||||
|
proxy_kwargs = {}
|
||||||
|
if timeout is not None:
|
||||||
|
proxy_kwargs['timeout'] = timeout
|
||||||
|
|
||||||
|
proxy = AuthServiceProxy(url, **proxy_kwargs)
|
||||||
|
proxy.url = url # store URL on proxy for info
|
||||||
|
|
||||||
|
coverage_logfile = coverage.get_filename(
|
||||||
|
COVERAGE_DIR, node_number) if COVERAGE_DIR else None
|
||||||
|
|
||||||
|
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
|
||||||
|
|
||||||
|
|
||||||
|
def p2p_port(n):
|
||||||
|
assert(n <= MAX_NODES)
|
||||||
|
return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
|
def rpc_port(n):
|
||||||
|
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
|
def check_json_precision():
|
||||||
|
"""Make sure json library being used does not lose precision converting NAV values"""
|
||||||
|
n = Decimal("20000000.00000003")
|
||||||
|
satoshis = int(json.loads(json.dumps(float(n)))*1.0e8)
|
||||||
|
if satoshis != 2000000000000003:
|
||||||
|
raise RuntimeError("JSON encode/decode loses precision")
|
||||||
|
|
||||||
|
def count_bytes(hex_string):
|
||||||
|
return len(bytearray.fromhex(hex_string))
|
||||||
|
|
||||||
|
def bytes_to_hex_str(byte_str):
|
||||||
|
return hexlify(byte_str).decode('ascii')
|
||||||
|
|
||||||
|
def hex_str_to_bytes(hex_str):
|
||||||
|
return unhexlify(hex_str.encode('ascii'))
|
||||||
|
|
||||||
|
def str_to_b64str(string):
|
||||||
|
return b64encode(string.encode('utf-8')).decode('ascii')
|
||||||
|
|
||||||
|
def sync_blocks(rpc_connections, wait=1, timeout=60):
|
||||||
|
"""
|
||||||
|
Wait until everybody has the same tip
|
||||||
|
"""
|
||||||
|
while timeout > 0:
|
||||||
|
tips = [ x.getbestblockhash() for x in rpc_connections ]
|
||||||
|
if tips == [ tips[0] ]*len(tips):
|
||||||
|
#if all x.getblockhash() in tips are the same return True
|
||||||
|
return True
|
||||||
|
time.sleep(wait)
|
||||||
|
timeout -= wait
|
||||||
|
raise AssertionError("Block sync failed")
|
||||||
|
|
||||||
|
def sync_mempools(rpc_connections, wait=1, timeout=60):
|
||||||
|
"""
|
||||||
|
Wait until everybody has the same transactions in their memory
|
||||||
|
pools
|
||||||
|
"""
|
||||||
|
while timeout > 0:
|
||||||
|
pool = set(rpc_connections[0].getrawmempool())
|
||||||
|
num_match = 1
|
||||||
|
for i in range(1, len(rpc_connections)):
|
||||||
|
if set(rpc_connections[i].getrawmempool()) == pool:
|
||||||
|
num_match = num_match+1
|
||||||
|
if num_match == len(rpc_connections):
|
||||||
|
return True
|
||||||
|
time.sleep(wait)
|
||||||
|
timeout -= wait
|
||||||
|
raise AssertionError("Mempool sync failed")
|
||||||
|
|
||||||
|
navcoind_processes = {}
|
||||||
|
|
||||||
|
def initialize_datadir(dirname, n):
|
||||||
|
datadir = os.path.join(dirname, "node"+str(n))
|
||||||
|
if not os.path.isdir(datadir):
|
||||||
|
os.makedirs(datadir)
|
||||||
|
rpc_u, rpc_p = rpc_auth_pair(n)
|
||||||
|
with open(os.path.join(datadir, "navcoin.conf"), 'w') as f:
|
||||||
|
f.write("devnet=1\n")
|
||||||
|
f.write("rpcuser=" + rpc_u + "\n")
|
||||||
|
f.write("rpcpassword=" + rpc_p + "\n")
|
||||||
|
f.write("port="+str(p2p_port(n))+"\n")
|
||||||
|
f.write("rpcport="+str(rpc_port(n))+"\n")
|
||||||
|
f.write("listenonion=0\n")
|
||||||
|
f.write("dandelion=0\n")
|
||||||
|
f.write("ntpminmeasures=-1\n")
|
||||||
|
f.write("torserver=0\n")
|
||||||
|
f.write("suppressblsctwarning=1\n")
|
||||||
|
return datadir
|
||||||
|
|
||||||
|
def rpc_auth_pair(n):
|
||||||
|
return 'rpcuser💻' + str(n), 'rpcpass🔑' + str(n)
|
||||||
|
|
||||||
|
def rpc_url(i, rpchost=None):
|
||||||
|
rpc_u, rpc_p = rpc_auth_pair(i)
|
||||||
|
return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, rpchost or '127.0.0.1', rpc_port(i))
|
||||||
|
|
||||||
|
def wait_for_navcoind_start(process, url, i):
|
||||||
|
'''
|
||||||
|
Wait for navcoind to start. This means that RPC is accessible and fully initialized.
|
||||||
|
Raise an exception if navcoind exits during initialization.
|
||||||
|
'''
|
||||||
|
polls_interval = 1.0 / 4
|
||||||
|
runtime = 60
|
||||||
|
while runtime > 0:
|
||||||
|
if process.poll() is not None:
|
||||||
|
raise Exception('navcoind exited with status %i during initialization' % process.returncode)
|
||||||
|
try:
|
||||||
|
# print('Checking RPC')
|
||||||
|
rpc = get_rpc_proxy(url, i)
|
||||||
|
blocks = rpc.getblockcount()
|
||||||
|
# print('RPC replied with blocks: %i' % blocks)
|
||||||
|
return # break out of loop on success
|
||||||
|
except IOError as e:
|
||||||
|
if e.errno != errno.ECONNREFUSED: # Port not yet open?
|
||||||
|
raise # unknown IO error
|
||||||
|
# else:
|
||||||
|
# print('Waiting for port')
|
||||||
|
except JSONRPCException as e: # Initialization phase
|
||||||
|
if e.error['code'] != -28: # RPC in warmup?
|
||||||
|
raise # unkown JSON RPC exception
|
||||||
|
# else:
|
||||||
|
# print('RPC in warmup')
|
||||||
|
time.sleep(polls_interval)
|
||||||
|
runtime -= polls_interval
|
||||||
|
raise Exception('navcoind RPC timeout')
|
||||||
|
|
||||||
|
def initialize_chain(test_dir, num_nodes):
|
||||||
|
"""
|
||||||
|
Create a cache of a 200-block-long chain (with wallet) for MAX_NODES
|
||||||
|
Afterward, create num_nodes copies from the cache
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert num_nodes <= MAX_NODES
|
||||||
|
create_cache = False
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
if not os.path.isdir(os.path.join('cache', 'node'+str(i))):
|
||||||
|
create_cache = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if create_cache:
|
||||||
|
|
||||||
|
#find and delete old cache directories if any exist
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
if os.path.isdir(os.path.join("cache","node"+str(i))):
|
||||||
|
shutil.rmtree(os.path.join("cache","node"+str(i)))
|
||||||
|
|
||||||
|
# Create cache directories, run navcoinds:
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
datadir=initialize_datadir("cache", i)
|
||||||
|
args = [ os.getenv("NAVCOIND", "navcoind"), "-server", "-keypool=1", "-datadir="+datadir, "-discover=0" ]
|
||||||
|
if i > 0:
|
||||||
|
args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
|
||||||
|
navcoind_processes[i] = subprocess.Popen(args)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print("initialize_chain: navcoind started, waiting for RPC to come up")
|
||||||
|
wait_for_navcoind_start(navcoind_processes[i], rpc_url(i), i)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print("initialize_chain: RPC succesfully started")
|
||||||
|
|
||||||
|
rpcs = []
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
try:
|
||||||
|
rpcs.append(get_rpc_proxy(rpc_url(i), i))
|
||||||
|
except:
|
||||||
|
sys.stderr.write("Error connecting to "+url+"\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create a 200-block-long chain; each of the 4 first nodes
|
||||||
|
# gets 25 mature blocks and 25 immature.
|
||||||
|
# Note: To preserve compatibility with older versions of
|
||||||
|
# initialize_chain, only 4 nodes will generate coins.
|
||||||
|
#
|
||||||
|
# blocks are created with timestamps 10 minutes apart
|
||||||
|
# starting from 2010 minutes in the past
|
||||||
|
enable_mocktime()
|
||||||
|
block_time = get_mocktime() - (201 * 10 * 60)
|
||||||
|
for i in range(2):
|
||||||
|
for peer in range(4):
|
||||||
|
for j in range(25):
|
||||||
|
set_node_times(rpcs, block_time)
|
||||||
|
slow_gen(rpcs[peer], 1)
|
||||||
|
block_time += 10*60
|
||||||
|
# Must sync before next peer starts generating blocks
|
||||||
|
sync_blocks(rpcs)
|
||||||
|
|
||||||
|
# Shut them down, and clean up cache directories:
|
||||||
|
stop_nodes(rpcs)
|
||||||
|
wait_navcoinds()
|
||||||
|
disable_mocktime()
|
||||||
|
for i in range(MAX_NODES):
|
||||||
|
os.remove(log_filename("cache", i, "debug.log"))
|
||||||
|
os.remove(log_filename("cache", i, "db.log"))
|
||||||
|
os.remove(log_filename("cache", i, "peers.dat"))
|
||||||
|
os.remove(log_filename("cache", i, "fee_estimates.dat"))
|
||||||
|
|
||||||
|
for i in range(num_nodes):
|
||||||
|
from_dir = os.path.join("cache", "node"+str(i))
|
||||||
|
to_dir = os.path.join(test_dir, "node"+str(i))
|
||||||
|
shutil.copytree(from_dir, to_dir)
|
||||||
|
initialize_datadir(test_dir, i) # Overwrite port/rpcport in navcoin.conf
|
||||||
|
|
||||||
|
def initialize_chain_clean(test_dir, num_nodes):
|
||||||
|
"""
|
||||||
|
Create an empty blockchain and num_nodes wallets.
|
||||||
|
Useful if a test case wants complete control over initialization.
|
||||||
|
"""
|
||||||
|
for i in range(num_nodes):
|
||||||
|
datadir=initialize_datadir(test_dir, i)
|
||||||
|
|
||||||
|
|
||||||
|
def _rpchost_to_args(rpchost):
|
||||||
|
'''Convert optional IP:port spec to rpcconnect/rpcport args'''
|
||||||
|
if rpchost is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
match = re.match('(\[[0-9a-fA-f:]+\]|[^:]+)(?::([0-9]+))?$', rpchost)
|
||||||
|
if not match:
|
||||||
|
raise ValueError('Invalid RPC host spec ' + rpchost)
|
||||||
|
|
||||||
|
rpcconnect = match.group(1)
|
||||||
|
rpcport = match.group(2)
|
||||||
|
|
||||||
|
if rpcconnect.startswith('['): # remove IPv6 [...] wrapping
|
||||||
|
rpcconnect = rpcconnect[1:-1]
|
||||||
|
|
||||||
|
rv = ['-rpcconnect=' + rpcconnect]
|
||||||
|
if rpcport:
|
||||||
|
rv += ['-rpcport=' + rpcport]
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None):
|
||||||
|
"""
|
||||||
|
Start a navcoind and return RPC connection to it
|
||||||
|
"""
|
||||||
|
datadir = os.path.join(dirname, "node"+str(i))
|
||||||
|
if binary is None:
|
||||||
|
binary = os.getenv("NAVCOIND", "navcoind")
|
||||||
|
args = [ binary, "-datadir="+datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-mocktime="+str(get_mocktime()) ]
|
||||||
|
if extra_args is not None: args.extend(extra_args)
|
||||||
|
navcoind_processes[i] = subprocess.Popen(args)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print("start_node: navcoind started, waiting for RPC to come up")
|
||||||
|
url = rpc_url(i, rpchost)
|
||||||
|
wait_for_navcoind_start(navcoind_processes[i], url, i)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print("start_node: RPC succesfully started")
|
||||||
|
proxy = get_rpc_proxy(url, i, timeout=timewait)
|
||||||
|
|
||||||
|
if COVERAGE_DIR:
|
||||||
|
coverage.write_all_rpc_commands(COVERAGE_DIR, proxy)
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None):
|
||||||
|
"""
|
||||||
|
Start multiple navcoinds, return RPC connections to them
|
||||||
|
"""
|
||||||
|
if extra_args is None: extra_args = [ None for _ in range(num_nodes) ]
|
||||||
|
if binary is None: binary = [ None for _ in range(num_nodes) ]
|
||||||
|
rpcs = []
|
||||||
|
try:
|
||||||
|
for i in range(num_nodes):
|
||||||
|
rpcs.append(start_node(i, dirname, extra_args[i], rpchost, binary=binary[i]))
|
||||||
|
except: # If one node failed to start, stop the others
|
||||||
|
stop_nodes(rpcs)
|
||||||
|
raise
|
||||||
|
return rpcs
|
||||||
|
|
||||||
|
def log_filename(dirname, n_node, logname):
|
||||||
|
return os.path.join(dirname, "node"+str(n_node), "devnet", logname)
|
||||||
|
|
||||||
|
def stop_node(node, i):
|
||||||
|
try:
|
||||||
|
node.stop()
|
||||||
|
except http.client.CannotSendRequest as e:
|
||||||
|
print("WARN: Unable to stop node: " + repr(e))
|
||||||
|
navcoind_processes[i].wait(timeout=NAVCOIND_PROC_WAIT_TIMEOUT)
|
||||||
|
del navcoind_processes[i]
|
||||||
|
|
||||||
|
def stop_nodes(nodes):
|
||||||
|
for node in nodes:
|
||||||
|
try:
|
||||||
|
node.stop()
|
||||||
|
except http.client.CannotSendRequest as e:
|
||||||
|
print("WARN: Unable to stop node: " + repr(e))
|
||||||
|
del nodes[:] # Emptying array closes connections as a side effect
|
||||||
|
|
||||||
|
def set_node_times(nodes, t):
|
||||||
|
for node in nodes:
|
||||||
|
node.setmocktime(t)
|
||||||
|
|
||||||
|
def wait_navcoinds():
|
||||||
|
# Wait for all navcoinds to cleanly exit
|
||||||
|
for navcoind in navcoind_processes.values():
|
||||||
|
navcoind.wait(timeout=NAVCOIND_PROC_WAIT_TIMEOUT)
|
||||||
|
navcoind_processes.clear()
|
||||||
|
|
||||||
|
def connect_nodes(from_connection, node_num):
|
||||||
|
ip_port = "127.0.0.1:"+str(p2p_port(node_num))
|
||||||
|
from_connection.addnode(ip_port, "onetry")
|
||||||
|
# poll until version handshake complete to avoid race conditions
|
||||||
|
# with transaction relaying
|
||||||
|
while any(peer['version'] == 0 for peer in from_connection.getpeerinfo()):
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def connect_nodes_bi(nodes, a, b):
|
||||||
|
connect_nodes(nodes[a], b)
|
||||||
|
connect_nodes(nodes[b], a)
|
||||||
|
|
||||||
|
def find_output(node, txid, amount):
|
||||||
|
"""
|
||||||
|
Return index to output of txid with value amount
|
||||||
|
Raises exception if there is none.
|
||||||
|
"""
|
||||||
|
txdata = node.getrawtransaction(txid, 1)
|
||||||
|
for i in range(len(txdata["vout"])):
|
||||||
|
if txdata["vout"][i]["value"] == amount:
|
||||||
|
return i
|
||||||
|
raise RuntimeError("find_output txid %s : %s not found"%(txid,str(amount)))
|
||||||
|
|
||||||
|
|
||||||
|
def gather_inputs(from_node, amount_needed, confirmations_required=1):
|
||||||
|
"""
|
||||||
|
Return a random set of unspent txouts that are enough to pay amount_needed
|
||||||
|
"""
|
||||||
|
assert(confirmations_required >=0)
|
||||||
|
utxo = from_node.listunspent(confirmations_required)
|
||||||
|
random.shuffle(utxo)
|
||||||
|
inputs = []
|
||||||
|
total_in = Decimal("0.00000000")
|
||||||
|
while total_in < amount_needed and len(utxo) > 0:
|
||||||
|
t = utxo.pop()
|
||||||
|
total_in += t["amount"]
|
||||||
|
inputs.append({ "txid" : t["txid"], "vout" : t["vout"], "address" : t["address"] } )
|
||||||
|
if total_in < amount_needed:
|
||||||
|
raise RuntimeError("Insufficient funds: need %d, have %d"%(amount_needed, total_in))
|
||||||
|
return (total_in, inputs)
|
||||||
|
|
||||||
|
def make_change(from_node, amount_in, amount_out, fee):
|
||||||
|
"""
|
||||||
|
Create change output(s), return them
|
||||||
|
"""
|
||||||
|
outputs = {}
|
||||||
|
amount = amount_out+fee
|
||||||
|
change = amount_in - amount
|
||||||
|
if change > amount*2:
|
||||||
|
# Create an extra change output to break up big inputs
|
||||||
|
change_address = from_node.getnewaddress()
|
||||||
|
# Split change in two, being careful of rounding:
|
||||||
|
outputs[change_address] = Decimal(change/2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||||
|
change = amount_in - amount - outputs[change_address]
|
||||||
|
if change > 0:
|
||||||
|
outputs[from_node.getnewaddress()] = change
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
def send_zeropri_transaction(from_node, to_node, amount, fee):
|
||||||
|
"""
|
||||||
|
Create&broadcast a zero-priority transaction.
|
||||||
|
Returns (txid, hex-encoded-txdata)
|
||||||
|
Ensures transaction is zero-priority by first creating a send-to-self,
|
||||||
|
then using its output
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a send-to-self with confirmed inputs:
|
||||||
|
self_address = from_node.getnewaddress()
|
||||||
|
(total_in, inputs) = gather_inputs(from_node, amount+fee*2)
|
||||||
|
outputs = make_change(from_node, total_in, amount+fee, fee)
|
||||||
|
outputs[self_address] = float(amount+fee)
|
||||||
|
|
||||||
|
self_rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
self_signresult = from_node.signrawtransaction(self_rawtx)
|
||||||
|
self_txid = from_node.sendrawtransaction(self_signresult["hex"], True)
|
||||||
|
|
||||||
|
vout = find_output(from_node, self_txid, amount+fee)
|
||||||
|
# Now immediately spend the output to create a 1-input, 1-output
|
||||||
|
# zero-priority transaction:
|
||||||
|
inputs = [ { "txid" : self_txid, "vout" : vout } ]
|
||||||
|
outputs = { to_node.getnewaddress() : float(amount) }
|
||||||
|
|
||||||
|
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = from_node.signrawtransaction(rawtx)
|
||||||
|
txid = from_node.sendrawtransaction(signresult["hex"], True)
|
||||||
|
|
||||||
|
return (txid, signresult["hex"])
|
||||||
|
|
||||||
|
def random_zeropri_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||||
|
"""
|
||||||
|
Create a random zero-priority transaction.
|
||||||
|
Returns (txid, hex-encoded-transaction-data, fee)
|
||||||
|
"""
|
||||||
|
from_node = random.choice(nodes)
|
||||||
|
to_node = random.choice(nodes)
|
||||||
|
fee = min_fee + fee_increment*random.randint(0,fee_variants)
|
||||||
|
(txid, txhex) = send_zeropri_transaction(from_node, to_node, amount, fee)
|
||||||
|
return (txid, txhex, fee)
|
||||||
|
|
||||||
|
def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||||
|
"""
|
||||||
|
Create a random transaction.
|
||||||
|
Returns (txid, hex-encoded-transaction-data, fee)
|
||||||
|
"""
|
||||||
|
from_node = random.choice(nodes)
|
||||||
|
to_node = random.choice(nodes)
|
||||||
|
fee = min_fee + fee_increment*random.randint(0,fee_variants)
|
||||||
|
|
||||||
|
(total_in, inputs) = gather_inputs(from_node, amount+fee)
|
||||||
|
outputs = make_change(from_node, total_in, amount, fee)
|
||||||
|
outputs[to_node.getnewaddress()] = float(amount)
|
||||||
|
|
||||||
|
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = from_node.signrawtransaction(rawtx)
|
||||||
|
txid = from_node.sendrawtransaction(signresult["hex"], True)
|
||||||
|
|
||||||
|
return (txid, signresult["hex"], fee)
|
||||||
|
|
||||||
|
def assert_fee_amount(fee, tx_size, fee_per_kB):
|
||||||
|
"""Assert the fee was in range"""
|
||||||
|
target_fee = tx_size * fee_per_kB / 1000
|
||||||
|
if fee < target_fee:
|
||||||
|
raise AssertionError("Fee of %s NAV too low! (Should be %s NAV)"%(str(fee), str(target_fee)))
|
||||||
|
# allow the wallet's estimation to be at most 2 bytes off
|
||||||
|
if fee > (tx_size + 2) * fee_per_kB / 1000:
|
||||||
|
raise AssertionError("Fee of %s NAV too high! (Should be %s NAV)"%(str(fee), str(target_fee)))
|
||||||
|
|
||||||
|
def assert_equal(thing1, thing2):
|
||||||
|
if thing1 != thing2:
|
||||||
|
raise AssertionError("%s != %s"%(str(thing1),str(thing2)))
|
||||||
|
|
||||||
|
def assert_greater_than(thing1, thing2):
|
||||||
|
if thing1 <= thing2:
|
||||||
|
raise AssertionError("%s <= %s"%(str(thing1),str(thing2)))
|
||||||
|
|
||||||
|
def assert_raises(exc, fun, *args, **kwds):
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except exc:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Unexpected exception raised: "+type(e).__name__)
|
||||||
|
else:
|
||||||
|
raise AssertionError("No exception raised")
|
||||||
|
|
||||||
|
def assert_is_hex_string(string):
|
||||||
|
try:
|
||||||
|
int(string, 16)
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError(
|
||||||
|
"Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
|
||||||
|
|
||||||
|
def assert_is_hash_string(string, length=64):
|
||||||
|
if not isinstance(string, str):
|
||||||
|
raise AssertionError("Expected a string, got type %r" % type(string))
|
||||||
|
elif length and len(string) != length:
|
||||||
|
raise AssertionError(
|
||||||
|
"String of length %d expected; got %d" % (length, len(string)))
|
||||||
|
elif not re.match('[abcdef0-9]+$', string):
|
||||||
|
raise AssertionError(
|
||||||
|
"String %r contains invalid characters for a hash." % string)
|
||||||
|
|
||||||
|
def assert_array_result(object_array, to_match, expected, should_not_find = False):
|
||||||
|
"""
|
||||||
|
Pass in array of JSON objects, a dictionary with key/value pairs
|
||||||
|
to match against, and another dictionary with expected key/value
|
||||||
|
pairs.
|
||||||
|
If the should_not_find flag is true, to_match should not be found
|
||||||
|
in object_array
|
||||||
|
"""
|
||||||
|
if should_not_find == True:
|
||||||
|
assert_equal(expected, { })
|
||||||
|
num_matched = 0
|
||||||
|
for item in object_array:
|
||||||
|
all_match = True
|
||||||
|
for key,value in to_match.items():
|
||||||
|
if item[key] != value:
|
||||||
|
all_match = False
|
||||||
|
if not all_match:
|
||||||
|
continue
|
||||||
|
elif should_not_find == True:
|
||||||
|
num_matched = num_matched+1
|
||||||
|
for key,value in expected.items():
|
||||||
|
if item[key] != value:
|
||||||
|
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
|
||||||
|
num_matched = num_matched+1
|
||||||
|
if num_matched == 0 and should_not_find != True:
|
||||||
|
raise AssertionError("No objects matched %s"%(str(to_match)))
|
||||||
|
if num_matched > 0 and should_not_find == True:
|
||||||
|
raise AssertionError("Objects were found %s"%(str(to_match)))
|
||||||
|
|
||||||
|
def assert_raises_rpc_error(code, message, fun, *args, **kwds):
|
||||||
|
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
|
||||||
|
Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
|
||||||
|
and verifies that the error code and message are as expected. Throws AssertionError if
|
||||||
|
no JSONRPCException was raised or if the error code/message are not as expected.
|
||||||
|
Args:
|
||||||
|
code (int), optional: the error code returned by the RPC call (defined
|
||||||
|
in src/rpc/protocol.h). Set to None if checking the error code is not required.
|
||||||
|
message (string), optional: [a substring of] the error string returned by the
|
||||||
|
RPC call. Set to None if checking the error string is not required.
|
||||||
|
fun (function): the function to call. This should be the name of an RPC.
|
||||||
|
args*: positional arguments for the function.
|
||||||
|
kwds**: named arguments for the function.
|
||||||
|
"""
|
||||||
|
assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
|
||||||
|
|
||||||
|
def try_rpc(code, message, fun, *args, **kwds):
|
||||||
|
"""Tries to run an rpc command.
|
||||||
|
Test against error code and message if the rpc fails.
|
||||||
|
Returns whether a JSONRPCException was raised."""
|
||||||
|
try:
|
||||||
|
fun(*args, **kwds)
|
||||||
|
except JSONRPCException as e:
|
||||||
|
# JSONRPCException was thrown as expected. Check the code and message values are correct.
|
||||||
|
if (code is not None) and (code != e.error["code"]):
|
||||||
|
raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
|
||||||
|
if (message is not None) and (message not in e.error['message']):
|
||||||
|
raise AssertionError("Expected substring not found:" + e.error['message'])
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def satoshi_round(amount):
|
||||||
|
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
|
||||||
|
|
||||||
|
# Helper to create at least "count" utxos
|
||||||
|
# Pass in a fee that is sufficient for relay and mining new transactions.
|
||||||
|
def create_confirmed_utxos(fee, node, count):
|
||||||
|
node.generate(int(0.5*count)+101)
|
||||||
|
utxos = node.listunspent()
|
||||||
|
iterations = count - len(utxos)
|
||||||
|
addr1 = node.getnewaddress()
|
||||||
|
addr2 = node.getnewaddress()
|
||||||
|
if iterations <= 0:
|
||||||
|
return utxos
|
||||||
|
for i in range(iterations):
|
||||||
|
t = utxos.pop()
|
||||||
|
inputs = []
|
||||||
|
inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
|
||||||
|
outputs = {}
|
||||||
|
send_value = t['amount'] - fee
|
||||||
|
outputs[addr1] = satoshi_round(send_value/2)
|
||||||
|
outputs[addr2] = satoshi_round(send_value/2)
|
||||||
|
raw_tx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signed_tx = node.signrawtransaction(raw_tx)["hex"]
|
||||||
|
txid = node.sendrawtransaction(signed_tx)
|
||||||
|
|
||||||
|
while (node.getmempoolinfo()['size'] > 0):
|
||||||
|
node.generate(1)
|
||||||
|
|
||||||
|
utxos = node.listunspent()
|
||||||
|
assert(len(utxos) >= count)
|
||||||
|
return utxos
|
||||||
|
|
||||||
|
# Create large OP_RETURN txouts that can be appended to a transaction
|
||||||
|
# to make it large (helper for constructing large transactions).
|
||||||
|
def gen_return_txouts():
|
||||||
|
# Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
|
||||||
|
# So we have big transactions (and therefore can't fit very many into each block)
|
||||||
|
# create one script_pubkey
|
||||||
|
script_pubkey = "6a4d0200" #OP_RETURN OP_PUSH2 512 bytes
|
||||||
|
for i in range (512):
|
||||||
|
script_pubkey = script_pubkey + "01"
|
||||||
|
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
|
||||||
|
txouts = "81"
|
||||||
|
for k in range(128):
|
||||||
|
# add txout value
|
||||||
|
txouts = txouts + "0000000000000000"
|
||||||
|
# add length of script_pubkey
|
||||||
|
txouts = txouts + "fd0402"
|
||||||
|
# add script_pubkey
|
||||||
|
txouts = txouts + script_pubkey
|
||||||
|
return txouts
|
||||||
|
|
||||||
|
def create_tx(node, coinbase, to_address, amount):
|
||||||
|
inputs = [{ "txid" : coinbase, "vout" : 0}]
|
||||||
|
outputs = { to_address : amount }
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = node.signrawtransaction(rawtx)
|
||||||
|
assert_equal(signresult["complete"], True)
|
||||||
|
return signresult["hex"]
|
||||||
|
|
||||||
|
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
|
||||||
|
# transaction to make it large. See gen_return_txouts() above.
|
||||||
|
def create_lots_of_big_transactions(node, txouts, utxos, fee):
|
||||||
|
addr = node.getnewaddress()
|
||||||
|
txids = []
|
||||||
|
for i in range(len(utxos)):
|
||||||
|
t = utxos.pop()
|
||||||
|
inputs = []
|
||||||
|
inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
|
||||||
|
outputs = {}
|
||||||
|
send_value = t['amount'] - fee
|
||||||
|
outputs[addr] = satoshi_round(send_value)
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
newtx = rawtx[0:92]
|
||||||
|
newtx = newtx + txouts
|
||||||
|
newtx = newtx + rawtx[94:]
|
||||||
|
signresult = node.signrawtransaction(newtx, None, None, "NONE")
|
||||||
|
txid = node.sendrawtransaction(signresult["hex"], True)
|
||||||
|
txids.append(txid)
|
||||||
|
return txids
|
||||||
|
|
||||||
|
def get_bip9_status(node, key):
|
||||||
|
info = node.getblockchaininfo()
|
||||||
|
return info['bip9_softforks'][key]
|
||||||
|
|
||||||
|
|
||||||
|
def slow_gen(node, count, sleep = 0.1):
|
||||||
|
total = count
|
||||||
|
blocks = []
|
||||||
|
while total > 0:
|
||||||
|
now = min(total, 10)
|
||||||
|
blocks.extend(node.generate(now))
|
||||||
|
total -= now
|
||||||
|
time.sleep(sleep)
|
||||||
|
return blocks
|
||||||
@@ -51,9 +51,6 @@ MSG_TYPE_MASK = 0xffffffff >> 2
|
|||||||
def sha256(s):
|
def sha256(s):
|
||||||
return hashlib.new('sha256', s).digest()
|
return hashlib.new('sha256', s).digest()
|
||||||
|
|
||||||
def ripemd160(s):
|
|
||||||
return hashlib.new('ripemd160', s).digest()
|
|
||||||
|
|
||||||
def hash256(s):
|
def hash256(s):
|
||||||
return sha256(sha256(s))
|
return sha256(sha256(s))
|
||||||
|
|
||||||
|
|||||||
@@ -25,24 +25,24 @@ class DASHInterface(BTCInterface):
|
|||||||
self._wallet_passphrase = ''
|
self._wallet_passphrase = ''
|
||||||
self._have_checked_seed = False
|
self._have_checked_seed = False
|
||||||
|
|
||||||
def seedToMnemonic(self, key):
|
def seedToMnemonic(self, key: bytes) -> str:
|
||||||
return Mnemonic('english').to_mnemonic(key)
|
return Mnemonic('english').to_mnemonic(key)
|
||||||
|
|
||||||
def initialiseWallet(self, key):
|
def initialiseWallet(self, key: bytes):
|
||||||
words = self.seedToMnemonic(key)
|
words = self.seedToMnemonic(key)
|
||||||
|
|
||||||
mnemonic_passphrase = ''
|
mnemonic_passphrase = ''
|
||||||
self.rpc_callback('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
|
self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
|
||||||
self._have_checked_seed = False
|
self._have_checked_seed = False
|
||||||
if self._wallet_passphrase != '':
|
if self._wallet_passphrase != '':
|
||||||
self.unlockWallet(self._wallet_passphrase)
|
self.unlockWallet(self._wallet_passphrase)
|
||||||
|
|
||||||
def decodeAddress(self, address):
|
def decodeAddress(self, address: str) -> bytes:
|
||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def checkExpectedSeed(self, key_hash):
|
def checkExpectedSeed(self, key_hash: str):
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('dumphdinfo')
|
rv = self.rpc_wallet('dumphdinfo')
|
||||||
entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' '))
|
entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' '))
|
||||||
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
||||||
self._have_checked_seed = True
|
self._have_checked_seed = True
|
||||||
@@ -53,10 +53,10 @@ class DASHInterface(BTCInterface):
|
|||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
|
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
|
||||||
return self.rpc_callback('sendtoaddress', params)
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
return self.make_int(self.rpc_wallet('getwalletinfo')['balance'])
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
# Return P2PKH
|
# Return P2PKH
|
||||||
@@ -72,7 +72,7 @@ class DASHInterface(BTCInterface):
|
|||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
rv = self.rpc_wallet('gettransaction', [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import random
|
||||||
import hashlib
|
import hashlib
|
||||||
from .btc import BTCInterface, find_vout_for_address_from_txobj
|
|
||||||
from basicswap.chainparams import Coins
|
|
||||||
|
|
||||||
|
from .btc import BTCInterface, find_vout_for_address_from_txobj
|
||||||
|
from basicswap.util import (
|
||||||
|
i2b,
|
||||||
|
ensure,
|
||||||
|
)
|
||||||
|
from basicswap.rpc import make_rpc_func
|
||||||
|
from basicswap.util.crypto import hash160
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.chainparams import Coins
|
||||||
|
from basicswap.interface.contrib.firo_test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
OP_0,
|
|
||||||
OP_DUP,
|
OP_DUP,
|
||||||
OP_EQUAL,
|
OP_EQUAL,
|
||||||
OP_HASH160,
|
OP_HASH160,
|
||||||
OP_CHECKSIG,
|
OP_CHECKSIG,
|
||||||
OP_EQUALVERIFY,
|
OP_EQUALVERIFY,
|
||||||
hash160,
|
|
||||||
)
|
)
|
||||||
from basicswap.contrib.test_framework.messages import (
|
from basicswap.interface.contrib.firo_test_framework.mininode import (
|
||||||
|
CBlock,
|
||||||
|
FromHex,
|
||||||
CTransaction,
|
CTransaction,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +37,14 @@ class FIROInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.FIRO
|
return Coins.FIRO
|
||||||
|
|
||||||
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
|
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
|
# No multiwallet support
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
|
|
||||||
|
def checkWallets(self) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
def getExchangeName(self, exchange_name):
|
def getExchangeName(self, exchange_name):
|
||||||
return 'zcoin'
|
return 'zcoin'
|
||||||
|
|
||||||
@@ -38,9 +53,9 @@ class FIROInterface(BTCInterface):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit, label='swap_receive'):
|
def getNewAddress(self, use_segwit, label='swap_receive'):
|
||||||
return self.rpc_callback('getnewaddress', [label])
|
return self.rpc('getnewaddress', [label])
|
||||||
# addr_plain = self.rpc_callback('getnewaddress', [label])
|
# addr_plain = self.rpc('getnewaddress', [label])
|
||||||
# return self.rpc_callback('addwitnessaddress', [addr_plain])
|
# return self.rpc('addwitnessaddress', [addr_plain])
|
||||||
|
|
||||||
def decodeAddress(self, address):
|
def decodeAddress(self, address):
|
||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
@@ -52,11 +67,11 @@ class FIROInterface(BTCInterface):
|
|||||||
raise ValueError('TODO')
|
raise ValueError('TODO')
|
||||||
|
|
||||||
def isWatchOnlyAddress(self, address):
|
def isWatchOnlyAddress(self, address):
|
||||||
addr_info = self.rpc_callback('validateaddress', [address])
|
addr_info = self.rpc('validateaddress', [address])
|
||||||
return addr_info['iswatchonly']
|
return addr_info['iswatchonly']
|
||||||
|
|
||||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||||
addr_info = self.rpc_callback('validateaddress', [address])
|
addr_info = self.rpc('validateaddress', [address])
|
||||||
if not or_watch_only:
|
if not or_watch_only:
|
||||||
return addr_info['ismine']
|
return addr_info['ismine']
|
||||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||||
@@ -67,24 +82,23 @@ class FIROInterface(BTCInterface):
|
|||||||
|
|
||||||
if not self.isAddressMine(address, or_watch_only=True):
|
if not self.isAddressMine(address, or_watch_only=True):
|
||||||
# Expects P2WSH nested in BIP16_P2SH
|
# Expects P2WSH nested in BIP16_P2SH
|
||||||
ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
||||||
addr_info = self.rpc_callback('validateaddress', [address])
|
addr_info = self.rpc('validateaddress', [address])
|
||||||
|
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def getLockTxHeightFiro(self, txid, lock_script, bid_amount, rescan_from, find_index=False):
|
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||||
# Add watchonly address and rescan if required
|
# Add watchonly address and rescan if required
|
||||||
lock_tx_dest = self.getScriptDest(lock_script)
|
|
||||||
dest_address = self.encodeScriptDest(lock_tx_dest)
|
|
||||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||||
self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
self.importWatchOnlyAddress(dest_address, 'bid')
|
||||||
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
||||||
self.rpc_callback('rescanblockchain', [rescan_from])
|
self.rescanBlockchainForAddress(rescan_from, dest_address)
|
||||||
|
|
||||||
return_txid = True if txid is None else False
|
return_txid = True if txid is None else False
|
||||||
if txid is None:
|
if txid is None:
|
||||||
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
|
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
|
||||||
|
|
||||||
for tx in txns:
|
for tx in txns:
|
||||||
if self.make_int(tx['amount']) == bid_amount:
|
if self.make_int(tx['amount']) == bid_amount:
|
||||||
@@ -95,11 +109,11 @@ class FIROInterface(BTCInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx = self.rpc_callback('gettransaction', [txid.hex()])
|
tx = self.rpc('gettransaction', [txid.hex()])
|
||||||
|
|
||||||
block_height = 0
|
block_height = 0
|
||||||
if 'blockhash' in tx:
|
if 'blockhash' in tx:
|
||||||
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
|
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
||||||
block_height = block_header['height']
|
block_height = block_header['height']
|
||||||
|
|
||||||
rv = {
|
rv = {
|
||||||
@@ -111,7 +125,7 @@ class FIROInterface(BTCInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if find_index:
|
if find_index:
|
||||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
|
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
||||||
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||||
|
|
||||||
if return_txid:
|
if return_txid:
|
||||||
@@ -130,11 +144,11 @@ class FIROInterface(BTCInterface):
|
|||||||
return self.fundTx(tx_bytes, feerate)
|
return self.fundTx(tx_bytes, feerate)
|
||||||
|
|
||||||
def signTxWithWallet(self, tx):
|
def signTxWithWallet(self, tx):
|
||||||
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
rv = self.rpc('signrawtransaction', [tx.hex()])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||||
options = {
|
options = {
|
||||||
@@ -143,46 +157,45 @@ class FIROInterface(BTCInterface):
|
|||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options['subtractFeeFromOutputs'] = [0,]
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
return self.rpc('fundrawtransaction', [txn, options])['hex']
|
||||||
|
|
||||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
# Return P2PKH
|
# Return P2PKH
|
||||||
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
|
|
||||||
def getScriptDest(self, script: bytearray) -> bytearray:
|
def getScriptDest(self, script: bytearray) -> bytearray:
|
||||||
# P2WSH nested in BIP16_P2SH
|
# P2SH
|
||||||
|
|
||||||
script_hash = hashlib.sha256(script).digest()
|
script_hash = hash160(script)
|
||||||
assert len(script_hash) == 32
|
assert len(script_hash) == 20
|
||||||
script_hash_hash = hash160(script_hash)
|
|
||||||
assert len(script_hash_hash) == 20
|
|
||||||
|
|
||||||
return CScript([OP_HASH160, script_hash_hash, OP_EQUAL])
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
def getSeedHash(self, seed) -> bytes:
|
def getSeedHash(self, seed: bytes) -> bytes:
|
||||||
return hash160(seed)[::-1]
|
return hash160(seed)[::-1]
|
||||||
|
|
||||||
def encodeScriptDest(self, script):
|
def encodeScriptDest(self, script_dest: bytes) -> str:
|
||||||
# Extract hash from script
|
# Extract hash from script
|
||||||
script_hash = script[2:-1]
|
script_hash = script_dest[2:-1]
|
||||||
return self.sh_to_address(script_hash)
|
return self.sh_to_address(script_hash)
|
||||||
|
|
||||||
def getScriptScriptSig(self, script):
|
def getDestForScriptHash(self, script_hash):
|
||||||
return CScript([OP_0, hashlib.sha256(script).digest()])
|
assert len(script_hash) == 20
|
||||||
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee]
|
params = [addr_to, value, '', '', subfee]
|
||||||
return self.rpc_callback('sendtoaddress', params)
|
return self.rpc('sendtoaddress', params)
|
||||||
|
|
||||||
def getWalletSeedID(self):
|
def getWalletSeedID(self):
|
||||||
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']
|
return self.rpc('getwalletinfo')['hdmasterkeyid']
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
||||||
|
|
||||||
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||||
add_bytes = 107
|
add_bytes = 107
|
||||||
@@ -193,13 +206,13 @@ class FIROInterface(BTCInterface):
|
|||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
rv = self.rpc('gettransaction', [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
return None
|
return None
|
||||||
@@ -207,3 +220,148 @@ class FIROInterface(BTCInterface):
|
|||||||
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
||||||
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getProofOfFunds(self, amount_for, extra_commit_bytes):
|
||||||
|
# TODO: Lock unspent and use same output/s to fund bid
|
||||||
|
|
||||||
|
unspents_by_addr = dict()
|
||||||
|
unspents = self.rpc('listunspent')
|
||||||
|
for u in unspents:
|
||||||
|
if u['spendable'] is not True:
|
||||||
|
continue
|
||||||
|
if u['address'] not in unspents_by_addr:
|
||||||
|
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []}
|
||||||
|
utxo_amount: int = self.make_int(u['amount'], r=1)
|
||||||
|
unspents_by_addr[u['address']]['total'] += utxo_amount
|
||||||
|
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout']))
|
||||||
|
|
||||||
|
max_utxos: int = 4
|
||||||
|
|
||||||
|
viable_addrs = []
|
||||||
|
for addr, data in unspents_by_addr.items():
|
||||||
|
if data['total'] >= amount_for:
|
||||||
|
# Sort from largest to smallest amount
|
||||||
|
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0])
|
||||||
|
|
||||||
|
# Max outputs required to reach amount_for
|
||||||
|
utxos_req: int = 0
|
||||||
|
sum_value: int = 0
|
||||||
|
for utxo in sorted_utxos:
|
||||||
|
sum_value += utxo[0]
|
||||||
|
utxos_req += 1
|
||||||
|
if sum_value >= amount_for:
|
||||||
|
break
|
||||||
|
|
||||||
|
if utxos_req <= max_utxos:
|
||||||
|
viable_addrs.append(addr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof')
|
||||||
|
|
||||||
|
sign_for_addr: str = random.choice(viable_addrs)
|
||||||
|
self._log.debug('sign_for_addr %s', sign_for_addr)
|
||||||
|
|
||||||
|
prove_utxos = []
|
||||||
|
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0])
|
||||||
|
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
|
||||||
|
sum_value: int = 0
|
||||||
|
for utxo in sorted_utxos:
|
||||||
|
sum_value += utxo[0]
|
||||||
|
outpoint = (bytes.fromhex(utxo[1]), utxo[2])
|
||||||
|
prove_utxos.append(outpoint)
|
||||||
|
hasher.update(outpoint[0])
|
||||||
|
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
||||||
|
if sum_value >= amount_for:
|
||||||
|
break
|
||||||
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
|
self._log.debug('sign_for_addr %s', sign_for_addr)
|
||||||
|
|
||||||
|
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
||||||
|
# 'Address does not refer to key' for non p2pkh
|
||||||
|
pkh = self.decodeAddress(sign_for_addr)
|
||||||
|
sign_for_addr = self.pkh_to_address(pkh)
|
||||||
|
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
||||||
|
|
||||||
|
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||||
|
|
||||||
|
return (sign_for_addr, signature, prove_utxos)
|
||||||
|
|
||||||
|
def verifyProofOfFunds(self, address, signature, utxos, extra_commit_bytes):
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
sum_value: int = 0
|
||||||
|
for outpoint in utxos:
|
||||||
|
hasher.update(outpoint[0])
|
||||||
|
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
||||||
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
|
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature)
|
||||||
|
ensure(passed is True, 'Proof of funds signature invalid')
|
||||||
|
|
||||||
|
if self.using_segwit():
|
||||||
|
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
|
||||||
|
|
||||||
|
sum_value: int = 0
|
||||||
|
for outpoint in utxos:
|
||||||
|
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
|
||||||
|
sum_value += self.make_int(txout['value'])
|
||||||
|
|
||||||
|
return sum_value
|
||||||
|
|
||||||
|
def rescanBlockchainForAddress(self, height_start: int, addr_find: str):
|
||||||
|
# Very ugly workaround for missing `rescanblockchain` rpc command
|
||||||
|
|
||||||
|
chain_blocks: int = self.getChainHeight()
|
||||||
|
|
||||||
|
current_height: int = chain_blocks
|
||||||
|
block_hash = self.rpc('getblockhash', [current_height])
|
||||||
|
|
||||||
|
script_hash: bytes = self.decodeAddress(addr_find)
|
||||||
|
find_scriptPubKey = self.getDestForScriptHash(script_hash)
|
||||||
|
|
||||||
|
while current_height > height_start:
|
||||||
|
block_hash = self.rpc('getblockhash', [current_height])
|
||||||
|
|
||||||
|
block = self.rpc('getblock', [block_hash, False])
|
||||||
|
decoded_block = CBlock()
|
||||||
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
for tx in decoded_block.vtx:
|
||||||
|
for txo in tx.vout:
|
||||||
|
if txo.scriptPubKey == find_scriptPubKey:
|
||||||
|
tx.rehash()
|
||||||
|
txid = i2b(tx.sha256)
|
||||||
|
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
|
||||||
|
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
|
||||||
|
self.rpc('invalidateblock', [block_hash])
|
||||||
|
self.rpc('reconsiderblock', [block_hash])
|
||||||
|
return
|
||||||
|
current_height -= 1
|
||||||
|
|
||||||
|
def getBlockWithTxns(self, block_hash):
|
||||||
|
# TODO: Bypass decoderawtransaction and getblockheader
|
||||||
|
block = self.rpc('getblock', [block_hash, False])
|
||||||
|
block_header = self.rpc('getblockheader', [block_hash])
|
||||||
|
decoded_block = CBlock()
|
||||||
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
|
||||||
|
tx_rv = []
|
||||||
|
for tx in decoded_block.vtx:
|
||||||
|
tx_hex = tx.serialize_with_witness().hex()
|
||||||
|
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
|
||||||
|
if 'hex' not in tx_dec:
|
||||||
|
tx_dec['hex'] = tx_hex
|
||||||
|
|
||||||
|
tx_rv.append(tx_dec)
|
||||||
|
|
||||||
|
block_rv = {
|
||||||
|
'hash': block_hash,
|
||||||
|
'tx': tx_rv,
|
||||||
|
'confirmations': block_header['confirmations'],
|
||||||
|
'height': block_header['height'],
|
||||||
|
'version': block_header['version'],
|
||||||
|
'merkleroot': block_header['merkleroot'],
|
||||||
|
}
|
||||||
|
|
||||||
|
return block_rv
|
||||||
|
|||||||
@@ -1,15 +1,122 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020 tecnovert
|
# Copyright (c) 2020-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
from .btc import BTCInterface
|
from .btc import BTCInterface
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.rpc import make_rpc_func
|
||||||
|
from basicswap.chainparams import Coins, chainparams
|
||||||
|
|
||||||
|
|
||||||
class LTCInterface(BTCInterface):
|
class LTCInterface(BTCInterface):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.LTC
|
return Coins.LTC
|
||||||
|
|
||||||
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
|
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
|
self._rpc_wallet_mweb = 'mweb'
|
||||||
|
self.rpc_wallet_mweb = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet_mweb)
|
||||||
|
|
||||||
|
def getNewMwebAddress(self, use_segwit=False, label='swap_receive') -> str:
|
||||||
|
return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb'])
|
||||||
|
|
||||||
|
def getNewStealthAddress(self, label=''):
|
||||||
|
return self.getNewMwebAddress(False, label)
|
||||||
|
|
||||||
|
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
||||||
|
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||||
|
if type_from == 'mweb':
|
||||||
|
return self.rpc_wallet_mweb('sendtoaddress', params)
|
||||||
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
|
def getWalletInfo(self):
|
||||||
|
rv = super(LTCInterface, self).getWalletInfo()
|
||||||
|
|
||||||
|
mweb_info = self.rpc_wallet_mweb('getwalletinfo')
|
||||||
|
rv['mweb_balance'] = mweb_info['balance']
|
||||||
|
rv['mweb_unconfirmed'] = mweb_info['unconfirmed_balance']
|
||||||
|
rv['mweb_immature'] = mweb_info['immature_balance']
|
||||||
|
|
||||||
|
# Add unconfirmed mweb -> plain txns to the unconfirmed_balance
|
||||||
|
txns = self.rpc_wallet('listtransactions')
|
||||||
|
for tx in reversed(txns):
|
||||||
|
amount: float = tx.get('amount', 0.0)
|
||||||
|
if tx['confirmations'] == 0 and tx.get('mweb_out', None) and amount > 0.0:
|
||||||
|
rv['unconfirmed_balance'] += amount
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class LTCInterfaceMWEB(LTCInterface):
|
||||||
|
@staticmethod
|
||||||
|
def coin_type():
|
||||||
|
return Coins.LTC_MWEB
|
||||||
|
|
||||||
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
|
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
||||||
|
self._rpc_wallet = 'mweb'
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
||||||
|
|
||||||
|
def chainparams(self):
|
||||||
|
return chainparams[Coins.LTC]
|
||||||
|
|
||||||
|
def chainparams_network(self):
|
||||||
|
return chainparams[Coins.LTC][self._network]
|
||||||
|
|
||||||
|
def coin_name(self) -> str:
|
||||||
|
coin_chainparams = chainparams[Coins.LTC]
|
||||||
|
if coin_chainparams.get('use_ticker_as_name', False):
|
||||||
|
return coin_chainparams['ticker'] + ' MWEB'
|
||||||
|
return coin_chainparams['name'].capitalize() + ' MWEB'
|
||||||
|
|
||||||
|
def ticker(self) -> str:
|
||||||
|
ticker = chainparams[Coins.LTC]['ticker']
|
||||||
|
if self._network == 'testnet':
|
||||||
|
ticker = 't' + ticker
|
||||||
|
elif self._network == 'regtest':
|
||||||
|
ticker = 'rt' + ticker
|
||||||
|
return ticker + '_MWEB'
|
||||||
|
|
||||||
|
def getNewAddress(self, use_segwit=False, label='swap_receive') -> str:
|
||||||
|
return self.getNewMwebAddress()
|
||||||
|
|
||||||
|
def has_mweb_wallet(self) -> bool:
|
||||||
|
return 'mweb' in self.rpc('listwallets')
|
||||||
|
|
||||||
|
def init_wallet(self, password=None):
|
||||||
|
# If system is encrypted mweb wallet will be created at first unlock
|
||||||
|
|
||||||
|
self._log.info('init_wallet - {}'.format(self.ticker()))
|
||||||
|
|
||||||
|
self._log.info('Creating mweb wallet for {}.'.format(self.coin_name()))
|
||||||
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
||||||
|
self.rpc('createwallet', ['mweb', False, True, password, False, False, True])
|
||||||
|
|
||||||
|
if password is not None:
|
||||||
|
# Max timeout value, ~3 years
|
||||||
|
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||||
|
|
||||||
|
if self.getWalletSeedID() == 'Not found':
|
||||||
|
self._sc.initialiseWallet(self.coin_type())
|
||||||
|
|
||||||
|
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
|
||||||
|
self.rpc('unloadwallet', ['mweb'])
|
||||||
|
self.rpc('loadwallet', ['mweb'])
|
||||||
|
if password is not None:
|
||||||
|
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||||
|
self.rpc_wallet('keypoolrefill')
|
||||||
|
|
||||||
|
def unlockWallet(self, password: str):
|
||||||
|
if password == '':
|
||||||
|
return
|
||||||
|
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
||||||
|
|
||||||
|
if not self.has_mweb_wallet():
|
||||||
|
self.init_wallet(password)
|
||||||
|
else:
|
||||||
|
# Max timeout value, ~3 years
|
||||||
|
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||||
|
|
||||||
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|||||||
738
basicswap/interface/nav.py
Normal file
@@ -0,0 +1,738 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2023 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import random
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from coincurve.keys import (
|
||||||
|
PublicKey,
|
||||||
|
PrivateKey,
|
||||||
|
)
|
||||||
|
from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
|
||||||
|
from basicswap.rpc import make_rpc_func
|
||||||
|
from basicswap.chainparams import Coins
|
||||||
|
from basicswap.interface.contrib.nav_test_framework.mininode import (
|
||||||
|
CTxIn,
|
||||||
|
CTxOut,
|
||||||
|
CBlock,
|
||||||
|
COutPoint,
|
||||||
|
CTransaction,
|
||||||
|
CTxInWitness,
|
||||||
|
FromHex,
|
||||||
|
uint256_from_str,
|
||||||
|
)
|
||||||
|
from basicswap.util.crypto import hash160
|
||||||
|
from basicswap.util.address import (
|
||||||
|
decodeWif,
|
||||||
|
pubkeyToAddress,
|
||||||
|
encodeAddress,
|
||||||
|
)
|
||||||
|
from basicswap.util import (
|
||||||
|
i2b, i2h,
|
||||||
|
ensure,
|
||||||
|
)
|
||||||
|
from basicswap.basicswap_util import (
|
||||||
|
getVoutByScriptPubKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
from basicswap.interface.contrib.nav_test_framework.script import (
|
||||||
|
CScript,
|
||||||
|
OP_0,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG,
|
||||||
|
SIGHASH_ALL,
|
||||||
|
SegwitVersion1SignatureHash,
|
||||||
|
)
|
||||||
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
|
|
||||||
|
class NAVInterface(BTCInterface):
|
||||||
|
@staticmethod
|
||||||
|
def coin_type():
|
||||||
|
return Coins.NAV
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def txVersion() -> int:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def txoType():
|
||||||
|
return CTxOut
|
||||||
|
|
||||||
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
|
super(NAVInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
|
# No multiwallet support
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
|
|
||||||
|
def checkWallets(self) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def use_p2shp2wsh(self) -> bool:
|
||||||
|
# p2sh-p2wsh
|
||||||
|
return True
|
||||||
|
|
||||||
|
def seedToMnemonic(self, key):
|
||||||
|
return Mnemonic('english').to_mnemonic(key)
|
||||||
|
|
||||||
|
def initialiseWallet(self, key):
|
||||||
|
# load with -importmnemonic= parameter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getWalletSeedID(self):
|
||||||
|
return self.rpc('getwalletinfo')['hdmasterkeyid']
|
||||||
|
|
||||||
|
def withdrawCoin(self, value, addr_to: str, subfee: bool):
|
||||||
|
strdzeel = ''
|
||||||
|
params = [addr_to, value, '', '', strdzeel, subfee]
|
||||||
|
return self.rpc('sendtoaddress', params)
|
||||||
|
|
||||||
|
def getSpendableBalance(self) -> int:
|
||||||
|
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
||||||
|
|
||||||
|
def signTxWithWallet(self, tx: bytes) -> bytes:
|
||||||
|
rv = self.rpc('signrawtransaction', [tx.hex()])
|
||||||
|
|
||||||
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
|
def checkExpectedSeed(self, key_hash: str):
|
||||||
|
try:
|
||||||
|
rv = self.rpc('dumpmnemonic')
|
||||||
|
entropy = Mnemonic('english').to_entropy(rv.split(' '))
|
||||||
|
|
||||||
|
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
||||||
|
self._have_checked_seed = True
|
||||||
|
return entropy_hash == key_hash
|
||||||
|
except Exception as e:
|
||||||
|
self._log.warning('checkExpectedSeed failed: {}'.format(str(e)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getScriptForP2PKH(self, pkh: bytes) -> bytearray:
|
||||||
|
# Return P2PKH
|
||||||
|
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
|
|
||||||
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
|
# Return P2SH-p2wpkh
|
||||||
|
|
||||||
|
script = CScript([OP_0, pkh])
|
||||||
|
script_hash = hash160(script)
|
||||||
|
assert len(script_hash) == 20
|
||||||
|
|
||||||
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def getInputScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
|
script = CScript([OP_0, pkh])
|
||||||
|
return bytes((len(script),)) + script
|
||||||
|
|
||||||
|
def encodeSegwitAddress(self, pkh: bytes) -> str:
|
||||||
|
# P2SH-p2wpkh
|
||||||
|
script = CScript([OP_0, pkh])
|
||||||
|
script_hash = hash160(script)
|
||||||
|
assert len(script_hash) == 20
|
||||||
|
return encodeAddress(bytes((self.chainparams_network()['script_address'],)) + script_hash)
|
||||||
|
|
||||||
|
def encodeSegwitAddressScript(self, script: bytes) -> str:
|
||||||
|
if len(script) == 23 and script[0] == OP_HASH160 and script[1] == 20 and script[22] == OP_EQUAL:
|
||||||
|
script_hash = script[2:22]
|
||||||
|
return encodeAddress(bytes((self.chainparams_network()['script_address'],)) + script_hash)
|
||||||
|
raise ValueError('Unknown Script')
|
||||||
|
|
||||||
|
def loadTx(self, tx_bytes: bytes) -> CTransaction:
|
||||||
|
# Load tx from bytes to internal representation
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.deserialize(BytesIO(tx_bytes))
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script, prevout_value: int):
|
||||||
|
tx = self.loadTx(tx_bytes)
|
||||||
|
sig_hash = SegwitVersion1SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||||
|
eck = PrivateKey(key_bytes)
|
||||||
|
return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,))
|
||||||
|
|
||||||
|
def setTxSignature(self, tx_bytes: bytes, stack) -> bytes:
|
||||||
|
tx = self.loadTx(tx_bytes)
|
||||||
|
tx.wit.vtxinwit.clear()
|
||||||
|
tx.wit.vtxinwit.append(CTxInWitness())
|
||||||
|
tx.wit.vtxinwit[0].scriptWitness.stack = stack
|
||||||
|
return tx.serialize_with_witness()
|
||||||
|
|
||||||
|
def getProofOfFunds(self, amount_for, extra_commit_bytes):
|
||||||
|
# TODO: Lock unspent and use same output/s to fund bid
|
||||||
|
|
||||||
|
unspents_by_addr = dict()
|
||||||
|
unspents = self.rpc('listunspent')
|
||||||
|
for u in unspents:
|
||||||
|
if u['spendable'] is not True:
|
||||||
|
continue
|
||||||
|
if u['address'] not in unspents_by_addr:
|
||||||
|
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []}
|
||||||
|
utxo_amount: int = self.make_int(u['amount'], r=1)
|
||||||
|
unspents_by_addr[u['address']]['total'] += utxo_amount
|
||||||
|
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout']))
|
||||||
|
|
||||||
|
max_utxos: int = 4
|
||||||
|
|
||||||
|
viable_addrs = []
|
||||||
|
for addr, data in unspents_by_addr.items():
|
||||||
|
if data['total'] >= amount_for:
|
||||||
|
# Sort from largest to smallest amount
|
||||||
|
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0])
|
||||||
|
|
||||||
|
# Max outputs required to reach amount_for
|
||||||
|
utxos_req: int = 0
|
||||||
|
sum_value: int = 0
|
||||||
|
for utxo in sorted_utxos:
|
||||||
|
sum_value += utxo[0]
|
||||||
|
utxos_req += 1
|
||||||
|
if sum_value >= amount_for:
|
||||||
|
break
|
||||||
|
|
||||||
|
if utxos_req <= max_utxos:
|
||||||
|
viable_addrs.append(addr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof')
|
||||||
|
|
||||||
|
sign_for_addr: str = random.choice(viable_addrs)
|
||||||
|
self._log.debug('sign_for_addr %s', sign_for_addr)
|
||||||
|
|
||||||
|
prove_utxos = []
|
||||||
|
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0])
|
||||||
|
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
|
||||||
|
sum_value: int = 0
|
||||||
|
for utxo in sorted_utxos:
|
||||||
|
sum_value += utxo[0]
|
||||||
|
outpoint = (bytes.fromhex(utxo[1]), utxo[2])
|
||||||
|
prove_utxos.append(outpoint)
|
||||||
|
hasher.update(outpoint[0])
|
||||||
|
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
||||||
|
if sum_value >= amount_for:
|
||||||
|
break
|
||||||
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
|
self._log.debug('sign_for_addr %s', sign_for_addr)
|
||||||
|
|
||||||
|
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
||||||
|
# 'Address does not refer to key' for non p2pkh
|
||||||
|
addr_info = self.rpc('validateaddress', [addr, ])
|
||||||
|
if 'isscript' in addr_info and addr_info['isscript'] and 'hex' in addr_info:
|
||||||
|
pkh = bytes.fromhex(addr_info['hex'])[2:]
|
||||||
|
sign_for_addr = self.pkh_to_address(pkh)
|
||||||
|
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
||||||
|
|
||||||
|
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||||
|
|
||||||
|
return (sign_for_addr, signature, prove_utxos)
|
||||||
|
|
||||||
|
def verifyProofOfFunds(self, address, signature, utxos, extra_commit_bytes):
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
sum_value: int = 0
|
||||||
|
for outpoint in utxos:
|
||||||
|
hasher.update(outpoint[0])
|
||||||
|
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
||||||
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
|
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature)
|
||||||
|
ensure(passed is True, 'Proof of funds signature invalid')
|
||||||
|
|
||||||
|
if self.using_segwit():
|
||||||
|
address = self.encodeSegwitAddress(self.decodeAddress(address)[1:])
|
||||||
|
|
||||||
|
sum_value: int = 0
|
||||||
|
for outpoint in utxos:
|
||||||
|
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
|
||||||
|
sum_value += self.make_int(txout['value'])
|
||||||
|
|
||||||
|
return sum_value
|
||||||
|
|
||||||
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
|
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
|
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||||
|
if sub_fee:
|
||||||
|
raise ValueError('Navcoin fundrawtransaction is missing the subtractFeeFromOutputs parameter')
|
||||||
|
# options['subtractFeeFromOutputs'] = [0,]
|
||||||
|
|
||||||
|
fee_rate = self.make_int(fee_rate, r=1)
|
||||||
|
return self.fundTx(txn, fee_rate, lock_unspents).hex()
|
||||||
|
|
||||||
|
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||||
|
addr_info = self.rpc('validateaddress', [address])
|
||||||
|
if not or_watch_only:
|
||||||
|
return addr_info['ismine']
|
||||||
|
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||||
|
|
||||||
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
|
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
||||||
|
|
||||||
|
def getBlockchainInfo(self):
|
||||||
|
rv = self.rpc('getblockchaininfo')
|
||||||
|
synced = round(rv['verificationprogress'], 3)
|
||||||
|
if synced >= 0.997:
|
||||||
|
rv['verificationprogress'] = 1.0
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def encodeScriptDest(self, script_dest: bytes) -> str:
|
||||||
|
script_hash = script_dest[2:-1] # Extract hash from script
|
||||||
|
return self.sh_to_address(script_hash)
|
||||||
|
|
||||||
|
def encode_p2wsh(self, script: bytes) -> str:
|
||||||
|
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
|
||||||
|
|
||||||
|
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
|
||||||
|
txjs = self.rpc('decoderawtransaction', [txn_hex])
|
||||||
|
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'txid': txjs['txid'],
|
||||||
|
'vout': n,
|
||||||
|
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
|
||||||
|
'redeemScript': txn_script.hex(),
|
||||||
|
'amount': txjs['vout'][n]['value']
|
||||||
|
}
|
||||||
|
|
||||||
|
def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str:
|
||||||
|
address: str = self.rpc('getnewaddress', [label,])
|
||||||
|
if use_segwit:
|
||||||
|
return self.rpc('addwitnessaddress', [address,])
|
||||||
|
return address
|
||||||
|
|
||||||
|
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||||
|
|
||||||
|
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
|
||||||
|
scriptSig=self.getScriptScriptSig(txn_script)))
|
||||||
|
pkh = self.decodeAddress(output_addr)
|
||||||
|
script = self.getScriptForPubkeyHash(pkh)
|
||||||
|
tx.vout.append(self.txoType()(output_value, script))
|
||||||
|
tx.rehash()
|
||||||
|
return tx.serialize().hex()
|
||||||
|
|
||||||
|
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes) -> str:
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.nLockTime = locktime
|
||||||
|
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||||
|
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
|
||||||
|
nSequence=sequence,
|
||||||
|
scriptSig=self.getScriptScriptSig(txn_script)))
|
||||||
|
pkh = self.decodeAddress(output_addr)
|
||||||
|
script = self.getScriptForPubkeyHash(pkh)
|
||||||
|
tx.vout.append(self.txoType()(output_value, script))
|
||||||
|
tx.rehash()
|
||||||
|
return tx.serialize().hex()
|
||||||
|
|
||||||
|
def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str:
|
||||||
|
key = decodeWif(key_wif)
|
||||||
|
redeem_script = bytes.fromhex(prevout_data['redeemScript'])
|
||||||
|
sig = self.signTx(key, bytes.fromhex(tx_hex), 0, redeem_script, self.make_int(prevout_data['amount']))
|
||||||
|
|
||||||
|
return sig.hex()
|
||||||
|
|
||||||
|
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||||
|
tx = self.loadTx(tx_bytes)
|
||||||
|
sig_hash = SegwitVersion1SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||||
|
|
||||||
|
pubkey = PublicKey(K)
|
||||||
|
return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte
|
||||||
|
|
||||||
|
def verifyRawTransaction(self, tx_hex: str, prevouts):
|
||||||
|
# Only checks signature
|
||||||
|
# verifyrawtransaction
|
||||||
|
self._log.warning('NAV verifyRawTransaction only checks signature')
|
||||||
|
inputs_valid: bool = False
|
||||||
|
validscripts: int = 0
|
||||||
|
|
||||||
|
tx_bytes = bytes.fromhex(tx_hex)
|
||||||
|
tx = self.loadTx(bytes.fromhex(tx_hex))
|
||||||
|
|
||||||
|
signature = tx.wit.vtxinwit[0].scriptWitness.stack[0]
|
||||||
|
pubkey = tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
||||||
|
|
||||||
|
input_n: int = 0
|
||||||
|
prevout_data = prevouts[input_n]
|
||||||
|
redeem_script = bytes.fromhex(prevout_data['redeemScript'])
|
||||||
|
prevout_value = self.make_int(prevout_data['amount'])
|
||||||
|
|
||||||
|
if self.verifyTxSig(tx_bytes, signature, pubkey, input_n, redeem_script, prevout_value):
|
||||||
|
validscripts += 1
|
||||||
|
|
||||||
|
# TODO: validate inputs
|
||||||
|
inputs_valid = True
|
||||||
|
|
||||||
|
return {
|
||||||
|
'inputs_valid': inputs_valid,
|
||||||
|
'validscripts': validscripts,
|
||||||
|
}
|
||||||
|
|
||||||
|
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
|
||||||
|
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes
|
||||||
|
|
||||||
|
tx_vsize += 184 if redeem else 187
|
||||||
|
return tx_vsize
|
||||||
|
|
||||||
|
def getTxid(self, tx) -> bytes:
|
||||||
|
if isinstance(tx, str):
|
||||||
|
tx = bytes.fromhex(tx)
|
||||||
|
if isinstance(tx, bytes):
|
||||||
|
tx = self.loadTx(tx)
|
||||||
|
tx.rehash()
|
||||||
|
return i2b(tx.sha256)
|
||||||
|
|
||||||
|
def rescanBlockchainForAddress(self, height_start: int, addr_find: str):
|
||||||
|
# Very ugly workaround for missing `rescanblockchain` rpc command
|
||||||
|
|
||||||
|
chain_blocks: int = self.getChainHeight()
|
||||||
|
|
||||||
|
current_height: int = chain_blocks
|
||||||
|
block_hash = self.rpc('getblockhash', [current_height])
|
||||||
|
|
||||||
|
script_hash: bytes = self.decodeAddress(addr_find)
|
||||||
|
find_scriptPubKey = self.getDestForScriptHash(script_hash)
|
||||||
|
|
||||||
|
while current_height > height_start:
|
||||||
|
block_hash = self.rpc('getblockhash', [current_height])
|
||||||
|
|
||||||
|
block = self.rpc('getblock', [block_hash, False])
|
||||||
|
decoded_block = CBlock()
|
||||||
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
for tx in decoded_block.vtx:
|
||||||
|
for txo in tx.vout:
|
||||||
|
if txo.scriptPubKey == find_scriptPubKey:
|
||||||
|
tx.rehash()
|
||||||
|
txid = i2b(tx.sha256)
|
||||||
|
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
|
||||||
|
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
|
||||||
|
self.rpc('invalidateblock', [block_hash])
|
||||||
|
self.rpc('reconsiderblock', [block_hash])
|
||||||
|
return
|
||||||
|
current_height -= 1
|
||||||
|
|
||||||
|
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||||
|
# Add watchonly address and rescan if required
|
||||||
|
|
||||||
|
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||||
|
self.importWatchOnlyAddress(dest_address, 'bid')
|
||||||
|
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
||||||
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
||||||
|
self.rescanBlockchainForAddress(rescan_from, dest_address)
|
||||||
|
|
||||||
|
return_txid = True if txid is None else False
|
||||||
|
if txid is None:
|
||||||
|
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
|
||||||
|
|
||||||
|
for tx in txns:
|
||||||
|
if self.make_int(tx['amount']) == bid_amount:
|
||||||
|
txid = bytes.fromhex(tx['txid'])
|
||||||
|
break
|
||||||
|
|
||||||
|
if txid is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
tx = self.rpc('gettransaction', [txid.hex()])
|
||||||
|
|
||||||
|
block_height = 0
|
||||||
|
if 'blockhash' in tx:
|
||||||
|
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
||||||
|
block_height = block_header['height']
|
||||||
|
|
||||||
|
rv = {
|
||||||
|
'depth': 0 if 'confirmations' not in tx else tx['confirmations'],
|
||||||
|
'height': block_height}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
if find_index:
|
||||||
|
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
||||||
|
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||||
|
|
||||||
|
if return_txid:
|
||||||
|
rv['txid'] = txid.hex()
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def getBlockWithTxns(self, block_hash):
|
||||||
|
# TODO: Bypass decoderawtransaction and getblockheader
|
||||||
|
block = self.rpc('getblock', [block_hash, False])
|
||||||
|
block_header = self.rpc('getblockheader', [block_hash])
|
||||||
|
decoded_block = CBlock()
|
||||||
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
|
||||||
|
tx_rv = []
|
||||||
|
for tx in decoded_block.vtx:
|
||||||
|
tx_hex = tx.serialize_with_witness().hex()
|
||||||
|
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
|
||||||
|
if 'hex' not in tx_dec:
|
||||||
|
tx_dec['hex'] = tx_hex
|
||||||
|
|
||||||
|
tx_rv.append(tx_dec)
|
||||||
|
|
||||||
|
block_rv = {
|
||||||
|
'hash': block_hash,
|
||||||
|
'tx': tx_rv,
|
||||||
|
'confirmations': block_header['confirmations'],
|
||||||
|
'height': block_header['height'],
|
||||||
|
'version': block_header['version'],
|
||||||
|
'merkleroot': block_header['merkleroot'],
|
||||||
|
}
|
||||||
|
|
||||||
|
return block_rv
|
||||||
|
|
||||||
|
def getScriptScriptSig(self, script: bytes) -> bytearray:
|
||||||
|
return self.getP2SHP2WSHScriptSig(script)
|
||||||
|
|
||||||
|
def getScriptDest(self, script):
|
||||||
|
return self.getP2SHP2WSHDest(script)
|
||||||
|
|
||||||
|
def getDestForScriptHash(self, script_hash):
|
||||||
|
assert len(script_hash) == 20
|
||||||
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def pubkey_to_segwit_address(self, pk: bytes) -> str:
|
||||||
|
pkh = hash160(pk)
|
||||||
|
script_out = self.getScriptForPubkeyHash(pkh)
|
||||||
|
return self.encodeSegwitAddressScript(script_out)
|
||||||
|
|
||||||
|
def createBLockTx(self, Kbs: bytes, output_amount: int, vkbv=None) -> bytes:
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
script_pk = self.getPkDest(Kbs)
|
||||||
|
tx.vout.append(self.txoType()(output_amount, script_pk))
|
||||||
|
return tx.serialize()
|
||||||
|
|
||||||
|
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||||
|
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||||
|
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||||
|
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||||
|
|
||||||
|
Kbs = self.getPubkey(kbs)
|
||||||
|
script_pk = self.getPkDest(Kbs)
|
||||||
|
locked_n = findOutput(lock_tx, script_pk)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
pkh_to = self.decodeAddress(address_to)
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
|
||||||
|
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
|
||||||
|
|
||||||
|
script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs))
|
||||||
|
|
||||||
|
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
|
||||||
|
nSequence=0,
|
||||||
|
scriptSig=script_sig))
|
||||||
|
tx.vout.append(self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to)))
|
||||||
|
|
||||||
|
pay_fee = self.getBLockSpendTxFee(tx, b_fee)
|
||||||
|
tx.vout[0].nValue = cb_swap_value - pay_fee
|
||||||
|
|
||||||
|
b_lock_spend_tx = tx.serialize()
|
||||||
|
b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs, cb_swap_value)
|
||||||
|
|
||||||
|
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
|
||||||
|
|
||||||
|
def signTxWithKey(self, tx: bytes, key: bytes, prev_amount: int) -> bytes:
|
||||||
|
Key = self.getPubkey(key)
|
||||||
|
pkh = self.getPubkeyHash(Key)
|
||||||
|
script = self.getScriptForP2PKH(pkh)
|
||||||
|
|
||||||
|
sig = self.signTx(key, tx, 0, script, prev_amount)
|
||||||
|
|
||||||
|
stack = [
|
||||||
|
sig,
|
||||||
|
Key,
|
||||||
|
]
|
||||||
|
return self.setTxSignature(tx, stack)
|
||||||
|
|
||||||
|
def findTxnByHash(self, txid_hex: str):
|
||||||
|
# Only works for wallet txns
|
||||||
|
try:
|
||||||
|
rv = self.rpc('gettransaction', [txid_hex])
|
||||||
|
except Exception as ex:
|
||||||
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
|
return None
|
||||||
|
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
||||||
|
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
||||||
|
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
||||||
|
|
||||||
|
return tx.serialize()
|
||||||
|
|
||||||
|
def fundTx(self, tx_hex: str, feerate: int, lock_unspents: bool = True):
|
||||||
|
feerate_str = self.format_amount(feerate)
|
||||||
|
# TODO: unlock unspents if bid cancelled
|
||||||
|
options = {
|
||||||
|
'lockUnspents': lock_unspents,
|
||||||
|
'feeRate': feerate_str,
|
||||||
|
}
|
||||||
|
rv = self.rpc('fundrawtransaction', [tx_hex, options])
|
||||||
|
|
||||||
|
# Sign transaction then strip witness data to fill scriptsig
|
||||||
|
rv = self.rpc('signrawtransaction', [rv['hex']])
|
||||||
|
|
||||||
|
tx_signed = self.loadTx(bytes.fromhex(rv['hex']))
|
||||||
|
if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit):
|
||||||
|
raise ValueError('txn has non segwit input')
|
||||||
|
for witness_data in tx_signed.wit.vtxinwit:
|
||||||
|
if len(witness_data.scriptWitness.stack) < 2:
|
||||||
|
raise ValueError('txn has non segwit input')
|
||||||
|
|
||||||
|
return tx_signed.serialize_without_witness()
|
||||||
|
|
||||||
|
def fundSCLockTx(self, tx_bytes: bytes, feerate, vkbv=None) -> bytes:
|
||||||
|
tx_funded = self.fundTx(tx_bytes.hex(), feerate)
|
||||||
|
return tx_funded
|
||||||
|
|
||||||
|
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
|
||||||
|
tx_lock = CTransaction()
|
||||||
|
tx_lock = self.loadTx(tx_lock_bytes)
|
||||||
|
|
||||||
|
output_script = self.getScriptDest(script_lock)
|
||||||
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock.rehash()
|
||||||
|
tx_lock_id_int = tx_lock.sha256
|
||||||
|
|
||||||
|
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
|
||||||
|
nSequence=lock1_value,
|
||||||
|
scriptSig=self.getScriptScriptSig(script_lock)))
|
||||||
|
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
|
||||||
|
|
||||||
|
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||||
|
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = round(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx.serialize(), refund_script, tx.vout[0].nValue
|
||||||
|
|
||||||
|
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
|
||||||
|
# Returns the coinA locked coin to the leader
|
||||||
|
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
||||||
|
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
||||||
|
|
||||||
|
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||||
|
|
||||||
|
output_script = self.getScriptDest(script_lock_refund)
|
||||||
|
locked_n = findOutput(tx_lock_refund, output_script)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock_refund.rehash()
|
||||||
|
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
|
||||||
|
nSequence=0,
|
||||||
|
scriptSig=self.getScriptScriptSig(script_lock_refund)))
|
||||||
|
|
||||||
|
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
|
||||||
|
|
||||||
|
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund)
|
||||||
|
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = round(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx.serialize()
|
||||||
|
|
||||||
|
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
|
||||||
|
# lock refund swipe tx
|
||||||
|
# Sends the coinA locked coin to the follower
|
||||||
|
|
||||||
|
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||||
|
|
||||||
|
output_script = self.getScriptDest(script_lock_refund)
|
||||||
|
locked_n = findOutput(tx_lock_refund, output_script)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||||
|
|
||||||
|
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||||
|
|
||||||
|
tx_lock_refund.rehash()
|
||||||
|
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
|
||||||
|
nSequence=lock2_value,
|
||||||
|
scriptSig=self.getScriptScriptSig(script_lock_refund)))
|
||||||
|
|
||||||
|
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
|
||||||
|
|
||||||
|
dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund)
|
||||||
|
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = round(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx.serialize()
|
||||||
|
|
||||||
|
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}):
|
||||||
|
tx_lock = self.loadTx(tx_lock_bytes)
|
||||||
|
output_script = self.getScriptDest(script_lock)
|
||||||
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
|
ensure(locked_n is not None, 'Output not found in tx')
|
||||||
|
locked_coin = tx_lock.vout[locked_n].nValue
|
||||||
|
|
||||||
|
tx_lock.rehash()
|
||||||
|
tx_lock_id_int = tx_lock.sha256
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.nVersion = self.txVersion()
|
||||||
|
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
|
||||||
|
scriptSig=self.getScriptScriptSig(script_lock)))
|
||||||
|
|
||||||
|
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
|
||||||
|
|
||||||
|
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||||
|
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||||
|
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||||
|
pay_fee = round(tx_fee_rate * vsize / 1000)
|
||||||
|
tx.vout[0].nValue = locked_coin - pay_fee
|
||||||
|
|
||||||
|
fee_info['fee_paid'] = pay_fee
|
||||||
|
fee_info['rate_used'] = tx_fee_rate
|
||||||
|
fee_info['witness_bytes'] = witness_bytes
|
||||||
|
fee_info['vsize'] = vsize
|
||||||
|
|
||||||
|
tx.rehash()
|
||||||
|
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
|
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
return tx.serialize()
|
||||||
@@ -19,7 +19,7 @@ class NMCInterface(BTCInterface):
|
|||||||
|
|
||||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
|
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
|
||||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||||
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
||||||
self._log.debug('[rm] scantxoutset end')
|
self._log.debug('[rm] scantxoutset end')
|
||||||
return_txid = True if txid is None else False
|
return_txid = True if txid is None else False
|
||||||
for o in ro['unspents']:
|
for o in ro['unspents']:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -83,14 +83,14 @@ class PARTInterface(BTCInterface):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit, label='swap_receive') -> str:
|
def getNewAddress(self, use_segwit, label='swap_receive') -> str:
|
||||||
return self.rpc_callback('getnewaddress', [label])
|
return self.rpc_wallet('getnewaddress', [label])
|
||||||
|
|
||||||
def getNewStealthAddress(self, label='swap_stealth') -> str:
|
def getNewStealthAddress(self, label='swap_stealth') -> str:
|
||||||
return self.rpc_callback('getnewstealthaddress', [label])
|
return self.rpc_wallet('getnewstealthaddress', [label])
|
||||||
|
|
||||||
def haveSpentIndex(self):
|
def haveSpentIndex(self):
|
||||||
version = self.getDaemonVersion()
|
version = self.getDaemonVersion()
|
||||||
index_info = self.rpc_callback('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
|
index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
|
||||||
return index_info['spentindex']
|
return index_info['spentindex']
|
||||||
|
|
||||||
def initialiseWallet(self, key):
|
def initialiseWallet(self, key):
|
||||||
@@ -98,14 +98,14 @@ class PARTInterface(BTCInterface):
|
|||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee, '', True, self._conf_target]
|
params = [addr_to, value, '', '', subfee, '', True, self._conf_target]
|
||||||
return self.rpc_callback('sendtoaddress', params)
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
def sendTypeTo(self, type_from, type_to, value, addr_to, subfee):
|
def sendTypeTo(self, type_from, type_to, value, addr_to, subfee):
|
||||||
params = [type_from, type_to,
|
params = [type_from, type_to,
|
||||||
[{'address': addr_to, 'amount': value, 'subfee': subfee}, ],
|
[{'address': addr_to, 'amount': value, 'subfee': subfee}, ],
|
||||||
'', '', self._anon_tx_ring_size, 1, False,
|
'', '', self._anon_tx_ring_size, 1, False,
|
||||||
{'conf_target': self._conf_target}]
|
{'conf_target': self._conf_target}]
|
||||||
return self.rpc_callback('sendtypeto', params)
|
return self.rpc_wallet('sendtypeto', params)
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
||||||
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
@@ -122,9 +122,9 @@ class PARTInterface(BTCInterface):
|
|||||||
return length
|
return length
|
||||||
|
|
||||||
def getWalletRestoreHeight(self) -> int:
|
def getWalletRestoreHeight(self) -> int:
|
||||||
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
|
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
||||||
|
|
||||||
blockchaininfo = self.rpc_callback('getblockchaininfo')
|
blockchaininfo = self.getBlockchainInfo()
|
||||||
best_block = blockchaininfo['bestblockhash']
|
best_block = blockchaininfo['bestblockhash']
|
||||||
|
|
||||||
chain_synced = round(blockchaininfo['verificationprogress'], 3)
|
chain_synced = round(blockchaininfo['verificationprogress'], 3)
|
||||||
@@ -132,10 +132,26 @@ class PARTInterface(BTCInterface):
|
|||||||
raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
|
raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
|
||||||
|
|
||||||
self._log.debug('Finding block at time: {}'.format(start_time))
|
self._log.debug('Finding block at time: {}'.format(start_time))
|
||||||
block_hash = self.rpc_callback('getblockhashafter', [start_time])
|
block_hash = self.rpc('getblockhashafter', [start_time])
|
||||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
block_header = self.rpc('getblockheader', [block_hash])
|
||||||
return block_header['height']
|
return block_header['height']
|
||||||
|
|
||||||
|
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
|
||||||
|
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes
|
||||||
|
tx_vsize += 204 if redeem else 187
|
||||||
|
return tx_vsize
|
||||||
|
|
||||||
|
def getUnspentsByAddr(self):
|
||||||
|
unspent_addr = dict()
|
||||||
|
unspent = self.rpc_wallet('listunspent')
|
||||||
|
for u in unspent:
|
||||||
|
if u['spendable'] is not True:
|
||||||
|
continue
|
||||||
|
if 'address' not in u:
|
||||||
|
continue
|
||||||
|
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
|
||||||
|
return unspent_addr
|
||||||
|
|
||||||
|
|
||||||
class PARTInterfaceBlind(PARTInterface):
|
class PARTInterfaceBlind(PARTInterface):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -166,16 +182,16 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
if txo['type'] != 'blind':
|
if txo['type'] != 'blind':
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||||
output_n = txo['n']
|
output_n = txo['n']
|
||||||
|
|
||||||
self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.debug('Searching for locked output: {}'.format(str(e)))
|
self._log.debug('Searching for locked output: {}'.format(str(e)))
|
||||||
continue
|
continue
|
||||||
# Should not be possible for commitment not to match
|
# Should not be possible for commitment not to match
|
||||||
v = self.rpc_callback('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
|
v = self.rpc('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
|
||||||
ensure(v['result'] is True, 'verifycommitment failed')
|
ensure(v['result'] is True, 'verifycommitment failed')
|
||||||
return output_n, blinded_info
|
return output_n, blinded_info
|
||||||
|
|
||||||
@@ -190,7 +206,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
inputs = []
|
inputs = []
|
||||||
outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
||||||
params = [inputs, outputs]
|
params = [inputs, outputs]
|
||||||
rv = self.rpc_callback('createrawparttransaction', params)
|
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||||
|
|
||||||
tx_bytes = bytes.fromhex(rv['hex'])
|
tx_bytes = bytes.fromhex(rv['hex'])
|
||||||
return tx_bytes
|
return tx_bytes
|
||||||
@@ -202,11 +218,11 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
tx_hex = tx_bytes.hex()
|
tx_hex = tx_bytes.hex()
|
||||||
nonce = self.getScriptLockTxNonce(vkbv)
|
nonce = self.getScriptLockTxNonce(vkbv)
|
||||||
|
|
||||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx_hex])
|
tx_obj = self.rpc('decoderawtransaction', [tx_hex])
|
||||||
|
|
||||||
assert (len(tx_obj['vout']) == 1)
|
assert (len(tx_obj['vout']) == 1)
|
||||||
txo = tx_obj['vout'][0]
|
txo = tx_obj['vout'][0]
|
||||||
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||||
|
|
||||||
outputs_info = {0: {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'nonce': nonce.hex()}}
|
outputs_info = {0: {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'nonce': nonce.hex()}}
|
||||||
|
|
||||||
@@ -214,11 +230,11 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
'lockUnspents': True,
|
'lockUnspents': True,
|
||||||
'feeRate': feerate_str,
|
'feeRate': feerate_str,
|
||||||
}
|
}
|
||||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
|
rv = self.rpc('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
|
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||||
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
|
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
|
||||||
# Nonce is derived from vkbv, ephemeral_key isn't used
|
# Nonce is derived from vkbv, ephemeral_key isn't used
|
||||||
ephemeral_key = self.getNewSecretKey()
|
ephemeral_key = self.getNewSecretKey()
|
||||||
@@ -239,7 +255,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
inputs = [{'txid': tx_lock_id, 'vout': spend_n, 'sequence': lock1_value, 'blindingfactor': input_blinded_info['blind']}]
|
inputs = [{'txid': tx_lock_id, 'vout': spend_n, 'sequence': lock1_value, 'blindingfactor': input_blinded_info['blind']}]
|
||||||
outputs = [{'type': 'blind', 'amount': locked_coin, 'address': p2wsh_addr, 'nonce': output_nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
outputs = [{'type': 'blind', 'amount': locked_coin, 'address': p2wsh_addr, 'nonce': output_nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
||||||
params = [inputs, outputs]
|
params = [inputs, outputs]
|
||||||
rv = self.rpc_callback('createrawparttransaction', params)
|
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||||
lock_refund_tx_hex = rv['hex']
|
lock_refund_tx_hex = rv['hex']
|
||||||
|
|
||||||
# Set dummy witness data for fee estimation
|
# Set dummy witness data for fee estimation
|
||||||
@@ -256,7 +272,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
'feeRate': self.format_amount(tx_fee_rate),
|
'feeRate': self.format_amount(tx_fee_rate),
|
||||||
'subtractFeeFromOutputs': [0, ]
|
'subtractFeeFromOutputs': [0, ]
|
||||||
}
|
}
|
||||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
|
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
|
||||||
lock_refund_tx_hex = rv['hex']
|
lock_refund_tx_hex = rv['hex']
|
||||||
|
|
||||||
for vout, txo in rv['output_amounts'].items():
|
for vout, txo in rv['output_amounts'].items():
|
||||||
@@ -270,7 +286,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
||||||
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
||||||
|
|
||||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||||
# Nonce is derived from vkbv
|
# Nonce is derived from vkbv
|
||||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||||
|
|
||||||
@@ -280,7 +296,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
|
|
||||||
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
||||||
addr_out = self.pkh_to_address(pkh_refund_to)
|
addr_out = self.pkh_to_address(pkh_refund_to)
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
|
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
|
||||||
output_pubkey_hex = addr_info['pubkey']
|
output_pubkey_hex = addr_info['pubkey']
|
||||||
|
|
||||||
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
|
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
|
||||||
@@ -288,7 +304,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': 0, 'blindingfactor': input_blinded_info['blind']}]
|
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': 0, 'blindingfactor': input_blinded_info['blind']}]
|
||||||
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
||||||
params = [inputs, outputs]
|
params = [inputs, outputs]
|
||||||
rv = self.rpc_callback('createrawparttransaction', params)
|
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||||
lock_refund_spend_tx_hex = rv['hex']
|
lock_refund_spend_tx_hex = rv['hex']
|
||||||
|
|
||||||
# Set dummy witness data for fee estimation
|
# Set dummy witness data for fee estimation
|
||||||
@@ -306,7 +322,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
'subtractFeeFromOutputs': [0, ]
|
'subtractFeeFromOutputs': [0, ]
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
|
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
|
||||||
lock_refund_spend_tx_hex = rv['hex']
|
lock_refund_spend_tx_hex = rv['hex']
|
||||||
|
|
||||||
return bytes.fromhex(lock_refund_spend_tx_hex)
|
return bytes.fromhex(lock_refund_spend_tx_hex)
|
||||||
@@ -316,7 +332,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
Kal, Kaf,
|
Kal, Kaf,
|
||||||
feerate,
|
feerate,
|
||||||
check_lock_tx_inputs, vkbv):
|
check_lock_tx_inputs, vkbv):
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
lock_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||||
lock_txid_hex = lock_tx_obj['txid']
|
lock_txid_hex = lock_tx_obj['txid']
|
||||||
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
|
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
|
||||||
|
|
||||||
@@ -358,7 +374,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
|
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
|
||||||
prevout_id, prevout_n, prevout_seq, prevout_script,
|
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||||
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
|
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
|
||||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||||
lock_refund_txid_hex = lock_refund_tx_obj['txid']
|
lock_refund_txid_hex = lock_refund_tx_obj['txid']
|
||||||
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
|
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
|
||||||
|
|
||||||
@@ -391,10 +407,10 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
ensure(C == Kaf, 'Bad script pubkey')
|
ensure(C == Kaf, 'Bad script pubkey')
|
||||||
|
|
||||||
# Check rangeproofs and commitments sum
|
# Check rangeproofs and commitments sum
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
|
lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||||
prevout = lock_tx_obj['vout'][prevout_n]
|
prevout = lock_tx_obj['vout'][prevout_n]
|
||||||
prevtxns = [{'txid': prevout_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
prevtxns = [{'txid': prevout_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||||
|
|
||||||
@@ -417,7 +433,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
lock_refund_tx_id, prevout_script,
|
lock_refund_tx_id, prevout_script,
|
||||||
Kal,
|
Kal,
|
||||||
prevout_n, prevout_value, feerate, vkbv):
|
prevout_n, prevout_value, feerate, vkbv):
|
||||||
lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
lock_refund_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||||
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
|
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
|
||||||
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
|
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
|
||||||
|
|
||||||
@@ -436,10 +452,10 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
# Follower is not concerned with them as they pay to leader
|
# Follower is not concerned with them as they pay to leader
|
||||||
|
|
||||||
# Check rangeproofs and commitments sum
|
# Check rangeproofs and commitments sum
|
||||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [lock_refund_tx_bytes.hex()])
|
lock_refund_tx_obj = self.rpc('decoderawtransaction', [lock_refund_tx_bytes.hex()])
|
||||||
prevout = lock_refund_tx_obj['vout'][prevout_n]
|
prevout = lock_refund_tx_obj['vout'][prevout_n]
|
||||||
prevtxns = [{'txid': lock_refund_tx_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
prevtxns = [{'txid': lock_refund_tx_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||||
|
|
||||||
@@ -454,28 +470,28 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
|
lock_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
|
||||||
nonce = self.getScriptLockTxNonce(xmr_swap.vkbv)
|
nonce = self.getScriptLockTxNonce(xmr_swap.vkbv)
|
||||||
output_n, _ = self.findOutputByNonce(lock_tx_obj, nonce)
|
output_n, _ = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||||
ensure(output_n is not None, 'Output not found in tx')
|
ensure(output_n is not None, 'Output not found in tx')
|
||||||
return bytes.fromhex(lock_tx_obj['vout'][output_n]['valueCommitment'])
|
return bytes.fromhex(lock_tx_obj['vout'][output_n]['valueCommitment'])
|
||||||
|
|
||||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
||||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||||
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
||||||
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||||
ensure(output_n is not None, 'Output not found in tx')
|
ensure(output_n is not None, 'Output not found in tx')
|
||||||
return bytes.fromhex(lock_refund_tx_obj['vout'][output_n]['valueCommitment'])
|
return bytes.fromhex(lock_refund_tx_obj['vout'][output_n]['valueCommitment'])
|
||||||
|
|
||||||
def getLockRefundTxSwapOutput(self, xmr_swap):
|
def getLockRefundTxSwapOutput(self, xmr_swap):
|
||||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||||
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
||||||
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||||
ensure(output_n is not None, 'Output not found in tx')
|
ensure(output_n is not None, 'Output not found in tx')
|
||||||
return output_n
|
return output_n
|
||||||
|
|
||||||
def createSCLockSpendTx(self, tx_lock_bytes: bytes, script_lock: bytes, pk_dest: bytes, tx_fee_rate: int, vkbv: bytes, fee_info={}) -> bytes:
|
def createSCLockSpendTx(self, tx_lock_bytes: bytes, script_lock: bytes, pk_dest: bytes, tx_fee_rate: int, vkbv: bytes, fee_info={}) -> bytes:
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||||
lock_txid_hex = lock_tx_obj['txid']
|
lock_txid_hex = lock_tx_obj['txid']
|
||||||
|
|
||||||
ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version')
|
ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||||
@@ -491,7 +507,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
inputs = [{'txid': lock_txid_hex, 'vout': spend_n, 'sequence': 0, 'blindingfactor': blinded_info['blind']}]
|
inputs = [{'txid': lock_txid_hex, 'vout': spend_n, 'sequence': 0, 'blindingfactor': blinded_info['blind']}]
|
||||||
outputs = [{'type': 'blind', 'amount': blinded_info['amount'], 'address': addr_out, 'pubkey': pk_dest.hex()}]
|
outputs = [{'type': 'blind', 'amount': blinded_info['amount'], 'address': addr_out, 'pubkey': pk_dest.hex()}]
|
||||||
params = [inputs, outputs]
|
params = [inputs, outputs]
|
||||||
rv = self.rpc_callback('createrawparttransaction', params)
|
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||||
lock_spend_tx_hex = rv['hex']
|
lock_spend_tx_hex = rv['hex']
|
||||||
|
|
||||||
# Set dummy witness data for fee estimation
|
# Set dummy witness data for fee estimation
|
||||||
@@ -508,9 +524,9 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
'subtractFeeFromOutputs': [0, ]
|
'subtractFeeFromOutputs': [0, ]
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
||||||
lock_spend_tx_hex = rv['hex']
|
lock_spend_tx_hex = rv['hex']
|
||||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [lock_spend_tx_hex])
|
lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex])
|
||||||
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||||
|
|
||||||
# lock_spend_tx_hex does not include the dummy witness stack
|
# lock_spend_tx_hex does not include the dummy witness stack
|
||||||
@@ -530,7 +546,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
def verifySCLockSpendTx(self, tx_bytes,
|
def verifySCLockSpendTx(self, tx_bytes,
|
||||||
lock_tx_bytes, lock_tx_script,
|
lock_tx_bytes, lock_tx_script,
|
||||||
a_pk_f, feerate, vkbv):
|
a_pk_f, feerate, vkbv):
|
||||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
lock_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||||
lock_spend_txid_hex = lock_spend_tx_obj['txid']
|
lock_spend_txid_hex = lock_spend_tx_obj['txid']
|
||||||
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
|
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
|
||||||
|
|
||||||
@@ -538,7 +554,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
ensure(lock_spend_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
ensure(lock_spend_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||||
ensure(len(lock_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
|
ensure(len(lock_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
|
||||||
|
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
|
lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||||
lock_txid_hex = lock_tx_obj['txid']
|
lock_txid_hex = lock_tx_obj['txid']
|
||||||
|
|
||||||
# Find the output of the lock tx to verify
|
# Find the output of the lock tx to verify
|
||||||
@@ -554,7 +570,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
ensure(len(lock_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
|
ensure(len(lock_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
|
||||||
|
|
||||||
addr_out = self.pubkey_to_address(a_pk_f)
|
addr_out = self.pubkey_to_address(a_pk_f)
|
||||||
privkey = self.rpc_callback('dumpprivkey', [addr_out])
|
privkey = self.rpc_wallet('dumpprivkey', [addr_out])
|
||||||
|
|
||||||
# Find output:
|
# Find output:
|
||||||
output_blinded_info = None
|
output_blinded_info = None
|
||||||
@@ -563,7 +579,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
if txo['type'] != 'blind':
|
if txo['type'] != 'blind':
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
output_blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
|
output_blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
|
||||||
output_n = txo['n']
|
output_n = txo['n']
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -572,13 +588,13 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
ensure(output_n is not None, 'Output not found in tx')
|
ensure(output_n is not None, 'Output not found in tx')
|
||||||
|
|
||||||
# Commitment
|
# Commitment
|
||||||
v = self.rpc_callback('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
|
v = self.rpc('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
|
||||||
ensure(v['result'] is True, 'verifycommitment failed')
|
ensure(v['result'] is True, 'verifycommitment failed')
|
||||||
|
|
||||||
# Check rangeproofs and commitments sum
|
# Check rangeproofs and commitments sum
|
||||||
prevout = lock_tx_obj['vout'][spend_n]
|
prevout = lock_tx_obj['vout'][spend_n]
|
||||||
prevtxns = [{'txid': lock_txid_hex, 'vout': spend_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
prevtxns = [{'txid': lock_txid_hex, 'vout': spend_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||||
|
|
||||||
@@ -602,7 +618,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
|
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
|
||||||
# lock refund swipe tx
|
# lock refund swipe tx
|
||||||
# Sends the coinA locked coin to the follower
|
# Sends the coinA locked coin to the follower
|
||||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||||
|
|
||||||
# Find the output of the lock refund tx to spend
|
# Find the output of the lock refund tx to spend
|
||||||
@@ -611,7 +627,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
|
|
||||||
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
||||||
addr_out = self.pkh_to_address(pkh_dest)
|
addr_out = self.pkh_to_address(pkh_dest)
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
|
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
|
||||||
output_pubkey_hex = addr_info['pubkey']
|
output_pubkey_hex = addr_info['pubkey']
|
||||||
|
|
||||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||||
@@ -621,7 +637,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': lock2_value, 'blindingfactor': input_blinded_info['blind']}]
|
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': lock2_value, 'blindingfactor': input_blinded_info['blind']}]
|
||||||
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
||||||
params = [inputs, outputs]
|
params = [inputs, outputs]
|
||||||
rv = self.rpc_callback('createrawparttransaction', params)
|
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||||
|
|
||||||
lock_refund_swipe_tx_hex = rv['hex']
|
lock_refund_swipe_tx_hex = rv['hex']
|
||||||
|
|
||||||
@@ -640,13 +656,13 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
'subtractFeeFromOutputs': [0, ]
|
'subtractFeeFromOutputs': [0, ]
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
|
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
|
||||||
lock_refund_swipe_tx_hex = rv['hex']
|
lock_refund_swipe_tx_hex = rv['hex']
|
||||||
|
|
||||||
return bytes.fromhex(lock_refund_swipe_tx_hex)
|
return bytes.fromhex(lock_refund_swipe_tx_hex)
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getbalances')['mine']['blind_trusted'])
|
return self.make_int(self.rpc_wallet('getbalances')['mine']['blind_trusted'])
|
||||||
|
|
||||||
def publishBLockTx(self, vkbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
def publishBLockTx(self, vkbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||||
Kbv = self.getPubkey(vkbv)
|
Kbv = self.getPubkey(vkbv)
|
||||||
@@ -659,7 +675,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
'', '', self._anon_tx_ring_size, 1, False,
|
'', '', self._anon_tx_ring_size, 1, False,
|
||||||
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
|
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
|
||||||
|
|
||||||
txid = self.rpc_callback('sendtypeto', params)
|
txid = self.rpc_wallet('sendtypeto', params)
|
||||||
return bytes.fromhex(txid)
|
return bytes.fromhex(txid)
|
||||||
|
|
||||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height: int, bid_sender: bool):
|
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height: int, bid_sender: bool):
|
||||||
@@ -670,17 +686,17 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
if bid_sender:
|
if bid_sender:
|
||||||
cb_swap_value *= -1
|
cb_swap_value *= -1
|
||||||
else:
|
else:
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||||
if not addr_info['iswatchonly']:
|
if not addr_info['iswatchonly']:
|
||||||
wif_prefix = self.chainparams_network()['key_prefix']
|
wif_prefix = self.chainparams_network()['key_prefix']
|
||||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||||
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||||
self.rpc_callback('rescanblockchain', [restore_height])
|
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||||
|
|
||||||
params = [{'include_watchonly': True, 'search': sx_addr}]
|
params = [{'include_watchonly': True, 'search': sx_addr}]
|
||||||
txns = self.rpc_callback('filtertransactions', params)
|
txns = self.rpc_wallet('filtertransactions', params)
|
||||||
|
|
||||||
if len(txns) == 1:
|
if len(txns) == 1:
|
||||||
tx = txns[0]
|
tx = txns[0]
|
||||||
@@ -690,7 +706,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||||
height = 0
|
height = 0
|
||||||
if tx['confirmations'] > 0:
|
if tx['confirmations'] > 0:
|
||||||
chain_height = self.rpc_callback('getblockcount')
|
chain_height = self.rpc('getblockcount')
|
||||||
height = chain_height - (tx['confirmations'] - 1)
|
height = chain_height - (tx['confirmations'] - 1)
|
||||||
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
||||||
else:
|
else:
|
||||||
@@ -702,20 +718,20 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||||
if not addr_info['ismine']:
|
if not addr_info['ismine']:
|
||||||
wif_prefix = self.chainparams_network()['key_prefix']
|
wif_prefix = self.chainparams_network()['key_prefix']
|
||||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||||
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
|
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||||
self.rpc_callback('rescanblockchain', [restore_height])
|
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||||
|
|
||||||
# TODO: Remove workaround
|
# TODO: Remove workaround
|
||||||
# utxos = self.rpc_callback('listunspentblind', [1, 9999999, [sx_addr]])
|
# utxos = self.rpc_wallet('listunspentblind', [1, 9999999, [sx_addr]])
|
||||||
utxos = []
|
utxos = []
|
||||||
all_utxos = self.rpc_callback('listunspentblind', [1, 9999999])
|
all_utxos = self.rpc_wallet('listunspentblind', [1, 9999999])
|
||||||
for utxo in all_utxos:
|
for utxo in all_utxos:
|
||||||
if utxo.get('stealth_address', '_') == sx_addr:
|
if utxo.get('stealth_address', '_') == sx_addr:
|
||||||
utxos.append(utxo)
|
utxos.append(utxo)
|
||||||
@@ -736,14 +752,14 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
||||||
'', '', self._anon_tx_ring_size, 1, False,
|
'', '', self._anon_tx_ring_size, 1, False,
|
||||||
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
||||||
rv = self.rpc_callback('sendtypeto', params)
|
rv = self.rpc_wallet('sendtypeto', params)
|
||||||
return bytes.fromhex(rv['txid'])
|
return bytes.fromhex(rv['txid'])
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex):
|
def findTxnByHash(self, txid_hex):
|
||||||
# txindex is enabled for Particl
|
# txindex is enabled for Particl
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
|
rv = self.rpc('getrawtransaction', [txid_hex, True])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
return None
|
return None
|
||||||
@@ -754,7 +770,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
txn = self.rpc_wallet('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': lock_unspents,
|
'lockUnspents': lock_unspents,
|
||||||
@@ -762,7 +778,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options['subtractFeeFromOutputs'] = [0,]
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return self.rpc_callback('fundrawtransactionfrom', ['blind', txn, options])['hex']
|
return self.rpc_wallet('fundrawtransactionfrom', ['blind', txn, options])['hex']
|
||||||
|
|
||||||
|
|
||||||
class PARTInterfaceAnon(PARTInterface):
|
class PARTInterfaceAnon(PARTInterface):
|
||||||
@@ -796,7 +812,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
'', '', self._anon_tx_ring_size, 1, False,
|
'', '', self._anon_tx_ring_size, 1, False,
|
||||||
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
|
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
|
||||||
|
|
||||||
txid = self.rpc_callback('sendtypeto', params)
|
txid = self.rpc_wallet('sendtypeto', params)
|
||||||
return bytes.fromhex(txid)
|
return bytes.fromhex(txid)
|
||||||
|
|
||||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||||
@@ -808,17 +824,17 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
if bid_sender:
|
if bid_sender:
|
||||||
cb_swap_value *= -1
|
cb_swap_value *= -1
|
||||||
else:
|
else:
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||||
if not addr_info['iswatchonly']:
|
if not addr_info['iswatchonly']:
|
||||||
wif_prefix = self.chainparams_network()['key_prefix']
|
wif_prefix = self.chainparams_network()['key_prefix']
|
||||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||||
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||||
self.rpc_callback('rescanblockchain', [restore_height])
|
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||||
|
|
||||||
params = [{'include_watchonly': True, 'search': sx_addr}]
|
params = [{'include_watchonly': True, 'search': sx_addr}]
|
||||||
txns = self.rpc_callback('filtertransactions', params)
|
txns = self.rpc_wallet('filtertransactions', params)
|
||||||
|
|
||||||
if len(txns) == 1:
|
if len(txns) == 1:
|
||||||
tx = txns[0]
|
tx = txns[0]
|
||||||
@@ -828,7 +844,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||||
height = 0
|
height = 0
|
||||||
if tx['confirmations'] > 0:
|
if tx['confirmations'] > 0:
|
||||||
chain_height = self.rpc_callback('getblockcount')
|
chain_height = self.rpc('getblockcount')
|
||||||
height = chain_height - (tx['confirmations'] - 1)
|
height = chain_height - (tx['confirmations'] - 1)
|
||||||
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
||||||
else:
|
else:
|
||||||
@@ -840,17 +856,17 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||||
if not addr_info['ismine']:
|
if not addr_info['ismine']:
|
||||||
wif_prefix = self.chainparams_network()['key_prefix']
|
wif_prefix = self.chainparams_network()['key_prefix']
|
||||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||||
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
|
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||||
self.rpc_callback('rescanblockchain', [restore_height])
|
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||||
|
|
||||||
autxos = self.rpc_callback('listunspentanon', [1, 9999999, [sx_addr]])
|
autxos = self.rpc_wallet('listunspentanon', [1, 9999999, [sx_addr]])
|
||||||
|
|
||||||
if len(autxos) < 1:
|
if len(autxos) < 1:
|
||||||
raise TemporaryError('No spendable outputs')
|
raise TemporaryError('No spendable outputs')
|
||||||
@@ -869,14 +885,14 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
||||||
'', '', self._anon_tx_ring_size, 1, False,
|
'', '', self._anon_tx_ring_size, 1, False,
|
||||||
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
||||||
rv = self.rpc_callback('sendtypeto', params)
|
rv = self.rpc_wallet('sendtypeto', params)
|
||||||
return bytes.fromhex(rv['txid'])
|
return bytes.fromhex(rv['txid'])
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# txindex is enabled for Particl
|
# txindex is enabled for Particl
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
|
rv = self.rpc('getrawtransaction', [txid_hex, True])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
return None
|
return None
|
||||||
@@ -887,4 +903,4 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getbalances')['mine']['anon_trusted'])
|
return self.make_int(self.rpc_wallet('getbalances')['mine']['anon_trusted'])
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from .btc import BTCInterface
|
from .btc import BTCInterface
|
||||||
|
from basicswap.rpc import make_rpc_func
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress
|
||||||
from .contrib.pivx_test_framework.messages import (
|
from .contrib.pivx_test_framework.messages import (
|
||||||
@@ -29,12 +30,20 @@ class PIVXInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.PIVX
|
return Coins.PIVX
|
||||||
|
|
||||||
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
|
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
|
# No multiwallet support
|
||||||
|
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
|
|
||||||
|
def checkWallets(self) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
def signTxWithWallet(self, tx):
|
def signTxWithWallet(self, tx):
|
||||||
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
rv = self.rpc('signrawtransaction', [tx.hex()])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||||
options = {
|
options = {
|
||||||
@@ -43,25 +52,25 @@ class PIVXInterface(BTCInterface):
|
|||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options['subtractFeeFromOutputs'] = [0,]
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
return self.rpc('fundrawtransaction', [txn, options])['hex']
|
||||||
|
|
||||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
||||||
|
|
||||||
def decodeAddress(self, address):
|
def decodeAddress(self, address):
|
||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def getBlockWithTxns(self, block_hash):
|
def getBlockWithTxns(self, block_hash):
|
||||||
# TODO: Bypass decoderawtransaction and getblockheader
|
# TODO: Bypass decoderawtransaction and getblockheader
|
||||||
block = self.rpc_callback('getblock', [block_hash, False])
|
block = self.rpc('getblock', [block_hash, False])
|
||||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
block_header = self.rpc('getblockheader', [block_hash])
|
||||||
decoded_block = CBlock()
|
decoded_block = CBlock()
|
||||||
decoded_block = FromHex(decoded_block, block)
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
|
||||||
tx_rv = []
|
tx_rv = []
|
||||||
for tx in decoded_block.vtx:
|
for tx in decoded_block.vtx:
|
||||||
tx_dec = self.rpc_callback('decoderawtransaction', [ToHex(tx)])
|
tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)])
|
||||||
tx_rv.append(tx_dec)
|
tx_rv.append(tx_dec)
|
||||||
|
|
||||||
block_rv = {
|
block_rv = {
|
||||||
@@ -77,10 +86,10 @@ class PIVXInterface(BTCInterface):
|
|||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee]
|
params = [addr_to, value, '', '', subfee]
|
||||||
return self.rpc_callback('sendtoaddress', params)
|
return self.rpc('sendtoaddress', params)
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
||||||
|
|
||||||
def loadTx(self, tx_bytes):
|
def loadTx(self, tx_bytes):
|
||||||
# Load tx from bytes to internal representation
|
# Load tx from bytes to internal representation
|
||||||
@@ -101,13 +110,13 @@ class PIVXInterface(BTCInterface):
|
|||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv['hex'])
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
rv = self.rpc('gettransaction', [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -27,16 +27,16 @@ from coincurve.dleag import (
|
|||||||
from basicswap.interface import (
|
from basicswap.interface import (
|
||||||
Curves)
|
Curves)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
i2b,
|
i2b, b2i, b2h,
|
||||||
dumpj,
|
dumpj,
|
||||||
ensure,
|
ensure,
|
||||||
make_int,
|
make_int,
|
||||||
TemporaryError)
|
TemporaryError)
|
||||||
|
from basicswap.util.network import (
|
||||||
|
is_private_ip_address)
|
||||||
from basicswap.rpc_xmr import (
|
from basicswap.rpc_xmr import (
|
||||||
make_xmr_rpc_func,
|
make_xmr_rpc_func,
|
||||||
make_xmr_rpc2_func)
|
make_xmr_rpc2_func)
|
||||||
from basicswap.util import (
|
|
||||||
b2i, b2h)
|
|
||||||
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
|
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
|
||||||
|
|
||||||
|
|
||||||
@@ -80,12 +80,6 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(network)
|
super().__init__(network)
|
||||||
daemon_login = None
|
|
||||||
if coin_settings.get('rpcuser', '') != '':
|
|
||||||
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
|
|
||||||
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1'))
|
|
||||||
self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
|
|
||||||
self.rpc_wallet_cb = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
|
|
||||||
|
|
||||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||||
self._restore_height = coin_settings.get('restore_height', 0)
|
self._restore_height = coin_settings.get('restore_height', 0)
|
||||||
@@ -95,6 +89,46 @@ class XMRInterface(CoinInterface):
|
|||||||
self._wallet_password = None
|
self._wallet_password = None
|
||||||
self._have_checked_seed = False
|
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 XMR)'
|
||||||
|
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_xmr_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_xmr_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_xmr_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):
|
def setFeePriority(self, new_priority):
|
||||||
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
|
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
|
||||||
self._fee_priority = new_priority
|
self._fee_priority = new_priority
|
||||||
@@ -105,7 +139,7 @@ class XMRInterface(CoinInterface):
|
|||||||
def createWallet(self, params):
|
def createWallet(self, params):
|
||||||
if self._wallet_password is not None:
|
if self._wallet_password is not None:
|
||||||
params['password'] = self._wallet_password
|
params['password'] = self._wallet_password
|
||||||
rv = self.rpc_wallet_cb('generate_from_keys', params)
|
rv = self.rpc_wallet('generate_from_keys', params)
|
||||||
self._log.info('generate_from_keys %s', dumpj(rv))
|
self._log.info('generate_from_keys %s', dumpj(rv))
|
||||||
|
|
||||||
def openWallet(self, filename):
|
def openWallet(self, filename):
|
||||||
@@ -115,10 +149,10 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Can't reopen the same wallet in windows, !is_keys_file_locked()
|
# Can't reopen the same wallet in windows, !is_keys_file_locked()
|
||||||
self.rpc_wallet_cb('close_wallet')
|
self.rpc_wallet('close_wallet')
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.rpc_wallet_cb('open_wallet', params)
|
self.rpc_wallet('open_wallet', params)
|
||||||
|
|
||||||
def initialiseWallet(self, key_view, key_spend, restore_height=None):
|
def initialiseWallet(self, key_view, key_spend, restore_height=None):
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
@@ -147,14 +181,14 @@ class XMRInterface(CoinInterface):
|
|||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
|
|
||||||
def testDaemonRPC(self, with_wallet=True):
|
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||||
self.rpc_wallet_cb('get_languages')
|
self.rpc_wallet('get_languages')
|
||||||
|
|
||||||
def getDaemonVersion(self):
|
def getDaemonVersion(self):
|
||||||
return self.rpc_wallet_cb('get_version')['version']
|
return self.rpc_wallet('get_version')['version']
|
||||||
|
|
||||||
def getBlockchainInfo(self):
|
def getBlockchainInfo(self):
|
||||||
get_height = self.rpc_cb2('get_height', timeout=30)
|
get_height = self.rpc2('get_height', timeout=self._rpctimeout)
|
||||||
rv = {
|
rv = {
|
||||||
'blocks': get_height['height'],
|
'blocks': get_height['height'],
|
||||||
'verificationprogress': 0.0,
|
'verificationprogress': 0.0,
|
||||||
@@ -165,7 +199,7 @@ class XMRInterface(CoinInterface):
|
|||||||
# get_block_count returns "Internal error" if bootstrap-daemon is active
|
# get_block_count returns "Internal error" if bootstrap-daemon is active
|
||||||
if get_height['untrusted'] is True:
|
if get_height['untrusted'] is True:
|
||||||
rv['bootstrapping'] = True
|
rv['bootstrapping'] = True
|
||||||
get_info = self.rpc_cb2('get_info', timeout=30)
|
get_info = self.rpc2('get_info', timeout=self._rpctimeout)
|
||||||
if 'height_without_bootstrap' in get_info:
|
if 'height_without_bootstrap' in get_info:
|
||||||
rv['blocks'] = get_info['height_without_bootstrap']
|
rv['blocks'] = get_info['height_without_bootstrap']
|
||||||
|
|
||||||
@@ -173,7 +207,7 @@ class XMRInterface(CoinInterface):
|
|||||||
if rv['known_block_count'] > rv['blocks']:
|
if rv['known_block_count'] > rv['blocks']:
|
||||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
||||||
else:
|
else:
|
||||||
rv['known_block_count'] = self.rpc_cb('get_block_count', timeout=30)['count']
|
rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count']
|
||||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.warning('XMR get_block_count failed with: %s', str(e))
|
self._log.warning('XMR get_block_count failed with: %s', str(e))
|
||||||
@@ -182,7 +216,7 @@ class XMRInterface(CoinInterface):
|
|||||||
return rv
|
return rv
|
||||||
|
|
||||||
def getChainHeight(self):
|
def getChainHeight(self):
|
||||||
return self.rpc_cb2('get_height', timeout=30)['height']
|
return self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
@@ -195,8 +229,8 @@ class XMRInterface(CoinInterface):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
rv = {}
|
rv = {}
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet('refresh')
|
||||||
balance_info = self.rpc_wallet_cb('get_balance')
|
balance_info = self.rpc_wallet('get_balance')
|
||||||
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
|
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
|
||||||
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - 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['encrypted'] = False if self._wallet_password is None else True
|
||||||
@@ -209,15 +243,17 @@ class XMRInterface(CoinInterface):
|
|||||||
def getMainWalletAddress(self) -> str:
|
def getMainWalletAddress(self) -> str:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
return self.rpc_wallet_cb('get_address')['address']
|
return self.rpc_wallet('get_address')['address']
|
||||||
|
|
||||||
def getNewAddress(self, placeholder) -> str:
|
def getNewAddress(self, placeholder) -> str:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
return self.rpc_wallet_cb('create_address', {'account_index': 0})['address']
|
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):
|
def get_fee_rate(self, conf_target: int = 2):
|
||||||
self._log.warning('TODO - estimate fee rate?')
|
self._log.warning('TODO - estimate XMR fee rate?')
|
||||||
return 0.0, 'unused'
|
return 0.0, 'unused'
|
||||||
|
|
||||||
def getNewSecretKey(self) -> bytes:
|
def getNewSecretKey(self) -> bytes:
|
||||||
@@ -278,7 +314,7 @@ class XMRInterface(CoinInterface):
|
|||||||
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet('refresh')
|
||||||
|
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
shared_addr = xmr_util.encode_address(Kbv, Kbs)
|
shared_addr = xmr_util.encode_address(Kbv, Kbs)
|
||||||
@@ -286,7 +322,7 @@ class XMRInterface(CoinInterface):
|
|||||||
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
|
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params['priority'] = self._fee_priority
|
||||||
rv = self.rpc_wallet_cb('transfer', params)
|
rv = self.rpc_wallet('transfer', params)
|
||||||
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
|
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
|
||||||
tx_hash = bytes.fromhex(rv['tx_hash'])
|
tx_hash = bytes.fromhex(rv['tx_hash'])
|
||||||
|
|
||||||
@@ -294,7 +330,7 @@ class XMRInterface(CoinInterface):
|
|||||||
i = 0
|
i = 0
|
||||||
while not self._sc.delay_event.is_set():
|
while not self._sc.delay_event.is_set():
|
||||||
gt_params = {'out': True, 'pending': True, 'failed': True, 'pool': True, }
|
gt_params = {'out': True, 'pending': True, 'failed': True, 'pool': True, }
|
||||||
rv = self.rpc_wallet_cb('get_transfers', gt_params)
|
rv = self.rpc_wallet('get_transfers', gt_params)
|
||||||
self._log.debug('get_transfers {}'.format(dumpj(rv)))
|
self._log.debug('get_transfers {}'.format(dumpj(rv)))
|
||||||
if 'pending' not in rv:
|
if 'pending' not in rv:
|
||||||
break
|
break
|
||||||
@@ -323,26 +359,26 @@ class XMRInterface(CoinInterface):
|
|||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(address_b58)
|
self.openWallet(address_b58)
|
||||||
|
|
||||||
self.rpc_wallet_cb('refresh', timeout=600)
|
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# Debug
|
# Debug
|
||||||
try:
|
try:
|
||||||
current_height = self.rpc_wallet_cb('get_height')['height']
|
current_height = self.rpc_wallet('get_height')['height']
|
||||||
self._log.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58)
|
self._log.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.info('rpc_cb failed %s', str(e))
|
self._log.info('rpc failed %s', str(e))
|
||||||
current_height = None # If the transfer is available it will be deep enough
|
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):
|
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||||
'''
|
'''
|
||||||
params = {'transfer_type': 'available'}
|
params = {'transfer_type': 'available'}
|
||||||
transfers = self.rpc_wallet_cb('incoming_transfers', params)
|
transfers = self.rpc_wallet('incoming_transfers', params)
|
||||||
rv = None
|
rv = None
|
||||||
if 'transfers' in transfers:
|
if 'transfers' in transfers:
|
||||||
for transfer in transfers['transfers']:
|
for transfer in transfers['transfers']:
|
||||||
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
||||||
if not transfer['unlocked']:
|
if not transfer['unlocked']:
|
||||||
full_tx = self.rpc_wallet_cb('get_transfer_by_txid', {'txid': transfer['tx_hash']})
|
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
|
||||||
unlock_time = full_tx['transfer']['unlock_time']
|
unlock_time = full_tx['transfer']['unlock_time']
|
||||||
if unlock_time != 0:
|
if unlock_time != 0:
|
||||||
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
|
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
|
||||||
@@ -358,17 +394,17 @@ class XMRInterface(CoinInterface):
|
|||||||
def findTxnByHash(self, txid):
|
def findTxnByHash(self, txid):
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet_cb('refresh', timeout=600)
|
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_height = self.rpc_cb2('get_height', timeout=30)['height']
|
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
||||||
self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid)
|
self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.info('rpc_cb failed %s', str(e))
|
self._log.info('rpc failed %s', str(e))
|
||||||
current_height = None # If the transfer is available it will be deep enough
|
current_height = None # If the transfer is available it will be deep enough
|
||||||
|
|
||||||
params = {'transfer_type': 'available'}
|
params = {'transfer_type': 'available'}
|
||||||
rv = self.rpc_wallet_cb('incoming_transfers', params)
|
rv = self.rpc_wallet('incoming_transfers', params)
|
||||||
if 'transfers' in rv:
|
if 'transfers' in rv:
|
||||||
for transfer in rv['transfers']:
|
for transfer in rv['transfers']:
|
||||||
if transfer['tx_hash'] == txid \
|
if transfer['tx_hash'] == txid \
|
||||||
@@ -403,11 +439,11 @@ class XMRInterface(CoinInterface):
|
|||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(wallet_filename)
|
self.openWallet(wallet_filename)
|
||||||
|
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet('refresh')
|
||||||
rv = self.rpc_wallet_cb('get_balance')
|
rv = self.rpc_wallet('get_balance')
|
||||||
if rv['balance'] < cb_swap_value:
|
if rv['balance'] < cb_swap_value:
|
||||||
self._log.warning('Balance is too low, checking for existing spend.')
|
self._log.warning('Balance is too low, checking for existing spend.')
|
||||||
txns = self.rpc_wallet_cb('get_transfers', {'out': True})
|
txns = self.rpc_wallet('get_transfers', {'out': True})
|
||||||
if 'out' in txns:
|
if 'out' in txns:
|
||||||
txns = txns['out']
|
txns = txns['out']
|
||||||
if len(txns) > 0:
|
if len(txns) > 0:
|
||||||
@@ -432,7 +468,7 @@ class XMRInterface(CoinInterface):
|
|||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params['priority'] = self._fee_priority
|
||||||
|
|
||||||
rv = self.rpc_wallet_cb('sweep_all', params)
|
rv = self.rpc_wallet('sweep_all', params)
|
||||||
self._log.debug('sweep_all {}'.format(json.dumps(rv)))
|
self._log.debug('sweep_all {}'.format(json.dumps(rv)))
|
||||||
|
|
||||||
return bytes.fromhex(rv['tx_hash_list'][0])
|
return bytes.fromhex(rv['tx_hash_list'][0])
|
||||||
@@ -442,24 +478,24 @@ class XMRInterface(CoinInterface):
|
|||||||
value_sats = make_int(value, self.exp())
|
value_sats = make_int(value, self.exp())
|
||||||
|
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet('refresh')
|
||||||
|
|
||||||
if subfee:
|
if subfee:
|
||||||
balance = self.rpc_wallet_cb('get_balance')
|
balance = self.rpc_wallet('get_balance')
|
||||||
diff = balance['unlocked_balance'] - value_sats
|
diff = balance['unlocked_balance'] - value_sats
|
||||||
if diff >= 0 and diff <= 10:
|
if diff >= 0 and diff <= 10:
|
||||||
self._log.info('subfee enabled and value close to total, using sweep_all.')
|
self._log.info('subfee enabled and value close to total, using sweep_all.')
|
||||||
params = {'address': addr_to}
|
params = {'address': addr_to}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params['priority'] = self._fee_priority
|
||||||
rv = self.rpc_wallet_cb('sweep_all', params)
|
rv = self.rpc_wallet('sweep_all', params)
|
||||||
return rv['tx_hash_list'][0]
|
return rv['tx_hash_list'][0]
|
||||||
raise ValueError('Withdraw value must be close to total to use subfee/sweep_all.')
|
raise ValueError('Withdraw value must be close to total to use subfee/sweep_all.')
|
||||||
|
|
||||||
params = {'destinations': [{'amount': value_sats, 'address': addr_to}]}
|
params = {'destinations': [{'amount': value_sats, 'address': addr_to}]}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params['priority'] = self._fee_priority
|
||||||
rv = self.rpc_wallet_cb('transfer', params)
|
rv = self.rpc_wallet('transfer', params)
|
||||||
return rv['tx_hash']
|
return rv['tx_hash']
|
||||||
|
|
||||||
def showLockTransfers(self, kbv, Kbs, restore_height):
|
def showLockTransfers(self, kbv, Kbs, restore_height):
|
||||||
@@ -486,9 +522,9 @@ class XMRInterface(CoinInterface):
|
|||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(address_b58)
|
self.openWallet(address_b58)
|
||||||
|
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet('refresh')
|
||||||
|
|
||||||
rv = self.rpc_wallet_cb('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
|
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
|
||||||
rv['filename'] = wallet_file
|
rv['filename'] = wallet_file
|
||||||
return rv
|
return rv
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -498,8 +534,8 @@ class XMRInterface(CoinInterface):
|
|||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
|
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet('refresh')
|
||||||
balance_info = self.rpc_wallet_cb('get_balance')
|
balance_info = self.rpc_wallet('get_balance')
|
||||||
return balance_info['unlocked_balance']
|
return balance_info['unlocked_balance']
|
||||||
|
|
||||||
def changeWalletPassword(self, old_password, new_password):
|
def changeWalletPassword(self, old_password, new_password):
|
||||||
@@ -509,7 +545,7 @@ class XMRInterface(CoinInterface):
|
|||||||
self._wallet_password = old_password
|
self._wallet_password = old_password
|
||||||
try:
|
try:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet_cb('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
|
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._wallet_password = orig_password
|
self._wallet_password = orig_password
|
||||||
raise e
|
raise e
|
||||||
@@ -534,4 +570,4 @@ class XMRInterface(CoinInterface):
|
|||||||
raise ValueError('Balance too low')
|
raise ValueError('Balance too low')
|
||||||
|
|
||||||
def getTransaction(self, txid: bytes):
|
def getTransaction(self, txid: bytes):
|
||||||
return self.rpc_cb2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -63,6 +63,9 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
|
|||||||
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
|
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
|
||||||
type_to = get_data_entry_or(post_data, 'type_to', 'plain')
|
type_to = get_data_entry_or(post_data, 'type_to', 'plain')
|
||||||
txid_hex = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
|
txid_hex = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
|
||||||
|
elif coin_type == Coins.LTC:
|
||||||
|
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
|
||||||
|
txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee)
|
||||||
else:
|
else:
|
||||||
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee)
|
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee)
|
||||||
|
|
||||||
@@ -81,17 +84,22 @@ def js_coins(self, url_split, post_string, is_json) -> bytes:
|
|||||||
for coin in Coins:
|
for coin in Coins:
|
||||||
cc = swap_client.coin_clients[coin]
|
cc = swap_client.coin_clients[coin]
|
||||||
coin_chainparams = chainparams[cc['coin']]
|
coin_chainparams = chainparams[cc['coin']]
|
||||||
|
coin_active: bool = False if cc['connection_type'] == 'none' else True
|
||||||
|
if coin == Coins.LTC_MWEB:
|
||||||
|
coin_active = False
|
||||||
entry = {
|
entry = {
|
||||||
'id': int(coin),
|
'id': int(coin),
|
||||||
'ticker': coin_chainparams['ticker'],
|
'ticker': coin_chainparams['ticker'],
|
||||||
'name': getCoinName(coin),
|
'name': getCoinName(coin),
|
||||||
'active': False if cc['connection_type'] == 'none' else True,
|
'active': coin_active,
|
||||||
'decimal_places': coin_chainparams['decimal_places'],
|
'decimal_places': coin_chainparams['decimal_places'],
|
||||||
}
|
}
|
||||||
if coin == Coins.PART_ANON:
|
if coin == Coins.PART_ANON:
|
||||||
entry['variant'] = 'Anon'
|
entry['variant'] = 'Anon'
|
||||||
elif coin == Coins.PART_BLIND:
|
elif coin == Coins.PART_BLIND:
|
||||||
entry['variant'] = 'Blind'
|
entry['variant'] = 'Blind'
|
||||||
|
elif coin == Coins.LTC_MWEB:
|
||||||
|
entry['variant'] = 'MWEB'
|
||||||
coins.append(entry)
|
coins.append(entry)
|
||||||
|
|
||||||
return bytes(json.dumps(coins), 'UTF-8')
|
return bytes(json.dumps(coins), 'UTF-8')
|
||||||
@@ -108,19 +116,30 @@ def js_wallets(self, url_split, post_string, is_json):
|
|||||||
cmd = url_split[4]
|
cmd = url_split[4]
|
||||||
if cmd == 'withdraw':
|
if cmd == 'withdraw':
|
||||||
return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8')
|
return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8')
|
||||||
if cmd == 'nextdepositaddr':
|
elif cmd == 'nextdepositaddr':
|
||||||
return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8')
|
return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8')
|
||||||
if cmd == 'createutxo':
|
elif cmd == 'createutxo':
|
||||||
post_data = getFormData(post_string, is_json)
|
post_data = getFormData(post_string, is_json)
|
||||||
ci = swap_client.ci(coin_type)
|
ci = swap_client.ci(coin_type)
|
||||||
value = ci.make_int(get_data_entry(post_data, 'value'))
|
value = ci.make_int(get_data_entry(post_data, 'value'))
|
||||||
txid_hex, new_addr = ci.createUTXO(value)
|
txid_hex, new_addr = ci.createUTXO(value)
|
||||||
return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8')
|
return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8')
|
||||||
if cmd == 'reseed':
|
elif cmd == 'reseed':
|
||||||
swap_client.reseedWallet(coin_type)
|
swap_client.reseedWallet(coin_type)
|
||||||
return bytes(json.dumps({'reseeded': True}), 'UTF-8')
|
return bytes(json.dumps({'reseeded': True}), 'UTF-8')
|
||||||
|
elif cmd == 'newstealthaddress':
|
||||||
|
if coin_type != Coins.PART:
|
||||||
|
raise ValueError('Invalid coin for command')
|
||||||
|
return bytes(json.dumps(swap_client.ci(coin_type).getNewStealthAddress()), 'UTF-8')
|
||||||
|
elif cmd == 'newmwebaddress':
|
||||||
|
if coin_type not in (Coins.LTC, Coins.LTC_MWEB):
|
||||||
|
raise ValueError('Invalid coin for command')
|
||||||
|
return bytes(json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), 'UTF-8')
|
||||||
|
|
||||||
raise ValueError('Unknown command')
|
raise ValueError('Unknown command')
|
||||||
|
|
||||||
|
if coin_type == Coins.LTC_MWEB:
|
||||||
|
coin_type = Coins.LTC
|
||||||
rv = swap_client.getWalletInfo(coin_type)
|
rv = swap_client.getWalletInfo(coin_type)
|
||||||
rv.update(swap_client.getBlockchainInfo(coin_type))
|
rv.update(swap_client.getBlockchainInfo(coin_type))
|
||||||
ci = swap_client.ci(coin_type)
|
ci = swap_client.ci(coin_type)
|
||||||
@@ -647,7 +666,7 @@ def js_setpassword(self, url_split, post_string, is_json) -> bytes:
|
|||||||
if have_data_entry(post_data, 'coin'):
|
if have_data_entry(post_data, 'coin'):
|
||||||
# Set password for one coin
|
# Set password for one coin
|
||||||
coin = getCoinType(get_data_entry(post_data, 'coin'))
|
coin = getCoinType(get_data_entry(post_data, 'coin'))
|
||||||
if coin in (Coins.PART_ANON, Coins.PART_BLIND):
|
if coin in (Coins.PART_ANON, Coins.PART_BLIND, Coins.LTC_MWEB):
|
||||||
raise ValueError('Invalid coin.')
|
raise ValueError('Invalid coin.')
|
||||||
swap_client.changeWalletPasswords(old_password, new_password, coin)
|
swap_client.changeWalletPasswords(old_password, new_password, coin)
|
||||||
return bytes(json.dumps({'success': True}), 'UTF-8')
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ message OfferMessage {
|
|||||||
uint32 protocol_version = 16;
|
uint32 protocol_version = 16;
|
||||||
bool amount_negotiable = 17;
|
bool amount_negotiable = 17;
|
||||||
bool rate_negotiable = 18;
|
bool rate_negotiable = 18;
|
||||||
|
|
||||||
|
bytes proof_utxos = 19; /* 32 byte txid 2 byte vout, repeated */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 2, buyer -> seller */
|
/* Step 2, buyer -> seller */
|
||||||
@@ -46,8 +48,25 @@ message BidMessage {
|
|||||||
string proof_signature = 7;
|
string proof_signature = 7;
|
||||||
|
|
||||||
uint32 protocol_version = 8;
|
uint32 protocol_version = 8;
|
||||||
|
|
||||||
|
bytes proof_utxos = 9; /* 32 byte txid 2 byte vout, repeated */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For tests */
|
||||||
|
message BidMessage_v1Deprecated {
|
||||||
|
bytes offer_msg_id = 1;
|
||||||
|
uint64 time_valid = 2; /* seconds bid is valid for */
|
||||||
|
uint64 amount = 3; /* amount of amount_from bid is for */
|
||||||
|
uint64 rate = 4;
|
||||||
|
bytes pkhash_buyer = 5; /* buyer's address to receive amount_from */
|
||||||
|
string proof_address = 6;
|
||||||
|
string proof_signature = 7;
|
||||||
|
|
||||||
|
uint32 protocol_version = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Step 3, seller -> buyer */
|
/* Step 3, seller -> buyer */
|
||||||
message BidAcceptMessage {
|
message BidAcceptMessage {
|
||||||
bytes bid_msg_id = 1;
|
bytes bid_msg_id = 1;
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
# source: messages.proto
|
# source: messages.proto
|
||||||
"""Generated protocol buffer code."""
|
"""Generated protocol buffer code."""
|
||||||
from google.protobuf.internal import builder as _builder
|
|
||||||
from google.protobuf import descriptor as _descriptor
|
from google.protobuf import descriptor as _descriptor
|
||||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||||
from google.protobuf import symbol_database as _symbol_database
|
from google.protobuf import symbol_database as _symbol_database
|
||||||
|
from google.protobuf.internal import builder as _builder
|
||||||
# @@protoc_insertion_point(imports)
|
# @@protoc_insertion_point(imports)
|
||||||
|
|
||||||
_sym_db = _symbol_database.Default()
|
_sym_db = _symbol_database.Default()
|
||||||
@@ -13,39 +13,41 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xa6\x04\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\x12\x18\n\x10protocol_version\x18\x10 \x01(\r\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xb4\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb2\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x0c\n\x04pkaf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\x12\x18\n\x10protocol_version\x18\t \x01(\r\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x04 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x05 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x08 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\t \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\n \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0b \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x8f\x01\n\x13\x41\x44SBidIntentMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x11\n\tamount_to\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\x12\x18\n\x10protocol_version\x18\x06 \x01(\r\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xa6\x04\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\x12\x18\n\x10protocol_version\x18\x10 \x01(\r\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xc9\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\"\xc1\x01\n\x17\x42idMessage_v1Deprecated\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb2\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x0c\n\x04pkaf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\x12\x18\n\x10protocol_version\x18\t \x01(\r\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x04 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x05 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x08 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\t \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\n \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0b \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x8f\x01\n\x13\x41\x44SBidIntentMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x11\n\tamount_to\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\x12\x18\n\x10protocol_version\x18\x06 \x01(\r\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
|
||||||
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
_globals = globals()
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', globals())
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
_OFFERMESSAGE._serialized_start=30
|
_globals['_OFFERMESSAGE']._serialized_start=30
|
||||||
_OFFERMESSAGE._serialized_end=580
|
_globals['_OFFERMESSAGE']._serialized_end=580
|
||||||
_OFFERMESSAGE_LOCKTYPE._serialized_start=467
|
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_start=467
|
||||||
_OFFERMESSAGE_LOCKTYPE._serialized_end=580
|
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_end=580
|
||||||
_BIDMESSAGE._serialized_start=583
|
_globals['_BIDMESSAGE']._serialized_start=583
|
||||||
_BIDMESSAGE._serialized_end=763
|
_globals['_BIDMESSAGE']._serialized_end=784
|
||||||
_BIDACCEPTMESSAGE._serialized_start=765
|
_globals['_BIDMESSAGE_V1DEPRECATED']._serialized_start=787
|
||||||
_BIDACCEPTMESSAGE._serialized_end=851
|
_globals['_BIDMESSAGE_V1DEPRECATED']._serialized_end=980
|
||||||
_OFFERREVOKEMESSAGE._serialized_start=853
|
_globals['_BIDACCEPTMESSAGE']._serialized_start=982
|
||||||
_OFFERREVOKEMESSAGE._serialized_end=914
|
_globals['_BIDACCEPTMESSAGE']._serialized_end=1068
|
||||||
_BIDREJECTMESSAGE._serialized_start=916
|
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1070
|
||||||
_BIDREJECTMESSAGE._serialized_end=975
|
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1131
|
||||||
_XMRBIDMESSAGE._serialized_start=978
|
_globals['_BIDREJECTMESSAGE']._serialized_start=1133
|
||||||
_XMRBIDMESSAGE._serialized_end=1156
|
_globals['_BIDREJECTMESSAGE']._serialized_end=1192
|
||||||
_XMRSPLITMESSAGE._serialized_start=1158
|
_globals['_XMRBIDMESSAGE']._serialized_start=1195
|
||||||
_XMRSPLITMESSAGE._serialized_end=1242
|
_globals['_XMRBIDMESSAGE']._serialized_end=1373
|
||||||
_XMRBIDACCEPTMESSAGE._serialized_start=1245
|
_globals['_XMRSPLITMESSAGE']._serialized_start=1375
|
||||||
_XMRBIDACCEPTMESSAGE._serialized_end=1501
|
_globals['_XMRSPLITMESSAGE']._serialized_end=1459
|
||||||
_XMRBIDLOCKTXSIGSMESSAGE._serialized_start=1503
|
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1462
|
||||||
_XMRBIDLOCKTXSIGSMESSAGE._serialized_end=1617
|
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1718
|
||||||
_XMRBIDLOCKSPENDTXMESSAGE._serialized_start=1619
|
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1720
|
||||||
_XMRBIDLOCKSPENDTXMESSAGE._serialized_end=1707
|
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1834
|
||||||
_XMRBIDLOCKRELEASEMESSAGE._serialized_start=1709
|
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1836
|
||||||
_XMRBIDLOCKRELEASEMESSAGE._serialized_end=1786
|
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1924
|
||||||
_ADSBIDINTENTMESSAGE._serialized_start=1789
|
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1926
|
||||||
_ADSBIDINTENTMESSAGE._serialized_end=1932
|
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=2003
|
||||||
_ADSBIDINTENTACCEPTMESSAGE._serialized_start=1934
|
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=2006
|
||||||
_ADSBIDINTENTACCEPTMESSAGE._serialized_end=2046
|
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2149
|
||||||
|
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2151
|
||||||
|
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2263
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import queue
|
|||||||
import random
|
import random
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import struct
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
@@ -41,7 +40,7 @@ from basicswap.contrib.rfc6979 import (
|
|||||||
|
|
||||||
|
|
||||||
START_TOKEN = 0xabcd
|
START_TOKEN = 0xabcd
|
||||||
MSG_START_TOKEN = struct.pack('>H', START_TOKEN)
|
MSG_START_TOKEN = START_TOKEN.to_bytes(2, 'big')
|
||||||
|
|
||||||
MSG_MAX_SIZE = 0x200000 # 2MB
|
MSG_MAX_SIZE = 0x200000 # 2MB
|
||||||
|
|
||||||
@@ -83,8 +82,8 @@ class MsgHandshake:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def encode_aad(self): # Additional Authenticated Data
|
def encode_aad(self): # Additional Authenticated Data
|
||||||
return struct.pack('>H', NetMessageTypes.HANDSHAKE) + \
|
return int(NetMessageTypes.HANDSHAKE).to_bytes(2, 'big') + \
|
||||||
struct.pack('>Q', self._timestamp) + \
|
self._timestamp.to_bytes(8, 'big') + \
|
||||||
self._ephem_pk
|
self._ephem_pk
|
||||||
|
|
||||||
def encode(self):
|
def encode(self):
|
||||||
@@ -92,7 +91,7 @@ class MsgHandshake:
|
|||||||
|
|
||||||
def decode(self, msg_mv):
|
def decode(self, msg_mv):
|
||||||
o = 2
|
o = 2
|
||||||
self._timestamp = struct.unpack('>Q', msg_mv[o: o + 8])[0]
|
self._timestamp = int.from_bytes(msg_mv[o: o + 8], 'big')
|
||||||
o += 8
|
o += 8
|
||||||
self._ephem_pk = bytes(msg_mv[o: o + 33])
|
self._ephem_pk = bytes(msg_mv[o: o + 33])
|
||||||
o += 33
|
o += 33
|
||||||
@@ -333,7 +332,7 @@ class Network:
|
|||||||
|
|
||||||
ss = k.ecdh(peer._pubkey)
|
ss = k.ecdh(peer._pubkey)
|
||||||
|
|
||||||
hashed = hashlib.sha512(ss + struct.pack('>Q', msg._timestamp)).digest()
|
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).digest()
|
||||||
peer._ke = hashed[:32]
|
peer._ke = hashed[:32]
|
||||||
peer._km = hashed[32:]
|
peer._km = hashed[32:]
|
||||||
|
|
||||||
@@ -386,7 +385,7 @@ class Network:
|
|||||||
nk = PrivateKey(self._network_key)
|
nk = PrivateKey(self._network_key)
|
||||||
ss = nk.ecdh(msg._ephem_pk)
|
ss = nk.ecdh(msg._ephem_pk)
|
||||||
|
|
||||||
hashed = hashlib.sha512(ss + struct.pack('>Q', msg._timestamp)).digest()
|
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).digest()
|
||||||
peer._ke = hashed[:32]
|
peer._ke = hashed[:32]
|
||||||
peer._km = hashed[32:]
|
peer._km = hashed[32:]
|
||||||
|
|
||||||
@@ -427,7 +426,7 @@ class Network:
|
|||||||
mac = msg_mv[-16:]
|
mac = msg_mv[-16:]
|
||||||
plaintext = cipher.decrypt_and_verify(msg_mv[2: -16], mac)
|
plaintext = cipher.decrypt_and_verify(msg_mv[2: -16], mac)
|
||||||
|
|
||||||
ping_nonce = struct.unpack('>I', plaintext[:4])[0]
|
ping_nonce = int.from_bytes(plaintext[:4], 'big')
|
||||||
# Version is added to a ping following a handshake message
|
# Version is added to a ping following a handshake message
|
||||||
if len(plaintext) >= 10:
|
if len(plaintext) >= 10:
|
||||||
peer._ready = True
|
peer._ready = True
|
||||||
@@ -450,7 +449,7 @@ class Network:
|
|||||||
mac = msg_mv[-16:]
|
mac = msg_mv[-16:]
|
||||||
plaintext = cipher.decrypt_and_verify(msg_mv[2: -16], mac)
|
plaintext = cipher.decrypt_and_verify(msg_mv[2: -16], mac)
|
||||||
|
|
||||||
pong_nonce = struct.unpack('>I', plaintext[:4])[0]
|
pong_nonce = int.from_bytes(plaintext[:4], 'big')
|
||||||
|
|
||||||
if pong_nonce == peer._ping_nonce:
|
if pong_nonce == peer._ping_nonce:
|
||||||
peer._last_ping_rtt = (time.time_ns() // 1000) - peer._last_ping_at
|
peer._last_ping_rtt = (time.time_ns() // 1000) - peer._last_ping_at
|
||||||
@@ -462,14 +461,14 @@ class Network:
|
|||||||
def send_ping(self, peer):
|
def send_ping(self, peer):
|
||||||
ping_nonce = random.getrandbits(32)
|
ping_nonce = random.getrandbits(32)
|
||||||
|
|
||||||
msg_bytes = struct.pack('>H', NetMessageTypes.PING)
|
msg_bytes = int(NetMessageTypes.PING).to_bytes(2, 'big')
|
||||||
nonce = peer._sent_nonce[:24]
|
nonce = peer._sent_nonce[:24]
|
||||||
|
|
||||||
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
||||||
cipher.update(msg_bytes)
|
cipher.update(msg_bytes)
|
||||||
cipher.update(nonce)
|
cipher.update(nonce)
|
||||||
|
|
||||||
payload = struct.pack('>I', ping_nonce)
|
payload = ping_nonce.to_bytes(4, 'big')
|
||||||
if peer._last_ping_at == 0:
|
if peer._last_ping_at == 0:
|
||||||
payload += self._sc._version
|
payload += self._sc._version
|
||||||
ct, mac = cipher.encrypt_and_digest(payload)
|
ct, mac = cipher.encrypt_and_digest(payload)
|
||||||
@@ -484,14 +483,14 @@ class Network:
|
|||||||
self.send_msg(peer, msg_bytes)
|
self.send_msg(peer, msg_bytes)
|
||||||
|
|
||||||
def send_pong(self, peer, ping_nonce):
|
def send_pong(self, peer, ping_nonce):
|
||||||
msg_bytes = struct.pack('>H', NetMessageTypes.PONG)
|
msg_bytes = int(NetMessageTypes.PONG).to_bytes(2, 'big')
|
||||||
nonce = peer._sent_nonce[:24]
|
nonce = peer._sent_nonce[:24]
|
||||||
|
|
||||||
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
||||||
cipher.update(msg_bytes)
|
cipher.update(msg_bytes)
|
||||||
cipher.update(nonce)
|
cipher.update(nonce)
|
||||||
|
|
||||||
payload = struct.pack('>I', ping_nonce)
|
payload = ping_nonce.to_bytes(4, 'big')
|
||||||
ct, mac = cipher.encrypt_and_digest(payload)
|
ct, mac = cipher.encrypt_and_digest(payload)
|
||||||
msg_bytes += ct + mac
|
msg_bytes += ct + mac
|
||||||
|
|
||||||
@@ -503,7 +502,7 @@ class Network:
|
|||||||
msg_encoded = msg if isinstance(msg, bytes) else msg.encode()
|
msg_encoded = msg if isinstance(msg, bytes) else msg.encode()
|
||||||
len_encoded = len(msg_encoded)
|
len_encoded = len(msg_encoded)
|
||||||
|
|
||||||
msg_packed = bytearray(MSG_START_TOKEN) + struct.pack('>I', len_encoded) + msg_encoded
|
msg_packed = bytearray(MSG_START_TOKEN) + len_encoded.to_bytes(4, 'big') + msg_encoded
|
||||||
peer._socket.sendall(msg_packed)
|
peer._socket.sendall(msg_packed)
|
||||||
|
|
||||||
peer._bytes_sent += len_encoded
|
peer._bytes_sent += len_encoded
|
||||||
@@ -515,7 +514,7 @@ class Network:
|
|||||||
try:
|
try:
|
||||||
mv = memoryview(msg_bytes)
|
mv = memoryview(msg_bytes)
|
||||||
o = 0
|
o = 0
|
||||||
msg_type = struct.unpack('>H', mv[o: o + 2])[0]
|
msg_type = int.from_bytes(mv[o: o + 2], 'big')
|
||||||
if msg_type == NetMessageTypes.HANDSHAKE:
|
if msg_type == NetMessageTypes.HANDSHAKE:
|
||||||
self.process_handshake(peer, mv)
|
self.process_handshake(peer, mv)
|
||||||
elif msg_type == NetMessageTypes.PING:
|
elif msg_type == NetMessageTypes.PING:
|
||||||
@@ -548,13 +547,13 @@ class Network:
|
|||||||
raise ValueError('Invalid start token')
|
raise ValueError('Invalid start token')
|
||||||
o += 2
|
o += 2
|
||||||
|
|
||||||
msg_len = struct.unpack('>I', mv[o: o + 4])[0]
|
msg_len = int.from_bytes(mv[o: o + 4], 'big')
|
||||||
o += 4
|
o += 4
|
||||||
if msg_len < 2 or msg_len > MSG_MAX_SIZE:
|
if msg_len < 2 or msg_len > MSG_MAX_SIZE:
|
||||||
raise ValueError('Invalid data length')
|
raise ValueError('Invalid data length')
|
||||||
|
|
||||||
# Precheck msg_type
|
# Precheck msg_type
|
||||||
msg_type = struct.unpack('>H', mv[o: o + 2])[0]
|
msg_type = int.from_bytes(mv[o: o + 2], 'big')
|
||||||
# o += 2 # Don't inc offset, msg includes type
|
# o += 2 # Don't inc offset, msg includes type
|
||||||
if not NetMessageTypes.has_value(msg_type):
|
if not NetMessageTypes.has_value(msg_type):
|
||||||
raise ValueError('Invalid msg type')
|
raise ValueError('Invalid msg type')
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
from basicswap.script import (
|
from basicswap.script import (
|
||||||
OpCodes,
|
OpCodes,
|
||||||
)
|
)
|
||||||
from basicswap.util.script import (
|
|
||||||
getP2WSH,
|
|
||||||
)
|
|
||||||
from basicswap.interface.btc import (
|
from basicswap.interface.btc import (
|
||||||
find_vout_for_address_from_txobj,
|
find_vout_for_address_from_txobj,
|
||||||
)
|
)
|
||||||
@@ -27,11 +24,11 @@ class ProtocolInterface:
|
|||||||
|
|
||||||
def getMockScriptScriptPubkey(self, ci) -> bytearray:
|
def getMockScriptScriptPubkey(self, ci) -> bytearray:
|
||||||
script = self.getMockScript()
|
script = self.getMockScript()
|
||||||
return ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
|
return ci.getScriptDest(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
|
||||||
|
|
||||||
def getMockAddrTo(self, ci):
|
def getMockAddrTo(self, ci):
|
||||||
script = self.getMockScript()
|
script = self.getMockScript()
|
||||||
return ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script)
|
return ci.encodeScriptDest(ci.getScriptDest(script)) if ci._use_segwit else ci.encode_p2sh(script)
|
||||||
|
|
||||||
def findMockVout(self, ci, itx_decoded):
|
def findMockVout(self, ci, itx_decoded):
|
||||||
mock_addr = self.getMockAddrTo(ci)
|
mock_addr = self.getMockAddrTo(ci)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class AtomicSwapInterface(ProtocolInterface):
|
|||||||
|
|
||||||
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
||||||
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
||||||
real_txo_script = ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
|
real_txo_script = ci.getScriptDest(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
|
||||||
|
|
||||||
found: int = 0
|
found: int = 0
|
||||||
ctx = ci.loadTx(mock_tx)
|
ctx = ci.loadTx(mock_tx)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2022 tecnovert
|
# Copyright (c) 2020-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -23,10 +23,7 @@ from .util import jsonDecimal
|
|||||||
def waitForRPC(rpc_func, expect_wallet=True, max_tries=7):
|
def waitForRPC(rpc_func, expect_wallet=True, max_tries=7):
|
||||||
for i in range(max_tries + 1):
|
for i in range(max_tries + 1):
|
||||||
try:
|
try:
|
||||||
if expect_wallet:
|
rpc_func('getwalletinfo' if expect_wallet else 'getblockchaininfo')
|
||||||
rpc_func('getwalletinfo')
|
|
||||||
else:
|
|
||||||
rpc_func('getblockchaininfo')
|
|
||||||
return
|
return
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if i < max_tries:
|
if i < max_tries:
|
||||||
@@ -111,7 +108,7 @@ def callrpc(rpc_port, auth, method, params=[], wallet=None, host='127.0.0.1'):
|
|||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode('utf-8'))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise ValueError('RPC server error ' + str(ex))
|
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method)
|
||||||
|
|
||||||
if 'error' in r and r['error'] is not None:
|
if 'error' in r and r['error'] is not None:
|
||||||
raise ValueError('RPC error ' + str(r['error']))
|
raise ValueError('RPC error ' + str(r['error']))
|
||||||
@@ -165,3 +162,9 @@ def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'):
|
|||||||
nonlocal port, auth, wallet, host
|
nonlocal port, auth, wallet, host
|
||||||
return callrpc(port, auth, method, params, wallet if wallet_override is None else wallet_override, host)
|
return callrpc(port, auth, method, params, wallet if wallet_override is None else wallet_override, host)
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
|
def escape_rpcauth(auth_str: str) -> str:
|
||||||
|
username, password = auth_str.split(':', 1)
|
||||||
|
password = urllib.parse.quote(password, safe='')
|
||||||
|
return f'{username}:{password}'
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import socks
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -10,9 +11,32 @@ from xmlrpc.client import (
|
|||||||
Transport,
|
Transport,
|
||||||
SafeTransport,
|
SafeTransport,
|
||||||
)
|
)
|
||||||
|
from sockshandler import SocksiPyConnection
|
||||||
from .util import jsonDecimal
|
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():
|
class JsonrpcDigest():
|
||||||
# __getattr__ complicates extending ServerProxy
|
# __getattr__ complicates extending ServerProxy
|
||||||
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
||||||
@@ -148,7 +172,7 @@ class JsonrpcDigest():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120):
|
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)
|
# auth is a tuple: (username, password)
|
||||||
try:
|
try:
|
||||||
if rpc_host.count('://') > 0:
|
if rpc_host.count('://') > 0:
|
||||||
@@ -156,7 +180,7 @@ def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rp
|
|||||||
else:
|
else:
|
||||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
||||||
|
|
||||||
x = JsonrpcDigest(url)
|
x = JsonrpcDigest(url, transport=transport)
|
||||||
request_body = {
|
request_body = {
|
||||||
'method': method,
|
'method': method,
|
||||||
'params': params,
|
'params': params,
|
||||||
@@ -170,22 +194,22 @@ def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rp
|
|||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode('utf-8'))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise ValueError('RPC Server Error: {}'.format(str(ex)))
|
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
||||||
|
|
||||||
if 'error' in r and r['error'] is not None:
|
if 'error' in r and r['error'] is not None:
|
||||||
raise ValueError('RPC error ' + str(r['error']))
|
raise ValueError(tag + 'RPC error ' + str(r['error']))
|
||||||
|
|
||||||
return r['result']
|
return r['result']
|
||||||
|
|
||||||
|
|
||||||
def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120):
|
def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''):
|
||||||
try:
|
try:
|
||||||
if rpc_host.count('://') > 0:
|
if rpc_host.count('://') > 0:
|
||||||
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
||||||
else:
|
else:
|
||||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
|
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
|
||||||
|
|
||||||
x = JsonrpcDigest(url)
|
x = JsonrpcDigest(url, transport=transport)
|
||||||
if auth:
|
if auth:
|
||||||
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
|
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
|
||||||
else:
|
else:
|
||||||
@@ -193,28 +217,42 @@ def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='1
|
|||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode('utf-8'))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise ValueError('RPC Server Error: {}'.format(str(ex)))
|
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def make_xmr_rpc2_func(port, auth, host='127.0.0.1'):
|
def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
||||||
port = port
|
port = port
|
||||||
auth = auth
|
auth = auth
|
||||||
host = host
|
host = host
|
||||||
|
transport = None
|
||||||
|
default_timeout = default_timeout
|
||||||
|
tag = tag
|
||||||
|
|
||||||
def rpc_func(method, params=None, wallet=None, timeout=120):
|
if proxy_host:
|
||||||
nonlocal port, auth, host
|
transport = SocksTransport()
|
||||||
return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout)
|
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
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
def make_xmr_rpc_func(port, auth, host='127.0.0.1'):
|
def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
||||||
port = port
|
port = port
|
||||||
auth = auth
|
auth = auth
|
||||||
host = host
|
host = host
|
||||||
|
transport = None
|
||||||
|
default_timeout = default_timeout
|
||||||
|
tag = tag
|
||||||
|
|
||||||
def rpc_func(method, params=None, wallet=None, timeout=120):
|
if proxy_host:
|
||||||
nonlocal port, auth, host
|
transport = SocksTransport()
|
||||||
return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout)
|
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
|
return rpc_func
|
||||||
|
|||||||
@@ -96,6 +96,17 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blurred {
|
||||||
|
filter: blur(4px);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-overlay.non-blurred {
|
||||||
|
filter: none;
|
||||||
|
pointer-events: auto;
|
||||||
|
user-select: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Disable opacity on disabled form elements in Chrome */
|
/* Disable opacity on disabled form elements in Chrome */
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
|
|||||||
BIN
basicswap/static/images/coins/Ethereum-20.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
basicswap/static/images/coins/Ethereum.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
basicswap/static/images/coins/Litecoin%MWEB.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
basicswap/static/images/coins/Litecoin-MWEB-20.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
basicswap/static/images/coins/Litecoin-MWEB.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" id="mscgenjsreplaceme" width="1272" height="2063.25" class="mscgenjsreplaceme" style="font-family:Helvetica,sans-serif;font-size:12px;font-weight:400;font-style:normal;text-decoration:none;background-color:#fff;stroke:#000;stroke-width:2" version="1.1">
|
<svg xmlns="http://www.w3.org/2000/svg" id="mscgenjsreplaceme" width="1272" height="2101.25" class="mscgenjsreplaceme" style="font-family:Helvetica,sans-serif;font-size:12px;font-weight:400;font-style:normal;text-decoration:none;background-color:#fff;stroke:#000;stroke-width:2" version="1.1">
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="mscgenjsreplacemecallback-#0000FF" class="arrow-marker" markerHeight="10" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="9" refY="3" viewBox="0 0 10 10">
|
<marker id="mscgenjsreplacemecallback-#0000FF" class="arrow-marker" markerHeight="10" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="9" refY="3" viewBox="0 0 10 10">
|
||||||
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
|
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
|
||||||
@@ -35,10 +35,10 @@
|
|||||||
</style>
|
</style>
|
||||||
</defs>
|
</defs>
|
||||||
<g id="mscgenjsreplaceme_body" transform="translate(51 3)">
|
<g id="mscgenjsreplaceme_body" transform="translate(51 3)">
|
||||||
<path id="mscgenjsreplaceme_background" d="M-51-3h1272v2063.25H-51z" class="bglayer" style="fill:#fff;stroke:#fff;stroke-width:0"/>
|
<path id="mscgenjsreplaceme_background" d="M-51-3h1272v2101.25H-51z" class="bglayer" style="fill:#fff;stroke:#fff;stroke-width:0"/>
|
||||||
<g id="mscgenjsreplaceme_arcspans">
|
<g id="mscgenjsreplaceme_arcspans">
|
||||||
<path d="M-41 869.1h1044v1169.15H-41z" class="box inline_expression alt"/>
|
<path d="M-41 907.1h1044v1169.15H-41z" class="box inline_expression alt"/>
|
||||||
<path d="M-37 1410.15H999v590.1H-37z" class="box inline_expression alt"/>
|
<path d="M-37 1448.15H999v590.1H-37z" class="box inline_expression alt"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="mscgenjsreplaceme_lifelines">
|
<g id="mscgenjsreplaceme_lifelines">
|
||||||
<path d="M65 38v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M65 38v38" class="arcrow" style="stroke:transparent"/>
|
||||||
@@ -97,39 +97,39 @@
|
|||||||
<path d="M273 548v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 548v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 548v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 548v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 548v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 548v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 548v38M1105 548v38M65 586v75.05" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 548v38M1105 548v38M65 586v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 586v75.05" class="arcrow" style="stroke:#080"/>
|
<path d="M273 586v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 586v75.05" class="arcrow" style="stroke:red"/>
|
<path d="M481 586v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 586v75.05" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 586v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 586v75.05M1105 586v75.05M65 661.05v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 586v38M1105 586v38M65 624v75.05" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 661.05v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 624v75.05" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 661.05v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 624v75.05" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 661.05v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 624v75.05" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 661.05v38M1105 661.05v38M65 699.05v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 624v75.05M1105 624v75.05M65 699.05v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 699.05v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 699.05v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 699.05v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 699.05v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 699.05v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 699.05v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 699.05v38M1105 699.05v38M65 737.05v75.05" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 699.05v38M1105 699.05v38M65 737.05v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 737.05v75.05" class="arcrow" style="stroke:#080"/>
|
<path d="M273 737.05v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 737.05v75.05" class="arcrow" style="stroke:red"/>
|
<path d="M481 737.05v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 737.05v75.05" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 737.05v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 737.05v75.05M1105 737.05v75.05M65 812.1v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 737.05v38M1105 737.05v38M65 775.05v75.05" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 812.1v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 775.05v75.05" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 812.1v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 775.05v75.05" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 812.1v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 775.05v75.05" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 812.1v38M1105 812.1v38M65 850.1v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 775.05v75.05M1105 775.05v75.05M65 850.1v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 850.1v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 850.1v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 850.1v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 850.1v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 850.1v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 850.1v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 850.1v38M1105 850.1v38M65 888.1v86" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 850.1v38M1105 850.1v38M65 888.1v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 888.1v86" class="arcrow" style="stroke:#080"/>
|
<path d="M273 888.1v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 888.1v86" class="arcrow" style="stroke:red"/>
|
<path d="M481 888.1v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 888.1v86" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 888.1v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 888.1v86M1105 888.1v86M65 974.1v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 888.1v38M1105 888.1v38M65 926.1v86" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 974.1v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 926.1v86" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 974.1v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 926.1v86" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 974.1v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 926.1v86" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 974.1v38M1105 974.1v38M65 1012.1v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 926.1v86M1105 926.1v86M65 1012.1v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1012.1v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1012.1v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1012.1v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1012.1v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1012.1v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1012.1v38" class="arcrow" style="stroke:#00f"/>
|
||||||
@@ -149,15 +149,15 @@
|
|||||||
<path d="M273 1164.1v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1164.1v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1164.1v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1164.1v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1164.1v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1164.1v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1164.1v38M1105 1164.1v38M65 1202.1v75.05" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1164.1v38M1105 1164.1v38M65 1202.1v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1202.1v75.05" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1202.1v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1202.1v75.05" class="arcrow" style="stroke:red"/>
|
<path d="M481 1202.1v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1202.1v75.05" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1202.1v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1202.1v75.05M1105 1202.1v75.05M65 1277.15v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1202.1v38M1105 1202.1v38M65 1240.1v75.05" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1277.15v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1240.1v75.05" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1277.15v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1240.1v75.05" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1277.15v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1240.1v75.05" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1277.15v38M1105 1277.15v38M65 1315.15v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1240.1v75.05M1105 1240.1v75.05M65 1315.15v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1315.15v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1315.15v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1315.15v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1315.15v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1315.15v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1315.15v38" class="arcrow" style="stroke:#00f"/>
|
||||||
@@ -173,19 +173,19 @@
|
|||||||
<path d="M273 1429.15v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1429.15v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1429.15v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1429.15v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1429.15v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1429.15v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1429.15v38M1105 1429.15v38M65 1467.15v59.05" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1429.15v38M1105 1429.15v38M65 1467.15v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1467.15v59.05" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1467.15v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1467.15v59.05" class="arcrow" style="stroke:red"/>
|
<path d="M481 1467.15v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1467.15v59.05" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1467.15v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1467.15v59.05M1105 1467.15v59.05M65 1526.2v54" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1467.15v38M1105 1467.15v38M65 1505.15v59.05" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1526.2v54" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1505.15v59.05" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1526.2v54" class="arcrow" style="stroke:red"/>
|
<path d="M481 1505.15v59.05" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1526.2v54" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1505.15v59.05" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1526.2v54M1105 1526.2v54M65 1580.2v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1505.15v59.05M1105 1505.15v59.05M65 1564.2v54" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1580.2v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1564.2v54" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1580.2v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1564.2v54" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1580.2v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1564.2v54" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1580.2v38M1105 1580.2v38M65 1618.2v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1564.2v54M1105 1564.2v54M65 1618.2v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1618.2v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1618.2v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1618.2v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1618.2v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1618.2v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1618.2v38" class="arcrow" style="stroke:#00f"/>
|
||||||
@@ -209,15 +209,15 @@
|
|||||||
<path d="M273 1808.2v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1808.2v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1808.2v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1808.2v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1808.2v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1808.2v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1808.2v38M1105 1808.2v38M65 1846.2v59.05" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1808.2v38M1105 1808.2v38M65 1846.2v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1846.2v59.05" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1846.2v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1846.2v59.05" class="arcrow" style="stroke:red"/>
|
<path d="M481 1846.2v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1846.2v59.05" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1846.2v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1846.2v59.05M1105 1846.2v59.05M65 1905.25v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1846.2v38M1105 1846.2v38M65 1884.2v59.05" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1905.25v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1884.2v59.05" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1905.25v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1884.2v59.05" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1905.25v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1884.2v59.05" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 1905.25v38M1105 1905.25v38M65 1943.25v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 1884.2v59.05M1105 1884.2v59.05M65 1943.25v38" class="arcrow" style="stroke:transparent"/>
|
||||||
<path d="M273 1943.25v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 1943.25v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 1943.25v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 1943.25v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 1943.25v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 1943.25v38" class="arcrow" style="stroke:#00f"/>
|
||||||
@@ -229,7 +229,11 @@
|
|||||||
<path d="M273 2019.25v38" class="arcrow" style="stroke:#080"/>
|
<path d="M273 2019.25v38" class="arcrow" style="stroke:#080"/>
|
||||||
<path d="M481 2019.25v38" class="arcrow" style="stroke:red"/>
|
<path d="M481 2019.25v38" class="arcrow" style="stroke:red"/>
|
||||||
<path d="M689 2019.25v38" class="arcrow" style="stroke:#00f"/>
|
<path d="M689 2019.25v38" class="arcrow" style="stroke:#00f"/>
|
||||||
<path d="M897 2019.25v38M1105 2019.25v38" class="arcrow" style="stroke:transparent"/>
|
<path d="M897 2019.25v38M1105 2019.25v38M65 2057.25v38" class="arcrow" style="stroke:transparent"/>
|
||||||
|
<path d="M273 2057.25v38" class="arcrow" style="stroke:#080"/>
|
||||||
|
<path d="M481 2057.25v38" class="arcrow" style="stroke:red"/>
|
||||||
|
<path d="M689 2057.25v38" class="arcrow" style="stroke:#00f"/>
|
||||||
|
<path d="M897 2057.25v38M1105 2057.25v38" class="arcrow" style="stroke:transparent"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="mscgenjsreplaceme_sequence">
|
<g id="mscgenjsreplaceme_sequence">
|
||||||
<path d="M0 0h130v38H0z" class="entity" style="stroke:transparent"/>
|
<path d="M0 0h130v38H0z" class="entity" style="stroke:transparent"/>
|
||||||
@@ -268,85 +272,85 @@
|
|||||||
<path marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 529H273" class="arc directional callback" style="stroke:#00f"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 529H273" class="arc directional callback" style="stroke:#00f"/>
|
||||||
<path d="M415.64 513.25h130.72v14H415.64z" class="label-text-background"/>
|
<path d="M415.64 513.25h130.72v14H415.64z" class="label-text-background"/>
|
||||||
<text x="481" y="524.25" class="directional-text callback-text"><tspan>Sends script-coin-lock-tx</tspan></text>
|
<text x="481" y="524.25" class="directional-text callback-text"><tspan>Sends script-coin-lock-tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 615.92c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 653.92c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
||||||
<path d="M484 567.67h40.91v14H484z" class="label-text-background"/>
|
<path d="M484 605.67h40.91v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="578.67" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
<text x="484" y="616.67" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
||||||
<path d="M484 583.67h107.01v14H484z" class="label-text-background"/>
|
<path d="M484 621.67h107.01v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="594.67" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx to</tspan></text>
|
<text x="484" y="632.67" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx to</tspan></text>
|
||||||
<path d="M484 599.67h39.34v14H484z" class="label-text-background"/>
|
<path d="M484 637.67h39.34v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="610.67" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
|
<text x="484" y="648.67" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 718.05H273" class="arc directional callback" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 756.05H273" class="arc directional callback" style="stroke:red"/>
|
||||||
<path d="M304.97 702.3h144.06v14H304.97z" class="label-text-background"/>
|
<path d="M304.97 740.3h144.06v14H304.97z" class="label-text-background"/>
|
||||||
<text x="377" y="713.3" class="directional-text callback-text"><tspan>Sends noscript-coin-lock-tx</tspan></text>
|
<text x="377" y="751.3" class="directional-text callback-text"><tspan>Sends noscript-coin-lock-tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 766.97c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 804.97c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
||||||
<path d="M484 718.72h40.91v14H484z" class="label-text-background"/>
|
<path d="M484 756.72h40.91v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="729.72" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
<text x="484" y="767.72" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
||||||
<path d="M484 734.72h120.36v14H484z" class="label-text-background"/>
|
<path d="M484 772.72h120.36v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="745.72" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
|
<text x="484" y="783.72" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
|
||||||
<path d="M484 750.72h39.34v14H484z" class="label-text-background"/>
|
<path d="M484 788.72h39.34v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="761.72" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
|
<text x="484" y="799.72" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#0000FF)" d="M689 766.97c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#0000FF)" d="M689 804.97c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f"/>
|
||||||
<path d="M692 718.72h40.91v14H692z" class="label-text-background"/>
|
<path d="M692 756.72h40.91v14H692z" class="label-text-background"/>
|
||||||
<text x="692" y="729.72" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
<text x="692" y="767.72" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
||||||
<path d="M692 734.72h120.36v14H692z" class="label-text-background"/>
|
<path d="M692 772.72h120.36v14H692z" class="label-text-background"/>
|
||||||
<text x="692" y="745.72" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
|
<text x="692" y="783.72" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
|
||||||
<path d="M692 750.72h39.34v14H692z" class="label-text-background"/>
|
<path d="M692 788.72h39.34v14H692z" class="label-text-background"/>
|
||||||
<text x="692" y="761.72" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
|
<text x="692" y="799.72" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#0000FF)" d="M689 931.1H481" class="arc directional method" style="stroke:#00f"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#0000FF)" d="M689 969.1H481" class="arc directional method" style="stroke:#00f"/>
|
||||||
<path d="M519.64 915.35h130.72v14H519.64z" class="label-text-background"/>
|
<path d="M519.64 953.35h130.72v14H519.64z" class="label-text-background"/>
|
||||||
<text x="585" y="926.35" class="directional-text method-text"><tspan>Sends script-coin-lock-tx</tspan></text>
|
<text x="585" y="964.35" class="directional-text method-text"><tspan>Sends script-coin-lock-tx</tspan></text>
|
||||||
<path d="M539.3 933.35h91.71v14H539.3z" class="label-text-background"/>
|
<path d="M539.3 971.35h91.71v14H539.3z" class="label-text-background"/>
|
||||||
<text x="585" y="944.35" class="directional-text method-text"><tspan>release message</tspan></text>
|
<text x="585" y="982.35" class="directional-text method-text"><tspan>release message</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1031.1H273" class="arc directional callback" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1069.1H273" class="arc directional callback" style="stroke:red"/>
|
||||||
<path d="M293.3 1015.35h167.41v14H293.3z" class="label-text-background"/>
|
<path d="M293.3 1053.35h167.41v14H293.3z" class="label-text-background"/>
|
||||||
<text x="377" y="1026.35" class="directional-text callback-text"><tspan>Sends script-coin-lock-spend-tx</tspan></text>
|
<text x="377" y="1064.35" class="directional-text callback-text"><tspan>Sends script-coin-lock-spend-tx</tspan></text>
|
||||||
<path d="M-41 1145.1h1044" class="inline_expression_divider"/>
|
<path d="M-41 1183.1h1044" class="inline_expression_divider"/>
|
||||||
<path d="M459.98 1137.85h42.03v14h-42.03z" class="label-text-background"/>
|
<path d="M459.98 1175.85h42.03v14h-42.03z" class="label-text-background"/>
|
||||||
<text x="481" y="1148.85" class="empty-text comment-row-text"><tspan>fail path</tspan></text>
|
<text x="481" y="1186.85" class="empty-text comment-row-text"><tspan>fail path</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 1232.02c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 1270.02c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
||||||
<path d="M484 1183.77h40.91v14H484z" class="label-text-background"/>
|
<path d="M484 1221.77h40.91v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="1194.77" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
<text x="484" y="1232.77" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
||||||
<path d="M484 1199.77h131.69v14H484z" class="label-text-background"/>
|
<path d="M484 1237.77h131.69v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="1210.77" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx lock to</tspan></text>
|
<text x="484" y="1248.77" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx lock to</tspan></text>
|
||||||
<path d="M484 1215.77h33.01v14H484z" class="label-text-background"/>
|
<path d="M484 1253.77h33.01v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="1226.77" class="directional-text method-text anchor-start"><tspan>expire</tspan></text>
|
<text x="484" y="1264.77" class="directional-text method-text anchor-start"><tspan>expire</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1296.15H273" class="arc directional callback" style="stroke:#00f"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1334.15H273" class="arc directional callback" style="stroke:#00f"/>
|
||||||
<path d="M385.96 1280.4h190.08v14H385.96z" class="label-text-background"/>
|
<path d="M385.96 1318.4h190.08v14H385.96z" class="label-text-background"/>
|
||||||
<text x="481" y="1291.4" class="directional-text callback-text"><tspan>Sends script-coin-lock-pre-refund-tx</tspan></text>
|
<text x="481" y="1329.4" class="directional-text callback-text"><tspan>Sends script-coin-lock-pre-refund-tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1334.15h416" class="arc directional return" style="stroke:#080"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1372.15h416" class="arc directional return" style="stroke:#080"/>
|
||||||
<path d="M404.64 1318.4h152.72v14H404.64z" class="label-text-background"/>
|
<path d="M404.64 1356.4h152.72v14H404.64z" class="label-text-background"/>
|
||||||
<text x="481" y="1329.4" class="directional-text return-text"><tspan>script-coin-lock-pre-refund-tx</tspan></text>
|
<text x="481" y="1367.4" class="directional-text return-text"><tspan>script-coin-lock-pre-refund-tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#0000FF)" d="M689 1489.07c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#0000FF)" d="M689 1527.07c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f"/>
|
||||||
<path d="M692 1456.82h40.91v14H692z" class="label-text-background"/>
|
<path d="M692 1494.82h40.91v14H692z" class="label-text-background"/>
|
||||||
<text x="692" y="1467.82" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
<text x="692" y="1505.82" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
||||||
<path d="M692 1472.82h124.06v14H692z" class="label-text-background"/>
|
<path d="M692 1510.82h124.06v14H692z" class="label-text-background"/>
|
||||||
<text x="692" y="1483.82" class="directional-text method-text anchor-start"><tspan>pre-refund tx to confirm</tspan></text>
|
<text x="692" y="1521.82" class="directional-text method-text anchor-start"><tspan>pre-refund tx to confirm</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1553.2H273" class="arc directional callback" style="stroke:#00f"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1591.2H273" class="arc directional callback" style="stroke:#00f"/>
|
||||||
<path d="M367.62 1537.45h226.77v14H367.62z" class="label-text-background"/>
|
<path d="M367.62 1575.45h226.77v14H367.62z" class="label-text-background"/>
|
||||||
<text x="481" y="1548.45" class="directional-text callback-text"><tspan>Sends script-coin-lock-pre-refund-spend-tx</tspan></text>
|
<text x="481" y="1586.45" class="directional-text callback-text"><tspan>Sends script-coin-lock-pre-refund-spend-tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1637.2h208" class="arc directional return" style="stroke:#080"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1675.2h208" class="arc directional return" style="stroke:#080"/>
|
||||||
<path d="M356.66 1621.45h40.69v14h-40.69z" class="label-text-background"/>
|
<path d="M356.66 1659.45h40.69v14h-40.69z" class="label-text-background"/>
|
||||||
<text x="377" y="1632.45" class="directional-text return-text"><tspan>Detects</tspan></text>
|
<text x="377" y="1670.45" class="directional-text return-text"><tspan>Detects</tspan></text>
|
||||||
<path d="M282.3 1639.45h189.41v14H282.3z" class="label-text-background"/>
|
<path d="M282.3 1677.45h189.41v14H282.3z" class="label-text-background"/>
|
||||||
<text x="377" y="1650.45" class="directional-text return-text"><tspan>script-coin-lock-pre-refund-spend-tx</tspan></text>
|
<text x="377" y="1688.45" class="directional-text return-text"><tspan>script-coin-lock-pre-refund-spend-tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1675.2H273" class="arc directional callback" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1713.2H273" class="arc directional callback" style="stroke:red"/>
|
||||||
<path d="M359.98 1659.45h34.03v14h-34.03z" class="label-text-background"/>
|
<path d="M359.98 1697.45h34.03v14h-34.03z" class="label-text-background"/>
|
||||||
<text x="377" y="1670.45" class="directional-text callback-text"><tspan>Sends</tspan></text>
|
<text x="377" y="1708.45" class="directional-text callback-text"><tspan>Sends</tspan></text>
|
||||||
<path d="M297.65 1677.45h158.7v14h-158.7z" class="label-text-background"/>
|
<path d="M297.65 1715.45h158.7v14h-158.7z" class="label-text-background"/>
|
||||||
<text x="377" y="1688.45" class="directional-text callback-text"><tspan>scriptless-coin-lock-recover-tx</tspan></text>
|
<text x="377" y="1726.45" class="directional-text callback-text"><tspan>scriptless-coin-lock-recover-tx</tspan></text>
|
||||||
<path d="M-37 1789.2H999" class="inline_expression_divider"/>
|
<path d="M-37 1827.2H999" class="inline_expression_divider"/>
|
||||||
<path d="M396.41 1781.95h169.17v14H396.41z" class="label-text-background"/>
|
<path d="M396.41 1819.95h169.17v14H396.41z" class="label-text-background"/>
|
||||||
<text x="481" y="1792.95" class="empty-text comment-row-text"><tspan>offerer swipes script coin lock tx</tspan></text>
|
<text x="481" y="1830.95" class="empty-text comment-row-text"><tspan>offerer swipes script coin lock tx</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 1868.12c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 1906.12c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red"/>
|
||||||
<path d="M484 1835.87h40.91v14H484z" class="label-text-background"/>
|
<path d="M484 1873.87h40.91v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="1846.87" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
<text x="484" y="1884.87" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
|
||||||
<path d="M484 1851.87h142.39v14H484z" class="label-text-background"/>
|
<path d="M484 1889.87h142.39v14H484z" class="label-text-background"/>
|
||||||
<text x="484" y="1862.87" class="directional-text method-text anchor-start"><tspan>pre-refund tx lock to expire</tspan></text>
|
<text x="484" y="1900.87" class="directional-text method-text anchor-start"><tspan>pre-refund tx lock to expire</tspan></text>
|
||||||
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1924.25H273" class="arc directional callback" style="stroke:red"/>
|
<path marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1962.25H273" class="arc directional callback" style="stroke:red"/>
|
||||||
<path d="M359.98 1908.5h34.03v14h-34.03z" class="label-text-background"/>
|
<path d="M359.98 1946.5h34.03v14h-34.03z" class="label-text-background"/>
|
||||||
<text x="377" y="1919.5" class="directional-text callback-text"><tspan>Sends</tspan></text>
|
<text x="377" y="1957.5" class="directional-text callback-text"><tspan>Sends</tspan></text>
|
||||||
<path d="M283.3 1926.5h187.39v14H283.3z" class="label-text-background"/>
|
<path d="M283.3 1964.5h187.39v14H283.3z" class="label-text-background"/>
|
||||||
<text x="377" y="1937.5" class="directional-text callback-text"><tspan>script-coin-lock-pre-refund-swipe-tx</tspan></text>
|
<text x="377" y="1975.5" class="directional-text callback-text"><tspan>script-coin-lock-pre-refund-swipe-tx</tspan></text>
|
||||||
</g>
|
</g>
|
||||||
<g id="mscgenjsreplaceme_notes">
|
<g id="mscgenjsreplaceme_notes">
|
||||||
<path d="m383 209 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 209 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
@@ -371,50 +375,50 @@
|
|||||||
<path d="M799 474h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
|
<path d="M799 474h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
|
||||||
<text x="1001" y="486.75" class="box-text note-text"><tspan>The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and</tspan></text>
|
<text x="1001" y="486.75" class="box-text note-text"><tspan>The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and</tspan></text>
|
||||||
<text x="1001" y="502.75" class="box-text note-text"><tspan>proof the bidder can sign it.</tspan></text>
|
<text x="1001" y="502.75" class="box-text note-text"><tspan>proof the bidder can sign it.</tspan></text>
|
||||||
<path d="m383 529 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
|
||||||
<text x="481" y="532.75" class="box-text abox-text"><tspan>Bid Script coin spend tx valid</tspan></text>
|
|
||||||
<path d="m383 567 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 567 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="481" y="562.75" class="box-text abox-text"><tspan>Exchanged script lock spend tx</tspan></text>
|
<text x="481" y="570.75" class="box-text abox-text"><tspan>Bid Script coin spend tx valid</tspan></text>
|
||||||
<text x="481" y="578.75" class="box-text abox-text"><tspan>msg</tspan></text>
|
<path d="m383 605 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<path d="m383 680.05 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<text x="481" y="600.75" class="box-text abox-text"><tspan>Exchanged script lock spend tx</tspan></text>
|
||||||
<text x="481" y="683.8" class="box-text abox-text"><tspan>Bid Script coin locked</tspan></text>
|
<text x="481" y="616.75" class="box-text abox-text"><tspan>msg</tspan></text>
|
||||||
<path d="m383 831.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 718.05 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="481" y="834.85" class="box-text abox-text"><tspan>Bid Scriptless coin locked</tspan></text>
|
<text x="481" y="721.8" class="box-text abox-text"><tspan>Bid Script coin locked</tspan></text>
|
||||||
<path d="M-40 869.1h98.39v11l-7 7H-40" class="box inline_expression_label"/>
|
<path d="m383 869.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="-38" y="882.35" class="inline_expression-text alt-text anchor-start"><tspan>alt: success path</tspan></text>
|
<text x="481" y="872.85" class="box-text abox-text"><tspan>Bid Scriptless coin locked</tspan></text>
|
||||||
<path d="M799 890.1h395v9h9m-9-9 9 9v73H799v-82z" class="box note" style="fill:#ffc"/>
|
<path d="M-40 907.1h98.39v11l-7 7H-40" class="box inline_expression_label"/>
|
||||||
<text x="1001" y="902.85" class="box-text note-text"><tspan>The XmrBidLockReleaseMessage contains the bidder's OTVES for it. </tspan></text>
|
<text x="-38" y="920.35" class="inline_expression-text alt-text anchor-start"><tspan>alt: success path</tspan></text>
|
||||||
<text x="1001" y="918.85" class="box-text note-text"><tspan> The offerer decodes the bidder's signature</tspan></text>
|
<path d="M799 928.1h395v9h9m-9-9 9 9v73H799v-82z" class="box note" style="fill:#ffc"/>
|
||||||
<text x="1001" y="934.85" class="box-text note-text"><tspan>from the OTVES. When the bidder has the</tspan></text>
|
<text x="1001" y="940.85" class="box-text note-text"><tspan>The XmrBidLockReleaseMessage contains the bidder's OTVES for it. </tspan></text>
|
||||||
<text x="1001" y="950.85" class="box-text note-text"><tspan>plaintext signature, they can decode the offerer's noscript-coin-lock-tx</tspan></text>
|
<text x="1001" y="956.85" class="box-text note-text"><tspan> The offerer decodes the bidder's signature</tspan></text>
|
||||||
<text x="1001" y="966.85" class="box-text note-text"><tspan>signature.</tspan></text>
|
<text x="1001" y="972.85" class="box-text note-text"><tspan>from the OTVES. When the bidder has the</tspan></text>
|
||||||
<path d="m383 993.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<text x="1001" y="988.85" class="box-text note-text"><tspan>plaintext signature, they can decode the offerer's noscript-coin-lock-tx</tspan></text>
|
||||||
<text x="481" y="996.85" class="box-text abox-text"><tspan>Script coin lock released</tspan></text>
|
<text x="1001" y="1004.85" class="box-text note-text"><tspan>signature.</tspan></text>
|
||||||
<path d="m383 1069.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 1031.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="481" y="1072.85" class="box-text abox-text"><tspan>Script tx redeemed</tspan></text>
|
<text x="481" y="1034.85" class="box-text abox-text"><tspan>Script coin lock released</tspan></text>
|
||||||
<path d="m383 1107.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 1107.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="481" y="1110.85" class="box-text abox-text"><tspan>Bid Completed</tspan></text>
|
<text x="481" y="1110.85" class="box-text abox-text"><tspan>Script tx redeemed</tspan></text>
|
||||||
<path d="M799 1279.15h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
|
<path d="m383 1145.1 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="1001" y="1299.9" class="box-text note-text"><tspan>tx can be sent by either party.</tspan></text>
|
<text x="481" y="1148.85" class="box-text abox-text"><tspan>Bid Completed</tspan></text>
|
||||||
<path d="m383 1372.15 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="M799 1317.15h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
|
||||||
<text x="481" y="1367.9" class="box-text abox-text"><tspan>Bid Script pre-refund tx in</tspan></text>
|
<text x="1001" y="1337.9" class="box-text note-text"><tspan>tx can be sent by either party.</tspan></text>
|
||||||
<text x="481" y="1383.9" class="box-text abox-text"><tspan>chain</tspan></text>
|
<path d="m383 1410.15 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<path d="M-36 1410.15h199.77v11l-7 7H-36" class="box inline_expression_label"/>
|
<text x="481" y="1405.9" class="box-text abox-text"><tspan>Bid Script pre-refund tx in</tspan></text>
|
||||||
<text x="-34" y="1423.4" class="inline_expression-text alt-text anchor-start"><tspan>alt: bidder refunds script coin lock tx</tspan></text>
|
<text x="481" y="1421.9" class="box-text abox-text"><tspan>chain</tspan></text>
|
||||||
<path d="M799 1528.2h395v9h9m-9-9 9 9v41H799v-50z" class="box note" style="fill:#ffc"/>
|
<path d="M-36 1448.15h199.77v11l-7 7H-36" class="box inline_expression_label"/>
|
||||||
<text x="1001" y="1540.95" class="box-text note-text"><tspan>Refunds the script lock tx, with the bidder's cleartext signature</tspan></text>
|
<text x="-34" y="1461.4" class="inline_expression-text alt-text anchor-start"><tspan>alt: bidder refunds script coin lock tx</tspan></text>
|
||||||
<text x="1001" y="1556.95" class="box-text note-text"><tspan>the offerer can refund the noscript lock tx. </tspan></text>
|
<path d="M799 1566.2h395v9h9m-9-9 9 9v41H799v-50z" class="box note" style="fill:#ffc"/>
|
||||||
<text x="1001" y="1572.95" class="box-text note-text"><tspan>Once the lock expires the pre-refund tx can be spent by the offerer.</tspan></text>
|
<text x="1001" y="1578.95" class="box-text note-text"><tspan>Refunds the script lock tx, with the bidder's cleartext signature</tspan></text>
|
||||||
<path d="m591 1599.2 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
|
<text x="1001" y="1594.95" class="box-text note-text"><tspan>the offerer can refund the noscript lock tx. </tspan></text>
|
||||||
<text x="689" y="1602.95" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
|
<text x="1001" y="1610.95" class="box-text note-text"><tspan>Once the lock expires the pre-refund tx can be spent by the offerer.</tspan></text>
|
||||||
<path d="M799 1620.2h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
|
<path d="m591 1637.2 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
|
||||||
<text x="1001" y="1640.95" class="box-text note-text"><tspan>offerer recovers the bidder's scriptless chain key-shard.</tspan></text>
|
<text x="689" y="1640.95" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
|
||||||
<path d="m383 1713.2 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="M799 1658.2h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
|
||||||
<text x="481" y="1716.95" class="box-text abox-text"><tspan>Bid Scriptless tx recovered</tspan></text>
|
<text x="1001" y="1678.95" class="box-text note-text"><tspan>offerer recovers the bidder's scriptless chain key-shard.</tspan></text>
|
||||||
<path d="m383 1751.2 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 1751.2 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="481" y="1754.95" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
|
<text x="481" y="1754.95" class="box-text abox-text"><tspan>Bid Scriptless tx recovered</tspan></text>
|
||||||
<path d="m383 1962.25 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
<path d="m383 1789.2 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
<text x="481" y="1966" class="box-text abox-text"><tspan>Bid Failed, swiped</tspan></text>
|
<text x="481" y="1792.95" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
|
||||||
|
<path d="m383 2000.25 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
|
||||||
|
<text x="481" y="2004" class="box-text abox-text"><tspan>Bid Failed, swiped</tspan></text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
@@ -1,4 +1,6 @@
|
|||||||
{% include 'header.html' %}
|
{% include 'header.html' %}
|
||||||
|
{% from 'style.html' import input_arrow_down_svg %}
|
||||||
|
|
||||||
<div class="container mx-auto">
|
<div class="container mx-auto">
|
||||||
<section class="p-5 mt-5">
|
<section class="p-5 mt-5">
|
||||||
<div class="flex flex-wrap items-center -m-2">
|
<div class="flex flex-wrap items-center -m-2">
|
||||||
@@ -503,10 +505,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<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">View Transaction:</td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
<div class="relative">
|
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||||
<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
|
|
||||||
</svg>
|
|
||||||
<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">
|
<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 %}
|
{% 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>
|
<option value="{{ tx.txid }}" {% if data.view_tx_ind==tx.txid %} selected{% endif %}>{{ tx.type }} {{ tx.txid }}</option>
|
||||||
@@ -723,10 +722,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<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 bold">Change Bid State:</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<div class="relative">
|
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||||
<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
|
|
||||||
</svg>
|
|
||||||
<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">
|
<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 %}
|
{% for s in data.bid_states %}
|
||||||
<option value="{{ s[0] }}" {% if data.bid_state_ind==s[0] %} selected{% endif %}>{{ s[1] }}</option>
|
<option value="{{ s[0] }}" {% if data.bid_state_ind==s[0] %} selected{% endif %}>{{ s[1] }}</option>
|
||||||
@@ -736,13 +732,22 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if data.debug_ui == true %}
|
{% 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">
|
<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 bold">Debug Option</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<div class="relative">
|
<div class="relative">{{ input_arrow_down_svg | safe }}
|
||||||
<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
|
|
||||||
</svg>
|
|
||||||
<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">
|
<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>
|
<option{% if data.debug_ind=="-1" %} selected{% endif %} value="-1">None</option>
|
||||||
{% for a in data.debug_options %}
|
{% for a in data.debug_options %}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
<div class="w-full md:w-1/2 mb-6 md:mb-0">
|
<div class="w-full md:w-1/2 mb-6 md:mb-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2023~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2024~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||||
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||||
<p class="text-sm text-coolGray-400 font-medium">GUI: v2.0.2</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
<p class="text-sm text-coolGray-400 font-medium">GUI: v2.0.3</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||||
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
<svg 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="#f80b0b" stroke-linejoin="round">
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#f80b0b" stroke-linejoin="round">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 p-1">
|
<div class="flex-1 p-1">
|
||||||
<h3 class="font-medium text-sm text-green-900">{{ m[1] }}</h3></div>
|
<h3 class="infomsg font-medium text-sm text-green-900">{{ m[1] }}</h3></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-auto p-2">
|
<div class="w-auto p-2">
|
||||||
|
|||||||
@@ -435,15 +435,14 @@
|
|||||||
<div class="flex flex-wrap justify-end">
|
<div class="flex flex-wrap justify-end">
|
||||||
<!--<div class="w-full md:w-auto p-1.5"><button name="show_rates_table" type="button" value="Show Rates Table" onclick='lookup_rates_table();' 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 shadow-button focus:ring-0 focus:outline-none"><svg class="mr-2"
|
<!--<div class="w-full md:w-auto p-1.5"><button name="show_rates_table" type="button" value="Show Rates Table" onclick='lookup_rates_table();' 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 shadow-button focus:ring-0 focus:outline-none"><svg class="mr-2"
|
||||||
xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24"><g fill="#556987"><path fill="#556987" 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="#556987" 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>Show Rates Table</span></button></div>-->
|
xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24"><g fill="#556987"><path fill="#556987" 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="#556987" 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>Show Rates Table</span></button></div>-->
|
||||||
{% if show_chart %}
|
{% if show_chart %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="loadPrices" type="button" value="Lookup Rates (RAW)" onclick="loadPrices();" 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">
|
<button name="loadPrices" id="loadPricesButton" type="button" value="Check Current Prices/Rates (TABLE)" 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"><span>Check Current Prices/Rates (TABLE)</span>
|
||||||
<span>Check Current Prices/Rates (TABLE)</span>
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="check_rates" type="button" value="Lookup Rates (RAW)" onclick='lookup_rates();' 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"><span>Check Current Prices/Rates (JSON)</span>
|
<button name="check_rates" type="button" value="Check Current Prices/Rates (JSON)" onclick='lookup_rates();' 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"><span>Check Current Prices/Rates (JSON)</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
@@ -540,22 +539,34 @@ xhr_rates_table.onload = () => {
|
|||||||
function lookup_rates() {
|
function lookup_rates() {
|
||||||
const coin_from = document.getElementById('coin_from').value;
|
const coin_from = document.getElementById('coin_from').value;
|
||||||
const coin_to = document.getElementById('coin_to').value;
|
const coin_to = document.getElementById('coin_to').value;
|
||||||
if (coin_from == '-1' || coin_to == '-1') {
|
|
||||||
|
if (coin_from === '-1' || coin_to === '-1') {
|
||||||
alert('Coins from and to must be set first.');
|
alert('Coins from and to must be set first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedCoin = (coin_from === '15') ? '3' : coin_from;
|
||||||
|
|
||||||
inner_html = '<p>Updating...</p>';
|
inner_html = '<p>Updating...</p>';
|
||||||
document.getElementById('rates_display').innerHTML = inner_html;
|
document.getElementById('rates_display').innerHTML = inner_html;
|
||||||
|
|
||||||
// Remove the 'hidden' class
|
|
||||||
document.querySelector(".pricejsonhidden").classList.remove("hidden");
|
document.querySelector(".pricejsonhidden").classList.remove("hidden");
|
||||||
|
|
||||||
|
const xhr_rates = new XMLHttpRequest();
|
||||||
|
xhr_rates.onreadystatechange = function() {
|
||||||
|
if (xhr_rates.readyState === XMLHttpRequest.DONE) {
|
||||||
|
if (xhr_rates.status === 200) {
|
||||||
|
document.getElementById('rates_display').innerHTML = xhr_rates.responseText;
|
||||||
|
} else {
|
||||||
|
console.error('Error fetching data:', xhr_rates.statusText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
xhr_rates.open('POST', '/json/rates');
|
xhr_rates.open('POST', '/json/rates');
|
||||||
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
xhr_rates.send('coin_from=' + coin_from + '&coin_to=' + coin_to);
|
xhr_rates.send('coin_from=' + selectedCoin + '&coin_to=' + coin_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function lookup_rates_table() {
|
function lookup_rates_table() {
|
||||||
const coin_from = document.getElementById('coin_from').value;
|
const coin_from = document.getElementById('coin_from').value;
|
||||||
const coin_to = document.getElementById('coin_to').value;
|
const coin_to = document.getElementById('coin_to').value;
|
||||||
@@ -575,8 +586,8 @@ function lookup_rates_table() {
|
|||||||
|
|
||||||
|
|
||||||
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
|
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
|
||||||
const adaptor_sig_only_coins = ['6' /* XMR */, '8' /* PART_ANON */, '7' /* PART_BLIND */];
|
const adaptor_sig_only_coins = ['6' /* XMR */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */];
|
||||||
const secret_hash_only_coins = ['11' /* PIVX */, '12' /* DASH */, '13' /* FIRO */];
|
const secret_hash_only_coins = ['11' /* PIVX */, '12' /* DASH */];
|
||||||
let make_hidden = false;
|
let make_hidden = false;
|
||||||
if (adaptor_sig_only_coins.includes(coin_from) || adaptor_sig_only_coins.includes(coin_to)) {
|
if (adaptor_sig_only_coins.includes(coin_from) || adaptor_sig_only_coins.includes(coin_to)) {
|
||||||
swap_type.disabled = true;
|
swap_type.disabled = true;
|
||||||
@@ -584,7 +595,7 @@ function set_swap_type_enabled(coin_from, coin_to, swap_type) {
|
|||||||
make_hidden = true;
|
make_hidden = true;
|
||||||
swap_type.classList.add('select-disabled'); // Add the class to the disabled select
|
swap_type.classList.add('select-disabled'); // Add the class to the disabled select
|
||||||
} else
|
} else
|
||||||
if (secret_hash_only_coins.includes(coin_from)) {
|
if (secret_hash_only_coins.includes(coin_from) && secret_hash_only_coins.includes(coin_to)) {
|
||||||
swap_type.disabled = true;
|
swap_type.disabled = true;
|
||||||
swap_type.value = 'seller_first';
|
swap_type.value = 'seller_first';
|
||||||
make_hidden = true;
|
make_hidden = true;
|
||||||
@@ -652,17 +663,10 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
const swap_type = document.getElementById('swap_type');
|
const swap_type = document.getElementById('swap_type');
|
||||||
set_swap_type_enabled(coin_from, coin_to, swap_type);
|
set_swap_type_enabled(coin_from, coin_to, swap_type);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
<script src="static/js/new_offer.js"></script>
|
|
||||||
<script src="static/js/coin_icons.js"></script>
|
|
||||||
<script src="static/js/coin_icons_2.js"></script>
|
|
||||||
</div>
|
|
||||||
{% include 'footer.html' %}
|
|
||||||
</div>
|
|
||||||
{% if show_chart %}
|
{% if show_chart %}
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.querySelector("button[name='loadPrices']").addEventListener("click", loadPrices);
|
const loadPricesButton = document.getElementById("loadPricesButton");
|
||||||
|
|
||||||
function loadPrices() {
|
function loadPrices() {
|
||||||
const api_key = '{{chart_api_key}}';
|
const api_key = '{{chart_api_key}}';
|
||||||
@@ -678,25 +682,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const priceBTC = data.RAW[coin].BTC.PRICE;
|
const priceBTC = data.RAW[coin].BTC.PRICE;
|
||||||
|
|
||||||
const tableRow = document.createElement("tr");
|
const tableRow = document.createElement("tr");
|
||||||
tableRow.classList.add("opacity-100", "text-gray-500", "dark:text-gray-100", "dark:text-gray-100", "hover:bg-coolGray-200", "dark:hover:bg-gray-600");
|
tableRow.classList.add("opacity-100", "text-gray-500", "dark:text-gray-100",
|
||||||
|
"dark:text-gray-100", "hover:bg-coolGray-200",
|
||||||
|
"dark:hover:bg-gray-600");
|
||||||
|
|
||||||
const coinCell = document.createElement("td", "py-3", "px-6");
|
const coinCell = document.createElement("td");
|
||||||
coinCell.textContent = coin;
|
coinCell.textContent = coin;
|
||||||
coinCell.classList.add("py-3", "px-6", "bold");
|
coinCell.classList.add("py-3", "px-6", "bold");
|
||||||
tableRow.appendChild(coinCell);
|
tableRow.appendChild(coinCell);
|
||||||
|
|
||||||
const usdPriceCell = document.createElement("td", "py-3", "px-6");
|
const usdPriceCell = document.createElement("td");
|
||||||
usdPriceCell.textContent = priceUSD.toFixed(2) + ' USD';
|
usdPriceCell.textContent = priceUSD.toFixed(2) + ' USD';
|
||||||
coinCell.classList.add("py-3", "px-6");
|
usdPriceCell.classList.add("py-3");
|
||||||
tableRow.appendChild(usdPriceCell);
|
tableRow.appendChild(usdPriceCell);
|
||||||
|
|
||||||
const btcPriceCell = document.createElement("td");
|
const btcPriceCell = document.createElement("td");
|
||||||
|
btcPriceCell.classList.add("py-3");
|
||||||
if (coin !== 'BTC') {
|
if (coin !== 'BTC') {
|
||||||
btcPriceCell.textContent = priceBTC.toFixed(8) + ' BTC';
|
btcPriceCell.textContent = priceBTC.toFixed(8) + ' BTC';
|
||||||
} else {
|
} else {
|
||||||
btcPriceCell.textContent = '-';
|
btcPriceCell.textContent = '-';
|
||||||
}
|
}
|
||||||
coinCell.classList.add("py-3", "px-6");
|
|
||||||
tableRow.appendChild(btcPriceCell);
|
tableRow.appendChild(btcPriceCell);
|
||||||
|
|
||||||
document.getElementById("priceTableBody").appendChild(tableRow);
|
document.getElementById("priceTableBody").appendChild(tableRow);
|
||||||
@@ -704,15 +710,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
.catch(error => console.error(`Error fetching ${coin} data:`, error));
|
.catch(error => console.error(`Error fetching ${coin} data:`, error));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove the 'hidden' class from the section when the button is clicked
|
|
||||||
document.querySelector(".pricetablehidden").classList.remove("hidden");
|
document.querySelector(".pricetablehidden").classList.remove("hidden");
|
||||||
|
loadPricesButton.disabled = true;
|
||||||
// Disable the button to prevent multiple clicks
|
|
||||||
const button = document.querySelector("button[name='loadPrices']");
|
|
||||||
button.disabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadPricesButton.addEventListener("click", loadPrices);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</script>
|
||||||
|
<script src="static/js/new_offer.js"></script>
|
||||||
|
<script src="static/js/coin_icons.js"></script>
|
||||||
|
<script src="static/js/coin_icons_2.js"></script>
|
||||||
|
</div>
|
||||||
|
{% include 'footer.html' %}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<input class="hover:border-blue-500 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" name="type_map">
|
<input class="hover:border-blue-500 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" name="type_map" title="Convert inputs when using http. Example: 'sibj' 1st parameter is a string, 2nd is converted to an int then boolean and json object or array">
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
<div class="px-6">
|
<div class="px-6">
|
||||||
<div class="flex flex-wrap justify-end">
|
<div class="flex flex-wrap justify-end">
|
||||||
<div class="w-full md:w-auto p-1.5 ml-2">
|
<div class="w-full md:w-auto p-1.5 ml-2">
|
||||||
<button name="" value="Apply" 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">
|
<button name="apply" value="Apply" 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">
|
||||||
<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">
|
<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 stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round">
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round">
|
||||||
<polyline points=" 6,12 10,16 18,8 " stroke="#ffffff"></polyline>
|
<polyline points=" 6,12 10,16 18,8 " stroke="#ffffff"></polyline>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<textarea class="hover:border-blue-500 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" rows="20">{{ result }}</textarea>
|
<textarea name="result" class="hover:border-blue-500 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" rows="20">{{ result }}</textarea>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<div class="text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700">
|
<div class="text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700">
|
||||||
<ul class="flex flex-wrap -mb-px" id="myTab" data-tabs-toggle="#settingstab" role="tablist">
|
<ul class="flex flex-wrap -mb-px" id="myTab" data-tabs-toggle="#settingstab" role="tablist">
|
||||||
<li class="mr-2" role="presentation">
|
<li class="mr-2" role="presentation">
|
||||||
<a class="inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="profile-tab" data-tabs-target="#coins" role="tab" aria-controls="coins" aria-selected={% if active_tab == 'default' %}"true"{% else %}"false"{% endif %}>Coins</a>
|
<a class="inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="coins-tab" data-tabs-target="#coins" role="tab" aria-controls="coins" aria-selected={% if active_tab == 'default' %}"true"{% else %}"false"{% endif %}>Coins</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mr-2" role="presentation">
|
<li class="mr-2" role="presentation">
|
||||||
<a class="inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="general-tab" data-tabs-target="#general" role="tab" aria-controls="general" aria-selected={% if active_tab == 'general' %}"true"{% else %}"false"{% endif %}>General</a>
|
<a class="inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="general-tab" data-tabs-target="#general" role="tab" aria-controls="general" aria-selected={% if active_tab == 'general' %}"true"{% else %}"false"{% endif %}>General</a>
|
||||||
|
|||||||
118
basicswap/templates/style.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
{% set select_box_arrow_svg = '<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
|
||||||
|
</svg>' %}
|
||||||
|
|
||||||
|
{% set circular_arrows_svg = '<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" class="nc-icon-wrapper">
|
||||||
|
<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.442C16.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.442C7.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>' %}
|
||||||
|
|
||||||
|
{% set circular_error_svg = '<svg class="relative top-0.5" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.4733 5.52667C10.4114 5.46419 10.3376 5.41459 10.2564 5.38075C10.1751 5.3469 10.088 5.32947 9.99999 5.32947C9.91198 5.32947 9.82485 5.3469 9.74361 5.38075C9.66237 5.41459 9.58863 5.46419 9.52666 5.52667L7.99999 7.06001L6.47333 5.52667C6.34779 5.40114 6.17753 5.33061 5.99999 5.33061C5.82246 5.33061 5.65219 5.40114 5.52666 5.52667C5.40112 5.65221 5.3306 5.82247 5.3306 6.00001C5.3306 6.17754 5.40112 6.3478 5.52666 6.47334L7.05999 8.00001L5.52666 9.52667C5.46417 9.58865 5.41458 9.66238 5.38073 9.74362C5.34689 9.82486 5.32946 9.912 5.32946 10C5.32946 10.088 5.34689 10.1752 5.38073 10.2564C5.41458 10.3376 5.46417 10.4114 5.52666 10.4733C5.58863 10.5358 5.66237 10.5854 5.74361 10.6193C5.82485 10.6531 5.91198 10.6705 5.99999 10.6705C6.088 10.6705 6.17514 10.6531 6.25638 10.6193C6.33762 10.5854 6.41135 10.5358 6.47333 10.4733L7.99999 8.94001L9.52666 10.4733C9.58863 10.5358 9.66237 10.5854 9.74361 10.6193C9.82485 10.6531 9.91198 10.6705 9.99999 10.6705C10.088 10.6705 10.1751 10.6531 10.2564 10.6193C10.3376 10.5854 10.4114 10.5358 10.4733 10.4733C10.5358 10.4114 10.5854 10.3376 10.6193 10.2564C10.6531 10.1752 10.6705 10.088 10.6705 10C10.6705 9.912 10.6531 9.82486 10.6193 9.74362C10.5854 9.66238 10.5358 9.58865 10.4733 9.52667L8.93999 8.00001L10.4733 6.47334C10.5358 6.41137 10.5854 6.33763 10.6193 6.25639C10.6531 6.17515 10.6705 6.08802 10.6705 6.00001C10.6705 5.912 10.6531 5.82486 10.6193 5.74362C10.5854 5.66238 10.5358 5.58865 10.4733 5.52667ZM12.7133 3.28667C12.0983 2.64994 11.3627 2.14206 10.5494 1.79266C9.736 1.44327 8.8612 1.25936 7.976 1.25167C7.0908 1.24398 6.21294 1.41266 5.39363 1.74786C4.57432 2.08307 3.82998 2.57809 3.20403 3.20404C2.57807 3.82999 2.08305 4.57434 1.74785 5.39365C1.41264 6.21296 1.24396 7.09082 1.25166 7.97602C1.25935 8.86121 1.44326 9.73601 1.79265 10.5494C2.14204 11.3627 2.64992 12.0984 3.28666 12.7133C3.90164 13.3501 4.63727 13.858 5.45063 14.2074C6.26399 14.5567 7.13879 14.7407 8.02398 14.7483C8.90918 14.756 9.78704 14.5874 10.6064 14.2522C11.4257 13.9169 12.17 13.4219 12.796 12.796C13.4219 12.17 13.9169 11.4257 14.2521 10.6064C14.5873 9.78706 14.756 8.90919 14.7483 8.024C14.7406 7.1388 14.5567 6.264 14.2073 5.45064C13.8579 4.63728 13.3501 3.90165 12.7133 3.28667ZM11.7733 11.7733C10.9014 12.6463 9.75368 13.1899 8.52585 13.3115C7.29802 13.4332 6.066 13.1254 5.03967 12.4405C4.01335 11.7557 3.25623 10.7361 2.89731 9.55566C2.53838 8.37518 2.59986 7.10677 3.07127 5.96653C3.54267 4.82629 4.39484 3.88477 5.48259 3.30238C6.57033 2.71999 7.82635 2.53276 9.03666 2.77259C10.247 3.01242 11.3367 3.66447 12.1202 4.61765C12.9036 5.57083 13.3324 6.76617 13.3333 8.00001C13.3357 8.70087 13.1991 9.39524 12.9313 10.0429C12.6635 10.6906 12.2699 11.2788 11.7733 11.7733Z" fill="#EF5944"></path>
|
||||||
|
</svg>' %}
|
||||||
|
|
||||||
|
{% set circular_info_svg = '<svg aria-hidden="true" class="flex-shrink-0 w-5 h-5 text-blue-700 dark:text-blue-800" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
|
||||||
|
</svg>' %}
|
||||||
|
|
||||||
|
{% set cross_close_svg = '<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||||
|
</svg>' %}
|
||||||
|
|
||||||
|
{% set breadcrumb_line_svg = '<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>' %}
|
||||||
|
|
||||||
|
{% set withdraw_svg = '<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" class="nc-icon-wrapper"><polygon data-color="color-2" points="6,10 12,17 18,10 13,10 13,1 11,1 11,10 "></polygon><path fill="#ffffff" d="M22,21H2v-6H0v7c0,0.552,0.448,1,1,1h22c0.552,0,1-0.448,1-1v-7h-2V21z"></path></g></svg>' %}
|
||||||
|
|
||||||
|
{% set utxo_groups_svg = '<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 stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round" class="nc-icon-wrapper">
|
||||||
|
<rect x="2" y="9" width="12" height="14"></rect>
|
||||||
|
<polyline points=" 6,5 18,5 18,19 " stroke="#ffffff"></polyline>
|
||||||
|
<polyline points=" 10,1 22,1 22,15 " stroke="#ffffff"></polyline>
|
||||||
|
</g>
|
||||||
|
</svg>' %}
|
||||||
|
|
||||||
|
{% set create_utxo_svg = '<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 stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round" class="nc-icon-wrapper">
|
||||||
|
<polyline points="1 15 1 21 23 21 23 15"></polyline>
|
||||||
|
<line x1="12" y1="3" x2="12" y2="13" stroke="#ffffff"></line>
|
||||||
|
<line x1="17" y1="8" x2="7" y2="8" stroke="#ffffff"></line>
|
||||||
|
</g>
|
||||||
|
</svg>' %}
|
||||||
|
|
||||||
|
{% set lock_svg = '<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 d="M19,10H5a3,3,0,0,0-3,3v8a3,3,0,0,0,3,3H19a3,3,0,0,0,3-3V13A3,3,0,0,0,19,10Zm-7,9a2,2,0,1,1,2-2A2,2,0,0,1,12,19Z" fill="#ffffff"></path><path data-color="color-2" d="M18,8H16V6a3.958,3.958,0,0,0-3.911-4h-.042A3.978,3.978,0,0,0,8,5.911V8H6V5.9A5.961,5.961,0,0,1,11.949,0h.061A5.979,5.979,0,0,1,18,6.01Z"></path></g>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
{% set eye_show_svg = '<svg xmlns="http://www.w3.org/2000/svg" fill="#ffffff" height="20" width="20" viewBox="0 0 24 24">
|
||||||
|
<path d="M23.444,10.239a22.936,22.936,0,0,0-2.492-2.948l-4.021,4.021A5.026,5.026,0,0,1,17,12a5,5,0,0,1-5,5,5.026,5.026,0,0,1-.688-.069L8.055,20.188A10.286,10.286,0,0,0,12,21c5.708,0,9.905-5.062,11.445-7.24A3.058,3.058,0,0,0,23.444,10.239Z"></path>
|
||||||
|
<path d="M12,3C6.292,3,2.1,8.062,.555,10.24a3.058,3.058,0,0,0,0,3.52h0a21.272,21.272,0,0,0,4.784,4.9l3.124-3.124a5,5,0,0,1,7.071-7.072L8.464,15.536l10.2-10.2A11.484,11.484,0,0,0,12,3Z"></path>
|
||||||
|
<path data-color="color-2" d="M1,24a1,1,0,0,1-.707-1.707l22-22a1,1,0,0,1,1.414,1.414l-22,22A1,1,0,0,1,1,24Z"></path>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
{% set place_new_offer_svg = '<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24">
|
||||||
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round">
|
||||||
|
<circle cx="5" cy="5" r="4"></circle>
|
||||||
|
<circle cx="19" cy="19" r="4"></circle>
|
||||||
|
<polyline data-cap="butt" points="13,5 21,5 21,11 " stroke="#ffffff"></polyline>
|
||||||
|
<polyline data-cap="butt" points="11,19 3,19 3,13 " stroke="#ffffff"></polyline>
|
||||||
|
<polyline points=" 16,2 13,5 16,8 " stroke="#ffffff"></polyline>
|
||||||
|
<polyline points=" 8,16 11,19 8,22 " stroke="#ffffff"></polyline>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
{% set page_back_svg = '<svg aria-hidden="true" class="mr-2 w-5 h-5" fill="#fff" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
{% set page_forwards_svg = '<svg aria-hidden="true" class="ml-2 w-5 h-5" fill="#fff" 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>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% set filter_clear_svg = '<svg class="mr-2 w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
|
||||||
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round">
|
||||||
|
<line x1="20" y1="2" x2="12.329" y2="11.506"></line>
|
||||||
|
<path d="M11,11a2,2,0,0,1,2,2,3.659,3.659,0,0,1-.2.891A9.958,9.958,0,0,0,13.258,23H1C1,16.373,4.373,11,11,11Z"></path>
|
||||||
|
<line x1="18" y1="15" x2="23" y2="15" stroke="#ffffff"></line>
|
||||||
|
<line x1="17" y1="19" x2="23" y2="19" stroke="#ffffff"></line>
|
||||||
|
<line x1="19" y1="23" x2="23" y2="23" stroke="#ffffff"></line>
|
||||||
|
<path d="M8.059,11.415A3.9,3.9,0,0,0,12,16c.041,0,.079-.011.12-.012" data-cap="butt"></path>
|
||||||
|
<path d="M5,23a13.279,13.279,0,0,1,.208-3.4" data-cap="butt"></path>
|
||||||
|
<path d="M9.042,23c-.688-1.083-.313-3.4-.313-3.4" data-cap="butt"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
{% set filter_apply_svg = ' <svg class="mr-2 w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
|
||||||
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round">
|
||||||
|
<rect x="2" y="2" width="7" height="7"></rect>
|
||||||
|
<rect x="15" y="15" width="7" height="7"></rect>
|
||||||
|
<rect x="2" y="15" width="7" height="7"></rect>
|
||||||
|
<polyline points="15 6 17 8 22 3" stroke="#ffffff"></polyline>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% set input_arrow_down_svg = '<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
|
||||||
|
</svg>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
{% set arrow_right_svg = ' <svg aria-hidden="true" class="w-5 h-5" 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>
|
||||||
|
' %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% set select_box_class = 'hover:border-blue-500 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-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0' %}
|
||||||
@@ -1,98 +1,71 @@
|
|||||||
{% include 'header.html' %}
|
{% include 'header.html' %}
|
||||||
|
{% from 'style.html' import breadcrumb_line_svg, circular_arrows_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, lock_svg, eye_show_svg %}
|
||||||
|
|
||||||
<div class="container mx-auto">
|
<div class="container mx-auto">
|
||||||
<section class="p-5 mt-5">
|
<section class="p-5 mt-5">
|
||||||
<div class="flex flex-wrap items-center -m-2">
|
<div class="flex flex-wrap items-center -m-2">
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
||||||
<li>
|
<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>
|
||||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">
|
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||||
<p>Home</p>
|
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallets">Wallets</a></li>
|
||||||
</a>
|
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||||
</li>
|
</ul>
|
||||||
<li>
|
</div>
|
||||||
<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="/wallets">Wallets</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>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="py-3">
|
|
||||||
|
<section class="py-3">
|
||||||
<div class="container px-4 mx-auto">
|
<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">
|
<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 left-4 top-4" src="/static/images/elements/dots-red.svg" alt="dots-red">
|
||||||
<img class="absolute z-10 right-4 bottom-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="dots-red">
|
||||||
<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="">
|
<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="wave">
|
||||||
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
||||||
<div class="w-full md:w-1/2 p-3 h-48">
|
<div class="w-full md:w-1/2 p-3 h-48">
|
||||||
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Wallets</h2>
|
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Wallets</h2>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h2 class="text-lg font-bold text-white tracking-tighter mr-2">Total Assets:</h2>
|
<h2 class="text-lg font-bold text-white tracking-tighter mr-2">Total Assets:</h2>
|
||||||
<button id="hide-usd-amount-toggle" class="flex items-center justify-center p-1 focus:ring-0 focus:outline-none">
|
<button id="hide-usd-amount-toggle" class="flex items-center justify-center p-1 focus:ring-0 focus:outline-none">{{ eye_show_svg | safe }}</button>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#ffffff" height="20" width="20" viewBox="0 0 24 24">
|
</div>
|
||||||
<path d="M23.444,10.239a22.936,22.936,0,0,0-2.492-2.948l-4.021,4.021A5.026,5.026,0,0,1,17,12a5,5,0,0,1-5,5,5.026,5.026,0,0,1-.688-.069L8.055,20.188A10.286,10.286,0,0,0,12,21c5.708,0,9.905-5.062,11.445-7.24A3.058,3.058,0,0,0,23.444,10.239Z"></path>
|
<div class="flex items-baseline mt-2">
|
||||||
<path d="M12,3C6.292,3,2.1,8.062,.555,10.24a3.058,3.058,0,0,0,0,3.52h0a21.272,21.272,0,0,0,4.784,4.9l3.124-3.124a5,5,0,0,1,7.071-7.072L8.464,15.536l10.2-10.2A11.484,11.484,0,0,0,12,3Z"></path>
|
<div id="total-usd-value" class="text-5xl font-bold text-white"></div>
|
||||||
<path data-color="color-2" d="M1,24a1,1,0,0,1-.707-1.707l22-22a1,1,0,0,1,1.414,1.414l-22,22A1,1,0,0,1,1,24Z"></path>
|
<div id="usd-text" class="text-sm text-white ml-1">USD</div>
|
||||||
</svg>
|
</div>
|
||||||
</button>
|
<div id="total-btc-value" class="text-sm text-white mt-2"></div>
|
||||||
</div>
|
|
||||||
<div class="flex items-baseline mt-2">
|
|
||||||
<div id="total-usd-value" class="text-5xl font-bold text-white"></div>
|
|
||||||
<div id="usd-text" class="text-sm text-white ml-1">USD</div>
|
|
||||||
</div>
|
|
||||||
<div id="total-btc-value" class="text-sm text-white mt-2"></div>
|
|
||||||
</div>
|
</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">
|
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
|
||||||
<a class="mr-5 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" id="refresh" href="/changepassword">
|
<a class="rounded-full mr-5 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" id="refresh" href="/changepassword">{{ lock_svg | safe }}<span>Change Password</span></a>
|
||||||
<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">
|
<a class="rounded-full 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" id="refresh" href="/wallets">{{ circular_arrows_svg | safe }}<span>Refresh</span></a>
|
||||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#ffffff" stroke-linejoin="round">
|
|
||||||
<rect x="3" y="11" width="18" height="12" rx="2"></rect>
|
|
||||||
<circle cx="12" cy="17" r="2" stroke="#ffffff"></circle>
|
|
||||||
<path d="M17,7V6a4.951,4.951,0,0,0-4.9-5H12A4.951,4.951,0,0,0,7,5.9V7" stroke="#ffffff"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<span>Change Password</span>
|
|
||||||
</a>
|
|
||||||
<a 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" id="refresh" href="/wallets">
|
|
||||||
<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" class="nc-icon-wrapper">
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% include 'inc_messages.html' %}
|
{% include 'inc_messages.html' %}
|
||||||
|
|
||||||
<section class="py-4">
|
<section class="py-4">
|
||||||
<div class="container px-4 mx-auto">
|
<div class="container px-4 mx-auto">
|
||||||
<div class="flex flex-wrap -m-4"> {% for w in wallets %} {% if w.havedata %} {% if w.error %} <p>Error: {{ w.error }}</p> {% else %} <div class="w-full lg:w-1/3 p-4">
|
<div class="flex flex-wrap -m-4">
|
||||||
|
{% for w in wallets %}
|
||||||
|
{% if w.havedata %}
|
||||||
|
{% if w.error %}<p>Error: {{ w.error }}</p>
|
||||||
|
{% else %}
|
||||||
|
<div class="w-full lg:w-1/3 p-4">
|
||||||
<div class="bg-gray-50 shadow rounded overflow-hidden dark:bg-gray-500">
|
<div class="bg-gray-50 shadow rounded overflow-hidden dark:bg-gray-500">
|
||||||
<div class="pt-6 px-6 mb-10 flex justify-between items-center">
|
<div class="pt-6 px-6 mb-10 flex justify-between items-center">
|
||||||
<span class="inline-flex items-center justify-center w-9 h-10 bg-white-50 rounded">
|
<span class="inline-flex items-center justify-center w-9 h-10 bg-white-50 rounded">
|
||||||
<img class="h-9" src="/static/images/coins/{{ w.name }}.png" alt="">
|
<img class="h-9" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}">
|
||||||
</span>
|
</span>
|
||||||
<a class="py-2 px-3 bg-blue-500 text-xs text-white rounded-full hover:bg-blue-600" href="/wallet/{{ w.ticker }}">Manage</a>
|
<a class="py-2 px-3 bg-blue-500 text-xs text-white rounded-full hover:bg-blue-600" href="/wallet/{{ w.ticker }}">Manage Wallet</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-6 mb-6">
|
<div class="px-6 mb-6">
|
||||||
<h4 class="text-xl font-bold dark:text-white">{{ w.name }}
|
<h4 class="text-xl font-bold dark:text-white">{{ w.name }}
|
||||||
<span class="inline-block font-medium text-xs text-gray-500 dark:text-white">({{ w.ticker }})</span>
|
<span class="inline-block font-medium text-xs text-gray-500 dark:text-white">({{ w.ticker }})</span>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-200">Version: {{ w.version }} {% if w.updating %} <span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-700 dark:hover:bg-gray-700">Updating..</span>
|
<p class="text-xs text-gray-500 dark:text-gray-200">Version: {{ w.version }} {% if w.updating %} <span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-700 dark:hover:bg-gray-700">Updating..</span></p>
|
||||||
</p>{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 bg-coolGray-100 dark:bg-gray-600">
|
<div class="p-6 bg-coolGray-100 dark:bg-gray-600">
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
@@ -103,17 +76,17 @@
|
|||||||
<h4 class="text-xs font-medium dark:text-white">{{ w.ticker }} USD value:</h4>
|
<h4 class="text-xs font-medium dark:text-white">{{ w.ticker }} USD value:</h4>
|
||||||
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 usd-value"></div>
|
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 usd-value"></div>
|
||||||
</div>
|
</div>
|
||||||
{% if w.unconfirmed %}
|
{% if w.pending %}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Unconfirmed:</h4>
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Pending:</h4>
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="{{ w.name }}">+{{ w.unconfirmed }} {{ w.ticker }}</span>
|
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="{{ w.name }}">+{{ w.pending }} {{ w.ticker }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Unconfirmed USD value:</h4>
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Pending USD value:</h4>
|
||||||
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if w.cid == '1' %}
|
{% if w.cid == '1' %} {# PART #}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4>
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span>
|
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span>
|
||||||
@@ -151,30 +124,59 @@
|
|||||||
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %} {# / PART #}
|
||||||
|
{% if w.cid == '3' %} {# LTC #}
|
||||||
|
<div class="flex mb-2 justify-between items-center">
|
||||||
|
<h4 class="text-xs font-medium dark:text-white">MWEB Balance:</h4>
|
||||||
|
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-2 justify-between items-center">
|
||||||
|
<h4 class="text-xs font-medium dark:text-white">MWEB USD value:</h4>
|
||||||
|
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 usd-value"></div>
|
||||||
|
</div>
|
||||||
|
{% if w.mweb_pending %}
|
||||||
|
<div class="flex mb-2 justify-between items-center">
|
||||||
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">MWEB Pending:</h4>
|
||||||
|
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="{{ w.name }}">
|
||||||
|
+{{ w.mweb_pending }} {{ w.ticker }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-2 justify-between items-center">
|
||||||
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">MWEB Pending USD value:</h4>
|
||||||
|
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{# / LTC #}
|
||||||
|
<hr class="border-t border-gray-100 dark:border-gray-500 my-5">
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Blocks:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Blocks:</h4>
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.blocks }}{% if w.known_block_count %} / {{ w.known_block_count }}{% endif %}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.blocks }}{% if w.known_block_count %} / {{ w.known_block_count }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Last Updated:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Last Updated:</h4>
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.lastupdated }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.lastupdated }}</span>
|
||||||
</div>{% if w.bootstrapping %}
|
</div>
|
||||||
|
{% if w.bootstrapping %}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Bootstrapping:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Bootstrapping:</h4>
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.bootstrapping }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.bootstrapping }}</span>
|
||||||
</div>{% endif %}
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if w.encrypted %}
|
{% if w.encrypted %}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Locked:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Locked:</h4>
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.locked }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.locked }}</span>
|
||||||
</div>{% endif %} <div class="flex mb-2 justify-between items-center">
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Expected Seed:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Expected Seed:</h4>
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.expected_seed }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.expected_seed }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mb-1 mt-10">
|
<div class="flex justify-between mb-1 mt-10">
|
||||||
<span class="text-xs font-medium text-blue-700 dark:text-gray-200">Blockchain</span>
|
<span class="text-xs font-medium dark:text-gray-200">Blockchain</span>
|
||||||
<span class="text-xs font-medium text-blue-700 dark:text-gray-200">{{ w.synced }}%</span>
|
<span class="text-xs font-medium dark:text-gray-200">{{ w.synced }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 rounded-full h-1">
|
<div class="w-full bg-gray-200 rounded-full h-1">
|
||||||
<div class="bg-blue-500 h-1 rounded-full" style="width: {{ w.synced }}%"></div>
|
<div class="bg-blue-500 h-1 rounded-full" style="width: {{ w.synced }}%"></div>
|
||||||
@@ -183,12 +185,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- havedata -->
|
</div>
|
||||||
</div> {% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const coinNameToSymbol = {
|
const coinNameToSymbol = {
|
||||||
'Bitcoin': 'BTC',
|
'Bitcoin': 'BTC',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ from .util import (
|
|||||||
get_data_entry,
|
get_data_entry,
|
||||||
have_data_entry,
|
have_data_entry,
|
||||||
get_data_entry_or,
|
get_data_entry_or,
|
||||||
|
listBidActions,
|
||||||
listBidStates,
|
listBidStates,
|
||||||
listOldBidStates,
|
listOldBidStates,
|
||||||
set_pagination_filters,
|
set_pagination_filters,
|
||||||
@@ -73,6 +74,7 @@ def page_bid(self, url_split, post_string):
|
|||||||
elif b'edit_bid_submit' in form_data:
|
elif b'edit_bid_submit' in form_data:
|
||||||
data = {
|
data = {
|
||||||
'bid_state': int(form_data[b'new_state'][0]),
|
'bid_state': int(form_data[b'new_state'][0]),
|
||||||
|
'bid_action': int(get_data_entry_or(form_data, 'new_action', -1)),
|
||||||
'debug_ind': int(get_data_entry_or(form_data, 'debugind', -1)),
|
'debug_ind': int(get_data_entry_or(form_data, 'debugind', -1)),
|
||||||
'kbs_other': get_data_entry_or(form_data, 'kbs_other', None),
|
'kbs_other': get_data_entry_or(form_data, 'kbs_other', None),
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,14 @@ def page_bid(self, url_split, post_string):
|
|||||||
if len(data['addr_from_label']) > 0:
|
if len(data['addr_from_label']) > 0:
|
||||||
data['addr_from_label'] = '(' + data['addr_from_label'] + ')'
|
data['addr_from_label'] = '(' + data['addr_from_label'] + ')'
|
||||||
|
|
||||||
|
page_data = {
|
||||||
|
'bid_states': listBidStates(),
|
||||||
|
'bid_actions': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if swap_client.debug_ui:
|
||||||
|
data['bid_actions'] = [(-1, 'None'), ] + listBidActions()
|
||||||
|
|
||||||
template = server.env.get_template('bid_xmr.html' if offer.swap_type == SwapTypes.XMR_SWAP else 'bid.html')
|
template = server.env.get_template('bid_xmr.html' if offer.swap_type == SwapTypes.XMR_SWAP else 'bid.html')
|
||||||
return self.render_template(template, {
|
return self.render_template(template, {
|
||||||
'bid_id': bid_id.hex(),
|
'bid_id': bid_id.hex(),
|
||||||
@@ -175,9 +185,8 @@ def page_bids(self, url_split, post_string, sent=False, available=False, receive
|
|||||||
bids = swap_client.listBids(sent=sent, filters=filters)
|
bids = swap_client.listBids(sent=sent, filters=filters)
|
||||||
|
|
||||||
page_data = {
|
page_data = {
|
||||||
'bid_states': listBidStates()
|
'bid_states': listBidStates(),
|
||||||
}
|
}
|
||||||
|
|
||||||
template = server.env.get_template('bids.html')
|
template = server.env.get_template('bids.html')
|
||||||
return self.render_template(template, {
|
return self.render_template(template, {
|
||||||
'page_type_sent': 'Bids Sent' if sent else '',
|
'page_type_sent': 'Bids Sent' if sent else '',
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ def page_unlock(self, url_split, post_string):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
return bytes()
|
return bytes()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if swap_client.debug is True:
|
||||||
|
swap_client.log.error(str(e))
|
||||||
err_messages.append(str(e))
|
err_messages.append(str(e))
|
||||||
|
|
||||||
template = server.env.get_template('unlock.html')
|
template = server.env.get_template('unlock.html')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -41,6 +41,9 @@ from basicswap.chainparams import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default_chart_api_key = '95dd900af910656e0e17c41f2ddc5dba77d01bf8b0e7d2787634a16bd976c553'
|
||||||
|
|
||||||
|
|
||||||
def value_or_none(v):
|
def value_or_none(v):
|
||||||
if v == -1 or v == '-1':
|
if v == -1 or v == '-1':
|
||||||
return None
|
return None
|
||||||
@@ -170,7 +173,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
try:
|
try:
|
||||||
swap_client.validateSwapType(coin_from, coin_to, swap_type)
|
swap_client.validateSwapType(coin_from, coin_to, swap_type)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append(f'Invalid Swap type {e}')
|
errors.append(f'{e}')
|
||||||
|
|
||||||
if have_data_entry(form_data, 'step1'):
|
if have_data_entry(form_data, 'step1'):
|
||||||
if len(errors) == 0 and have_data_entry(form_data, 'continue'):
|
if len(errors) == 0 and have_data_entry(form_data, 'continue'):
|
||||||
@@ -217,7 +220,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if len(errors) == 0 and page_data['swap_style'] == 'xmr':
|
if len(errors) == 0 and page_data['swap_style'] == 'xmr':
|
||||||
reverse_bid: bool = coin_from in swap_client.scriptless_coins
|
reverse_bid: bool = swap_client.is_reverse_ads_bid(coin_from)
|
||||||
ci_leader = ci_to if reverse_bid else ci_from
|
ci_leader = ci_to if reverse_bid else ci_from
|
||||||
ci_follower = ci_from if reverse_bid else ci_to
|
ci_follower = ci_from if reverse_bid else ci_to
|
||||||
|
|
||||||
@@ -261,10 +264,14 @@ def postNewOfferFromParsed(swap_client, parsed_data):
|
|||||||
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
|
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
|
||||||
swap_type = SwapTypes.XMR_SWAP
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
|
|
||||||
if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
|
# All coins capable of segwit should be capable of csv
|
||||||
lock_type = TxLockTypes.SEQUENCE_LOCK_TIME
|
lock_type = TxLockTypes.SEQUENCE_LOCK_TIME
|
||||||
else:
|
else:
|
||||||
lock_type = TxLockTypes.ABS_LOCK_TIME
|
if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']:
|
||||||
|
lock_type = TxLockTypes.SEQUENCE_LOCK_TIME
|
||||||
|
else:
|
||||||
|
lock_type = TxLockTypes.ABS_LOCK_TIME
|
||||||
|
|
||||||
extra_options = {}
|
extra_options = {}
|
||||||
|
|
||||||
@@ -441,7 +448,7 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
chart_api_key = swap_client.settings.get('chart_api_key', '')
|
chart_api_key = swap_client.settings.get('chart_api_key', '')
|
||||||
if chart_api_key == '':
|
if chart_api_key == '':
|
||||||
chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '')
|
chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '')
|
||||||
chart_api_key = 'cd7600e7b5fdd99c6f900673ff0ee8f64d6d4219a4bb87191ad4a2e3fc65d7f4' if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8')
|
chart_api_key = default_chart_api_key if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8')
|
||||||
|
|
||||||
return self.render_template(template, {
|
return self.render_template(template, {
|
||||||
'messages': messages,
|
'messages': messages,
|
||||||
@@ -484,7 +491,7 @@ def page_offer(self, url_split, post_string):
|
|||||||
ci_from = swap_client.ci(Coins(offer.coin_from))
|
ci_from = swap_client.ci(Coins(offer.coin_from))
|
||||||
ci_to = swap_client.ci(Coins(offer.coin_to))
|
ci_to = swap_client.ci(Coins(offer.coin_to))
|
||||||
|
|
||||||
reverse_bid: bool = Coins(offer.coin_from) in swap_client.scriptless_coins
|
reverse_bid: bool = True if offer.bid_reversed else False
|
||||||
|
|
||||||
# Set defaults
|
# Set defaults
|
||||||
debugind = -1
|
debugind = -1
|
||||||
@@ -586,7 +593,7 @@ def page_offer(self, url_split, post_string):
|
|||||||
'is_expired': offer.expire_at <= now,
|
'is_expired': offer.expire_at <= now,
|
||||||
'active_ind': offer.active_ind,
|
'active_ind': offer.active_ind,
|
||||||
'swap_type': strSwapDesc(offer.swap_type),
|
'swap_type': strSwapDesc(offer.swap_type),
|
||||||
'reverse': offer.bid_reversed
|
'reverse': reverse_bid,
|
||||||
}
|
}
|
||||||
data.update(extend_data)
|
data.update(extend_data)
|
||||||
|
|
||||||
@@ -742,7 +749,7 @@ def page_offers(self, url_split, post_string, sent=False):
|
|||||||
chart_api_key = swap_client.settings.get('chart_api_key', '')
|
chart_api_key = swap_client.settings.get('chart_api_key', '')
|
||||||
if chart_api_key == '':
|
if chart_api_key == '':
|
||||||
chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '')
|
chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '')
|
||||||
chart_api_key = 'cd7600e7b5fdd99c6f900673ff0ee8f64d6d4219a4bb87191ad4a2e3fc65d7f4' if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8')
|
chart_api_key = default_chart_api_key if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8')
|
||||||
|
|
||||||
template = server.env.get_template('offers.html')
|
template = server.env.get_template('offers.html')
|
||||||
return self.render_template(template, {
|
return self.render_template(template, {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def page_settings(self, url_split, post_string):
|
|||||||
data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False
|
data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False
|
||||||
data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name)
|
data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name)
|
||||||
data['rpcport'] = int(get_data_entry(form_data, 'rpcport_' + name))
|
data['rpcport'] = int(get_data_entry(form_data, 'rpcport_' + name))
|
||||||
data['remotedaemonurls'] = get_data_entry(form_data, 'remotedaemonurls_' + name)
|
data['remotedaemonurls'] = get_data_entry_or(form_data, 'remotedaemonurls_' + name, '')
|
||||||
data['automatically_select_daemon'] = True if get_data_entry(form_data, 'autosetdaemon_' + name) == 'true' else False
|
data['automatically_select_daemon'] = True if get_data_entry(form_data, 'autosetdaemon_' + name) == 'true' else False
|
||||||
else:
|
else:
|
||||||
data['conf_target'] = int(get_data_entry(form_data, 'conf_target_' + name))
|
data['conf_target'] = int(get_data_entry(form_data, 'conf_target_' + name))
|
||||||
|
|||||||
@@ -41,22 +41,34 @@ def format_wallet_data(swap_client, ci, w):
|
|||||||
wf['bootstrapping'] = True
|
wf['bootstrapping'] = True
|
||||||
if 'known_block_count' in w:
|
if 'known_block_count' in w:
|
||||||
wf['known_block_count'] = w['known_block_count']
|
wf['known_block_count'] = w['known_block_count']
|
||||||
|
if 'locked_utxos' in w:
|
||||||
|
wf['locked_utxos'] = w['locked_utxos']
|
||||||
|
|
||||||
if 'balance' in w and 'unconfirmed' in w:
|
if 'balance' in w and 'unconfirmed' in w:
|
||||||
wf['balance_all'] = float(w['balance']) + float(w['unconfirmed'])
|
wf['balance_all'] = float(w['balance']) + float(w['unconfirmed'])
|
||||||
if 'lastupdated' in w:
|
if 'lastupdated' in w:
|
||||||
wf['lastupdated'] = format_timestamp(w['lastupdated'])
|
wf['lastupdated'] = format_timestamp(w['lastupdated'])
|
||||||
|
|
||||||
|
pending: int = 0
|
||||||
if 'unconfirmed' in w and float(w['unconfirmed']) > 0.0:
|
if 'unconfirmed' in w and float(w['unconfirmed']) > 0.0:
|
||||||
wf['unconfirmed'] = w['unconfirmed']
|
pending += ci.make_int(w['unconfirmed'])
|
||||||
|
if 'immature' in w and float(w['immature']) > 0.0:
|
||||||
|
pending += ci.make_int(w['immature'])
|
||||||
|
if pending > 0.0:
|
||||||
|
wf['pending'] = ci.format_amount(pending)
|
||||||
|
|
||||||
if ci.coin_type() == Coins.PART:
|
if ci.coin_type() == Coins.PART:
|
||||||
wf['stealth_address'] = w.get('stealth_address', '?')
|
wf['stealth_address'] = w.get('stealth_address', '?')
|
||||||
wf['blind_balance'] = "{:.8f}".format(float(w['blind_balance']))
|
wf['blind_balance'] = w.get('blind_balance', '?')
|
||||||
if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0:
|
if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0:
|
||||||
wf['blind_unconfirmed'] = w['blind_unconfirmed']
|
wf['blind_unconfirmed'] = w['blind_unconfirmed']
|
||||||
wf['anon_balance'] = w.get('anon_balance', '?')
|
wf['anon_balance'] = w.get('anon_balance', '?')
|
||||||
if 'anon_pending' in w and float(w['anon_pending']) > 0.0:
|
if 'anon_pending' in w and float(w['anon_pending']) > 0.0:
|
||||||
wf['anon_pending'] = w['anon_pending']
|
wf['anon_pending'] = w['anon_pending']
|
||||||
|
elif ci.coin_type() == Coins.LTC:
|
||||||
|
wf['mweb_address'] = w.get('mweb_address', '?')
|
||||||
|
wf['mweb_balance'] = w.get('mweb_balance', '?')
|
||||||
|
wf['mweb_pending'] = w.get('mweb_pending', '?')
|
||||||
|
|
||||||
checkAddressesOwned(swap_client, ci, wf)
|
checkAddressesOwned(swap_client, ci, wf)
|
||||||
return wf
|
return wf
|
||||||
@@ -128,6 +140,8 @@ def page_wallet(self, url_split, post_string):
|
|||||||
|
|
||||||
if bytes('newaddr_' + cid, 'utf-8') in form_data:
|
if bytes('newaddr_' + cid, 'utf-8') in form_data:
|
||||||
swap_client.cacheNewAddressForCoin(coin_id)
|
swap_client.cacheNewAddressForCoin(coin_id)
|
||||||
|
elif bytes('newmwebaddr_' + cid, 'utf-8') in form_data:
|
||||||
|
swap_client.cacheNewStealthAddressForCoin(coin_id)
|
||||||
elif bytes('reseed_' + cid, 'utf-8') in form_data:
|
elif bytes('reseed_' + cid, 'utf-8') in form_data:
|
||||||
try:
|
try:
|
||||||
swap_client.reseedWallet(coin_id)
|
swap_client.reseedWallet(coin_id)
|
||||||
@@ -158,22 +172,28 @@ def page_wallet(self, url_split, post_string):
|
|||||||
page_data['wd_type_to_' + cid] = type_to
|
page_data['wd_type_to_' + cid] = type_to
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_messages.append('Missing type')
|
err_messages.append('Missing type')
|
||||||
|
elif coin_id == Coins.LTC:
|
||||||
|
try:
|
||||||
|
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8')
|
||||||
|
page_data['wd_type_from_' + cid] = type_from
|
||||||
|
except Exception as e:
|
||||||
|
err_messages.append('Missing type')
|
||||||
|
|
||||||
if len(messages) == 0:
|
if len(messages) == 0:
|
||||||
ci = swap_client.ci(coin_id)
|
ci = swap_client.ci(coin_id)
|
||||||
ticker = ci.ticker()
|
ticker = ci.ticker()
|
||||||
if coin_id == Coins.PART:
|
try:
|
||||||
try:
|
if coin_id == Coins.PART:
|
||||||
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
|
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
|
||||||
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
|
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
|
||||||
except Exception as e:
|
elif coin_id == Coins.LTC:
|
||||||
err_messages.append(str(e))
|
txid = swap_client.withdrawLTC(type_from, value, address, subfee)
|
||||||
else:
|
messages.append('Withdrew {} {} (from {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, address, txid))
|
||||||
try:
|
else:
|
||||||
txid = swap_client.withdrawCoin(coin_id, value, address, subfee)
|
txid = swap_client.withdrawCoin(coin_id, value, address, subfee)
|
||||||
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
|
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_messages.append(str(e))
|
err_messages.append(str(e))
|
||||||
swap_client.updateWalletsInfo(True, coin_id)
|
swap_client.updateWalletsInfo(True, coin_id)
|
||||||
elif have_data_entry(form_data, 'showutxogroups'):
|
elif have_data_entry(form_data, 'showutxogroups'):
|
||||||
show_utxo_groups = True
|
show_utxo_groups = True
|
||||||
@@ -227,6 +247,8 @@ def page_wallet(self, url_split, post_string):
|
|||||||
|
|
||||||
if k == Coins.XMR:
|
if k == Coins.XMR:
|
||||||
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary')
|
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary')
|
||||||
|
elif k == Coins.LTC:
|
||||||
|
wallet_data['mweb_address'] = w.get('mweb_address', 'Refresh necessary')
|
||||||
|
|
||||||
if 'wd_type_from_' + cid in page_data:
|
if 'wd_type_from_' + cid in page_data:
|
||||||
wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid]
|
wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid]
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ from basicswap.chainparams import (
|
|||||||
chainparams,
|
chainparams,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
TxTypes,
|
ActionTypes,
|
||||||
TxStates,
|
|
||||||
BidStates,
|
BidStates,
|
||||||
SwapTypes,
|
|
||||||
strTxType,
|
|
||||||
DebugTypes,
|
DebugTypes,
|
||||||
strTxState,
|
|
||||||
strBidState,
|
|
||||||
TxLockTypes,
|
|
||||||
getLastBidState,
|
getLastBidState,
|
||||||
|
strBidState,
|
||||||
|
strTxState,
|
||||||
|
strTxType,
|
||||||
|
SwapTypes,
|
||||||
|
TxLockTypes,
|
||||||
|
TxStates,
|
||||||
|
TxTypes,
|
||||||
)
|
)
|
||||||
|
|
||||||
from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSplitKey
|
from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSplitKey
|
||||||
@@ -145,11 +146,18 @@ def listBidStates():
|
|||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def listBidActions():
|
||||||
|
rv = []
|
||||||
|
for a in ActionTypes:
|
||||||
|
rv.append((int(a), a.name))
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_bid, show_txns, view_tx_ind=None, for_api=False, show_lock_transfers=False):
|
def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_bid, show_txns, view_tx_ind=None, for_api=False, show_lock_transfers=False):
|
||||||
ci_from = swap_client.ci(Coins(offer.coin_from))
|
ci_from = swap_client.ci(Coins(offer.coin_from))
|
||||||
ci_to = swap_client.ci(Coins(offer.coin_to))
|
ci_to = swap_client.ci(Coins(offer.coin_to))
|
||||||
|
|
||||||
reverse_bid: bool = Coins(offer.coin_from) in swap_client.scriptless_coins
|
reverse_bid: bool = swap_client.is_reverse_ads_bid(offer.coin_from)
|
||||||
ci_leader = ci_to if reverse_bid else ci_from
|
ci_leader = ci_to if reverse_bid else ci_from
|
||||||
ci_follower = ci_from if reverse_bid else ci_to
|
ci_follower = ci_from if reverse_bid else ci_to
|
||||||
|
|
||||||
@@ -291,15 +299,15 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
|
|||||||
txns = []
|
txns = []
|
||||||
if bid.xmr_a_lock_tx:
|
if bid.xmr_a_lock_tx:
|
||||||
confirms = None
|
confirms = None
|
||||||
if swap_client.coin_clients[ci_from.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height:
|
if swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height:
|
||||||
confirms = (swap_client.coin_clients[ci_from.coin_type()]['chain_height'] - bid.xmr_a_lock_tx.chain_height) + 1
|
confirms = (swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] - bid.xmr_a_lock_tx.chain_height) + 1
|
||||||
txns.append({'type': 'Chain A Lock', 'txid': bid.xmr_a_lock_tx.txid.hex(), 'confirms': confirms})
|
txns.append({'type': 'Chain A Lock', 'txid': bid.xmr_a_lock_tx.txid.hex(), 'confirms': confirms})
|
||||||
if bid.xmr_a_lock_spend_tx:
|
if bid.xmr_a_lock_spend_tx:
|
||||||
txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()})
|
txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()})
|
||||||
if bid.xmr_b_lock_tx:
|
if bid.xmr_b_lock_tx:
|
||||||
confirms = None
|
confirms = None
|
||||||
if swap_client.coin_clients[ci_to.coin_type()]['chain_height'] and bid.xmr_b_lock_tx.chain_height:
|
if swap_client.coin_clients[ci_follower.coin_type()]['chain_height'] and bid.xmr_b_lock_tx.chain_height:
|
||||||
confirms = (swap_client.coin_clients[ci_to.coin_type()]['chain_height'] - bid.xmr_b_lock_tx.chain_height) + 1
|
confirms = (swap_client.coin_clients[ci_follower.coin_type()]['chain_height'] - bid.xmr_b_lock_tx.chain_height) + 1
|
||||||
txns.append({'type': 'Chain B Lock', 'txid': bid.xmr_b_lock_tx.txid.hex(), 'confirms': confirms})
|
txns.append({'type': 'Chain B Lock', 'txid': bid.xmr_b_lock_tx.txid.hex(), 'confirms': confirms})
|
||||||
if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.spend_txid:
|
if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.spend_txid:
|
||||||
txns.append({'type': 'Chain B Lock Spend', 'txid': bid.xmr_b_lock_tx.spend_txid.hex()})
|
txns.append({'type': 'Chain B Lock Spend', 'txid': bid.xmr_b_lock_tx.spend_txid.hex()})
|
||||||
@@ -419,6 +427,8 @@ def getCoinName(c):
|
|||||||
return chainparams[Coins.PART]['name'].capitalize() + ' Anon'
|
return chainparams[Coins.PART]['name'].capitalize() + ' Anon'
|
||||||
if c == Coins.PART_BLIND:
|
if c == Coins.PART_BLIND:
|
||||||
return chainparams[Coins.PART]['name'].capitalize() + ' Blind'
|
return chainparams[Coins.PART]['name'].capitalize() + ' Blind'
|
||||||
|
if c == Coins.LTC_MWEB:
|
||||||
|
return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB'
|
||||||
|
|
||||||
coin_chainparams = chainparams[c]
|
coin_chainparams = chainparams[c]
|
||||||
if coin_chainparams.get('use_ticker_as_name', False):
|
if coin_chainparams.get('use_ticker_as_name', False):
|
||||||
@@ -441,6 +451,9 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
|
|||||||
coins.append((int(v), getCoinName(v)))
|
coins.append((int(v), getCoinName(v)))
|
||||||
if split_from and v not in invalid_coins_from:
|
if split_from and v not in invalid_coins_from:
|
||||||
coins_from.append(coins[-1])
|
coins_from.append(coins[-1])
|
||||||
|
if with_variants and k == Coins.LTC:
|
||||||
|
for v in (Coins.LTC_MWEB, ):
|
||||||
|
pass # Add when swappable
|
||||||
if split_from:
|
if split_from:
|
||||||
return coins_from, coins
|
return coins_from, coins
|
||||||
return coins
|
return coins
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ def format_timestamp(value: int, with_seconds: bool = False) -> str:
|
|||||||
str_format = '%Y-%m-%d %H:%M'
|
str_format = '%Y-%m-%d %H:%M'
|
||||||
if with_seconds:
|
if with_seconds:
|
||||||
str_format += ':%S'
|
str_format += ':%S'
|
||||||
str_format += ' %Z'
|
str_format += ' %z'
|
||||||
return time.strftime(str_format, time.localtime(value))
|
return time.strftime(str_format, time.localtime(value))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ def toWIF(prefix_byte: int, b: bytes, compressed: bool = True) -> str:
|
|||||||
return b58encode(b)
|
return b58encode(b)
|
||||||
|
|
||||||
|
|
||||||
def getKeyID(key_data: bytes) -> str:
|
def getKeyID(key_data: bytes) -> bytes:
|
||||||
sha256_hash = hashlib.sha256(key_data).digest()
|
sha256_hash = hashlib.sha256(key_data).digest()
|
||||||
return ripemd160(sha256_hash)
|
return ripemd160(sha256_hash)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
from Crypto.Hash import RIPEMD160 # pycryptodome
|
from Crypto.Hash import RIPEMD160, SHA256 # pycryptodome
|
||||||
|
|
||||||
|
|
||||||
|
def sha256(data):
|
||||||
|
h = SHA256.new()
|
||||||
|
h.update(data)
|
||||||
|
return h.digest()
|
||||||
|
|
||||||
|
|
||||||
def ripemd160(data):
|
def ripemd160(data):
|
||||||
h = RIPEMD160.new()
|
h = RIPEMD160.new()
|
||||||
h.update(data)
|
h.update(data)
|
||||||
return h.digest()
|
return h.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def hash160(s):
|
||||||
|
return ripemd160(sha256(s))
|
||||||
|
|||||||
17
basicswap/util/network.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
|
||||||
|
def is_private_ip_address(addr: str):
|
||||||
|
# Will return false for all URLs
|
||||||
|
if addr == 'localhost':
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
return ipaddress.ip_address(addr).is_private
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2023 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -38,31 +38,40 @@ from basicswap.util.rfc2440 import rfc2440_hash_password
|
|||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||||
from bin.basicswap_run import startDaemon, startXmrWalletDaemon
|
from bin.basicswap_run import startDaemon, startXmrWalletDaemon
|
||||||
|
|
||||||
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.1.5.0')
|
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
|
||||||
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
|
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
|
||||||
PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', 'nousb')
|
PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', 'nousb')
|
||||||
|
|
||||||
LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2')
|
LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2.2')
|
||||||
LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
|
LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
|
||||||
|
|
||||||
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '23.0')
|
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0')
|
||||||
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
||||||
|
|
||||||
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.2.2')
|
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.1')
|
||||||
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
||||||
XMR_SITE_COMMIT = 'a3b195eb90c7d5564cc9d9ec09c873783d21901b' # Lock hashes.txt to monero version
|
XMR_SITE_COMMIT = '1bdb0d456943a224a4d6241b1bb713172e5fa29f' # Lock hashes.txt to monero version
|
||||||
|
|
||||||
PIVX_VERSION = os.getenv('PIVX_VERSION', '5.5.0')
|
PIVX_VERSION = os.getenv('PIVX_VERSION', '5.5.0')
|
||||||
PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '')
|
PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '')
|
||||||
|
|
||||||
DASH_VERSION = os.getenv('DASH_VERSION', '19.1.0')
|
DASH_VERSION = os.getenv('DASH_VERSION', '20.0.2')
|
||||||
DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '')
|
DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '')
|
||||||
|
|
||||||
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.99.1')
|
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.13.1')
|
||||||
FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
|
FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
|
||||||
|
|
||||||
|
NAV_VERSION = os.getenv('NAV_VERSION', '7.0.3')
|
||||||
|
NAV_VERSION_TAG = os.getenv('NAV_VERSION_TAG', '')
|
||||||
|
|
||||||
GUIX_SSL_CERT_DIR = None
|
GUIX_SSL_CERT_DIR = None
|
||||||
|
|
||||||
|
ADD_PUBKEY_URL = os.getenv('ADD_PUBKEY_URL', '')
|
||||||
|
OVERRIDE_DISABLED_COINS = toBool(os.getenv('OVERRIDE_DISABLED_COINS', 'false'))
|
||||||
|
|
||||||
|
# If SKIP_GPG_VALIDATION is set to true the script will check hashes but not signatures
|
||||||
|
SKIP_GPG_VALIDATION = toBool(os.getenv('SKIP_GPG_VALIDATION', 'false'))
|
||||||
|
|
||||||
|
|
||||||
known_coins = {
|
known_coins = {
|
||||||
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
||||||
@@ -72,10 +81,14 @@ known_coins = {
|
|||||||
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
|
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
|
||||||
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
|
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
|
||||||
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
|
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
|
||||||
# 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
|
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
|
||||||
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('tecnovert',)),
|
'navcoin': (NAV_VERSION, NAV_VERSION_TAG, ('nav_builder',)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disabled_coins = [
|
||||||
|
'navcoin',
|
||||||
|
]
|
||||||
|
|
||||||
expected_key_ids = {
|
expected_key_ids = {
|
||||||
'tecnovert': ('13F13651C9CF0D6B',),
|
'tecnovert': ('13F13651C9CF0D6B',),
|
||||||
'thrasher': ('FE3348877809386C',),
|
'thrasher': ('FE3348877809386C',),
|
||||||
@@ -86,6 +99,7 @@ expected_key_ids = {
|
|||||||
'fuzzbawls': ('3BDCDA2D87A881D9',),
|
'fuzzbawls': ('3BDCDA2D87A881D9',),
|
||||||
'pasta': ('52527BEDABE87984',),
|
'pasta': ('52527BEDABE87984',),
|
||||||
'reuben': ('1290A1D0FA7EE109',),
|
'reuben': ('1290A1D0FA7EE109',),
|
||||||
|
'nav_builder': ('2782262BF6E7FADB',),
|
||||||
}
|
}
|
||||||
|
|
||||||
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
|
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
|
||||||
@@ -96,9 +110,17 @@ elif USE_PLATFORM == 'Windows':
|
|||||||
BIN_ARCH = 'win64'
|
BIN_ARCH = 'win64'
|
||||||
FILE_EXT = 'zip'
|
FILE_EXT = 'zip'
|
||||||
else:
|
else:
|
||||||
BIN_ARCH = 'x86_64-linux-gnu'
|
machine: str = platform.machine()
|
||||||
|
if 'arm' in machine:
|
||||||
|
BIN_ARCH = 'arm-linux-gnueabihf'
|
||||||
|
else:
|
||||||
|
BIN_ARCH = machine + '-linux-gnu'
|
||||||
FILE_EXT = 'tar.gz'
|
FILE_EXT = 'tar.gz'
|
||||||
|
|
||||||
|
# Allow manually overriding the arch tag
|
||||||
|
BIN_ARCH = os.getenv('BIN_ARCH', BIN_ARCH)
|
||||||
|
FILE_EXT = os.getenv('FILE_EXT', FILE_EXT)
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.INFO
|
logger.level = logging.INFO
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
@@ -159,6 +181,12 @@ FIRO_ONION_PORT = int(os.getenv('FIRO_ONION_PORT', 8168)) # nDefaultPort
|
|||||||
FIRO_RPC_USER = os.getenv('FIRO_RPC_USER', '')
|
FIRO_RPC_USER = os.getenv('FIRO_RPC_USER', '')
|
||||||
FIRO_RPC_PWD = os.getenv('FIRO_RPC_PWD', '')
|
FIRO_RPC_PWD = os.getenv('FIRO_RPC_PWD', '')
|
||||||
|
|
||||||
|
NAV_RPC_HOST = os.getenv('NAV_RPC_HOST', '127.0.0.1')
|
||||||
|
NAV_RPC_PORT = int(os.getenv('NAV_RPC_PORT', 44444))
|
||||||
|
NAV_ONION_PORT = int(os.getenv('NAV_ONION_PORT', 8334)) # TODO?
|
||||||
|
NAV_RPC_USER = os.getenv('NAV_RPC_USER', '')
|
||||||
|
NAV_RPC_PWD = os.getenv('NAV_RPC_PWD', '')
|
||||||
|
|
||||||
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
|
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
|
||||||
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
|
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
|
||||||
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
|
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
|
||||||
@@ -172,13 +200,32 @@ BITCOIN_FASTSYNC_FILE = os.getenv('BITCOIN_FASTSYNC_FILE', 'utxo-snapshot-bitcoi
|
|||||||
# Encrypt new wallets with this password, must match the Particl wallet password when adding coins
|
# Encrypt new wallets with this password, must match the Particl wallet password when adding coins
|
||||||
WALLET_ENCRYPTION_PWD = os.getenv('WALLET_ENCRYPTION_PWD', '')
|
WALLET_ENCRYPTION_PWD = os.getenv('WALLET_ENCRYPTION_PWD', '')
|
||||||
|
|
||||||
use_tor_proxy = False
|
use_tor_proxy: bool = False
|
||||||
|
|
||||||
|
monerod_proxy_config = [
|
||||||
|
f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}',
|
||||||
|
'proxy-allow-dns-leaks=0',
|
||||||
|
'no-igd=1', # Disable UPnP port mapping
|
||||||
|
'hide-my-port=1', # Don't share the p2p port
|
||||||
|
'p2p-bind-ip=127.0.0.1', # Don't broadcast ip
|
||||||
|
'in-peers=0', # Changes "error" in log to "incoming connections disabled"
|
||||||
|
]
|
||||||
|
|
||||||
|
monero_wallet_rpc_proxy_config = [
|
||||||
|
'daemon-ssl-allow-any-cert=1',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
default_socket = socket.socket
|
default_socket = socket.socket
|
||||||
default_socket_timeout = socket.getdefaulttimeout()
|
default_socket_timeout = socket.getdefaulttimeout()
|
||||||
default_socket_getaddrinfo = socket.getaddrinfo
|
default_socket_getaddrinfo = socket.getaddrinfo
|
||||||
|
|
||||||
|
|
||||||
|
def exitWithError(error_msg):
|
||||||
|
sys.stderr.write('Error: {}, exiting.\n'.format(error_msg))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def make_reporthook(read_start=0):
|
def make_reporthook(read_start=0):
|
||||||
read = read_start # Number of bytes read so far
|
read = read_start # Number of bytes read so far
|
||||||
last_percent_str = ''
|
last_percent_str = ''
|
||||||
@@ -198,9 +245,13 @@ def make_reporthook(read_start=0):
|
|||||||
dl_complete: bool = totalsize > 0 and read >= use_size
|
dl_complete: bool = totalsize > 0 and read >= use_size
|
||||||
time_now = time.time()
|
time_now = time.time()
|
||||||
time_delta = time_now - time_last
|
time_delta = time_now - time_last
|
||||||
if time_delta < 4 and not dl_complete:
|
if time_delta < 4.0 and not dl_complete:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Avoid division by zero by picking a value
|
||||||
|
if time_delta <= 0.0:
|
||||||
|
time_delta = 0.01
|
||||||
|
|
||||||
bytes_delta = read - read_last
|
bytes_delta = read - read_last
|
||||||
time_last = time_now
|
time_last = time_now
|
||||||
read_last = read
|
read_last = read
|
||||||
@@ -290,7 +341,7 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def setConnectionParameters(timeout=5):
|
def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True):
|
||||||
opener = urllib.request.build_opener()
|
opener = urllib.request.build_opener()
|
||||||
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
|
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
|
||||||
urllib.request.install_opener(opener)
|
urllib.request.install_opener(opener)
|
||||||
@@ -493,6 +544,8 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
|
|||||||
|
|
||||||
if coin == 'pivx':
|
if coin == 'pivx':
|
||||||
filename = '{}-{}/bin/{}'.format(dir_name, version, b)
|
filename = '{}-{}/bin/{}'.format(dir_name, version, b)
|
||||||
|
elif coin == 'particl' and '_nousb-' in release_path:
|
||||||
|
filename = '{}-{}_nousb/bin/{}'.format(dir_name, version + version_tag, b)
|
||||||
else:
|
else:
|
||||||
filename = '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
|
filename = '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
|
||||||
|
|
||||||
@@ -531,7 +584,14 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, use_file_ext)
|
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, use_file_ext)
|
||||||
if os_name == 'osx':
|
if os_name == 'osx':
|
||||||
os_name = 'mac'
|
os_name = 'mac'
|
||||||
release_url = 'https://downloads.getmonero.org/cli/monero-{}-x64-v{}.{}'.format(os_name, version, use_file_ext)
|
|
||||||
|
architecture = 'x64'
|
||||||
|
if 'aarch64' in BIN_ARCH:
|
||||||
|
architecture = 'armv8'
|
||||||
|
elif 'arm' in BIN_ARCH:
|
||||||
|
architecture = 'armv7'
|
||||||
|
|
||||||
|
release_url = 'https://downloads.getmonero.org/cli/monero-{}-{}-v{}.{}'.format(os_name, architecture, version, use_file_ext)
|
||||||
release_path = os.path.join(bin_dir, release_filename)
|
release_path = os.path.join(bin_dir, release_filename)
|
||||||
if not os.path.exists(release_path):
|
if not os.path.exists(release_path):
|
||||||
downloadFile(release_url, release_path)
|
downloadFile(release_url, release_path)
|
||||||
@@ -544,36 +604,36 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
downloadFile(assert_url, assert_path)
|
downloadFile(assert_url, assert_path)
|
||||||
else:
|
else:
|
||||||
major_version = int(version.split('.')[0])
|
major_version = int(version.split('.')[0])
|
||||||
|
|
||||||
|
use_guix: bool = coin in ('dash', ) or major_version >= 22
|
||||||
arch_name = BIN_ARCH
|
arch_name = BIN_ARCH
|
||||||
if major_version >= 23:
|
if os_name == 'osx' and use_guix:
|
||||||
if os_name == 'osx':
|
arch_name = 'x86_64-apple-darwin'
|
||||||
arch_name = 'x86_64-apple-darwin'
|
if coin == 'particl':
|
||||||
if coin == 'particl':
|
arch_name += '18'
|
||||||
arch_name += '18'
|
|
||||||
|
|
||||||
release_filename = '{}-{}-{}.{}'.format(coin, version + version_tag, arch_name, FILE_EXT)
|
release_filename = '{}-{}-{}.{}'.format(coin, version + version_tag, arch_name, FILE_EXT)
|
||||||
if filename_extra != '':
|
if filename_extra != '':
|
||||||
if major_version >= 23:
|
if use_guix:
|
||||||
release_filename = '{}-{}_{}-{}.{}'.format(coin, version + version_tag, filename_extra, arch_name, FILE_EXT)
|
release_filename = '{}-{}_{}-{}.{}'.format(coin, version + version_tag, filename_extra, arch_name, FILE_EXT)
|
||||||
else:
|
else:
|
||||||
release_filename = '{}-{}-{}_{}.{}'.format(coin, version + version_tag, arch_name, filename_extra, FILE_EXT)
|
release_filename = '{}-{}-{}_{}.{}'.format(coin, version + version_tag, arch_name, filename_extra, FILE_EXT)
|
||||||
|
|
||||||
release_filename = '{}-{}-{}.{}'.format(coin, version + version_tag, arch_name, FILE_EXT)
|
|
||||||
if coin == 'particl':
|
if coin == 'particl':
|
||||||
release_url = 'https://github.com/particl/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
release_url = 'https://github.com/particl/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
||||||
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version)
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version)
|
||||||
if major_version >= 22:
|
if use_guix:
|
||||||
assert_url = f'https://raw.githubusercontent.com/particl/guix.sigs/master/{version}/{signing_key_name}/all.SHA256SUMS'
|
assert_url = f'https://raw.githubusercontent.com/particl/guix.sigs/master/{version}/{signing_key_name}/all.SHA256SUMS'
|
||||||
else:
|
else:
|
||||||
assert_url = 'https://raw.githubusercontent.com/particl/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
|
assert_url = 'https://raw.githubusercontent.com/particl/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
|
||||||
elif coin == 'litecoin':
|
elif coin == 'litecoin':
|
||||||
release_url = 'https://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename)
|
release_url = 'https://github.com/litecoin-project/litecoin/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
||||||
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
|
||||||
assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
use_signing_key_name = (signing_key_name + '/' + signing_key_name) if os_name == 'win' else signing_key_name
|
||||||
|
assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, use_signing_key_name, assert_filename)
|
||||||
elif coin == 'bitcoin':
|
elif coin == 'bitcoin':
|
||||||
release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename)
|
release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename)
|
||||||
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
|
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
|
||||||
if major_version >= 22:
|
if use_guix:
|
||||||
assert_url = f'https://raw.githubusercontent.com/bitcoin-core/guix.sigs/main/{version}/{signing_key_name}/all.SHA256SUMS'
|
assert_url = f'https://raw.githubusercontent.com/bitcoin-core/guix.sigs/main/{version}/{signing_key_name}/all.SHA256SUMS'
|
||||||
else:
|
else:
|
||||||
assert_url = 'https://raw.githubusercontent.com/bitcoin-core/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
assert_url = 'https://raw.githubusercontent.com/bitcoin-core/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
||||||
@@ -587,33 +647,25 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
||||||
assert_url = 'https://raw.githubusercontent.com/PIVX-Project/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name.capitalize(), assert_filename)
|
assert_url = 'https://raw.githubusercontent.com/PIVX-Project/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name.capitalize(), assert_filename)
|
||||||
elif coin == 'dash':
|
elif coin == 'dash':
|
||||||
release_filename = '{}-{}-{}.{}'.format('dashcore', version + version_tag, BIN_ARCH, FILE_EXT)
|
release_filename = '{}-{}-{}.{}'.format('dashcore', version + version_tag, arch_name, FILE_EXT)
|
||||||
release_url = 'https://github.com/dashpay/dash/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
release_url = 'https://github.com/dashpay/dash/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
||||||
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, major_version)
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, arch_name, major_version)
|
||||||
assert_url = 'https://raw.githubusercontent.com/dashpay/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
|
assert_url = f'https://raw.githubusercontent.com/dashpay/guix.sigs/master/{version}/{signing_key_name}/codesigned.SHA256SUMS'
|
||||||
elif coin == 'firo':
|
elif coin == 'firo':
|
||||||
'''
|
arch_name = BIN_ARCH
|
||||||
if BIN_ARCH == 'x86_64-linux-gnu':
|
if BIN_ARCH == 'x86_64-linux-gnu':
|
||||||
arch_name = 'linux64'
|
arch_name = 'linux64'
|
||||||
file_ext = 'tar.gz'
|
|
||||||
elif BIN_ARCH == 'osx64':
|
elif BIN_ARCH == 'osx64':
|
||||||
arch_name = 'macos'
|
arch_name = 'macos'
|
||||||
file_ext = 'dmg'
|
release_filename = '{}-{}-{}{}.{}'.format('firo', version + version_tag, arch_name, filename_extra, FILE_EXT)
|
||||||
raise ValueError('TODO: Firo - Extract .dmg')
|
release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
||||||
else:
|
assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag)
|
||||||
raise ValueError('Firo: Unknown architecture')
|
elif coin == 'navcoin':
|
||||||
release_filename = '{}-{}-{}{}.{}'.format('firo', version + version_tag, arch_name, filename_extra, file_ext)
|
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, FILE_EXT)
|
||||||
# release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
release_url = 'https://github.com/navcoin/navcoin-core/releases/download/{}/{}'.format(version + version_tag, release_filename)
|
||||||
# assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag)
|
assert_filename = 'SHA256SUM_7.0.3.asc'
|
||||||
'''
|
assert_sig_filename = 'SHA256SUM_7.0.3.asc.sig'
|
||||||
if BIN_ARCH == 'x86_64-linux-gnu':
|
assert_url = 'https://github.com/navcoin/navcoin-core/releases/download/{}/{}'.format(version + version_tag, assert_filename)
|
||||||
release_filename = 'firo-0.14.99.1-x86_64-linux-gnu.tar.gz'
|
|
||||||
elif BIN_ARCH == 'osx64':
|
|
||||||
release_filename = 'firo-0.14.99.1-x86_64-apple-darwin18.tar.gz'
|
|
||||||
else:
|
|
||||||
raise ValueError('Firo: Unknown architecture')
|
|
||||||
release_url = 'https://github.com/tecnovert/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
|
||||||
assert_url = 'https://github.com/tecnovert/particl-core/releases/download/v%s/SHA256SUMS.asc' % (version + version_tag)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown coin')
|
raise ValueError('Unknown coin')
|
||||||
|
|
||||||
@@ -628,8 +680,9 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
downloadFile(assert_url, assert_path)
|
downloadFile(assert_url, assert_path)
|
||||||
|
|
||||||
if coin not in ('firo', ):
|
if coin not in ('firo', ):
|
||||||
assert_sig_url = assert_url + ('.asc' if major_version >= 22 else '.sig')
|
assert_sig_url = assert_url + ('.asc' if use_guix else '.sig')
|
||||||
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
|
if coin not in ('nav', ):
|
||||||
|
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
|
||||||
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
|
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
|
||||||
if not os.path.exists(assert_sig_path):
|
if not os.path.exists(assert_sig_path):
|
||||||
downloadFile(assert_sig_url, assert_sig_path)
|
downloadFile(assert_sig_url, assert_sig_path)
|
||||||
@@ -646,6 +699,11 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
else:
|
else:
|
||||||
logger.info('Found release hash in assert file.')
|
logger.info('Found release hash in assert file.')
|
||||||
|
|
||||||
|
if SKIP_GPG_VALIDATION:
|
||||||
|
logger.warning('Skipping binary signature check as SKIP_GPG_VALIDATION env var is set.')
|
||||||
|
extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts)
|
||||||
|
return
|
||||||
|
|
||||||
"""
|
"""
|
||||||
gnupghome = os.path.join(data_dir, 'gpg')
|
gnupghome = os.path.join(data_dir, 'gpg')
|
||||||
if not os.path.exists(gnupghome):
|
if not os.path.exists(gnupghome):
|
||||||
@@ -663,8 +721,8 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
for key in rv.fingerprints:
|
for key in rv.fingerprints:
|
||||||
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
|
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
|
||||||
|
|
||||||
if coin in ('firo', ):
|
if coin in ('navcoin', ):
|
||||||
pubkey_filename = '{}_{}.pgp'.format('particl', signing_key_name)
|
pubkey_filename = '{}_builder.pgp'.format(coin)
|
||||||
else:
|
else:
|
||||||
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
|
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
|
||||||
pubkeyurls = [
|
pubkeyurls = [
|
||||||
@@ -678,6 +736,9 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
if coin == 'firo':
|
if coin == 'firo':
|
||||||
pubkeyurls.append('https://firo.org/reuben.asc')
|
pubkeyurls.append('https://firo.org/reuben.asc')
|
||||||
|
|
||||||
|
if ADD_PUBKEY_URL != '':
|
||||||
|
pubkeyurls.append(ADD_PUBKEY_URL + '/' + pubkey_filename)
|
||||||
|
|
||||||
if coin in ('monero', 'firo'):
|
if coin in ('monero', 'firo'):
|
||||||
with open(assert_path, 'rb') as fp:
|
with open(assert_path, 'rb') as fp:
|
||||||
verified = gpg.verify_file(fp)
|
verified = gpg.verify_file(fp)
|
||||||
@@ -687,6 +748,21 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
importPubkeyFromUrls(gpg, pubkeyurls)
|
importPubkeyFromUrls(gpg, pubkeyurls)
|
||||||
with open(assert_path, 'rb') as fp:
|
with open(assert_path, 'rb') as fp:
|
||||||
verified = gpg.verify_file(fp)
|
verified = gpg.verify_file(fp)
|
||||||
|
elif coin in ('navcoin'):
|
||||||
|
with open(assert_sig_path, 'rb') as fp:
|
||||||
|
verified = gpg.verify_file(fp)
|
||||||
|
|
||||||
|
if not isValidSignature(verified) and verified.username is None:
|
||||||
|
logger.warning('Signature made by unknown key.')
|
||||||
|
importPubkeyFromUrls(gpg, pubkeyurls)
|
||||||
|
with open(assert_sig_path, 'rb') as fp:
|
||||||
|
verified = gpg.verify_file(fp)
|
||||||
|
|
||||||
|
# .sig file is not a detached signature, recheck release hash in decrypted data
|
||||||
|
logger.warning('Double checking Navcoin release hash.')
|
||||||
|
with open(assert_sig_path, 'rb') as fp:
|
||||||
|
decrypted = gpg.decrypt_file(fp)
|
||||||
|
assert (release_hash.hex() in str(decrypted))
|
||||||
else:
|
else:
|
||||||
with open(assert_sig_path, 'rb') as fp:
|
with open(assert_sig_path, 'rb') as fp:
|
||||||
verified = gpg.verify_file(fp, assert_path)
|
verified = gpg.verify_file(fp, assert_path)
|
||||||
@@ -752,9 +828,8 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
fp.write('prune-blockchain=1\n')
|
fp.write('prune-blockchain=1\n')
|
||||||
|
|
||||||
if tor_control_password is not None:
|
if tor_control_password is not None:
|
||||||
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
|
for opt_line in monerod_proxy_config:
|
||||||
fp.write('proxy-allow-dns-leaks=0\n')
|
fp.write(opt_line + '\n')
|
||||||
fp.write('no-igd=1\n')
|
|
||||||
|
|
||||||
if XMR_RPC_USER != '':
|
if XMR_RPC_USER != '':
|
||||||
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n')
|
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n')
|
||||||
@@ -771,7 +846,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
if extra_opts.get('use_containers', False) is True:
|
if extra_opts.get('use_containers', False) is True:
|
||||||
fp.write('daemon-address={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
fp.write('daemon-address={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
||||||
config_datadir = '/data'
|
config_datadir = '/data'
|
||||||
fp.write('untrusted-daemon=1\n')
|
|
||||||
fp.write('no-dns=1\n')
|
fp.write('no-dns=1\n')
|
||||||
fp.write('rpc-bind-port={}\n'.format(core_settings['walletrpcport']))
|
fp.write('rpc-bind-port={}\n'.format(core_settings['walletrpcport']))
|
||||||
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
|
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
|
||||||
@@ -785,7 +860,8 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
|
|
||||||
if tor_control_password is not None:
|
if tor_control_password is not None:
|
||||||
if not core_settings['manage_daemon']:
|
if not core_settings['manage_daemon']:
|
||||||
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
|
for opt_line in monero_wallet_rpc_proxy_config:
|
||||||
|
fp.write(opt_line + '\n')
|
||||||
return
|
return
|
||||||
|
|
||||||
core_conf_path = os.path.join(data_dir, coin + '.conf')
|
core_conf_path = os.path.join(data_dir, coin + '.conf')
|
||||||
@@ -793,8 +869,12 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
exitWithError('{} exists'.format(core_conf_path))
|
exitWithError('{} exists'.format(core_conf_path))
|
||||||
with open(core_conf_path, 'w') as fp:
|
with open(core_conf_path, 'w') as fp:
|
||||||
if chain != 'mainnet':
|
if chain != 'mainnet':
|
||||||
fp.write(chain + '=1\n')
|
if coin in ('navcoin',):
|
||||||
if coin != 'firo':
|
chainname = 'devnet' if chain == 'regtest' else chain
|
||||||
|
fp.write(chainname + '=1\n')
|
||||||
|
else:
|
||||||
|
fp.write(chain + '=1\n')
|
||||||
|
if coin not in ('firo', 'navcoin'):
|
||||||
if chain == 'testnet':
|
if chain == 'testnet':
|
||||||
fp.write('[test]\n\n')
|
fp.write('[test]\n\n')
|
||||||
elif chain == 'regtest':
|
elif chain == 'regtest':
|
||||||
@@ -830,6 +910,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
if LTC_RPC_USER != '':
|
if LTC_RPC_USER != '':
|
||||||
fp.write('rpcauth={}:{}${}\n'.format(LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD)))
|
fp.write('rpcauth={}:{}${}\n'.format(LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD)))
|
||||||
elif coin == 'bitcoin':
|
elif coin == 'bitcoin':
|
||||||
|
fp.write('deprecatedrpc=create_bdb\n')
|
||||||
fp.write('prune=2000\n')
|
fp.write('prune=2000\n')
|
||||||
fp.write('fallbackfee=0.0002\n')
|
fp.write('fallbackfee=0.0002\n')
|
||||||
if BTC_RPC_USER != '':
|
if BTC_RPC_USER != '':
|
||||||
@@ -856,6 +937,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
fp.write('usehd=1\n')
|
fp.write('usehd=1\n')
|
||||||
if FIRO_RPC_USER != '':
|
if FIRO_RPC_USER != '':
|
||||||
fp.write('rpcauth={}:{}${}\n'.format(FIRO_RPC_USER, salt, password_to_hmac(salt, FIRO_RPC_PWD)))
|
fp.write('rpcauth={}:{}${}\n'.format(FIRO_RPC_USER, salt, password_to_hmac(salt, FIRO_RPC_PWD)))
|
||||||
|
elif coin == 'navcoin':
|
||||||
|
fp.write('prune=4000\n')
|
||||||
|
fp.write('fallbackfee=0.0002\n')
|
||||||
|
if NAV_RPC_USER != '':
|
||||||
|
fp.write('rpcauth={}:{}${}\n'.format(NAV_RPC_USER, salt, password_to_hmac(salt, NAV_RPC_PWD)))
|
||||||
else:
|
else:
|
||||||
logger.warning('Unknown coin %s', coin)
|
logger.warning('Unknown coin %s', coin)
|
||||||
|
|
||||||
@@ -901,7 +987,7 @@ def addTorSettings(settings, tor_control_password):
|
|||||||
settings['tor_control_port'] = TOR_CONTROL_PORT
|
settings['tor_control_port'] = TOR_CONTROL_PORT
|
||||||
|
|
||||||
|
|
||||||
def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
|
def modify_tor_config(settings, coin, tor_control_password=None, enable=False, extra_opts={}):
|
||||||
coin_settings = settings['chainclients'][coin]
|
coin_settings = settings['chainclients'][coin]
|
||||||
data_dir = coin_settings['datadir']
|
data_dir = coin_settings['datadir']
|
||||||
|
|
||||||
@@ -918,30 +1004,29 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
|
|||||||
shutil.copyfile(core_conf_path, core_conf_path + '.last')
|
shutil.copyfile(core_conf_path, core_conf_path + '.last')
|
||||||
shutil.copyfile(wallet_conf_path, wallet_conf_path + '.last')
|
shutil.copyfile(wallet_conf_path, wallet_conf_path + '.last')
|
||||||
|
|
||||||
daemon_tor_settings = ('proxy=', 'proxy-allow-dns-leaks=', 'no-igd=')
|
|
||||||
with open(core_conf_path, 'w') as fp:
|
with open(core_conf_path, 'w') as fp:
|
||||||
with open(core_conf_path + '.last') as fp_in:
|
with open(core_conf_path + '.last') as fp_in:
|
||||||
# Disable tor first
|
# Disable tor first
|
||||||
for line in fp_in:
|
for line in fp_in:
|
||||||
skip_line = False
|
skip_line: bool = False
|
||||||
for setting in daemon_tor_settings:
|
for opt_line in monerod_proxy_config:
|
||||||
|
setting: str = opt_line[0: opt_line.find('=') + 1]
|
||||||
if line.startswith(setting):
|
if line.startswith(setting):
|
||||||
skip_line = True
|
skip_line = True
|
||||||
break
|
break
|
||||||
if not skip_line:
|
if not skip_line:
|
||||||
fp.write(line)
|
fp.write(line)
|
||||||
if enable:
|
if enable:
|
||||||
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
|
for opt_line in monerod_proxy_config:
|
||||||
fp.write('proxy-allow-dns-leaks=0\n')
|
fp.write(opt_line + '\n')
|
||||||
fp.write('no-igd=1\n')
|
|
||||||
|
|
||||||
wallet_tor_settings = ('proxy=',)
|
|
||||||
with open(wallet_conf_path, 'w') as fp:
|
with open(wallet_conf_path, 'w') as fp:
|
||||||
with open(wallet_conf_path + '.last') as fp_in:
|
with open(wallet_conf_path + '.last') as fp_in:
|
||||||
# Disable tor first
|
# Disable tor first
|
||||||
for line in fp_in:
|
for line in fp_in:
|
||||||
skip_line = False
|
skip_line = False
|
||||||
for setting in wallet_tor_settings:
|
for opt_line in monero_wallet_rpc_proxy_config + ['proxy=',]:
|
||||||
|
setting: str = opt_line[0: opt_line.find('=') + 1]
|
||||||
if line.startswith(setting):
|
if line.startswith(setting):
|
||||||
skip_line = True
|
skip_line = True
|
||||||
break
|
break
|
||||||
@@ -949,7 +1034,10 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
|
|||||||
fp.write(line)
|
fp.write(line)
|
||||||
if enable:
|
if enable:
|
||||||
if not coin_settings['manage_daemon']:
|
if not coin_settings['manage_daemon']:
|
||||||
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
|
for opt_line in monero_wallet_rpc_proxy_config:
|
||||||
|
fp.write(opt_line + '\n')
|
||||||
|
|
||||||
|
coin_settings['trusted_daemon'] = extra_opts.get('trust_remote_node', 'auto')
|
||||||
return
|
return
|
||||||
|
|
||||||
config_path = os.path.join(data_dir, coin + '.conf')
|
config_path = os.path.join(data_dir, coin + '.conf')
|
||||||
@@ -987,17 +1075,13 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
|
|||||||
writeTorSettings(fp, coin, coin_settings, tor_control_password)
|
writeTorSettings(fp, coin, coin_settings, tor_control_password)
|
||||||
|
|
||||||
|
|
||||||
def exitWithError(error_msg):
|
|
||||||
sys.stderr.write('Error: {}, exiting.\n'.format(error_msg))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def printVersion():
|
def printVersion():
|
||||||
logger.info(f'Basicswap version: {__version__}')
|
logger.info(f'Basicswap version: {__version__}')
|
||||||
|
|
||||||
logger.info('Core versions:')
|
logger.info('Core versions:')
|
||||||
for coin, version in known_coins.items():
|
for coin, version in known_coins.items():
|
||||||
logger.info('\t%s: %s%s', coin, version[0], version[1])
|
postfix = ' (Disabled)' if coin in disabled_coins else ''
|
||||||
|
logger.info('\t%s: %s%s%s', coin.capitalize(), version[0], version[1], postfix)
|
||||||
|
|
||||||
|
|
||||||
def printHelp():
|
def printHelp():
|
||||||
@@ -1021,10 +1105,12 @@ def printHelp():
|
|||||||
print('--usecontainers Expect each core to run in a unique container.')
|
print('--usecontainers Expect each core to run in a unique container.')
|
||||||
print('--portoffset=n Raise all ports by n.')
|
print('--portoffset=n Raise all ports by n.')
|
||||||
print('--htmlhost= Interface to host html server on, default:127.0.0.1.')
|
print('--htmlhost= Interface to host html server on, default:127.0.0.1.')
|
||||||
print('--wshost= Interface to host websocket server on, disable by setting to "none", default:127.0.0.1.')
|
print('--wshost= Interface to host websocket server on, disable by setting to "none", default\'s to --htmlhost.')
|
||||||
print('--xmrrestoreheight=n Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT))
|
print('--xmrrestoreheight=n Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT))
|
||||||
|
print('--trustremotenode Set trusted-daemon for XMR, defaults to auto: true when daemon rpchost value is a private ip address else false')
|
||||||
print('--noextractover Prevent extracting cores if files exist. Speeds up tests')
|
print('--noextractover Prevent extracting cores if files exist. Speeds up tests')
|
||||||
print('--usetorproxy Use TOR proxy during setup. Note that some download links may be inaccessible over TOR.')
|
print('--usetorproxy Use TOR proxy during setup. Note that some download links may be inaccessible over TOR.')
|
||||||
|
print('--notorproxy Force usetorproxy off, usetorproxy is automatically set when tor is enabled')
|
||||||
print('--enabletor Setup Basicswap instance to use TOR.')
|
print('--enabletor Setup Basicswap instance to use TOR.')
|
||||||
print('--disabletor Setup Basicswap instance to not use TOR.')
|
print('--disabletor Setup Basicswap instance to not use TOR.')
|
||||||
print('--usebtcfastsync Initialise the BTC chain with a snapshot from btcpayserver FastSync.\n'
|
print('--usebtcfastsync Initialise the BTC chain with a snapshot from btcpayserver FastSync.\n'
|
||||||
@@ -1033,7 +1119,11 @@ def printHelp():
|
|||||||
print('--initwalletsonly Setup coin wallets only.')
|
print('--initwalletsonly Setup coin wallets only.')
|
||||||
print('--keysdirpath Speed up tests by preloading all PGP keys in directory.')
|
print('--keysdirpath Speed up tests by preloading all PGP keys in directory.')
|
||||||
|
|
||||||
print('\n' + 'Known coins: {}'.format(', '.join(known_coins.keys())))
|
active_coins = []
|
||||||
|
for coin_name in known_coins.keys():
|
||||||
|
if coin_name not in disabled_coins:
|
||||||
|
active_coins.append(coin_name)
|
||||||
|
print('\n' + 'Known coins: {}'.format(', '.join(active_coins)))
|
||||||
|
|
||||||
|
|
||||||
def finalise_daemon(d):
|
def finalise_daemon(d):
|
||||||
@@ -1042,7 +1132,7 @@ def finalise_daemon(d):
|
|||||||
d.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
d.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
||||||
d.wait(timeout=120)
|
d.wait(timeout=120)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(f'Error {e}'.format(d.pid))
|
logging.info(f'Error {e} for process {d.pid}')
|
||||||
for fp in (d.stdout, d.stderr, d.stdin):
|
for fp in (d.stdout, d.stderr, d.stdin):
|
||||||
if fp:
|
if fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
@@ -1052,12 +1142,12 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
|
|||||||
swap_client = None
|
swap_client = None
|
||||||
daemons = []
|
daemons = []
|
||||||
daemon_args = ['-noconnect', '-nodnsseed', '-nofindpeers', '-nostaking']
|
daemon_args = ['-noconnect', '-nodnsseed', '-nofindpeers', '-nostaking']
|
||||||
if not use_tor_proxy:
|
|
||||||
# Cannot set -bind or -whitebind together with -listen=0
|
|
||||||
daemon_args.append('-nolisten')
|
|
||||||
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
||||||
try:
|
try:
|
||||||
swap_client = BasicSwap(fp, data_dir, settings, chain)
|
swap_client = BasicSwap(fp, data_dir, settings, chain, transient_instance=True)
|
||||||
|
if not swap_client.use_tor_proxy:
|
||||||
|
# Cannot set -bind or -whitebind together with -listen=0
|
||||||
|
daemon_args.append('-nolisten')
|
||||||
c = Coins.PART
|
c = Coins.PART
|
||||||
coin_name = 'particl'
|
coin_name = 'particl'
|
||||||
coin_settings = settings['chainclients'][coin_name]
|
coin_settings = settings['chainclients'][coin_name]
|
||||||
@@ -1082,18 +1172,23 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
|
|||||||
finalise_daemon(d)
|
finalise_daemon(d)
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_wallet(swap_client, coin_type) -> None:
|
||||||
|
ci = swap_client.ci(coin_type)
|
||||||
|
ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD)
|
||||||
|
ci.unlockWallet(WALLET_ENCRYPTION_PWD)
|
||||||
|
|
||||||
|
|
||||||
def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy):
|
def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy):
|
||||||
swap_client = None
|
swap_client = None
|
||||||
daemons = []
|
daemons = []
|
||||||
daemon_args = ['-noconnect', '-nodnsseed']
|
daemon_args = ['-noconnect', '-nodnsseed']
|
||||||
if not use_tor_proxy:
|
|
||||||
# Cannot set -bind or -whitebind together with -listen=0
|
|
||||||
daemon_args.append('-nolisten')
|
|
||||||
|
|
||||||
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
||||||
try:
|
try:
|
||||||
swap_client = BasicSwap(fp, data_dir, settings, chain)
|
swap_client = BasicSwap(fp, data_dir, settings, chain, transient_instance=True)
|
||||||
|
if not swap_client.use_tor_proxy:
|
||||||
|
# Cannot set -bind or -whitebind together with -listen=0
|
||||||
|
daemon_args.append('-nolisten')
|
||||||
coins_to_create_wallets_for = (Coins.PART, Coins.BTC, Coins.LTC, Coins.DASH)
|
coins_to_create_wallets_for = (Coins.PART, Coins.BTC, Coins.LTC, Coins.DASH)
|
||||||
# Always start Particl, it must be running to initialise a wallet in addcoin mode
|
# Always start Particl, it must be running to initialise a wallet in addcoin mode
|
||||||
# Particl must be loaded first as subsequent coins are initialised from the Particl mnemonic
|
# Particl must be loaded first as subsequent coins are initialised from the Particl mnemonic
|
||||||
@@ -1126,16 +1221,18 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
|||||||
if len(wallets) < 1:
|
if len(wallets) < 1:
|
||||||
logger.info('Creating wallet.dat for {}.'.format(getCoinName(c)))
|
logger.info('Creating wallet.dat for {}.'.format(getCoinName(c)))
|
||||||
|
|
||||||
if c == Coins.BTC:
|
if c in (Coins.BTC, Coins.LTC):
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||||
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat', False, True, '', False, False])
|
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat', False, True, WALLET_ENCRYPTION_PWD, False, False])
|
||||||
|
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
|
||||||
else:
|
else:
|
||||||
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat'])
|
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat'])
|
||||||
|
if WALLET_ENCRYPTION_PWD != '':
|
||||||
|
encrypt_wallet(swap_client, c)
|
||||||
|
|
||||||
if WALLET_ENCRYPTION_PWD != '':
|
if c == Coins.LTC:
|
||||||
ci = swap_client.ci(c)
|
password = WALLET_ENCRYPTION_PWD if WALLET_ENCRYPTION_PWD != '' else None
|
||||||
ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD)
|
swap_client.ci(Coins.LTC_MWEB).init_wallet(password)
|
||||||
ci.unlockWallet(WALLET_ENCRYPTION_PWD)
|
|
||||||
|
|
||||||
if c == Coins.PART:
|
if c == Coins.PART:
|
||||||
if 'particl' in with_coins:
|
if 'particl' in with_coins:
|
||||||
@@ -1213,6 +1310,13 @@ def check_btc_fastsync_data(base_dir, sync_file_path):
|
|||||||
ensureValidSignatureBy(verified, 'tecnovert')
|
ensureValidSignatureBy(verified, 'tecnovert')
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_coin_valid(coin: str, test_disabled: bool = True) -> None:
|
||||||
|
if coin not in known_coins:
|
||||||
|
exitWithError(f'Unknown coin {coin.capitalize()}')
|
||||||
|
if test_disabled and not OVERRIDE_DISABLED_COINS and coin in disabled_coins:
|
||||||
|
exitWithError(f'{coin.capitalize()} is disabled')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global use_tor_proxy
|
global use_tor_proxy
|
||||||
data_dir = None
|
data_dir = None
|
||||||
@@ -1225,7 +1329,6 @@ def main():
|
|||||||
disable_coin = ''
|
disable_coin = ''
|
||||||
coins_changed = False
|
coins_changed = False
|
||||||
htmlhost = '127.0.0.1'
|
htmlhost = '127.0.0.1'
|
||||||
wshost = '127.0.0.1'
|
|
||||||
xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
|
xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
|
||||||
prepare_bin_only = False
|
prepare_bin_only = False
|
||||||
no_cores = False
|
no_cores = False
|
||||||
@@ -1259,13 +1362,9 @@ def main():
|
|||||||
if name == 'h' or name == 'help':
|
if name == 'h' or name == 'help':
|
||||||
printHelp()
|
printHelp()
|
||||||
return 0
|
return 0
|
||||||
if name == 'mainnet':
|
|
||||||
continue
|
if name in ('mainnet', 'testnet', 'regtest'):
|
||||||
if name == 'testnet':
|
chain = name
|
||||||
chain = 'testnet'
|
|
||||||
continue
|
|
||||||
if name == 'regtest':
|
|
||||||
chain = 'regtest'
|
|
||||||
continue
|
continue
|
||||||
if name == 'preparebinonly':
|
if name == 'preparebinonly':
|
||||||
prepare_bin_only = True
|
prepare_bin_only = True
|
||||||
@@ -1282,6 +1381,9 @@ def main():
|
|||||||
if name == 'usetorproxy':
|
if name == 'usetorproxy':
|
||||||
use_tor_proxy = True
|
use_tor_proxy = True
|
||||||
continue
|
continue
|
||||||
|
if name == 'notorproxy':
|
||||||
|
extra_opts['no_tor_proxy'] = True
|
||||||
|
continue
|
||||||
if name == 'enabletor':
|
if name == 'enabletor':
|
||||||
enable_tor = True
|
enable_tor = True
|
||||||
continue
|
continue
|
||||||
@@ -1294,6 +1396,9 @@ def main():
|
|||||||
if name == 'skipbtcfastsyncchecks':
|
if name == 'skipbtcfastsyncchecks':
|
||||||
extra_opts['check_btc_fastsync'] = False
|
extra_opts['check_btc_fastsync'] = False
|
||||||
continue
|
continue
|
||||||
|
if name == 'trustremotenode':
|
||||||
|
extra_opts['trust_remote_node'] = True
|
||||||
|
continue
|
||||||
if name == 'initwalletsonly':
|
if name == 'initwalletsonly':
|
||||||
initwalletsonly = True
|
initwalletsonly = True
|
||||||
continue
|
continue
|
||||||
@@ -1310,36 +1415,32 @@ def main():
|
|||||||
if name == 'particl_mnemonic':
|
if name == 'particl_mnemonic':
|
||||||
particl_wallet_mnemonic = s[1].strip('"')
|
particl_wallet_mnemonic = s[1].strip('"')
|
||||||
continue
|
continue
|
||||||
if name == 'withcoin' or name == 'withcoins':
|
if name in ('withcoin', 'withcoins'):
|
||||||
for coin in [s.lower() for s in s[1].split(',')]:
|
for coin in [s.lower() for s in s[1].split(',')]:
|
||||||
if coin not in known_coins:
|
ensure_coin_valid(coin)
|
||||||
exitWithError('Unknown coin {}'.format(coin))
|
|
||||||
with_coins.add(coin)
|
with_coins.add(coin)
|
||||||
coins_changed = True
|
coins_changed = True
|
||||||
continue
|
continue
|
||||||
if name == 'withoutcoin' or name == 'withoutcoins':
|
if name in ('withoutcoin', 'withoutcoins'):
|
||||||
for coin in [s.lower() for s in s[1].split(',')]:
|
for coin in [s.lower() for s in s[1].split(',')]:
|
||||||
if coin not in known_coins:
|
ensure_coin_valid(coin, test_disabled=False)
|
||||||
exitWithError('Unknown coin {}'.format(coin))
|
|
||||||
with_coins.discard(coin)
|
with_coins.discard(coin)
|
||||||
coins_changed = True
|
coins_changed = True
|
||||||
continue
|
continue
|
||||||
if name == 'addcoin':
|
if name == 'addcoin':
|
||||||
add_coin = s[1].lower()
|
add_coin = s[1].lower()
|
||||||
if add_coin not in known_coins:
|
ensure_coin_valid(add_coin)
|
||||||
exitWithError('Unknown coin {}'.format(s[1]))
|
|
||||||
with_coins = {add_coin, }
|
with_coins = {add_coin, }
|
||||||
continue
|
continue
|
||||||
if name == 'disablecoin':
|
if name == 'disablecoin':
|
||||||
disable_coin = s[1].lower()
|
disable_coin = s[1].lower()
|
||||||
if disable_coin not in known_coins:
|
ensure_coin_valid(disable_coin, test_disabled=False)
|
||||||
exitWithError('Unknown coin {}'.format(s[1]))
|
|
||||||
continue
|
continue
|
||||||
if name == 'htmlhost':
|
if name == 'htmlhost':
|
||||||
htmlhost = s[1].strip('"')
|
htmlhost = s[1].strip('"')
|
||||||
continue
|
continue
|
||||||
if name == 'wshost':
|
if name == 'wshost':
|
||||||
wshost = s[1].strip('"')
|
extra_opts['wshost'] = s[1].strip('"')
|
||||||
continue
|
continue
|
||||||
if name == 'xmrrestoreheight':
|
if name == 'xmrrestoreheight':
|
||||||
xmr_restore_height = int(s[1])
|
xmr_restore_height = int(s[1])
|
||||||
@@ -1347,23 +1448,19 @@ def main():
|
|||||||
if name == 'keysdirpath':
|
if name == 'keysdirpath':
|
||||||
extra_opts['keysdirpath'] = os.path.expanduser(s[1].strip('"'))
|
extra_opts['keysdirpath'] = os.path.expanduser(s[1].strip('"'))
|
||||||
continue
|
continue
|
||||||
|
if name == 'trustremotenode':
|
||||||
|
extra_opts['trust_remote_node'] = toBool(s[1])
|
||||||
|
continue
|
||||||
|
|
||||||
exitWithError('Unknown argument {}'.format(v))
|
exitWithError('Unknown argument {}'.format(v))
|
||||||
|
|
||||||
setConnectionParameters()
|
|
||||||
|
|
||||||
if use_tor_proxy and TEST_TOR_PROXY:
|
|
||||||
testTorConnection()
|
|
||||||
|
|
||||||
if use_tor_proxy and TEST_ONION_LINK:
|
|
||||||
testOnionLink()
|
|
||||||
|
|
||||||
if data_dir is None:
|
if data_dir is None:
|
||||||
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
|
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
|
||||||
if bin_dir is None:
|
if bin_dir is None:
|
||||||
bin_dir = os.path.join(data_dir, 'bin')
|
bin_dir = os.path.join(data_dir, 'bin')
|
||||||
|
|
||||||
logger.info(f'BasicSwap prepare script {__version__}\n')
|
logger.info(f'BasicSwap prepare script {__version__}\n')
|
||||||
|
logger.info(f'Python version: {platform.python_version()}')
|
||||||
logger.info(f'Data dir: {data_dir}')
|
logger.info(f'Data dir: {data_dir}')
|
||||||
logger.info(f'Bin dir: {bin_dir}')
|
logger.info(f'Bin dir: {bin_dir}')
|
||||||
logger.info(f'Chain: {chain}')
|
logger.info(f'Chain: {chain}')
|
||||||
@@ -1376,7 +1473,39 @@ def main():
|
|||||||
os.makedirs(data_dir)
|
os.makedirs(data_dir)
|
||||||
config_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
|
config_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
|
||||||
|
|
||||||
|
if use_tor_proxy and extra_opts.get('no_tor_proxy', False):
|
||||||
|
exitWithError('Can\'t use --usetorproxy and --notorproxy together')
|
||||||
|
|
||||||
|
# Automatically enable tor for certain commands if it's set in basicswap config
|
||||||
|
if not (initwalletsonly or enable_tor or disable_tor or disable_coin) and \
|
||||||
|
not use_tor_proxy and os.path.exists(config_path):
|
||||||
|
settings = load_config(config_path)
|
||||||
|
settings_use_tor = settings.get('use_tor', False)
|
||||||
|
if settings_use_tor:
|
||||||
|
logger.info('use_tor is set in the config')
|
||||||
|
if extra_opts.get('no_tor_proxy', False):
|
||||||
|
use_tor_proxy = False
|
||||||
|
logger.warning('Not automatically setting --usetorproxy as --notorproxy is set')
|
||||||
|
else:
|
||||||
|
use_tor_proxy = True
|
||||||
|
logger.info('Automatically setting --usetorproxy')
|
||||||
|
|
||||||
|
setConnectionParameters(allow_set_tor=False)
|
||||||
|
|
||||||
|
if use_tor_proxy and TEST_TOR_PROXY:
|
||||||
|
testTorConnection()
|
||||||
|
|
||||||
|
if use_tor_proxy and TEST_ONION_LINK:
|
||||||
|
testOnionLink()
|
||||||
|
|
||||||
|
should_download_btc_fastsync = False
|
||||||
if extra_opts.get('use_btc_fastsync', False) is True:
|
if extra_opts.get('use_btc_fastsync', False) is True:
|
||||||
|
if 'bitcoin' in with_coins or add_coin == 'bitcoin':
|
||||||
|
should_download_btc_fastsync = True
|
||||||
|
else:
|
||||||
|
logger.warning('Ignoring usebtcfastsync option without Bitcoin selected.')
|
||||||
|
|
||||||
|
if should_download_btc_fastsync:
|
||||||
logger.info(f'Preparing BTC Fastsync file {BITCOIN_FASTSYNC_FILE}')
|
logger.info(f'Preparing BTC Fastsync file {BITCOIN_FASTSYNC_FILE}')
|
||||||
sync_file_path = os.path.join(data_dir, BITCOIN_FASTSYNC_FILE)
|
sync_file_path = os.path.join(data_dir, BITCOIN_FASTSYNC_FILE)
|
||||||
sync_file_url = os.path.join(BITCOIN_FASTSYNC_URL, BITCOIN_FASTSYNC_FILE)
|
sync_file_url = os.path.join(BITCOIN_FASTSYNC_URL, BITCOIN_FASTSYNC_FILE)
|
||||||
@@ -1428,6 +1557,7 @@ def main():
|
|||||||
'blocks_confirmed': 2,
|
'blocks_confirmed': 2,
|
||||||
'conf_target': 2,
|
'conf_target': 2,
|
||||||
'core_version_group': 21,
|
'core_version_group': 21,
|
||||||
|
'min_relay_fee': 0.00001,
|
||||||
'chain_lookups': 'local',
|
'chain_lookups': 'local',
|
||||||
},
|
},
|
||||||
'bitcoin': {
|
'bitcoin': {
|
||||||
@@ -1466,6 +1596,7 @@ def main():
|
|||||||
'zmqport': BASE_XMR_ZMQ_PORT + port_offset,
|
'zmqport': BASE_XMR_ZMQ_PORT + port_offset,
|
||||||
'walletrpcport': BASE_XMR_WALLET_PORT + port_offset,
|
'walletrpcport': BASE_XMR_WALLET_PORT + port_offset,
|
||||||
'rpchost': XMR_RPC_HOST,
|
'rpchost': XMR_RPC_HOST,
|
||||||
|
'trusted_daemon': extra_opts.get('trust_remote_node', 'auto'),
|
||||||
'walletrpchost': XMR_WALLET_RPC_HOST,
|
'walletrpchost': XMR_WALLET_RPC_HOST,
|
||||||
'walletrpcuser': XMR_WALLET_RPC_USER,
|
'walletrpcuser': XMR_WALLET_RPC_USER,
|
||||||
'walletrpcpassword': XMR_WALLET_RPC_PWD,
|
'walletrpcpassword': XMR_WALLET_RPC_PWD,
|
||||||
@@ -1473,7 +1604,10 @@ def main():
|
|||||||
'datadir': os.getenv('XMR_DATA_DIR', os.path.join(data_dir, 'monero')),
|
'datadir': os.getenv('XMR_DATA_DIR', os.path.join(data_dir, 'monero')),
|
||||||
'bindir': os.path.join(bin_dir, 'monero'),
|
'bindir': os.path.join(bin_dir, 'monero'),
|
||||||
'restore_height': xmr_restore_height,
|
'restore_height': xmr_restore_height,
|
||||||
'blocks_confirmed': 7, # TODO: 10?
|
'blocks_confirmed': 3,
|
||||||
|
'rpctimeout': 60,
|
||||||
|
'walletrpctimeout': 120,
|
||||||
|
'walletrpctimeoutlong': 600,
|
||||||
},
|
},
|
||||||
'pivx': {
|
'pivx': {
|
||||||
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
|
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
|
||||||
@@ -1487,7 +1621,7 @@ def main():
|
|||||||
'use_csv': False,
|
'use_csv': False,
|
||||||
'blocks_confirmed': 1,
|
'blocks_confirmed': 1,
|
||||||
'conf_target': 2,
|
'conf_target': 2,
|
||||||
'core_version_group': 20,
|
'core_version_group': 17,
|
||||||
'chain_lookups': 'local',
|
'chain_lookups': 'local',
|
||||||
},
|
},
|
||||||
'dash': {
|
'dash': {
|
||||||
@@ -1514,11 +1648,28 @@ def main():
|
|||||||
'datadir': os.getenv('FIRO_DATA_DIR', os.path.join(data_dir, 'firo')),
|
'datadir': os.getenv('FIRO_DATA_DIR', os.path.join(data_dir, 'firo')),
|
||||||
'bindir': os.path.join(bin_dir, 'firo'),
|
'bindir': os.path.join(bin_dir, 'firo'),
|
||||||
'use_segwit': False,
|
'use_segwit': False,
|
||||||
|
'use_csv': False,
|
||||||
|
'blocks_confirmed': 1,
|
||||||
|
'conf_target': 2,
|
||||||
|
'core_version_group': 14,
|
||||||
|
'min_relay_fee': 0.00001,
|
||||||
|
'chain_lookups': 'local',
|
||||||
|
},
|
||||||
|
'navcoin': {
|
||||||
|
'connection_type': 'rpc' if 'navcoin' in with_coins else 'none',
|
||||||
|
'manage_daemon': True if ('navcoin' in with_coins and NAV_RPC_HOST == '127.0.0.1') else False,
|
||||||
|
'rpchost': NAV_RPC_HOST,
|
||||||
|
'rpcport': NAV_RPC_PORT + port_offset,
|
||||||
|
'onionport': NAV_ONION_PORT + port_offset,
|
||||||
|
'datadir': os.getenv('NAV_DATA_DIR', os.path.join(data_dir, 'navcoin')),
|
||||||
|
'bindir': os.path.join(bin_dir, 'navcoin'),
|
||||||
|
'use_segwit': True,
|
||||||
'use_csv': True,
|
'use_csv': True,
|
||||||
'blocks_confirmed': 1,
|
'blocks_confirmed': 1,
|
||||||
'conf_target': 2,
|
'conf_target': 2,
|
||||||
'core_version_group': 18,
|
'core_version_group': 18,
|
||||||
'chain_lookups': 'local',
|
'chain_lookups': 'local',
|
||||||
|
'startup_tries': 40,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1543,6 +1694,9 @@ def main():
|
|||||||
if FIRO_RPC_USER != '':
|
if FIRO_RPC_USER != '':
|
||||||
chainclients['firo']['rpcuser'] = FIRO_RPC_USER
|
chainclients['firo']['rpcuser'] = FIRO_RPC_USER
|
||||||
chainclients['firo']['rpcpassword'] = FIRO_RPC_PWD
|
chainclients['firo']['rpcpassword'] = FIRO_RPC_PWD
|
||||||
|
if NAV_RPC_USER != '':
|
||||||
|
chainclients['nav']['rpcuser'] = NAV_RPC_USER
|
||||||
|
chainclients['nav']['rpcpassword'] = NAV_RPC_PWD
|
||||||
|
|
||||||
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
|
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
|
||||||
|
|
||||||
@@ -1572,7 +1726,7 @@ def main():
|
|||||||
|
|
||||||
addTorSettings(settings, tor_control_password)
|
addTorSettings(settings, tor_control_password)
|
||||||
for coin in settings['chainclients']:
|
for coin in settings['chainclients']:
|
||||||
modify_tor_config(settings, coin, tor_control_password, enable=True)
|
modify_tor_config(settings, coin, tor_control_password, enable=True, extra_opts=extra_opts)
|
||||||
|
|
||||||
with open(config_path, 'w') as fp:
|
with open(config_path, 'w') as fp:
|
||||||
json.dump(settings, fp, indent=4)
|
json.dump(settings, fp, indent=4)
|
||||||
@@ -1585,7 +1739,7 @@ def main():
|
|||||||
settings = load_config(config_path)
|
settings = load_config(config_path)
|
||||||
settings['use_tor'] = False
|
settings['use_tor'] = False
|
||||||
for coin in settings['chainclients']:
|
for coin in settings['chainclients']:
|
||||||
modify_tor_config(settings, coin, tor_control_password=None, enable=False)
|
modify_tor_config(settings, coin, tor_control_password=None, enable=False, extra_opts=extra_opts)
|
||||||
|
|
||||||
with open(config_path, 'w') as fp:
|
with open(config_path, 'w') as fp:
|
||||||
json.dump(settings, fp, indent=4)
|
json.dump(settings, fp, indent=4)
|
||||||
@@ -1598,9 +1752,13 @@ def main():
|
|||||||
settings = load_config(config_path)
|
settings = load_config(config_path)
|
||||||
|
|
||||||
if disable_coin not in settings['chainclients']:
|
if disable_coin not in settings['chainclients']:
|
||||||
exitWithError('{} has not been prepared'.format(disable_coin))
|
exitWithError(f'{disable_coin} not configured')
|
||||||
settings['chainclients'][disable_coin]['connection_type'] = 'none'
|
|
||||||
settings['chainclients'][disable_coin]['manage_daemon'] = False
|
coin_settings = settings['chainclients'][disable_coin]
|
||||||
|
if coin_settings['connection_type'] == 'none' and coin_settings['manage_daemon'] is False:
|
||||||
|
exitWithError(f'{disable_coin} is already disabled')
|
||||||
|
coin_settings['connection_type'] = 'none'
|
||||||
|
coin_settings['manage_daemon'] = False
|
||||||
|
|
||||||
with open(config_path, 'w') as fp:
|
with open(config_path, 'w') as fp:
|
||||||
json.dump(settings, fp, indent=4)
|
json.dump(settings, fp, indent=4)
|
||||||
@@ -1678,9 +1836,11 @@ def main():
|
|||||||
'max_delay_event': 50, # Max delay in seconds before reacting to an event
|
'max_delay_event': 50, # Max delay in seconds before reacting to an event
|
||||||
'check_progress_seconds': 60,
|
'check_progress_seconds': 60,
|
||||||
'check_watched_seconds': 60,
|
'check_watched_seconds': 60,
|
||||||
'check_expired_seconds': 60
|
'check_expired_seconds': 60,
|
||||||
|
'wallet_update_timeout': 10, # Seconds to wait for wallet page update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wshost: str = extra_opts.get('wshost', htmlhost)
|
||||||
if wshost != 'none':
|
if wshost != 'none':
|
||||||
settings['wshost'] = wshost
|
settings['wshost'] = wshost
|
||||||
settings['wsport'] = UI_WS_PORT + port_offset
|
settings['wsport'] = UI_WS_PORT + port_offset
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2022 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
import logging
|
import logging
|
||||||
@@ -22,12 +21,25 @@ from basicswap.basicswap import BasicSwap
|
|||||||
from basicswap.http_server import HttpThread
|
from basicswap.http_server import HttpThread
|
||||||
from basicswap.contrib.websocket_server import WebsocketServer
|
from basicswap.contrib.websocket_server import WebsocketServer
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
swap_client = None
|
swap_client = None
|
||||||
|
# TODO: deduplicate
|
||||||
|
known_coins = [
|
||||||
|
'particl',
|
||||||
|
'litecoin',
|
||||||
|
'bitcoin',
|
||||||
|
'namecoin',
|
||||||
|
'monero',
|
||||||
|
'pivx',
|
||||||
|
'dash',
|
||||||
|
'firo',
|
||||||
|
'navcoin',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
@@ -113,7 +125,7 @@ def ws_message_received(client, server, message):
|
|||||||
swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
|
swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
|
||||||
|
|
||||||
|
|
||||||
def runClient(fp, data_dir, chain):
|
def runClient(fp, data_dir, chain, start_only_coins):
|
||||||
global swap_client
|
global swap_client
|
||||||
daemons = []
|
daemons = []
|
||||||
pids = []
|
pids = []
|
||||||
@@ -144,6 +156,9 @@ def runClient(fp, data_dir, chain):
|
|||||||
try:
|
try:
|
||||||
# Try start daemons
|
# Try start daemons
|
||||||
for c, v in settings['chainclients'].items():
|
for c, v in settings['chainclients'].items():
|
||||||
|
|
||||||
|
if len(start_only_coins) > 0 and c not in start_only_coins:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
coin_id = swap_client.getCoinIdFromName(c)
|
coin_id = swap_client.getCoinIdFromName(c)
|
||||||
display_name = getCoinName(coin_id)
|
display_name = getCoinName(coin_id)
|
||||||
@@ -161,13 +176,24 @@ def runClient(fp, data_dir, chain):
|
|||||||
if v['manage_wallet_daemon'] is True:
|
if v['manage_wallet_daemon'] is True:
|
||||||
swap_client.log.info(f'Starting {display_name} wallet daemon')
|
swap_client.log.info(f'Starting {display_name} wallet daemon')
|
||||||
daemon_addr = '{}:{}'.format(v['rpchost'], v['rpcport'])
|
daemon_addr = '{}:{}'.format(v['rpchost'], v['rpcport'])
|
||||||
swap_client.log.info('daemon-address: {}'.format(daemon_addr))
|
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(coin_id, v['rpchost'])
|
||||||
opts = ['--daemon-address', daemon_addr, ]
|
opts = ['--daemon-address', daemon_addr, ]
|
||||||
|
|
||||||
|
proxy_log_str = ''
|
||||||
|
proxy_host, proxy_port = swap_client.getXMRWalletProxy(coin_id, v['rpchost'])
|
||||||
|
if proxy_host:
|
||||||
|
proxy_log_str = ' through proxy'
|
||||||
|
opts += ['--proxy', f'{proxy_host}:{proxy_port}', ]
|
||||||
|
|
||||||
|
swap_client.log.info('daemon-address: {} ({}){}'.format(daemon_addr, 'trusted' if trusted_daemon else 'untrusted', proxy_log_str))
|
||||||
|
|
||||||
daemon_rpcuser = v.get('rpcuser', '')
|
daemon_rpcuser = v.get('rpcuser', '')
|
||||||
daemon_rpcpass = v.get('rpcpassword', '')
|
daemon_rpcpass = v.get('rpcpassword', '')
|
||||||
if daemon_rpcuser != '':
|
if daemon_rpcuser != '':
|
||||||
opts.append('--daemon-login')
|
opts.append('--daemon-login')
|
||||||
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
|
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
|
||||||
|
|
||||||
|
opts.append('--trusted-daemon' if trusted_daemon else '--untrusted-daemon')
|
||||||
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||||
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
|
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
|
||||||
pid = daemons[-1].pid
|
pid = daemons[-1].pid
|
||||||
@@ -190,29 +216,33 @@ def runClient(fp, data_dir, chain):
|
|||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
swap_client.start()
|
|
||||||
|
|
||||||
if 'htmlhost' in settings:
|
if len(start_only_coins) > 0:
|
||||||
swap_client.log.info('Starting http server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport']))
|
logger.info(f'Only running {start_only_coins}. Manually exit with Ctrl + c when ready.')
|
||||||
allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS
|
while not swap_client.delay_event.wait(0.5):
|
||||||
thread_http = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
|
pass
|
||||||
threads.append(thread_http)
|
else:
|
||||||
thread_http.start()
|
swap_client.start()
|
||||||
|
if 'htmlhost' in settings:
|
||||||
|
swap_client.log.info('Starting http server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport']))
|
||||||
|
allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS
|
||||||
|
thread_http = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
|
||||||
|
threads.append(thread_http)
|
||||||
|
thread_http.start()
|
||||||
|
|
||||||
if 'wshost' in settings:
|
if 'wshost' in settings:
|
||||||
ws_url = 'ws://{}:{}'.format(settings['wshost'], settings['wsport'])
|
ws_url = 'ws://{}:{}'.format(settings['wshost'], settings['wsport'])
|
||||||
swap_client.log.info(f'Starting ws server at {ws_url}.')
|
swap_client.log.info(f'Starting ws server at {ws_url}.')
|
||||||
|
|
||||||
swap_client.ws_server = WebsocketServer(host=settings['wshost'], port=settings['wsport'])
|
swap_client.ws_server = WebsocketServer(host=settings['wshost'], port=settings['wsport'])
|
||||||
swap_client.ws_server.set_fn_new_client(ws_new_client)
|
swap_client.ws_server.set_fn_new_client(ws_new_client)
|
||||||
swap_client.ws_server.set_fn_client_left(ws_client_left)
|
swap_client.ws_server.set_fn_client_left(ws_client_left)
|
||||||
swap_client.ws_server.set_fn_message_received(ws_message_received)
|
swap_client.ws_server.set_fn_message_received(ws_message_received)
|
||||||
swap_client.ws_server.run_forever(threaded=True)
|
swap_client.ws_server.run_forever(threaded=True)
|
||||||
|
|
||||||
logger.info('Exit with Ctrl + c.')
|
logger.info('Exit with Ctrl + c.')
|
||||||
while swap_client.is_running:
|
while not swap_client.delay_event.wait(0.5):
|
||||||
time.sleep(0.5)
|
swap_client.update()
|
||||||
swap_client.update()
|
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -269,18 +299,20 @@ def printVersion():
|
|||||||
|
|
||||||
|
|
||||||
def printHelp():
|
def printHelp():
|
||||||
logger.info('Usage: basicswap-run ')
|
print('Usage: basicswap-run ')
|
||||||
logger.info('\n--help, -h Print help.')
|
print('\n--help, -h Print help.')
|
||||||
logger.info('--version, -v Print version.')
|
print('--version, -v Print version.')
|
||||||
logger.info('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR))
|
print('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR))
|
||||||
logger.info('--mainnet Run in mainnet mode.')
|
print('--mainnet Run in mainnet mode.')
|
||||||
logger.info('--testnet Run in testnet mode.')
|
print('--testnet Run in testnet mode.')
|
||||||
logger.info('--regtest Run in regtest mode.')
|
print('--regtest Run in regtest mode.')
|
||||||
|
print('--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing.')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
data_dir = None
|
data_dir = None
|
||||||
chain = 'mainnet'
|
chain = 'mainnet'
|
||||||
|
start_only_coins = set()
|
||||||
|
|
||||||
for v in sys.argv[1:]:
|
for v in sys.argv[1:]:
|
||||||
if len(v) < 2 or v[0] != '-':
|
if len(v) < 2 or v[0] != '-':
|
||||||
@@ -301,17 +333,20 @@ def main():
|
|||||||
printHelp()
|
printHelp()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if name == 'testnet':
|
if name in ('mainnet', 'testnet', 'regtest'):
|
||||||
chain = 'testnet'
|
chain = name
|
||||||
continue
|
|
||||||
if name == 'regtest':
|
|
||||||
chain = 'regtest'
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(s) == 2:
|
if len(s) == 2:
|
||||||
if name == 'datadir':
|
if name == 'datadir':
|
||||||
data_dir = os.path.expanduser(s[1])
|
data_dir = os.path.expanduser(s[1])
|
||||||
continue
|
continue
|
||||||
|
if name == 'startonlycoin':
|
||||||
|
for coin in [s.lower() for s in s[1].split(',')]:
|
||||||
|
if coin not in known_coins:
|
||||||
|
raise ValueError(f'Unknown coin: {coin}')
|
||||||
|
start_only_coins.add(coin)
|
||||||
|
continue
|
||||||
|
|
||||||
logger.warning('Unknown argument %s', v)
|
logger.warning('Unknown argument %s', v)
|
||||||
|
|
||||||
@@ -325,7 +360,7 @@ def main():
|
|||||||
|
|
||||||
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
||||||
logger.info(os.path.basename(sys.argv[0]) + ', version: ' + __version__ + '\n\n')
|
logger.info(os.path.basename(sys.argv[0]) + ', version: ' + __version__ + '\n\n')
|
||||||
runClient(fp, data_dir, chain)
|
runClient(fp, data_dir, chain, start_only_coins)
|
||||||
|
|
||||||
logger.info('Done.')
|
logger.info('Done.')
|
||||||
return swap_client.fail_code if swap_client is not None else 0
|
return swap_client.fail_code if swap_client is not None else 0
|
||||||
|
|||||||
12
doc/ci.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
## Run Cirrus CI Tests Locally
|
||||||
|
|
||||||
|
Install cirrus-cli:
|
||||||
|
https://github.com/cirruslabs/cirrus-cli/blob/master/INSTALL.md
|
||||||
|
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
cd basicswap
|
||||||
|
cirrus run -v -o simple
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ Continue from the [Run Using Docker](#run-using-docker) section.
|
|||||||
|
|
||||||
### Ubuntu Setup:
|
### Ubuntu Setup:
|
||||||
|
|
||||||
apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config curl jq
|
apt-get install -y wget git python3-venv python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config curl jq
|
||||||
|
|
||||||
### OSX Setup:
|
### OSX Setup:
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ Close the terminal and open a new one to update the python symlinks.
|
|||||||
python3 -m venv "$SWAP_DATADIR/venv"
|
python3 -m venv "$SWAP_DATADIR/venv"
|
||||||
. $SWAP_DATADIR/venv/bin/activate && python -V
|
. $SWAP_DATADIR/venv/bin/activate && python -V
|
||||||
cd $SWAP_DATADIR
|
cd $SWAP_DATADIR
|
||||||
wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.1.zip
|
wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
||||||
unzip -d coincurve-anonswap coincurve-anonswap.zip
|
unzip -d coincurve-anonswap coincurve-anonswap.zip
|
||||||
mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
||||||
cd $SWAP_DATADIR/coincurve-anonswap
|
cd $SWAP_DATADIR/coincurve-anonswap
|
||||||
@@ -173,6 +173,7 @@ Close the terminal and open a new one to update the python symlinks.
|
|||||||
git clone https://github.com/tecnovert/basicswap.git
|
git clone https://github.com/tecnovert/basicswap.git
|
||||||
cd $SWAP_DATADIR/basicswap
|
cd $SWAP_DATADIR/basicswap
|
||||||
|
|
||||||
|
|
||||||
If installed on OSX, you may need to install additional root ssl certificates for the ssl module.
|
If installed on OSX, you may need to install additional root ssl certificates for the ssl module.
|
||||||
From https://pypi.org/project/certifi/
|
From https://pypi.org/project/certifi/
|
||||||
|
|
||||||
@@ -189,10 +190,11 @@ Prepare the datadir:
|
|||||||
|
|
||||||
CURRENT_XMR_HEIGHT=$(curl https://localmonero.co/blocks/api/get_stats | jq .height)
|
CURRENT_XMR_HEIGHT=$(curl https://localmonero.co/blocks/api/get_stats | jq .height)
|
||||||
|
|
||||||
|
basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=$CURRENT_XMR_HEIGHT
|
||||||
|
|
||||||
|
OR using a remote/public XMR daemon (not recommended):
|
||||||
XMR_RPC_HOST="node.xmr.to" BASE_XMR_RPC_PORT=18081 basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=$CURRENT_XMR_HEIGHT
|
XMR_RPC_HOST="node.xmr.to" BASE_XMR_RPC_PORT=18081 basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=$CURRENT_XMR_HEIGHT
|
||||||
|
|
||||||
OR using a local XMR daemon:
|
|
||||||
basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=$CURRENT_XMR_HEIGHT
|
|
||||||
|
|
||||||
Record the mnemonic from the output of the above command.
|
Record the mnemonic from the output of the above command.
|
||||||
|
|
||||||
@@ -200,6 +202,7 @@ Start Basicswap:
|
|||||||
|
|
||||||
basicswap-run --datadir=$SWAP_DATADIR
|
basicswap-run --datadir=$SWAP_DATADIR
|
||||||
|
|
||||||
|
|
||||||
Open in browser: `http://localhost:12700`
|
Open in browser: `http://localhost:12700`
|
||||||
It may take a few minutes to start as the coin daemons are started before the http interface.
|
It may take a few minutes to start as the coin daemons are started before the http interface.
|
||||||
|
|
||||||
|
|||||||
15
doc/notes.md
@@ -111,9 +111,7 @@ Test:
|
|||||||
|
|
||||||
## Run One Test
|
## Run One Test
|
||||||
|
|
||||||
```
|
pytest -v -s tests/basicswap/test_xmr.py::Test::test_02_leader_recover_a_lock_tx
|
||||||
pytest -v -s tests/basicswap/test_xmr.py::Test::test_02_leader_recover_a_lock_tx
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Private Offers
|
## Private Offers
|
||||||
@@ -127,6 +125,17 @@ To send a private offer:
|
|||||||
Nodes will ignore offers sent on keys other than the network key or keys created for offer-receiving.
|
Nodes will ignore offers sent on keys other than the network key or keys created for offer-receiving.
|
||||||
|
|
||||||
|
|
||||||
|
## Coin reindexing
|
||||||
|
|
||||||
|
export COINDATA_PATH=/var/data/coinswaps
|
||||||
|
cd $COINDATA_PATH/bin/firo
|
||||||
|
./firod -reindex -datadir=$COINDATA_PATH/firo -nodebuglogfile -printtoconsole > /tmp/firo.log
|
||||||
|
|
||||||
|
Observe progress with
|
||||||
|
|
||||||
|
tail -f /tmp/firo.log
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ xu {
|
|||||||
C note C2
|
C note C2
|
||||||
[label="The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and proof the bidder can sign it.",
|
[label="The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and proof the bidder can sign it.",
|
||||||
textbgcolor="#FFFFCC"];
|
textbgcolor="#FFFFCC"];
|
||||||
B =>> N [label="Sends script-coin-lock-tx"],
|
B =>> N [label="Sends script-coin-lock-tx"];
|
||||||
O abox O [label="Bid Script coin spend tx valid"];
|
O abox O [label="Bid Script coin spend tx valid"];
|
||||||
O abox O [label="Exchanged script lock spend tx msg"];
|
O abox O [label="Exchanged script lock spend tx msg"];
|
||||||
O => O [label="Wait for script-coin-lock-tx to confirm"];
|
O => O [label="Wait for script-coin-lock-tx to confirm"];
|
||||||
|
|||||||
@@ -1,10 +1,97 @@
|
|||||||
|
0.12.7
|
||||||
0.0.66
|
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
- basicswap-prepare
|
||||||
|
- Sets --usetorproxy automatically when tor is enabled on existing installs for commands that access the network.
|
||||||
|
- Disable with --notorproxy
|
||||||
|
- Switch the LTC download URL to github (works over Tor)
|
||||||
|
- Sets `wshost` to match `htmlhost` by default
|
||||||
|
- doc: Simplify docker tor install notes.
|
||||||
|
- Basicswap will set monero-wallet-rpc proxy when Tor is enabled if the host ip setting (`rpchost`) for the monerod instance is not a private ip.
|
||||||
|
- Works for automatically selected daemons too.
|
||||||
|
- Override with a `use_tor` parameter in the Monero section of basicswap.json.
|
||||||
|
- Basicswap sets monero-wallet-rpc `--trusted-daemon` if the host ip setting for the monerod instance is a private ip.
|
||||||
|
- Override with the `trusted_daemon` parameter in the Monero section of basicswap.json.
|
||||||
|
- Defaults to auto.
|
||||||
|
- Override in basicswap-prepare with `--trustremotenode`
|
||||||
|
- Add settings in basicswap.json to set Monero rpc timeouts
|
||||||
|
- `rpctimeout`, `walletrpctimeout` and `walletrpctimeoutlong` in the Monero section of basicswap.json.
|
||||||
|
- `wallet_update_timeout` in basicswap.json to set how long the wallet ui page waits for an rpc response.
|
||||||
|
- ui: Renamed unconfirmed balance to pending and include immature balance in pending.
|
||||||
|
|
||||||
|
|
||||||
|
0.12.6
|
||||||
|
==============
|
||||||
|
|
||||||
|
- ui: Display count of locked UTXOs on wallet page.
|
||||||
|
- Only shows if locked UTXOs is > 0
|
||||||
|
|
||||||
|
|
||||||
|
0.12.5
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Unlock wallets logs an error when failing to unlock a wallet.
|
||||||
|
- Fixed bug where failed unlock prevents processing incoming smsg messages.
|
||||||
|
|
||||||
|
|
||||||
|
0.12.4
|
||||||
|
==============
|
||||||
|
|
||||||
|
- LTC creates a new wallet to hold MWEB balance.
|
||||||
|
- MWEB wallet should be be automatically created at startup or when unlocked if system is encrypted.
|
||||||
|
|
||||||
|
|
||||||
|
0.12.3
|
||||||
|
==============
|
||||||
|
|
||||||
|
- New basicswap-run parameter startonlycoin.
|
||||||
|
- If set basicswap-run will only start the specified coin daemon/s.
|
||||||
|
|
||||||
|
|
||||||
|
0.12.2
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Updated coincurve and libsecp256k1 versions.
|
||||||
|
- Avoids needing secp256k1_generator_const_g as it's not accessible from a dll.
|
||||||
|
- Fix missing ripemd160 on some systems.
|
||||||
|
|
||||||
|
|
||||||
|
0.12.1
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Firo and Navcoin send utxo outpoints with proof of funds address+sig.
|
||||||
|
- Avoids missing scantxoutset command
|
||||||
|
- Firo Bids will be incompatible with older versions.
|
||||||
|
- Bids for other coins should still work between versions.
|
||||||
|
- Firo uses workarounds for missing getblock verbosity and rescanblockchain
|
||||||
|
- Coins without segwit can be used in reverse adaptor sig swaps.
|
||||||
|
|
||||||
|
|
||||||
|
0.11.68
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Temporarily disabled Navcoin.
|
||||||
|
- Untested on mainnet.
|
||||||
|
- Fixed bug where requesting a new XMR subaddress would return an old one.
|
||||||
|
|
||||||
|
|
||||||
|
0.11.67
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Added support for p2sh-p2wsh coins
|
||||||
|
- Added Navcoin
|
||||||
|
- Fixed Particl fee estimation in secret hash swaps.
|
||||||
|
- Raised adaptor signature swap protocol version to 2
|
||||||
|
- Not backwards compatible with previous versions.
|
||||||
|
|
||||||
|
|
||||||
|
0.11.66
|
||||||
|
==============
|
||||||
|
|
||||||
- Fixed bugs in getLinkedMessageId and validateSwapType.
|
- Fixed bugs in getLinkedMessageId and validateSwapType.
|
||||||
|
|
||||||
|
|
||||||
0.0.65
|
0.11.65
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- smsg: Outbox messages are removed when expired.
|
- smsg: Outbox messages are removed when expired.
|
||||||
@@ -15,7 +102,7 @@
|
|||||||
- ui: Show ITX and PTX status for adaptor sig type swaps.
|
- ui: Show ITX and PTX status for adaptor sig type swaps.
|
||||||
|
|
||||||
|
|
||||||
0.0.64
|
0.11.64
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- protocol: Added reversed Adaptor sig protocol.
|
- protocol: Added reversed Adaptor sig protocol.
|
||||||
@@ -26,7 +113,7 @@
|
|||||||
- Not backwards compatible with previous versions.
|
- Not backwards compatible with previous versions.
|
||||||
|
|
||||||
|
|
||||||
0.0.63
|
0.11.63
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- cores: Raised Particl and Monero daemon version.
|
- cores: Raised Particl and Monero daemon version.
|
||||||
@@ -35,7 +122,7 @@
|
|||||||
- Abandoning a bid stops all processing.
|
- Abandoning a bid stops all processing.
|
||||||
|
|
||||||
|
|
||||||
0.0.62
|
0.11.62
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- ui: Persistent filters
|
- ui: Persistent filters
|
||||||
@@ -44,13 +131,13 @@
|
|||||||
- Adaptor signature swaps are not backwards compatible with previous versions.
|
- Adaptor signature swaps are not backwards compatible with previous versions.
|
||||||
|
|
||||||
|
|
||||||
0.0.61
|
0.11.61
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- GUI 2.0
|
- GUI 2.0
|
||||||
|
|
||||||
|
|
||||||
0.0.60
|
0.11.60
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- Accepted bids will timeout if the peer does not respond within an hour after the bid expires.
|
- Accepted bids will timeout if the peer does not respond within an hour after the bid expires.
|
||||||
@@ -62,7 +149,7 @@
|
|||||||
BITCOIN_FASTSYNC_FILE env var in the datadir without checking it's size or signature.
|
BITCOIN_FASTSYNC_FILE env var in the datadir without checking it's size or signature.
|
||||||
|
|
||||||
|
|
||||||
0.0.59
|
0.11.59
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- Added total_bids_value_multiplier option to automation strategies.
|
- Added total_bids_value_multiplier option to automation strategies.
|
||||||
@@ -86,7 +173,7 @@
|
|||||||
- ui: Can edit offer automation strategy.
|
- ui: Can edit offer automation strategy.
|
||||||
|
|
||||||
|
|
||||||
0.0.54
|
0.11.54
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- If the XMR daemon is busy the wallet can fail a transfer, later sending the tx unknown to bsx.
|
- If the XMR daemon is busy the wallet can fail a transfer, later sending the tx unknown to bsx.
|
||||||
|
|||||||
31
doc/tor.md
@@ -16,40 +16,49 @@ basicswap-prepare can be configured to download all binaries through tor and to
|
|||||||
Docker will create directories instead of files if these don't exist.
|
Docker will create directories instead of files if these don't exist.
|
||||||
|
|
||||||
mkdir -p $COINDATA_PATH/tor
|
mkdir -p $COINDATA_PATH/tor
|
||||||
touch $COINDATA_PATH/tor/torrc
|
echo 'SocksPort 0.0.0.0:9050' > $COINDATA_PATH/tor/torrc
|
||||||
|
|
||||||
|
|
||||||
#### For a new install
|
#### For a new install
|
||||||
|
|
||||||
|
Use the `--usetorproxy` argument to download the coin binaries over tor, then enable tor with `--enabletor`.
|
||||||
Note that some download links, notably for Litecoin, are unreachable when using tor.
|
Note that some download links, notably for Litecoin, are unreachable when using tor.
|
||||||
|
|
||||||
If running through docker start the tor container with the following command as the torrc configuration file won't exist yet.
|
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
||||||
|
basicswap-prepare --usetorproxy --datadir=/coindata --withcoins=monero,particl
|
||||||
|
|
||||||
docker compose -f docker-compose_with_tor.yml run --name tor --rm tor \
|
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
||||||
tor --allow-missing-torrc --SocksPort 0.0.0.0:9050
|
basicswap-prepare --enabletor --datadir=/coindata
|
||||||
|
|
||||||
docker compose -f docker-compose_with_tor.yml run -e TOR_PROXY_HOST=172.16.238.200 --rm swapclient \
|
|
||||||
basicswap-prepare --usetorproxy --datadir=/coindata --withcoins=monero,particl
|
The `--enabletor` option will add config to the torrc file, the tor container must afterwards be stopped to load the new config:
|
||||||
|
|
||||||
|
docker compose -f docker-compose_with_tor.yml stop
|
||||||
|
|
||||||
|
|
||||||
Start Basicswap with:
|
Start Basicswap with:
|
||||||
|
|
||||||
docker compose -f docker-compose_with_tor.yml up
|
docker compose -f docker-compose_with_tor.yml up
|
||||||
|
|
||||||
|
|
||||||
#### Enable tor on an existing datadir
|
#### Enable tor on an existing datadir
|
||||||
|
|
||||||
docker compose -f docker-compose_with_tor.yml run -e TOR_PROXY_HOST=172.16.238.200 --rm swapclient \
|
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
||||||
basicswap-prepare --datadir=/coindata --enabletor
|
basicswap-prepare --datadir=/coindata --enabletor
|
||||||
|
|
||||||
|
docker compose -f docker-compose_with_tor.yml stop
|
||||||
|
|
||||||
#### Disable tor on an existing datadir
|
#### Disable tor on an existing datadir
|
||||||
|
|
||||||
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
||||||
basicswap-prepare --datadir=/coindata --disabletor
|
basicswap-prepare --datadir=/coindata --disabletor
|
||||||
|
|
||||||
|
docker compose -f docker-compose_with_tor.yml stop
|
||||||
|
|
||||||
|
|
||||||
#### Update coin release
|
#### Update coin release
|
||||||
|
|
||||||
docker compose -f docker-compose_with_tor.yml up -d tor
|
docker compose -f docker-compose_with_tor.yml up -d tor
|
||||||
docker compose -f docker-compose_with_tor.yml run -e TOR_PROXY_HOST=172.16.238.200 --rm swapclient \
|
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
||||||
basicswap-prepare --usetorproxy --datadir=/coindata --preparebinonly --withcoins=bitcoin
|
basicswap-prepare --usetorproxy --datadir=/coindata --preparebinonly --withcoins=bitcoin
|
||||||
docker compose -f docker-compose_with_tor.yml stop
|
docker compose -f docker-compose_with_tor.yml stop
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
HTML_PORT=127.0.0.1:12700:12700
|
HTML_PORT=127.0.0.1:12700:12700
|
||||||
WS_PORT=127.0.0.1:11700:11700
|
WS_PORT=127.0.0.1:11700:11700
|
||||||
#COINDATA_PATH=/var/data/coinswaps
|
#COINDATA_PATH=/var/data/coinswaps
|
||||||
|
TOR_PROXY_HOST=172.16.238.200
|
||||||
TZ=UTC
|
TZ=UTC
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ services:
|
|||||||
image: i_swapclient
|
image: i_swapclient
|
||||||
container_name: swapclient
|
container_name: swapclient
|
||||||
stop_grace_period: 5m
|
stop_grace_period: 5m
|
||||||
|
depends_on:
|
||||||
|
- tor
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN wget -O protobuf_src.tar.gz https://github.com/protocolbuffers/protobuf/rele
|
|||||||
make -j$(nproc) install && \
|
make -j$(nproc) install && \
|
||||||
ldconfig
|
ldconfig
|
||||||
|
|
||||||
ARG COINCURVE_VERSION=v0.1
|
ARG COINCURVE_VERSION=v0.2
|
||||||
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
||||||
unzip coincurve-anonswap.zip && \
|
unzip coincurve-anonswap.zip && \
|
||||||
mv ./coincurve-anonswap_$COINCURVE_VERSION ./coincurve-anonswap && \
|
mv ./coincurve-anonswap_$COINCURVE_VERSION ./coincurve-anonswap && \
|
||||||
|
|||||||
21
guix.scm
@@ -26,7 +26,7 @@
|
|||||||
(define libsecp256k1-anonswap
|
(define libsecp256k1-anonswap
|
||||||
(package
|
(package
|
||||||
(name "libsecp256k1-anonswap")
|
(name "libsecp256k1-anonswap")
|
||||||
(version "anonswap_v0.1")
|
(version "anonswap_v0.2")
|
||||||
(source (origin
|
(source (origin
|
||||||
(method git-fetch)
|
(method git-fetch)
|
||||||
(uri (git-reference
|
(uri (git-reference
|
||||||
@@ -34,15 +34,17 @@
|
|||||||
(commit version)))
|
(commit version)))
|
||||||
(sha256
|
(sha256
|
||||||
(base32
|
(base32
|
||||||
"1lrcc5gjywlzvrgwzifva4baa2nsvwr3h0wmkc71q0zhag9pjbah"))
|
"1r07rkrw5qsnc5v1q7cb0zfs1cr62fqwq7kd2v8650g6ha4k5r8i"))
|
||||||
(file-name (git-file-name name version))))
|
(file-name (git-file-name name version))))
|
||||||
(build-system gnu-build-system)
|
(build-system gnu-build-system)
|
||||||
(arguments
|
(arguments
|
||||||
'(#:configure-flags '("--enable-shared"
|
'(#:configure-flags '("--enable-shared"
|
||||||
"--disable-dependency-tracking"
|
"--disable-dependency-tracking"
|
||||||
"--with-valgrind=no"
|
"--with-pic"
|
||||||
"--enable-experimental"
|
"--enable-module-extrakeys"
|
||||||
"--enable-module-recovery"
|
"--enable-module-recovery"
|
||||||
|
"--enable-module-schnorrsig"
|
||||||
|
"--enable-experimental"
|
||||||
"--enable-module-ecdh"
|
"--enable-module-ecdh"
|
||||||
"--enable-benchmark=no"
|
"--enable-benchmark=no"
|
||||||
"--enable-tests=no"
|
"--enable-tests=no"
|
||||||
@@ -50,6 +52,7 @@
|
|||||||
"--enable-module-generator"
|
"--enable-module-generator"
|
||||||
"--enable-module-dleag"
|
"--enable-module-dleag"
|
||||||
"--enable-module-ecdsaotves"
|
"--enable-module-ecdsaotves"
|
||||||
|
"--with-valgrind=no"
|
||||||
)))
|
)))
|
||||||
(native-inputs
|
(native-inputs
|
||||||
(list autoconf automake libtool))
|
(list autoconf automake libtool))
|
||||||
@@ -63,7 +66,7 @@
|
|||||||
(define python-coincurve-anonswap
|
(define python-coincurve-anonswap
|
||||||
(package
|
(package
|
||||||
(name "python-coincurve-anonswap")
|
(name "python-coincurve-anonswap")
|
||||||
(version "anonswap_v0.1")
|
(version "anonswap_v0.2")
|
||||||
(source
|
(source
|
||||||
(origin
|
(origin
|
||||||
(method git-fetch)
|
(method git-fetch)
|
||||||
@@ -74,7 +77,7 @@
|
|||||||
(file-name
|
(file-name
|
||||||
(git-file-name name version))
|
(git-file-name name version))
|
||||||
(sha256
|
(sha256
|
||||||
(base32 "0vyzvpp2s21js01185qbm1lgs4ps4hki2d6yzprfsjqap1i5qhmp"))))
|
(base32 "08fz02afh88m83axfm8jsgq1c65mw1f3g07x9hz361vblvqjwzqh"))))
|
||||||
(build-system python-build-system)
|
(build-system python-build-system)
|
||||||
(arguments
|
(arguments
|
||||||
'(#:tests? #f ;XXX fails to load "libsecp256k1.dll"
|
'(#:tests? #f ;XXX fails to load "libsecp256k1.dll"
|
||||||
@@ -117,15 +120,15 @@
|
|||||||
(define-public basicswap
|
(define-public basicswap
|
||||||
(package
|
(package
|
||||||
(name "basicswap")
|
(name "basicswap")
|
||||||
(version "0.11.66")
|
(version "0.12.1")
|
||||||
(source (origin
|
(source (origin
|
||||||
(method git-fetch)
|
(method git-fetch)
|
||||||
(uri (git-reference
|
(uri (git-reference
|
||||||
(url "https://github.com/tecnovert/basicswap")
|
(url "https://github.com/tecnovert/basicswap")
|
||||||
(commit "7735c9733a818a2cc13e1b5eaa78f81caa07d7d6")))
|
(commit "15bf9b2187d3b8a03939e61b4c3ebf4d90fcc919")))
|
||||||
(sha256
|
(sha256
|
||||||
(base32
|
(base32
|
||||||
"1hps0hr25b8hgwz8q7jfa4d67sp8xvpn6028iss9mrqp72385aan"))
|
"14gn6156x53c6panxdnd1awkd23jxnihvbqy886j66w5js3b5i8h"))
|
||||||
(file-name (git-file-name name version))))
|
(file-name (git-file-name name version))))
|
||||||
(build-system python-build-system)
|
(build-system python-build-system)
|
||||||
|
|
||||||
|
|||||||
40
pgp/keys/navcoin_builder.pgp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
xsDNBF/8KgoBDADpq1pIJh2u7J6eX1u8awKFA/k0M826KejUFIMY/B25MlYaBaQm
|
||||||
|
DMy2aCX9NuLCXtnA+Ys5UGlHb70KrsGLmlvA6Jk+nQZhjCM9RXTJtbMOrq84uTYc
|
||||||
|
zRgWBfAkMU+HIj2svkdjrmDdCjdbip6myBbketgg9UP09GA2TxdLweghDwNjjz/a
|
||||||
|
mVr2eaIoYWq13OFY7qoe8eKXQO0yGUv/T1abtJPiRFWpTwU4M7a8BqG2aFlJtzT2
|
||||||
|
WGDWn1pFkGEQMJqf+TKugRtDwoCv/TPJzATlCD9gzPUf3Xpbhv+f6Nj08WewBwhv
|
||||||
|
tqdpVFPTkp4xQP5QgN+JsplfQcEgWNczIYGNrna0RG3KjL4a+mHUGFi14NwFioIa
|
||||||
|
l4aqfBwbEOU5M+wLWfTsTAch7Pi42UeYEVpbEFZKQxxP3NeTere6sdwPJZg193nF
|
||||||
|
cb/rjnvuPUlGJlZ+TJbGptlkjkH/mNRDB04iTR8XbSHdYgo+rKo0EDQoxkRTeTWw
|
||||||
|
d1vGFOH9keg8Xq0AEQEAAc0kTmF2Q29pbiBDb3JlIDxidWlsZGVyQG5hdi5jb21t
|
||||||
|
dW5pdHk+wsEUBBMBCgA+FiEEG/m1G67VG6CzoXTuJ4ImK/bn+tsFAl/8KgoCGwMF
|
||||||
|
CQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQJ4ImK/bn+ttpxQv6AyWV
|
||||||
|
2mKyaWWxjsZTVBt0wJeajT+CaNZ0jU/zvEUOnpsr0/r3mbVsyyxWNRUdIcfceYrB
|
||||||
|
729x8B15GUE1RwpbRJwoKT5WjGeFd/sHkA+j6PRVg7x3pyJPzLYOB61rJsPTAOOM
|
||||||
|
06og0Kj+qbzIlcwDQdCgxObGuoHGXQvTFsxrOYiGRca4x3qHbIYlNEemgO41RHu+
|
||||||
|
zZCmHwfPocIljr6/FMHOuZQ7zbdRqXNmZtIBOYhjhEK7qypKEQwYXoUNJBkzdfHl
|
||||||
|
H8Mz/rSQ68jBcJ6fjXIs2XfUZgUaJAH/Xar9/52iApLr2AyBzHaXIGy+CUUWa+kR
|
||||||
|
Tpo6OqEkk92O2mNGwTx4I1T6ZED4ABkGWdKZS0rU7MlcNSe000pV6IQPrPoDw9CU
|
||||||
|
2PkJCyqZe1QqTdLtlHLoSlnLhLAd3vWPmw+T0MfMMSyuVMvqgC5vuCRJKDFs//uE
|
||||||
|
LuP58CiC+DmNyLFMCgjqv3GKsaDag21gNp8IRDnmgrJn/UrRRwEOMcDUYLaKzsDN
|
||||||
|
BF/8KgoBDADjAlvNASpQbzEQVOIUaJxXRP+09IvVavotS40LqOjvPuDWyOnA9+AP
|
||||||
|
VOrmFo/+HndicMjQzasXnkc249uiYiLyshlyrme6ebbMp+4aDcSxQRfx2+oXLR1l
|
||||||
|
0oBrchMiAJ+6xe0N6Itc6EhjAsPwG2IGfsFSmg9YYsm5NjHnujyvcQwcGG0OUBsy
|
||||||
|
ydJWtL5AU1z3H5fegN6DKFaUW/IMcxNN6el8NRZkHLwNh+JLyulHWKYDs/1YJ5sY
|
||||||
|
sknCvnicPAOpLqgdGopdu9ORnvOZ5wsJq8IFP6SBjdSjFj71eXam5dyWfMvMew1v
|
||||||
|
IVkBHi4y/G1J0Rlzi2f8j38htGa2H9A4eG4WUDO/uDJ/g76sWTAVPnWNNG3BQLgN
|
||||||
|
m2LDVNszGJNBTwFYlzEHrPipEA0foePHOLXc24o0/LZZYunP+zNvJiqqLEYCpvK/
|
||||||
|
nU7HozvHJvV8r+b/FK/vDLQbmp4HvvSL5ho/Dwn+yWU+Z+DPM+qIgukn4JrUUuZK
|
||||||
|
c553H1U2LtsAEQEAAcLA/AQYAQoAJhYhBBv5tRuu1Rugs6F07ieCJiv25/rbBQJf
|
||||||
|
/CoKAhsMBQkDwmcAAAoJECeCJiv25/rbN2gMAL28b3ou3c9aV99F8fEniZLsR2t7
|
||||||
|
EcZ93kBd9ozgeVnabSDsaRvlQ1uJDabemhcLyRY5fCCBAAXGCZ6jtxicOgt0cb+S
|
||||||
|
MHcrM7EUHLfxguM296V633svaFSUCwk3kBLMv9ukIrWu3oflE9MUyM/J92A0/TP/
|
||||||
|
PgBzD31xbiEEcSEqKt/CBP/pQbTSEgIa+JjGKYVPrN+n8kitY/Vu3yUNpATSH3j/
|
||||||
|
0cl/f5IXhg4uwqCzmopkU8lH/WGP90kIvG6ZwCstNJ4GN9sKRYKN++19PdDUmi++
|
||||||
|
z9FC0cu6GgSuWIWd2CyhGhOqMkFlwOnN5U5svH+wFlBK5+z6MgEkPCR6L2XPMonE
|
||||||
|
bd6JCKuw7SUHN4pUCZ2lnEStjiGM/cziuZ01U1TesH4W6CdOSl9+/sI4wwO//Qj/
|
||||||
|
/QYSjNwXNexgFA7pX90boRjQl1LNMocHVaYriGzw7n9fKJPwYI5oUam5By3sYmTT
|
||||||
|
i0WBmStFWb3bvqfkdjbe86g7ilbbLIoAlUS03w==
|
||||||
|
=Y9TC
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -293,7 +293,7 @@ def main():
|
|||||||
received_offers = read_json_api(args.port, 'offers', {'active': 'active', 'include_sent': False, 'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
|
received_offers = read_json_api(args.port, 'offers', {'active': 'active', 'include_sent': False, 'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
|
||||||
print('received_offers', received_offers)
|
print('received_offers', received_offers)
|
||||||
|
|
||||||
TODO - adjust rates based on extisting offers
|
TODO - adjust rates based on existing offers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rates = read_json_api('rates', {'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
|
rates = read_json_api('rates', {'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
|
||||||
@@ -326,6 +326,8 @@ def main():
|
|||||||
if args.debug:
|
if args.debug:
|
||||||
print('offer data {}'.format(offer_data))
|
print('offer data {}'.format(offer_data))
|
||||||
new_offer = read_json_api('offers/new', offer_data)
|
new_offer = read_json_api('offers/new', offer_data)
|
||||||
|
if 'error' in new_offer:
|
||||||
|
raise ValueError('Server failed to create offer: {}'.format(new_offer['error']))
|
||||||
print('New offer: {}'.format(new_offer['offer_id']))
|
print('New offer: {}'.format(new_offer['offer_id']))
|
||||||
if 'offers' not in script_state:
|
if 'offers' not in script_state:
|
||||||
script_state['offers'] = {}
|
script_state['offers'] = {}
|
||||||
@@ -516,6 +518,8 @@ def main():
|
|||||||
if args.debug:
|
if args.debug:
|
||||||
print('Creating bid: {}'.format(bid_data))
|
print('Creating bid: {}'.format(bid_data))
|
||||||
new_bid = read_json_api('bids/new', bid_data)
|
new_bid = read_json_api('bids/new', bid_data)
|
||||||
|
if 'error' in new_bid:
|
||||||
|
raise ValueError('Server failed to create bid: {}'.format(new_bid['error']))
|
||||||
print('New bid: {} on offer {}'.format(new_bid['bid_id'], offer['offer_id']))
|
print('New bid: {} on offer {}'.format(new_bid['bid_id'], offer['offer_id']))
|
||||||
bid_id = new_bid['bid_id']
|
bid_id = new_bid['bid_id']
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_P
|
|||||||
fp.write('wallet=wallet.dat\n')
|
fp.write('wallet=wallet.dat\n')
|
||||||
fp.write('findpeers=0\n')
|
fp.write('findpeers=0\n')
|
||||||
|
|
||||||
if base_p2p_port == BASE_PORT: # Particl
|
if base_p2p_port == BTC_BASE_PORT:
|
||||||
|
fp.write('deprecatedrpc=create_bdb\n')
|
||||||
|
elif base_p2p_port == BASE_PORT: # Particl
|
||||||
fp.write('zmqpubsmsg=tcp://127.0.0.1:{}\n'.format(BASE_ZMQ_PORT + node_id))
|
fp.write('zmqpubsmsg=tcp://127.0.0.1:{}\n'.format(BASE_ZMQ_PORT + node_id))
|
||||||
# minstakeinterval=5 # Using walletsettings stakelimit instead
|
# minstakeinterval=5 # Using walletsettings stakelimit instead
|
||||||
fp.write('stakethreadconddelayms=1000\n')
|
fp.write('stakethreadconddelayms=1000\n')
|
||||||
@@ -228,6 +230,14 @@ def wait_for_none_active(delay_event, port, wait_for=30):
|
|||||||
raise ValueError('wait_for_none_active timed out.')
|
raise ValueError('wait_for_none_active timed out.')
|
||||||
|
|
||||||
|
|
||||||
|
def abandon_all_swaps(delay_event, swap_client) -> None:
|
||||||
|
logging.info('abandon_all_swaps')
|
||||||
|
for bid in swap_client.listBids(sent=True):
|
||||||
|
swap_client.abandonBid(bid[2])
|
||||||
|
for bid in swap_client.listBids(sent=False):
|
||||||
|
swap_client.abandonBid(bid[2])
|
||||||
|
|
||||||
|
|
||||||
def waitForNumOffers(delay_event, port, offers, wait_for=20):
|
def waitForNumOffers(delay_event, port, offers, wait_for=20):
|
||||||
for i in range(wait_for):
|
for i in range(wait_for):
|
||||||
if delay_event.is_set():
|
if delay_event.is_set():
|
||||||
@@ -371,20 +381,25 @@ def compare_bid_states(states, expect_states, exact_match=True):
|
|||||||
if states[i][1] == 'Bid Delaying':
|
if states[i][1] == 'Bid Delaying':
|
||||||
del states[i]
|
del states[i]
|
||||||
|
|
||||||
if exact_match:
|
try:
|
||||||
assert (len(states) == len(expect_states))
|
if exact_match:
|
||||||
else:
|
assert (len(states) == len(expect_states))
|
||||||
assert (len(states) >= len(expect_states))
|
else:
|
||||||
|
assert (len(states) >= len(expect_states))
|
||||||
|
|
||||||
for i in range(len(expect_states)):
|
for i in range(len(expect_states)):
|
||||||
s = states[i]
|
s = states[i]
|
||||||
if s[1] != expect_states[i]:
|
if s[1] != expect_states[i]:
|
||||||
if 'Bid ' + expect_states[i] == s[1]:
|
if 'Bid ' + expect_states[i] == s[1]:
|
||||||
logging.warning(f'Expected state {expect_states[i]} not an exact match to {s[1]}.')
|
logging.warning(f'Expected state {expect_states[i]} not an exact match to {s[1]}.')
|
||||||
continue
|
continue
|
||||||
if [s[0], expect_states[i]] in states:
|
if [s[0], expect_states[i]] in states:
|
||||||
logging.warning(f'Expected state {expect_states[i]} found out of order at the same time as {s[1]}.')
|
logging.warning(f'Expected state {expect_states[i]} found out of order at the same time as {s[1]}.')
|
||||||
continue
|
continue
|
||||||
raise ValueError(f'Expected state {expect_states[i]}, found {s[1]}')
|
raise ValueError(f'Expected state {expect_states[i]}, found {s[1]}')
|
||||||
assert (s[1] == expect_states[i])
|
assert (s[1] == expect_states[i])
|
||||||
|
except Exception as e:
|
||||||
|
logging.info('Expecting states: {}'.format(json.dumps(expect_states, indent=4)))
|
||||||
|
logging.info('Have states: {}'.format(json.dumps(states, indent=4)))
|
||||||
|
raise e
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ 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))
|
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))
|
||||||
|
|
||||||
|
|
||||||
XMR_BASE_P2P_PORT = 17792
|
XMR_BASE_P2P_PORT = 17792
|
||||||
XMR_BASE_RPC_PORT = 29798
|
XMR_BASE_RPC_PORT = 29798
|
||||||
XMR_BASE_WALLET_RPC_PORT = 29998
|
XMR_BASE_WALLET_RPC_PORT = 29998
|
||||||
@@ -88,6 +93,7 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
|
|||||||
os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE)
|
os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE)
|
||||||
os.environ['BTC_RPC_PORT'] = str(BITCOIN_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['LTC_RPC_PORT'] = str(LITECOIN_RPC_PORT_BASE)
|
||||||
|
os.environ['FIRO_RPC_PORT'] = str(FIRO_RPC_PORT_BASE)
|
||||||
|
|
||||||
os.environ['XMR_RPC_USER'] = 'xmr_user'
|
os.environ['XMR_RPC_USER'] = 'xmr_user'
|
||||||
os.environ['XMR_RPC_PWD'] = 'xmr_pwd'
|
os.environ['XMR_RPC_PWD'] = 'xmr_pwd'
|
||||||
@@ -234,6 +240,33 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
|
|||||||
for opt in EXTRA_CONFIG_JSON.get('pivx{}'.format(node_id), []):
|
for opt in EXTRA_CONFIG_JSON.get('pivx{}'.format(node_id), []):
|
||||||
fp.write(opt + '\n')
|
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 'monero' in coins_array:
|
if 'monero' in coins_array:
|
||||||
with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
|
with open(os.path.join(datadir_path, 'monero', 'monerod.conf'), 'a') as fp:
|
||||||
fp.write('p2p-bind-ip=127.0.0.1\n')
|
fp.write('p2p-bind-ip=127.0.0.1\n')
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -83,6 +83,11 @@ BTC_NODE = 4
|
|||||||
delay_event = threading.Event()
|
delay_event = threading.Event()
|
||||||
stop_test = False
|
stop_test = False
|
||||||
|
|
||||||
|
DASH_BINDIR = os.path.expanduser(os.getenv('DASH_BINDIR', os.path.join(cfg.DEFAULT_TEST_BINDIR, 'dash')))
|
||||||
|
DASHD = os.getenv('DASHD', 'dashd' + cfg.bin_suffix)
|
||||||
|
DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + cfg.bin_suffix)
|
||||||
|
DASH_TX = os.getenv('DASH_TX', 'dash-tx' + cfg.bin_suffix)
|
||||||
|
|
||||||
|
|
||||||
def prepareOtherDir(datadir, nodeId, conf_file='dash.conf'):
|
def prepareOtherDir(datadir, nodeId, conf_file='dash.conf'):
|
||||||
node_dir = os.path.join(datadir, str(nodeId))
|
node_dir = os.path.join(datadir, str(nodeId))
|
||||||
@@ -180,7 +185,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||||||
'manage_daemon': False,
|
'manage_daemon': False,
|
||||||
'rpcport': BASE_RPC_PORT + DASH_NODE,
|
'rpcport': BASE_RPC_PORT + DASH_NODE,
|
||||||
'datadir': dashdatadir,
|
'datadir': dashdatadir,
|
||||||
'bindir': cfg.DASH_BINDIR,
|
'bindir': DASH_BINDIR,
|
||||||
'use_csv': True,
|
'use_csv': True,
|
||||||
'use_segwit': False,
|
'use_segwit': False,
|
||||||
},
|
},
|
||||||
@@ -219,7 +224,7 @@ def btcRpc(cmd):
|
|||||||
|
|
||||||
|
|
||||||
def dashRpc(cmd, wallet=None):
|
def dashRpc(cmd, wallet=None):
|
||||||
return callrpc_cli(cfg.DASH_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(DASH_NODE)), 'regtest', cmd, cfg.DASH_CLI, wallet=wallet)
|
return callrpc_cli(DASH_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(DASH_NODE)), 'regtest', cmd, DASH_CLI, wallet=wallet)
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
@@ -299,12 +304,12 @@ class Test(unittest.TestCase):
|
|||||||
'''
|
'''
|
||||||
dash-wallet does not seem to create valid wallet files.
|
dash-wallet does not seem to create valid wallet files.
|
||||||
|
|
||||||
if os.path.exists(os.path.join(cfg.DASH_BINDIR, 'dash-wallet')):
|
if os.path.exists(os.path.join(DASH_BINDIR, 'dash-wallet')):
|
||||||
logging.info('Creating DASH wallet.')
|
logging.info('Creating DASH wallet.')
|
||||||
callrpc_cli(cfg.DASH_BINDIR, dash_data_dir, 'regtest', '-wallet=wallet.dat create', 'dash-wallet')
|
callrpc_cli(DASH_BINDIR, dash_data_dir, 'regtest', '-wallet=wallet.dat create', 'dash-wallet')
|
||||||
'''
|
'''
|
||||||
cls.daemons.append(startDaemon(dash_data_dir, cfg.DASH_BINDIR, cfg.DASHD))
|
cls.daemons.append(startDaemon(dash_data_dir, DASH_BINDIR, DASHD))
|
||||||
logging.info('Started %s %d', cfg.DASHD, cls.daemons[-1].pid)
|
logging.info('Started %s %d', DASHD, cls.daemons[-1].pid)
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
||||||
@@ -667,7 +672,7 @@ class Test(unittest.TestCase):
|
|||||||
# Verify expected inputs were used
|
# Verify expected inputs were used
|
||||||
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
||||||
assert (bid.initiate_tx)
|
assert (bid.initiate_tx)
|
||||||
wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
|
wtx = ci_from.rpc_wallet('gettransaction', [bid.initiate_tx.txid.hex(),])
|
||||||
itx_after = ci_from.describeTx(wtx['hex'])
|
itx_after = ci_from.describeTx(wtx['hex'])
|
||||||
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
|
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
|
||||||
for i, txin in enumerate(itx_decoded['vin']):
|
for i, txin in enumerate(itx_decoded['vin']):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ import unittest
|
|||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
Coins,
|
Coins,
|
||||||
TxStates,
|
|
||||||
SwapTypes,
|
SwapTypes,
|
||||||
BidStates,
|
BidStates,
|
||||||
DebugTypes,
|
DebugTypes,
|
||||||
@@ -22,7 +21,6 @@ from basicswap.basicswap_util import (
|
|||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
)
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
COIN,
|
|
||||||
make_int,
|
make_int,
|
||||||
format_amount,
|
format_amount,
|
||||||
)
|
)
|
||||||
@@ -39,14 +37,11 @@ from tests.basicswap.common import (
|
|||||||
make_rpc_func,
|
make_rpc_func,
|
||||||
TEST_HTTP_PORT,
|
TEST_HTTP_PORT,
|
||||||
wait_for_offer,
|
wait_for_offer,
|
||||||
wait_for_balance,
|
|
||||||
wait_for_unspent,
|
|
||||||
wait_for_in_progress,
|
|
||||||
wait_for_bid_tx_state,
|
|
||||||
)
|
)
|
||||||
from basicswap.contrib.test_framework.messages import (
|
from basicswap.interface.contrib.firo_test_framework.mininode import (
|
||||||
FromHex,
|
FromHex,
|
||||||
CTransaction,
|
CTransaction,
|
||||||
|
set_regtest,
|
||||||
)
|
)
|
||||||
from bin.basicswap_run import startDaemon
|
from bin.basicswap_run import startDaemon
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||||
@@ -54,13 +49,18 @@ from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc
|
|||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
FIRO_BINDIR = os.path.expanduser(os.getenv('FIRO_BINDIR', os.path.join(cfg.DEFAULT_TEST_BINDIR, 'firo')))
|
||||||
|
FIROD = os.getenv('FIROD', 'firod' + cfg.bin_suffix)
|
||||||
|
FIRO_CLI = os.getenv('FIRO_CLI', 'firo-cli' + cfg.bin_suffix)
|
||||||
|
FIRO_TX = os.getenv('FIRO_TX', 'firo-tx' + cfg.bin_suffix)
|
||||||
|
|
||||||
FIRO_BASE_PORT = 34832
|
FIRO_BASE_PORT = 34832
|
||||||
FIRO_BASE_RPC_PORT = 35832
|
FIRO_BASE_RPC_PORT = 35832
|
||||||
FIRO_BASE_ZMQ_PORT = 36832
|
FIRO_BASE_ZMQ_PORT = 36832
|
||||||
|
|
||||||
|
|
||||||
def firoCli(cmd, node_id=0):
|
def firoCli(cmd, node_id=0):
|
||||||
return callrpc_cli(cfg.FIRO_BINDIR, os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(node_id)), 'regtest', cmd, cfg.FIRO_CLI)
|
return callrpc_cli(FIRO_BINDIR, os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(node_id)), 'regtest', cmd, FIRO_CLI)
|
||||||
|
|
||||||
|
|
||||||
def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3):
|
def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3):
|
||||||
@@ -92,11 +92,13 @@ def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_
|
|||||||
fp.write('fallbackfee=0.01\n')
|
fp.write('fallbackfee=0.01\n')
|
||||||
fp.write('acceptnonstdtxn=0\n')
|
fp.write('acceptnonstdtxn=0\n')
|
||||||
|
|
||||||
|
'''
|
||||||
# qa/rpc-tests/segwit.py
|
# qa/rpc-tests/segwit.py
|
||||||
fp.write('prematurewitness=1\n')
|
fp.write('prematurewitness=1\n')
|
||||||
fp.write('walletprematurewitness=1\n')
|
fp.write('walletprematurewitness=1\n')
|
||||||
fp.write('blockversion=4\n')
|
fp.write('blockversion=4\n')
|
||||||
fp.write('promiscuousmempoolflags=517\n')
|
fp.write('promiscuousmempoolflags=517\n')
|
||||||
|
'''
|
||||||
|
|
||||||
for i in range(0, num_nodes):
|
for i in range(0, num_nodes):
|
||||||
if node_id == i:
|
if node_id == i:
|
||||||
@@ -117,7 +119,7 @@ class Test(BaseTest):
|
|||||||
test_atomic = True
|
test_atomic = True
|
||||||
test_xmr = False
|
test_xmr = False
|
||||||
|
|
||||||
# Particl node mnemonics are set in test/basicswap/mnemonics.py
|
# Particl node mnemonics are test_xmr.py, node 2 is set randomly
|
||||||
firo_seeds = [
|
firo_seeds = [
|
||||||
'd90b7ed1be614e1c172653aee1f3b6230f43b7fa99cf07fa984a17966ad81de7',
|
'd90b7ed1be614e1c172653aee1f3b6230f43b7fa99cf07fa984a17966ad81de7',
|
||||||
'6c81d6d74ba33a0db9e41518c2b6789fbe938e98018a4597dac661cfc5f2dfc1',
|
'6c81d6d74ba33a0db9e41518c2b6789fbe938e98018a4597dac661cfc5f2dfc1',
|
||||||
@@ -126,15 +128,16 @@ class Test(BaseTest):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraDataDir(cls, i):
|
def prepareExtraDataDir(cls, i):
|
||||||
|
extra_opts = []
|
||||||
if not cls.restore_instance:
|
if not cls.restore_instance:
|
||||||
seed_hex = cls.firo_seeds[i]
|
seed_hex = cls.firo_seeds[i]
|
||||||
extra_opts = [f'-hdseed={seed_hex}', ]
|
extra_opts.append(f'-hdseed={seed_hex}')
|
||||||
data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'firo.conf', 'firo_', base_p2p_port=FIRO_BASE_PORT, base_rpc_port=FIRO_BASE_RPC_PORT)
|
data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'firo.conf', 'firo_', base_p2p_port=FIRO_BASE_PORT, base_rpc_port=FIRO_BASE_RPC_PORT)
|
||||||
if os.path.exists(os.path.join(cfg.FIRO_BINDIR, 'firo-wallet')):
|
if os.path.exists(os.path.join(FIRO_BINDIR, 'firo-wallet')):
|
||||||
callrpc_cli(cfg.FIRO_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'firo-wallet')
|
callrpc_cli(FIRO_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'firo-wallet')
|
||||||
|
|
||||||
cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), cfg.FIRO_BINDIR, cfg.FIROD, opts=extra_opts))
|
cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), FIRO_BINDIR, FIROD, opts=extra_opts))
|
||||||
logging.info('Started %s %d', cfg.FIROD, cls.firo_daemons[-1].pid)
|
logging.info('Started %s %d', FIROD, cls.firo_daemons[-1].pid)
|
||||||
|
|
||||||
waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT))
|
waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT))
|
||||||
|
|
||||||
@@ -144,6 +147,10 @@ class Test(BaseTest):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
|
|
||||||
|
# Raise MTP_SWITCH_TIME and PP_SWITCH_TIME in mininode.py
|
||||||
|
set_regtest()
|
||||||
|
|
||||||
if cls.restore_instance:
|
if cls.restore_instance:
|
||||||
void_block_rewards_pubkey = cls.getRandomPubkey()
|
void_block_rewards_pubkey = cls.getRandomPubkey()
|
||||||
cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey)
|
cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey)
|
||||||
@@ -162,7 +169,8 @@ class Test(BaseTest):
|
|||||||
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
|
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
|
||||||
void_block_rewards_pubkey = cls.getRandomPubkey()
|
void_block_rewards_pubkey = cls.getRandomPubkey()
|
||||||
cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey)
|
cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey)
|
||||||
num_blocks = 100
|
chain_height = callnoderpc(0, 'getblockcount', base_rpc_port=FIRO_BASE_RPC_PORT)
|
||||||
|
num_blocks = 1352 - chain_height # Activate CTLV (bip65)
|
||||||
logging.info('Mining %d Firo blocks to %s', num_blocks, cls.firo_addr)
|
logging.info('Mining %d Firo blocks to %s', num_blocks, cls.firo_addr)
|
||||||
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
|
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
|
||||||
|
|
||||||
@@ -182,8 +190,8 @@ class Test(BaseTest):
|
|||||||
'rpcuser': 'test' + str(node_id),
|
'rpcuser': 'test' + str(node_id),
|
||||||
'rpcpassword': 'test_pass' + str(node_id),
|
'rpcpassword': 'test_pass' + str(node_id),
|
||||||
'datadir': os.path.join(datadir, 'firo_' + str(node_id)),
|
'datadir': os.path.join(datadir, 'firo_' + str(node_id)),
|
||||||
'bindir': cfg.FIRO_BINDIR,
|
'bindir': FIRO_BINDIR,
|
||||||
'use_csv': True,
|
'use_csv': False,
|
||||||
'use_segwit': False,
|
'use_segwit': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +209,9 @@ class Test(BaseTest):
|
|||||||
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
|
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
|
||||||
return callnoderpc(node_id, method, params, wallet, base_rpc_port=FIRO_BASE_RPC_PORT)
|
return callnoderpc(node_id, method, params, wallet, base_rpc_port=FIRO_BASE_RPC_PORT)
|
||||||
|
|
||||||
|
def mineBlock(self, num_blocks: int = 1):
|
||||||
|
self.callnoderpc('generatetoaddress', [num_blocks, self.firo_addr])
|
||||||
|
|
||||||
def test_001_firo(self):
|
def test_001_firo(self):
|
||||||
logging.info('---------- Test {} segwit'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} segwit'.format(self.test_coin_from.name))
|
||||||
|
|
||||||
@@ -210,6 +221,7 @@ class Test(BaseTest):
|
|||||||
|
|
||||||
Txns spending segwit utxos don't get mined.
|
Txns spending segwit utxos don't get mined.
|
||||||
'''
|
'''
|
||||||
|
return
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
@@ -245,7 +257,8 @@ class Test(BaseTest):
|
|||||||
decoded_tx = CTransaction()
|
decoded_tx = CTransaction()
|
||||||
decoded_tx = FromHex(decoded_tx, tx_funded)
|
decoded_tx = FromHex(decoded_tx, tx_funded)
|
||||||
decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_witness_info['hex'])
|
decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_witness_info['hex'])
|
||||||
txid_with_scriptsig = decoded_tx.rehash()
|
decoded_tx.rehash()
|
||||||
|
txid_with_scriptsig = decoded_tx.hash
|
||||||
|
|
||||||
tx_funded_decoded = firoCli(f'decoderawtransaction {tx_funded}')
|
tx_funded_decoded = firoCli(f'decoderawtransaction {tx_funded}')
|
||||||
tx_signed_decoded = firoCli(f'decoderawtransaction {tx_signed}')
|
tx_signed_decoded = firoCli(f'decoderawtransaction {tx_signed}')
|
||||||
@@ -262,148 +275,45 @@ class Test(BaseTest):
|
|||||||
assert ('490ba1e2c3894d5534c467141ee3cdf77292c362' == ci.getWalletSeedID())
|
assert ('490ba1e2c3894d5534c467141ee3cdf77292c362' == ci.getWalletSeedID())
|
||||||
assert swap_client.checkWalletSeed(self.test_coin_from) is True
|
assert swap_client.checkWalletSeed(self.test_coin_from) is True
|
||||||
|
|
||||||
def test_02_part_coin(self):
|
def test_008_gettxout(self):
|
||||||
logging.info('---------- Test PART to {}'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} gettxout'.format(self.test_coin_from.name))
|
||||||
if not self.test_atomic:
|
|
||||||
logging.warning('Skipping test')
|
|
||||||
return
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(Coins.PART, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
|
swap_client = self.swap_clients[0]
|
||||||
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
# First address sometimes has a balance already
|
||||||
offer = swap_clients[1].getOffer(offer_id)
|
addr_plain = self.callnoderpc('getnewaddress', ['gettxout test',])
|
||||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
|
addr_plain1 = self.callnoderpc('getnewaddress', ['gettxout test 1',])
|
||||||
swap_clients[0].acceptBid(bid_id)
|
|
||||||
|
|
||||||
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
|
txid = self.callnoderpc('sendtoaddress', [addr_plain1, 1.0])
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
assert len(txid) == 64
|
||||||
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)
|
self.mineBlock()
|
||||||
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_03_coin_part(self):
|
unspents = self.callnoderpc('listunspent', [0, 999999999, [addr_plain1,]])
|
||||||
logging.info('---------- Test {} to PART'.format(self.test_coin_from.name))
|
assert (len(unspents) == 1)
|
||||||
swap_clients = self.swap_clients
|
|
||||||
|
|
||||||
offer_id = swap_clients[1].postOffer(self.test_coin_from, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
|
utxo = unspents[0]
|
||||||
|
txout = self.callnoderpc('gettxout', [utxo['txid'], utxo['vout']])
|
||||||
|
assert (addr_plain1 in txout['scriptPubKey']['addresses'])
|
||||||
|
# Spend
|
||||||
|
addr_plain2 = self.callnoderpc('getnewaddress', ['gettxout test 2',])
|
||||||
|
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': utxo['txid'], 'vout': utxo['vout']}], {addr_plain2: 0.99}])
|
||||||
|
tx_signed = self.callnoderpc('signrawtransaction', [tx_funded,])['hex']
|
||||||
|
self.callnoderpc('sendrawtransaction', [tx_signed,])
|
||||||
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
# utxo should be unavailable when spent in the mempool
|
||||||
offer = swap_clients[0].getOffer(offer_id)
|
txout = self.callnoderpc('gettxout', [utxo['txid'], utxo['vout']])
|
||||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
assert (txout is None)
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id)
|
self.mineBlock()
|
||||||
swap_clients[1].acceptBid(bid_id)
|
|
||||||
|
|
||||||
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
|
ci = swap_client.ci(Coins.FIRO)
|
||||||
|
require_amount: int = ci.make_int(1)
|
||||||
|
funds_proof = ci.getProofOfFunds(require_amount, 'test'.encode('utf-8'))
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
amount_proved = ci.verifyProofOfFunds(funds_proof[0], funds_proof[1], funds_proof[2], 'test'.encode('utf-8'))
|
||||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
assert (amount_proved >= require_amount)
|
||||||
|
|
||||||
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_04_coin_btc(self):
|
|
||||||
logging.info('---------- Test {} to BTC'.format(self.test_coin_from.name))
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * 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_0bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
|
||||||
|
|
||||||
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_05_refund(self):
|
|
||||||
# Seller submits initiate txn, buyer doesn't respond
|
|
||||||
logging.info('---------- Test refund, {} to BTC'.format(self.test_coin_from.name))
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST,
|
|
||||||
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10)
|
|
||||||
|
|
||||||
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[1].abandonBid(bid_id)
|
|
||||||
swap_clients[0].acceptBid(bid_id)
|
|
||||||
|
|
||||||
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.BID_ABANDONED, 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_06_self_bid(self):
|
|
||||||
logging.info('---------- Test same client, BTC to {}'.format(self.test_coin_from.name))
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
|
|
||||||
js_0_before = read_json_api(1800)
|
|
||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
|
|
||||||
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
|
||||||
offer = swap_clients[0].getOffer(offer_id)
|
|
||||||
bid_id = swap_clients[0].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_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60)
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
|
||||||
|
|
||||||
js_0 = read_json_api(1800)
|
|
||||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
|
||||||
assert (js_0['num_recv_bids'] == js_0_before['num_recv_bids'] + 1 and js_0['num_sent_bids'] == js_0_before['num_sent_bids'] + 1)
|
|
||||||
|
|
||||||
def test_07_error(self):
|
|
||||||
logging.info('---------- Test error, BTC to {}, set fee above bid value'.format(self.test_coin_from.name))
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
|
|
||||||
js_0_before = read_json_api(1800)
|
|
||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST)
|
|
||||||
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
|
||||||
offer = swap_clients[0].getOffer(offer_id)
|
|
||||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
|
|
||||||
swap_clients[0].acceptBid(bid_id)
|
|
||||||
try:
|
|
||||||
swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate'] = 10.0
|
|
||||||
swap_clients[0].getChainClientSettings(Coins.FIRO)['override_feerate'] = 10.0
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60)
|
|
||||||
swap_clients[0].abandonBid(bid_id)
|
|
||||||
finally:
|
|
||||||
del swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate']
|
|
||||||
del swap_clients[0].getChainClientSettings(Coins.FIRO)['override_feerate']
|
|
||||||
|
|
||||||
def test_08_wallet(self):
|
def test_08_wallet(self):
|
||||||
logging.info('---------- Test {} wallet'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} wallet'.format(self.test_coin_from.name))
|
||||||
@@ -428,85 +338,6 @@ class Test(BaseTest):
|
|||||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/{}/createutxo'.format(self.test_coin_from.name.lower()), post_json)
|
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/{}/createutxo'.format(self.test_coin_from.name.lower()), post_json)
|
||||||
assert (len(json_rv['txid']) == 64)
|
assert (len(json_rv['txid']) == 64)
|
||||||
|
|
||||||
def ensure_balance(self, coin_type, node_id, amount):
|
|
||||||
tla = coin_type.name
|
|
||||||
js_w = read_json_api(1800 + node_id, 'wallets')
|
|
||||||
if float(js_w[tla]['balance']) < amount:
|
|
||||||
post_json = {
|
|
||||||
'value': amount,
|
|
||||||
'address': js_w[tla]['deposit_address'],
|
|
||||||
'subfee': False,
|
|
||||||
}
|
|
||||||
json_rv = read_json_api(1800, 'wallets/{}/withdraw'.format(tla.lower()), post_json)
|
|
||||||
assert (len(json_rv['txid']) == 64)
|
|
||||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:{}/json/wallets/{}'.format(1800 + node_id, tla.lower()), 'balance', amount)
|
|
||||||
|
|
||||||
def test_10_prefunded_itx(self):
|
|
||||||
logging.info('---------- Test prefunded itx offer')
|
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
coin_from = Coins.FIRO
|
|
||||||
coin_to = Coins.BTC
|
|
||||||
swap_type = SwapTypes.SELLER_FIRST
|
|
||||||
ci_from = swap_clients[2].ci(coin_from)
|
|
||||||
ci_to = swap_clients[1].ci(coin_to)
|
|
||||||
tla_from = coin_from.name
|
|
||||||
|
|
||||||
# Prepare balance
|
|
||||||
self.ensure_balance(coin_from, 2, 10.0)
|
|
||||||
self.ensure_balance(coin_to, 1, 100.0)
|
|
||||||
|
|
||||||
js_w2 = read_json_api(1802, 'wallets')
|
|
||||||
post_json = {
|
|
||||||
'value': 10.0,
|
|
||||||
'address': read_json_api(1802, 'wallets/{}/nextdepositaddr'.format(tla_from.lower())),
|
|
||||||
'subfee': True,
|
|
||||||
}
|
|
||||||
json_rv = read_json_api(1802, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json)
|
|
||||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 9.0)
|
|
||||||
assert (len(json_rv['txid']) == 64)
|
|
||||||
|
|
||||||
# Create prefunded ITX
|
|
||||||
pi = swap_clients[2].pi(SwapTypes.XMR_SWAP)
|
|
||||||
js_w2 = read_json_api(1802, 'wallets')
|
|
||||||
swap_value = 10.0
|
|
||||||
if float(js_w2[tla_from]['balance']) < swap_value:
|
|
||||||
swap_value = js_w2[tla_from]['balance']
|
|
||||||
swap_value = ci_from.make_int(swap_value)
|
|
||||||
assert (swap_value > ci_from.make_int(9))
|
|
||||||
|
|
||||||
itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True)
|
|
||||||
itx_decoded = ci_from.describeTx(itx.hex())
|
|
||||||
n = pi.findMockVout(ci_from, itx_decoded)
|
|
||||||
value_after_subfee = ci_from.make_int(itx_decoded['vout'][n]['value'])
|
|
||||||
assert (value_after_subfee < swap_value)
|
|
||||||
swap_value = value_after_subfee
|
|
||||||
wait_for_unspent(test_delay_event, ci_from, swap_value)
|
|
||||||
|
|
||||||
extra_options = {'prefunded_itx': itx}
|
|
||||||
rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1)
|
|
||||||
offer_id = swap_clients[2].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type, extra_options=extra_options)
|
|
||||||
|
|
||||||
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[2], bid_id, BidStates.BID_RECEIVED)
|
|
||||||
swap_clients[2].acceptBid(bid_id)
|
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
|
||||||
|
|
||||||
# Verify expected inputs were used
|
|
||||||
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
|
||||||
assert (bid.initiate_tx)
|
|
||||||
wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
|
|
||||||
itx_after = ci_from.describeTx(wtx['hex'])
|
|
||||||
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
|
|
||||||
for i, txin in enumerate(itx_decoded['vin']):
|
|
||||||
assert (txin['txid'] == itx_after['vin'][i]['txid'])
|
|
||||||
assert (txin['vout'] == itx_after['vin'][i]['vout'])
|
|
||||||
|
|
||||||
def test_11_xmrswap_to(self):
|
def test_11_xmrswap_to(self):
|
||||||
logging.info('---------- Test xmr swap protocol to')
|
logging.info('---------- Test xmr swap protocol to')
|
||||||
|
|
||||||
@@ -558,6 +389,30 @@ class Test(BaseTest):
|
|||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||||
|
|
||||||
|
def test_13_adsswap_reverse(self):
|
||||||
|
logging.info('---------- Test ads swap protocol reverse')
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
coin_from = Coins.FIRO
|
||||||
|
coin_to = Coins.BTC
|
||||||
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
|
ci_from = swap_clients[0].ci(coin_from)
|
||||||
|
ci_to = swap_clients[1].ci(coin_to)
|
||||||
|
|
||||||
|
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||||
|
rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1)
|
||||||
|
offer_id = swap_clients[0].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type)
|
||||||
|
|
||||||
|
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, BidStates.BID_RECEIVED)
|
||||||
|
swap_clients[0].acceptBid(bid_id)
|
||||||
|
|
||||||
|
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||||
|
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||||
|
|
||||||
def test_101_full_swap(self):
|
def test_101_full_swap(self):
|
||||||
logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name))
|
||||||
if not self.test_xmr:
|
if not self.test_xmr:
|
||||||
|
|||||||
1030
tests/basicswap/extended/test_nav.py
Normal file
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -84,6 +84,11 @@ BTC_NODE = 4
|
|||||||
delay_event = threading.Event()
|
delay_event = threading.Event()
|
||||||
stop_test = False
|
stop_test = False
|
||||||
|
|
||||||
|
PIVX_BINDIR = os.path.expanduser(os.getenv('PIVX_BINDIR', os.path.join(cfg.DEFAULT_TEST_BINDIR, 'pivx')))
|
||||||
|
PIVXD = os.getenv('PIVXD', 'pivxd' + cfg.bin_suffix)
|
||||||
|
PIVX_CLI = os.getenv('PIVX_CLI', 'pivx-cli' + cfg.bin_suffix)
|
||||||
|
PIVX_TX = os.getenv('PIVX_TX', 'pivx-tx' + cfg.bin_suffix)
|
||||||
|
|
||||||
|
|
||||||
def prepareOtherDir(datadir, nodeId, conf_file='pivx.conf'):
|
def prepareOtherDir(datadir, nodeId, conf_file='pivx.conf'):
|
||||||
node_dir = os.path.join(datadir, str(nodeId))
|
node_dir = os.path.join(datadir, str(nodeId))
|
||||||
@@ -186,7 +191,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||||||
'manage_daemon': False,
|
'manage_daemon': False,
|
||||||
'rpcport': BASE_RPC_PORT + PIVX_NODE,
|
'rpcport': BASE_RPC_PORT + PIVX_NODE,
|
||||||
'datadir': pivxdatadir,
|
'datadir': pivxdatadir,
|
||||||
'bindir': cfg.PIVX_BINDIR,
|
'bindir': PIVX_BINDIR,
|
||||||
'use_csv': False,
|
'use_csv': False,
|
||||||
'use_segwit': False,
|
'use_segwit': False,
|
||||||
},
|
},
|
||||||
@@ -225,7 +230,7 @@ def btcRpc(cmd):
|
|||||||
|
|
||||||
|
|
||||||
def pivxRpc(cmd):
|
def pivxRpc(cmd):
|
||||||
return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), 'regtest', cmd, cfg.PIVX_CLI)
|
return callrpc_cli(PIVX_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), 'regtest', cmd, PIVX_CLI)
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
@@ -306,8 +311,8 @@ class Test(unittest.TestCase):
|
|||||||
callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
|
callrpc_cli(cfg.BITCOIN_BINDIR, btc_data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
|
||||||
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
||||||
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
|
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
|
||||||
cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), cfg.PIVX_BINDIR, cfg.PIVXD))
|
cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), PIVX_BINDIR, PIVXD))
|
||||||
logging.info('Started %s %d', cfg.PIVXD, cls.daemons[-1].pid)
|
logging.info('Started %s %d', PIVXD, cls.daemons[-1].pid)
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
||||||
@@ -672,7 +677,7 @@ class Test(unittest.TestCase):
|
|||||||
# Verify expected inputs were used
|
# Verify expected inputs were used
|
||||||
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
||||||
assert (bid.initiate_tx)
|
assert (bid.initiate_tx)
|
||||||
wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
|
wtx = ci_from.rpc('gettransaction', [bid.initiate_tx.txid.hex(),])
|
||||||
itx_after = ci_from.describeTx(wtx['hex'])
|
itx_after = ci_from.describeTx(wtx['hex'])
|
||||||
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
|
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
|
||||||
for i, txin in enumerate(itx_decoded['vin']):
|
for i, txin in enumerate(itx_decoded['vin']):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2022 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -91,7 +91,6 @@ class Test(unittest.TestCase):
|
|||||||
prepareSystem.main()
|
prepareSystem.main()
|
||||||
|
|
||||||
self.assertEqual(cm.exception.code, 1)
|
self.assertEqual(cm.exception.code, 1)
|
||||||
logger.info('fake_stderr.getvalue() %s', fake_stderr.getvalue())
|
|
||||||
self.assertTrue('exists, exiting' in fake_stderr.getvalue())
|
self.assertTrue('exists, exiting' in fake_stderr.getvalue())
|
||||||
|
|
||||||
logger.info('Test addcoin new')
|
logger.info('Test addcoin new')
|
||||||
@@ -117,6 +116,17 @@ class Test(unittest.TestCase):
|
|||||||
with open(config_path) as fs:
|
with open(config_path) as fs:
|
||||||
settings = json.load(fs)
|
settings = json.load(fs)
|
||||||
self.assertTrue(settings['chainclients']['namecoin']['connection_type'] == 'rpc')
|
self.assertTrue(settings['chainclients']['namecoin']['connection_type'] == 'rpc')
|
||||||
|
|
||||||
|
logging.info('notorproxy')
|
||||||
|
testargs = ['basicswap-prepare', '-datadir=' + test_path_plain, '-addcoin=firo', '--usetorproxy', '--notorproxy']
|
||||||
|
with patch('sys.stderr', new=StringIO()) as fake_stderr:
|
||||||
|
with patch.object(sys, 'argv', testargs):
|
||||||
|
with self.assertRaises(SystemExit) as cm:
|
||||||
|
prepareSystem.main()
|
||||||
|
|
||||||
|
self.assertEqual(cm.exception.code, 1)
|
||||||
|
self.assertTrue('--usetorproxy and --notorproxy together' in fake_stderr.getvalue())
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
del prepareSystem
|
del prepareSystem
|
||||||
|
|
||||||
|
|||||||
@@ -553,6 +553,40 @@ class Test(unittest.TestCase):
|
|||||||
rv_stdout = result.stdout.decode().split('\n')
|
rv_stdout = result.stdout.decode().split('\n')
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def test_error_messages(self):
|
||||||
|
waitForServer(self.delay_event, UI_PORT + 0)
|
||||||
|
waitForServer(self.delay_event, UI_PORT + 1)
|
||||||
|
|
||||||
|
# Reset test
|
||||||
|
clear_offers(self.delay_event, 0)
|
||||||
|
delete_file(self.node0_statefile)
|
||||||
|
delete_file(self.node1_statefile)
|
||||||
|
wait_for_offers(self.delay_event, 1, 0)
|
||||||
|
|
||||||
|
node0_test1_config = {
|
||||||
|
'offers': [
|
||||||
|
{
|
||||||
|
'name': 'offer should fail',
|
||||||
|
'coin_from': 'Particl',
|
||||||
|
'coin_to': 'XMR',
|
||||||
|
'amount': 20,
|
||||||
|
'minrate': 0.05,
|
||||||
|
'ratetweakpercent': 50000000,
|
||||||
|
'amount_variable': True,
|
||||||
|
'address': -1,
|
||||||
|
'min_coin_from_amt': 20,
|
||||||
|
'max_coin_to_amt': -1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
with open(self.node0_configfile, 'w') as fp:
|
||||||
|
json.dump(node0_test1_config, fp, indent=4)
|
||||||
|
|
||||||
|
logging.info('Test that an offer is created')
|
||||||
|
result = subprocess.run(self.node0_args, stdout=subprocess.PIPE)
|
||||||
|
rv_stdout = result.stdout.decode().split('\n')
|
||||||
|
assert (count_lines_with(rv_stdout, 'Error: Server failed to create offer: To amount above max') == 1)
|
||||||
|
|
||||||
def test_bid_tracking(self):
|
def test_bid_tracking(self):
|
||||||
|
|
||||||
waitForServer(self.delay_event, UI_PORT + 0)
|
waitForServer(self.delay_event, UI_PORT + 0)
|
||||||
|
|||||||
@@ -132,12 +132,12 @@ class Test(TestBase):
|
|||||||
callbtcnoderpc(2, 'generatetoaddress', [num_blocks, self.btc_addr])
|
callbtcnoderpc(2, 'generatetoaddress', [num_blocks, self.btc_addr])
|
||||||
|
|
||||||
num_blocks = 431
|
num_blocks = 431
|
||||||
self.ltc_addr = callltcnoderpc(1, 'getnewaddress', ['mining_addr', 'bech32'])
|
self.ltc_addr = callltcnoderpc(1, 'getnewaddress', ['mining_addr', 'bech32'], wallet='wallet.dat')
|
||||||
logging.info('Mining %d Litecoin blocks to %s', num_blocks, self.ltc_addr)
|
logging.info('Mining %d Litecoin blocks to %s', num_blocks, self.ltc_addr)
|
||||||
callltcnoderpc(1, 'generatetoaddress', [num_blocks, self.ltc_addr])
|
callltcnoderpc(1, 'generatetoaddress', [num_blocks, self.ltc_addr])
|
||||||
|
|
||||||
mweb_addr = callltcnoderpc(1, 'getnewaddress', ['mweb_addr', 'mweb'])
|
mweb_addr = callltcnoderpc(1, 'getnewaddress', ['mweb_addr', 'mweb'], wallet='mweb')
|
||||||
callltcnoderpc(1, 'sendtoaddress', [mweb_addr, 1])
|
callltcnoderpc(1, 'sendtoaddress', [mweb_addr, 1], wallet='wallet.dat')
|
||||||
num_blocks = 69
|
num_blocks = 69
|
||||||
callltcnoderpc(1, 'generatetoaddress', [num_blocks, self.ltc_addr])
|
callltcnoderpc(1, 'generatetoaddress', [num_blocks, self.ltc_addr])
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2022 tecnovert
|
# Copyright (c) 2021-2024 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ from basicswap.rpc import (
|
|||||||
from tests.basicswap.common import (
|
from tests.basicswap.common import (
|
||||||
BASE_RPC_PORT,
|
BASE_RPC_PORT,
|
||||||
BTC_BASE_RPC_PORT,
|
BTC_BASE_RPC_PORT,
|
||||||
|
LTC_BASE_RPC_PORT,
|
||||||
)
|
)
|
||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
make_boolean,
|
make_boolean,
|
||||||
@@ -59,8 +60,9 @@ UI_PORT = 12700 + PORT_OFS
|
|||||||
|
|
||||||
PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT))
|
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))
|
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))
|
||||||
XMR_BASE_RPC_PORT = int(os.getenv('XMR_BASE_RPC_PORT', XMR_BASE_RPC_PORT))
|
XMR_BASE_RPC_PORT = int(os.getenv('XMR_BASE_RPC_PORT', XMR_BASE_RPC_PORT))
|
||||||
|
TEST_COINS_LIST = os.getenv('TEST_COINS_LIST', 'bitcoin,monero')
|
||||||
|
|
||||||
NUM_NODES = int(os.getenv('NUM_NODES', 3))
|
NUM_NODES = int(os.getenv('NUM_NODES', 3))
|
||||||
EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
|
EXTRA_CONFIG_JSON = json.loads(os.getenv('EXTRA_CONFIG_JSON', '{}'))
|
||||||
@@ -81,6 +83,11 @@ def callbtcrpc(node_id, method, params=[], wallet=None, base_rpc_port=BITCOIN_RP
|
|||||||
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
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):
|
def updateThread(cls):
|
||||||
while not cls.delay_event.is_set():
|
while not cls.delay_event.is_set():
|
||||||
try:
|
try:
|
||||||
@@ -129,7 +136,7 @@ class Test(unittest.TestCase):
|
|||||||
logging.info(f'Continuing with existing directory: {test_path}')
|
logging.info(f'Continuing with existing directory: {test_path}')
|
||||||
else:
|
else:
|
||||||
logging.info('Preparing %d nodes.', NUM_NODES)
|
logging.info('Preparing %d nodes.', NUM_NODES)
|
||||||
prepare_nodes(NUM_NODES, 'bitcoin,monero', True, {'min_sequence_lock_seconds': 60}, PORT_OFS)
|
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))
|
signal.signal(signal.SIGINT, lambda signal, frame: cls.signal_handler(cls, signal, frame))
|
||||||
|
|
||||||
@@ -167,11 +174,28 @@ class Test(unittest.TestCase):
|
|||||||
logging.info('XMR blocks: %d', callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count', auth=xmr_auth)['count'])
|
logging.info('XMR blocks: %d', callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count', auth=xmr_auth)['count'])
|
||||||
|
|
||||||
self.btc_addr = callbtcrpc(0, 'getnewaddress', ['mining_addr', 'bech32'])
|
self.btc_addr = callbtcrpc(0, 'getnewaddress', ['mining_addr', 'bech32'])
|
||||||
num_blocks = 500 # Mine enough to activate segwit
|
num_blocks: int = 500 # Mine enough to activate segwit
|
||||||
if callbtcrpc(0, 'getblockchaininfo')['blocks'] < num_blocks:
|
if callbtcrpc(0, 'getblockcount') < num_blocks:
|
||||||
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, self.btc_addr)
|
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, self.btc_addr)
|
||||||
callbtcrpc(0, 'generatetoaddress', [num_blocks, self.btc_addr])
|
callbtcrpc(0, 'generatetoaddress', [num_blocks, self.btc_addr])
|
||||||
logging.info('BTC blocks: %d', callbtcrpc(0, 'getblockchaininfo')['blocks'])
|
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
|
# Lower output split threshold for more stakeable outputs
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
|
|||||||
2
tests/basicswap/selenium/notes.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
python tests/basicswap/extended/test_xmr_persistent.py
|
||||||
|
python tests/basicswap/selenium/test_wallets.py
|
||||||