Reformat with black.

This commit is contained in:
tecnovert
2024-11-15 18:52:19 +02:00
parent 6be9a14335
commit 732c87b013
66 changed files with 16755 additions and 9343 deletions

View File

@@ -6,7 +6,7 @@ lint_task:
- pip install flake8 codespell - pip install flake8 codespell
script: script:
- flake8 --version - flake8 --version
- PYTHONWARNINGS="ignore" flake8 --ignore=E203,E501,F841,W503,E702,E131 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py - flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
test_task: test_task:

View File

@@ -20,7 +20,7 @@ jobs:
pip install flake8 codespell pip install flake8 codespell
- name: Running flake8 - name: Running flake8
run: | run: |
flake8 --ignore=E203,E501,F841,W503 --per-file-ignores="basicswap/interface/bch.py:E131,E702" --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
- name: Running codespell - name: Running codespell
run: | run: |
codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static

View File

@@ -35,7 +35,7 @@ def getaddrinfo_tor(*args):
class BaseApp: 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.fail_code = 0 self.fail_code = 0
@@ -47,19 +47,19 @@ class BaseApp:
self.coin_clients = {} self.coin_clients = {}
self.coin_interfaces = {} self.coin_interfaces = {}
self.mxDB = threading.Lock() self.mxDB = threading.Lock()
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.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))
self.use_tor_proxy = self.settings.get('use_tor', False) self.use_tor_proxy = self.settings.get("use_tor", False)
self.tor_proxy_host = self.settings.get('tor_proxy_host', '127.0.0.1') self.tor_proxy_host = self.settings.get("tor_proxy_host", "127.0.0.1")
self.tor_proxy_port = self.settings.get('tor_proxy_port', 9050) self.tor_proxy_port = self.settings.get("tor_proxy_port", 9050)
self.tor_control_password = self.settings.get('tor_control_password', None) self.tor_control_password = self.settings.get("tor_control_password", None)
self.tor_control_port = self.settings.get('tor_control_port', 9051) self.tor_control_port = self.settings.get("tor_control_port", 9051)
self.default_socket = socket.socket self.default_socket = socket.socket
self.default_socket_timeout = socket.getdefaulttimeout() self.default_socket_timeout = socket.getdefaulttimeout()
self.default_socket_getaddrinfo = socket.getaddrinfo self.default_socket_getaddrinfo = socket.getaddrinfo
@@ -77,10 +77,17 @@ 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', '%Y-%m-%d %H:%M:%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', '%Y-%m-%d %H:%M:%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)
@@ -92,68 +99,91 @@ class BaseApp:
def getChainClientSettings(self, coin): def getChainClientSettings(self, coin):
try: try:
return self.settings['chainclients'][chainparams[coin]['name']] return self.settings["chainclients"][chainparams[coin]["name"]]
except Exception: except Exception:
return {} return {}
def setDaemonPID(self, name, pid) -> None: def setDaemonPID(self, name, pid) -> None:
if isinstance(name, Coins): if isinstance(name, Coins):
self.coin_clients[name]['pid'] = pid self.coin_clients[name]["pid"] = pid
return return
for c, v in self.coin_clients.items(): for c, v in self.coin_clients.items():
if v['name'] == name: if v["name"] == name:
v['pid'] = pid v["pid"] = pid
def getChainDatadirPath(self, coin) -> str: def getChainDatadirPath(self, coin) -> str:
datadir = self.coin_clients[coin]['datadir'] datadir = self.coin_clients[coin]["datadir"]
testnet_name = '' if self.chain == 'mainnet' else chainparams[coin][self.chain].get('name', self.chain) testnet_name = (
""
if self.chain == "mainnet"
else chainparams[coin][self.chain].get("name", self.chain)
)
return os.path.join(datadir, testnet_name) return os.path.join(datadir, testnet_name)
def getCoinIdFromName(self, coin_name: str): def getCoinIdFromName(self, coin_name: str):
for c, params in chainparams.items(): for c, params in chainparams.items():
if coin_name.lower() == params['name'].lower(): if coin_name.lower() == params["name"].lower():
return c return c
raise ValueError('Unknown coin: {}'.format(coin_name)) raise ValueError("Unknown coin: {}".format(coin_name))
def callrpc(self, method, params=[], wallet=None): def callrpc(self, method, params=[], wallet=None):
cc = self.coin_clients[Coins.PART] cc = self.coin_clients[Coins.PART]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost']) return callrpc(
cc["rpcport"], cc["rpcauth"], method, params, wallet, cc["rpchost"]
)
def callcoinrpc(self, coin, method, params=[], wallet=None): def callcoinrpc(self, coin, method, params=[], wallet=None):
cc = self.coin_clients[coin] cc = self.coin_clients[coin]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost']) return callrpc(
cc["rpcport"], cc["rpcauth"], method, params, wallet, cc["rpchost"]
)
def callcoincli(self, coin_type, params, wallet=None, timeout=None): def callcoincli(self, coin_type, params, wallet=None, timeout=None):
bindir = self.coin_clients[coin_type]['bindir'] bindir = self.coin_clients[coin_type]["bindir"]
datadir = self.coin_clients[coin_type]['datadir'] datadir = self.coin_clients[coin_type]["datadir"]
cli_bin: str = chainparams[coin_type].get('cli_binname', chainparams[coin_type]['name'] + '-cli') cli_bin: str = chainparams[coin_type].get(
command_cli = os.path.join(bindir, cli_bin + ('.exe' if os.name == 'nt' else '')) "cli_binname", chainparams[coin_type]["name"] + "-cli"
args = [command_cli, ] )
if self.chain != 'mainnet': command_cli = os.path.join(
args.append('-' + self.chain) bindir, cli_bin + (".exe" if os.name == "nt" else "")
args.append('-datadir=' + datadir) )
args = [
command_cli,
]
if self.chain != "mainnet":
args.append("-" + self.chain)
args.append("-datadir=" + datadir)
args += shlex.split(params) args += shlex.split(params)
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
out = p.communicate(timeout=timeout) out = p.communicate(timeout=timeout)
if len(out[1]) > 0: if len(out[1]) > 0:
raise ValueError('CLI error ' + str(out[1])) raise ValueError("CLI error " + str(out[1]))
return out[0].decode('utf-8').strip() return out[0].decode("utf-8").strip()
def is_transient_error(self, ex) -> bool: def is_transient_error(self, ex) -> bool:
if isinstance(ex, TemporaryError): if isinstance(ex, TemporaryError):
return True return True
str_error = str(ex).lower() str_error = str(ex).lower()
return 'read timed out' in str_error or 'no connection to daemon' in str_error return "read timed out" in str_error or "no connection to daemon" in str_error
def setConnectionParameters(self, timeout=120): def setConnectionParameters(self, timeout=120):
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)
if self.use_tor_proxy: if self.use_tor_proxy:
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port, rdns=True) socks.setdefaultproxy(
socks.PROXY_TYPE_SOCKS5,
self.tor_proxy_host,
self.tor_proxy_port,
rdns=True,
)
socket.socket = socks.socksocket socket.socket = socks.socksocket
socket.getaddrinfo = getaddrinfo_tor # Without this accessing .onion links would fail socket.getaddrinfo = (
getaddrinfo_tor # Without this accessing .onion links would fail
)
socket.setdefaulttimeout(timeout) socket.setdefaulttimeout(timeout)
@@ -166,10 +196,16 @@ class BaseApp:
def readURL(self, url: str, timeout: int = 120, headers={}) -> bytes: def readURL(self, url: str, timeout: int = 120, headers={}) -> bytes:
open_handler = None open_handler = None
if self.use_tor_proxy: if self.use_tor_proxy:
open_handler = SocksiPyHandler(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port) open_handler = SocksiPyHandler(
opener = urllib.request.build_opener(open_handler) if self.use_tor_proxy else urllib.request.build_opener() socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port
)
opener = (
urllib.request.build_opener(open_handler)
if self.use_tor_proxy
else urllib.request.build_opener()
)
if headers is None: if headers is None:
opener.addheaders = [('User-agent', 'Mozilla/5.0')] opener.addheaders = [("User-agent", "Mozilla/5.0")]
request = urllib.request.Request(url, headers=headers) request = urllib.request.Request(url, headers=headers)
return opener.open(request, timeout=timeout).read() return opener.open(request, timeout=timeout).read()
@@ -180,7 +216,9 @@ class BaseApp:
def torControl(self, query): def torControl(self, query):
try: try:
command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8') command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(
self.tor_control_password, query
).encode("utf-8")
c = socket.create_connection((self.tor_proxy_host, self.tor_control_port)) c = socket.create_connection((self.tor_proxy_host, self.tor_control_port))
c.send(command) c.send(command)
response = bytearray() response = bytearray()
@@ -192,23 +230,23 @@ class BaseApp:
c.close() c.close()
return response return response
except Exception as e: except Exception as e:
self.log.error(f'torControl {e}') self.log.error(f"torControl {e}")
return return
def getTime(self) -> int: def getTime(self) -> int:
return int(time.time()) + self.mock_time_offset return int(time.time()) + self.mock_time_offset
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: def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
value: int = self.settings.get(name, default_v) value: int = self.settings.get(name, default_v)
if value < min_v: if value < min_v:
self.log.warning(f'Setting {name} to {min_v}') self.log.warning(f"Setting {name} to {min_v}")
value = min_v value = min_v
if value > max_v: if value > max_v:
self.log.warning(f'Setting {name} to {max_v}') self.log.warning(f"Setting {name} to {max_v}")
value = max_v value = max_v
return value return value

File diff suppressed because it is too large Load Diff

View File

@@ -231,12 +231,12 @@ class AutomationOverrideOptions(IntEnum):
def strAutomationOverrideOption(option): def strAutomationOverrideOption(option):
if option == AutomationOverrideOptions.DEFAULT: if option == AutomationOverrideOptions.DEFAULT:
return 'Default' return "Default"
if option == AutomationOverrideOptions.ALWAYS_ACCEPT: if option == AutomationOverrideOptions.ALWAYS_ACCEPT:
return 'Always Accept' return "Always Accept"
if option == AutomationOverrideOptions.NEVER_ACCEPT: if option == AutomationOverrideOptions.NEVER_ACCEPT:
return 'Never Accept' return "Never Accept"
return 'Unknown' return "Unknown"
class VisibilityOverrideOptions(IntEnum): class VisibilityOverrideOptions(IntEnum):
@@ -247,250 +247,253 @@ class VisibilityOverrideOptions(IntEnum):
def strVisibilityOverrideOption(option): def strVisibilityOverrideOption(option):
if option == VisibilityOverrideOptions.DEFAULT: if option == VisibilityOverrideOptions.DEFAULT:
return 'Default' return "Default"
if option == VisibilityOverrideOptions.HIDE: if option == VisibilityOverrideOptions.HIDE:
return 'Hide' return "Hide"
if option == VisibilityOverrideOptions.BLOCK: if option == VisibilityOverrideOptions.BLOCK:
return 'Block' return "Block"
return 'Unknown' return "Unknown"
def strOfferState(state): def strOfferState(state):
if state == OfferStates.OFFER_SENT: if state == OfferStates.OFFER_SENT:
return 'Sent' return "Sent"
if state == OfferStates.OFFER_RECEIVED: if state == OfferStates.OFFER_RECEIVED:
return 'Received' return "Received"
if state == OfferStates.OFFER_ABANDONED: if state == OfferStates.OFFER_ABANDONED:
return 'Abandoned' return "Abandoned"
if state == OfferStates.OFFER_EXPIRED: if state == OfferStates.OFFER_EXPIRED:
return 'Expired' return "Expired"
return 'Unknown' return "Unknown"
def strBidState(state): def strBidState(state):
if state == BidStates.BID_SENT: if state == BidStates.BID_SENT:
return 'Sent' return "Sent"
if state == BidStates.BID_RECEIVING: if state == BidStates.BID_RECEIVING:
return 'Receiving' return "Receiving"
if state == BidStates.BID_RECEIVING_ACC: if state == BidStates.BID_RECEIVING_ACC:
return 'Receiving accept' return "Receiving accept"
if state == BidStates.BID_RECEIVED: if state == BidStates.BID_RECEIVED:
return 'Received' return "Received"
if state == BidStates.BID_ACCEPTED: if state == BidStates.BID_ACCEPTED:
return 'Accepted' return "Accepted"
if state == BidStates.SWAP_INITIATED: if state == BidStates.SWAP_INITIATED:
return 'Initiated' return "Initiated"
if state == BidStates.SWAP_PARTICIPATING: if state == BidStates.SWAP_PARTICIPATING:
return 'Participating' return "Participating"
if state == BidStates.SWAP_COMPLETED: if state == BidStates.SWAP_COMPLETED:
return 'Completed' return "Completed"
if state == BidStates.SWAP_TIMEDOUT: if state == BidStates.SWAP_TIMEDOUT:
return 'Timed-out' return "Timed-out"
if state == BidStates.BID_ABANDONED: if state == BidStates.BID_ABANDONED:
return 'Abandoned' return "Abandoned"
if state == BidStates.BID_STALLED_FOR_TEST: if state == BidStates.BID_STALLED_FOR_TEST:
return 'Stalled (debug)' return "Stalled (debug)"
if state == BidStates.BID_ERROR: if state == BidStates.BID_ERROR:
return 'Error' return "Error"
if state == BidStates.BID_REJECTED: if state == BidStates.BID_REJECTED:
return 'Rejected' return "Rejected"
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
return 'Script coin locked' return "Script coin locked"
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
return 'Script coin spend tx valid' return "Script coin spend tx valid"
if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED: if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
return 'Scriptless coin locked' return "Scriptless coin locked"
if state == BidStates.XMR_SWAP_LOCK_RELEASED: if state == BidStates.XMR_SWAP_LOCK_RELEASED:
return 'Script coin lock released' return "Script coin lock released"
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
return 'Script tx redeemed' return "Script tx redeemed"
if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND: if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND:
return 'Script pre-refund tx in chain' return "Script pre-refund tx in chain"
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED: if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
return 'Scriptless tx redeemed' return "Scriptless tx redeemed"
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED: if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
return 'Scriptless tx recovered' return "Scriptless tx recovered"
if state == BidStates.XMR_SWAP_FAILED_REFUNDED: if state == BidStates.XMR_SWAP_FAILED_REFUNDED:
return 'Failed, refunded' return "Failed, refunded"
if state == BidStates.XMR_SWAP_FAILED_SWIPED: if state == BidStates.XMR_SWAP_FAILED_SWIPED:
return 'Failed, swiped' return "Failed, swiped"
if state == BidStates.XMR_SWAP_FAILED: if state == BidStates.XMR_SWAP_FAILED:
return 'Failed' return "Failed"
if state == BidStates.SWAP_DELAYING: if state == BidStates.SWAP_DELAYING:
return 'Delaying' return "Delaying"
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS: if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS:
return 'Exchanged script lock tx sigs msg' return "Exchanged script lock tx sigs msg"
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX: if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
return 'Exchanged script lock spend tx msg' return "Exchanged script lock spend tx msg"
if state == BidStates.BID_REQUEST_SENT: if state == BidStates.BID_REQUEST_SENT:
return 'Request sent' return "Request sent"
if state == BidStates.BID_REQUEST_ACCEPTED: if state == BidStates.BID_REQUEST_ACCEPTED:
return 'Request accepted' return "Request accepted"
if state == BidStates.BID_STATE_UNKNOWN: if state == BidStates.BID_STATE_UNKNOWN:
return 'Unknown bid state' return "Unknown bid state"
if state == BidStates.BID_EXPIRED: if state == BidStates.BID_EXPIRED:
return 'Expired' return "Expired"
return 'Unknown' + ' ' + str(state) return "Unknown" + " " + str(state)
def strTxState(state): def strTxState(state):
if state == TxStates.TX_NONE: if state == TxStates.TX_NONE:
return 'None' return "None"
if state == TxStates.TX_SENT: if state == TxStates.TX_SENT:
return 'Sent' return "Sent"
if state == TxStates.TX_CONFIRMED: if state == TxStates.TX_CONFIRMED:
return 'Confirmed' return "Confirmed"
if state == TxStates.TX_REDEEMED: if state == TxStates.TX_REDEEMED:
return 'Redeemed' return "Redeemed"
if state == TxStates.TX_REFUNDED: if state == TxStates.TX_REFUNDED:
return 'Refunded' return "Refunded"
if state == TxStates.TX_IN_MEMPOOL: if state == TxStates.TX_IN_MEMPOOL:
return 'In Mempool' return "In Mempool"
if state == TxStates.TX_IN_CHAIN: if state == TxStates.TX_IN_CHAIN:
return 'In Chain' return "In Chain"
return 'Unknown' return "Unknown"
def strTxType(tx_type): def strTxType(tx_type):
if tx_type == TxTypes.XMR_SWAP_A_LOCK: if tx_type == TxTypes.XMR_SWAP_A_LOCK:
return 'Chain A Lock Tx' return "Chain A Lock Tx"
if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND: if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND:
return 'Chain A Lock Spend Tx' return "Chain A Lock Spend Tx"
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND: if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND:
return 'Chain A Lock Refund Tx' return "Chain A Lock Refund Tx"
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND: if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND:
return 'Chain A Lock Refund Spend Tx' return "Chain A Lock Refund Spend Tx"
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE: if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE:
return 'Chain A Lock Refund Swipe Tx' return "Chain A Lock Refund Swipe Tx"
if tx_type == TxTypes.XMR_SWAP_B_LOCK: if tx_type == TxTypes.XMR_SWAP_B_LOCK:
return 'Chain B Lock Tx' return "Chain B Lock Tx"
if tx_type == TxTypes.ITX_PRE_FUNDED: if tx_type == TxTypes.ITX_PRE_FUNDED:
return 'Funded mock initiate Tx' return "Funded mock initiate Tx"
if tx_type == TxTypes.BCH_MERCY: if tx_type == TxTypes.BCH_MERCY:
return 'BCH Mercy Tx' return "BCH Mercy Tx"
return 'Unknown' return "Unknown"
def strAddressType(addr_type): def strAddressType(addr_type):
if addr_type == AddressTypes.OFFER: if addr_type == AddressTypes.OFFER:
return 'Offer' return "Offer"
if addr_type == AddressTypes.BID: if addr_type == AddressTypes.BID:
return 'Bid' return "Bid"
if addr_type == AddressTypes.RECV_OFFER: if addr_type == AddressTypes.RECV_OFFER:
return 'Offer recv' return "Offer recv"
if addr_type == AddressTypes.SEND_OFFER: if addr_type == AddressTypes.SEND_OFFER:
return 'Offer send' return "Offer send"
return 'Unknown' return "Unknown"
def getLockName(lock_type): def getLockName(lock_type):
if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS: if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
return 'Sequence lock, blocks' return "Sequence lock, blocks"
if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
return 'Sequence lock, time' return "Sequence lock, time"
if lock_type == TxLockTypes.ABS_LOCK_BLOCKS: if lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
return 'blocks' return "blocks"
if lock_type == TxLockTypes.ABS_LOCK_TIME: if lock_type == TxLockTypes.ABS_LOCK_TIME:
return 'time' return "time"
def describeEventEntry(event_type, event_msg): def describeEventEntry(event_type, event_msg):
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH: if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
return 'Failed to publish lock tx B' return "Failed to publish lock tx B"
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
return 'Lock tx A published' return "Lock tx A published"
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
return 'Lock tx B published' return "Lock tx B published"
if event_type == EventLogTypes.FAILED_TX_B_SPEND: if event_type == EventLogTypes.FAILED_TX_B_SPEND:
return 'Failed to publish lock tx B spend: ' + event_msg return "Failed to publish lock tx B spend: " + event_msg
if event_type == EventLogTypes.LOCK_TX_A_SEEN: if event_type == EventLogTypes.LOCK_TX_A_SEEN:
return 'Lock tx A seen in chain' return "Lock tx A seen in chain"
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED: if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
return 'Lock tx A confirmed in chain' return "Lock tx A confirmed in chain"
if event_type == EventLogTypes.LOCK_TX_B_SEEN: if event_type == EventLogTypes.LOCK_TX_B_SEEN:
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: if event_type == EventLogTypes.LOCK_TX_B_IN_MEMPOOL:
return 'Lock tx B seen 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:
return 'Failed to publish lock tx B refund' return "Failed to publish lock tx B refund"
if event_type == EventLogTypes.LOCK_TX_B_INVALID: if event_type == EventLogTypes.LOCK_TX_B_INVALID:
return 'Detected invalid lock Tx B' return "Detected invalid lock Tx B"
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
return 'Lock tx A refund tx published' return "Lock tx A refund tx published"
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
return 'Lock tx A refund spend tx published' return "Lock tx A refund spend tx published"
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED:
return 'Lock tx A refund swipe tx published' return "Lock tx A refund swipe tx published"
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED:
return 'Lock tx B refund tx published' return "Lock tx B refund tx published"
if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED:
return 'Lock tx A spend tx published' return "Lock tx A spend tx published"
if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED: if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED:
return 'Lock tx B spend tx published' return "Lock tx B spend tx published"
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN: if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN:
return 'Lock tx A refund tx seen in chain' return "Lock tx A refund tx seen in chain"
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN: if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN:
return 'Lock tx A refund spend tx seen in chain' return "Lock tx A refund spend tx seen in chain"
if event_type == EventLogTypes.SYSTEM_WARNING: if event_type == EventLogTypes.SYSTEM_WARNING:
return 'Warning: ' + event_msg return "Warning: " + event_msg
if event_type == EventLogTypes.ERROR: if event_type == EventLogTypes.ERROR:
return 'Error: ' + event_msg return "Error: " + event_msg
if event_type == EventLogTypes.AUTOMATION_CONSTRAINT: if event_type == EventLogTypes.AUTOMATION_CONSTRAINT:
return 'Failed auto accepting' return "Failed auto accepting"
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID: if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
return 'Auto accepting' return "Auto accepting"
if event_type == EventLogTypes.ITX_PUBLISHED: if event_type == EventLogTypes.ITX_PUBLISHED:
return 'Initiate tx published' return "Initiate tx published"
if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED: if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED:
return 'Initiate tx redeem tx published' return "Initiate tx redeem tx published"
if event_type == EventLogTypes.ITX_REFUND_PUBLISHED: if event_type == EventLogTypes.ITX_REFUND_PUBLISHED:
return 'Initiate tx refund tx published' return "Initiate tx refund tx published"
if event_type == EventLogTypes.PTX_PUBLISHED: if event_type == EventLogTypes.PTX_PUBLISHED:
return 'Participate tx published' return "Participate tx published"
if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED: if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED:
return 'Participate tx redeem tx published' return "Participate tx redeem tx published"
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED: if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
return 'Participate tx refund tx published' return "Participate tx refund tx published"
if event_type == EventLogTypes.BCH_MERCY_TX_FOUND: if event_type == EventLogTypes.BCH_MERCY_TX_FOUND:
return 'BCH mercy tx found' return "BCH mercy tx found"
if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED: if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED:
return 'Lock tx B mercy tx published' return "Lock tx B mercy tx published"
def getVoutByAddress(txjs, p2sh): def getVoutByAddress(txjs, p2sh):
for o in txjs['vout']: for o in txjs["vout"]:
try: try:
if 'address' in o['scriptPubKey'] and o['scriptPubKey']['address'] == p2sh: if "address" in o["scriptPubKey"] and o["scriptPubKey"]["address"] == p2sh:
return o['n'] return o["n"]
if p2sh in o['scriptPubKey']['addresses']: if p2sh in o["scriptPubKey"]["addresses"]:
return o['n'] return o["n"]
except Exception: except Exception:
pass pass
raise ValueError('Address output not found in txn') raise ValueError("Address output not found in txn")
def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int: def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int:
for o in txjs['vout']: for o in txjs["vout"]:
try: try:
if scriptPubKey_hex == o['scriptPubKey']['hex']: if scriptPubKey_hex == o["scriptPubKey"]["hex"]:
return o['n'] return o["n"]
except Exception: except Exception:
pass pass
raise ValueError('scriptPubKey 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"):
return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:]) return encodeAddress(
bytes((chainparams[coin_type][chain_name][addr_type],))
+ decodeAddress(addr)[1:]
)
def getOfferProofOfFundsHash(offer_msg, offer_addr): def getOfferProofOfFundsHash(offer_msg, offer_addr):
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg # TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
h = hashlib.sha256() h = hashlib.sha256()
h.update(offer_addr.encode('utf-8')) h.update(offer_addr.encode("utf-8"))
offer_bytes = offer_msg.to_bytes() offer_bytes = offer_msg.to_bytes()
h.update(offer_bytes) h.update(offer_bytes)
return h.digest() return h.digest()
@@ -500,33 +503,40 @@ def getLastBidState(packed_states):
num_states = len(packed_states) // 12 num_states = len(packed_states) // 12
if num_states < 2: if num_states < 2:
return BidStates.BID_STATE_UNKNOWN return BidStates.BID_STATE_UNKNOWN
return struct.unpack_from('<i', packed_states[(num_states - 2) * 12:])[0] return struct.unpack_from("<i", packed_states[(num_states - 2) * 12 :])[0]
try: try:
num_states = len(packed_states) // 12 num_states = len(packed_states) // 12
if num_states < 2: if num_states < 2:
return BidStates.BID_STATE_UNKNOWN return BidStates.BID_STATE_UNKNOWN
return struct.unpack_from('<i', packed_states[(num_states - 2) * 12:])[0] return struct.unpack_from("<i", packed_states[(num_states - 2) * 12 :])[0]
except Exception: except Exception:
return BidStates.BID_STATE_UNKNOWN return BidStates.BID_STATE_UNKNOWN
def strSwapType(swap_type): def strSwapType(swap_type):
if swap_type == SwapTypes.SELLER_FIRST: if swap_type == SwapTypes.SELLER_FIRST:
return 'seller_first' return "seller_first"
if swap_type == SwapTypes.XMR_SWAP: if swap_type == SwapTypes.XMR_SWAP:
return 'xmr_swap' return "xmr_swap"
return None return None
def strSwapDesc(swap_type): def strSwapDesc(swap_type):
if swap_type == SwapTypes.SELLER_FIRST: if swap_type == SwapTypes.SELLER_FIRST:
return 'Secret Hash' return "Secret Hash"
if swap_type == SwapTypes.XMR_SWAP: if swap_type == SwapTypes.XMR_SWAP:
return 'Adaptor Sig' return "Adaptor Sig"
return None return None
inactive_states = [BidStates.SWAP_COMPLETED, BidStates.BID_ERROR, BidStates.BID_REJECTED, BidStates.SWAP_TIMEDOUT, BidStates.BID_ABANDONED, BidStates.BID_EXPIRED] inactive_states = [
BidStates.SWAP_COMPLETED,
BidStates.BID_ERROR,
BidStates.BID_REJECTED,
BidStates.SWAP_TIMEDOUT,
BidStates.BID_ABANDONED,
BidStates.BID_EXPIRED,
]
def isActiveBidState(state): def isActiveBidState(state):

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert # Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -32,7 +33,7 @@ swap_client = None
class Daemon: class Daemon:
__slots__ = ('handle', 'files') __slots__ = ("handle", "files")
def __init__(self, handle, files): def __init__(self, handle, files):
self.handle = handle self.handle = handle
@@ -41,14 +42,14 @@ class Daemon:
def is_known_coin(coin_name: str) -> bool: def is_known_coin(coin_name: str) -> bool:
for k, v in chainparams.items(): for k, v in chainparams.items():
if coin_name == v['name']: if coin_name == v["name"]:
return True return True
return False return False
def signal_handler(sig, frame): def signal_handler(sig, frame):
global swap_client global swap_client
logger.info('Signal %d detected, ending program.' % (sig)) logger.info("Signal %d detected, ending program." % (sig))
if swap_client is not None: if swap_client is not None:
swap_client.stopRunning() swap_client.stopRunning()
@@ -59,9 +60,9 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
# Rewrite litecoin.conf for 0.21.3 # Rewrite litecoin.conf for 0.21.3
# TODO: Remove # TODO: Remove
ltc_conf_path = os.path.join(datadir_path, 'litecoin.conf') ltc_conf_path = os.path.join(datadir_path, "litecoin.conf")
if os.path.exists(ltc_conf_path): if os.path.exists(ltc_conf_path):
config_to_add = ['blockfilterindex=0', 'peerblockfilters=0'] config_to_add = ["blockfilterindex=0", "peerblockfilters=0"]
with open(ltc_conf_path) as fp: with open(ltc_conf_path) as fp:
for line in fp: for line in fp:
line = line.strip() line = line.strip()
@@ -69,23 +70,30 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
config_to_add.remove(line) config_to_add.remove(line)
if len(config_to_add) > 0: if len(config_to_add) > 0:
logger.info('Rewriting litecoin.conf') logger.info("Rewriting litecoin.conf")
shutil.copyfile(ltc_conf_path, ltc_conf_path + '.last') shutil.copyfile(ltc_conf_path, ltc_conf_path + ".last")
with open(ltc_conf_path, 'a') as fp: with open(ltc_conf_path, "a") as fp:
for line in config_to_add: for line in config_to_add:
fp.write(line + '\n') fp.write(line + "\n")
args = [daemon_bin, ] args = [
add_datadir: bool = extra_config.get('add_datadir', True) daemon_bin,
]
add_datadir: bool = extra_config.get("add_datadir", True)
if add_datadir: if add_datadir:
args.append('-datadir=' + datadir_path) args.append("-datadir=" + datadir_path)
args += opts args += opts
logger.info('Starting node {}'.format(daemon_bin)) logger.info("Starting node {}".format(daemon_bin))
logger.debug('Arguments {}'.format(' '.join(args))) logger.debug("Arguments {}".format(" ".join(args)))
opened_files = [] opened_files = []
if extra_config.get('stdout_to_file', False): if extra_config.get("stdout_to_file", False):
stdout_dest = open(os.path.join(datadir_path, extra_config.get('stdout_filename', 'core_stdout.log')), 'w') stdout_dest = open(
os.path.join(
datadir_path, extra_config.get("stdout_filename", "core_stdout.log")
),
"w",
)
opened_files.append(stdout_dest) opened_files.append(stdout_dest)
stderr_dest = stdout_dest stderr_dest = stdout_dest
else: else:
@@ -93,62 +101,113 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
stderr_dest = subprocess.PIPE stderr_dest = subprocess.PIPE
shell: bool = False shell: bool = False
if extra_config.get('use_shell', False): if extra_config.get("use_shell", False):
args = ' '.join(args) args = " ".join(args)
shell = True shell = True
return Daemon(subprocess.Popen(args, shell=shell, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files) return Daemon(
subprocess.Popen(
args,
shell=shell,
stdin=subprocess.PIPE,
stdout=stdout_dest,
stderr=stderr_dest,
cwd=datadir_path,
),
opened_files,
)
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]): def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin)) daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
datadir_path = os.path.expanduser(node_dir) datadir_path = os.path.expanduser(node_dir)
config_filename = 'wownerod.conf' if daemon_bin.startswith('wow') else 'monerod.conf' config_filename = (
args = [daemon_path, '--non-interactive', '--config-file=' + os.path.join(datadir_path, config_filename)] + opts "wownerod.conf" if daemon_bin.startswith("wow") else "monerod.conf"
logger.info('Starting node {}'.format(daemon_bin)) )
logger.debug('Arguments {}'.format(' '.join(args))) args = [
daemon_path,
"--non-interactive",
"--config-file=" + os.path.join(datadir_path, config_filename),
] + opts
logger.info("Starting node {}".format(daemon_bin))
logger.debug("Arguments {}".format(" ".join(args)))
# return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
file_stdout = open(os.path.join(datadir_path, 'core_stdout.log'), 'w') file_stdout = open(os.path.join(datadir_path, "core_stdout.log"), "w")
file_stderr = open(os.path.join(datadir_path, 'core_stderr.log'), 'w') file_stderr = open(os.path.join(datadir_path, "core_stderr.log"), "w")
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=file_stdout, stderr=file_stderr, cwd=datadir_path), [file_stdout, file_stderr]) return Daemon(
subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=file_stdout,
stderr=file_stderr,
cwd=datadir_path,
),
[file_stdout, file_stderr],
)
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]): def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
daemon_path = os.path.expanduser(os.path.join(bin_dir, wallet_bin)) daemon_path = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
args = [daemon_path, '--non-interactive'] args = [daemon_path, "--non-interactive"]
needs_rewrite: bool = False needs_rewrite: bool = False
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy='] config_to_remove = [
"daemon-address=",
"untrusted-daemon=",
"trusted-daemon=",
"proxy=",
]
data_dir = os.path.expanduser(node_dir) data_dir = os.path.expanduser(node_dir)
wallet_config_filename = 'wownero-wallet-rpc.conf' if wallet_bin.startswith('wow') else 'monero_wallet.conf' wallet_config_filename = (
"wownero-wallet-rpc.conf"
if wallet_bin.startswith("wow")
else "monero_wallet.conf"
)
config_path = os.path.join(data_dir, wallet_config_filename) config_path = os.path.join(data_dir, wallet_config_filename)
if os.path.exists(config_path): if os.path.exists(config_path):
args += ['--config-file=' + config_path] args += ["--config-file=" + config_path]
with open(config_path) as fp: with open(config_path) as fp:
for line in fp: for line in fp:
if any(line.startswith(config_line) for config_line in config_to_remove): if any(
logger.warning('Found old config in monero_wallet.conf: {}'.format(line.strip())) line.startswith(config_line) for config_line in config_to_remove
):
logger.warning(
"Found old config in monero_wallet.conf: {}".format(
line.strip()
)
)
needs_rewrite = True needs_rewrite = True
args += opts args += opts
if needs_rewrite: if needs_rewrite:
logger.info('Rewriting wallet config') logger.info("Rewriting wallet config")
shutil.copyfile(config_path, config_path + '.last') shutil.copyfile(config_path, config_path + ".last")
with open(config_path + '.last') as fp_from, open(config_path, 'w') as fp_to: with open(config_path + ".last") as fp_from, open(config_path, "w") as fp_to:
for line in fp_from: for line in fp_from:
if not any(line.startswith(config_line) for config_line in config_to_remove): if not any(
line.startswith(config_line) for config_line in config_to_remove
):
fp_to.write(line) fp_to.write(line)
logger.info('Starting wallet daemon {}'.format(wallet_bin)) logger.info("Starting wallet daemon {}".format(wallet_bin))
logger.debug('Arguments {}'.format(' '.join(args))) logger.debug("Arguments {}".format(" ".join(args)))
# TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir) # TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir)
wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w') wallet_stdout = open(os.path.join(data_dir, "wallet_stdout.log"), "w")
wallet_stderr = open(os.path.join(data_dir, 'wallet_stderr.log'), 'w') wallet_stderr = open(os.path.join(data_dir, "wallet_stderr.log"), "w")
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir), [wallet_stdout, wallet_stderr]) return Daemon(
subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=wallet_stdout,
stderr=wallet_stderr,
cwd=data_dir,
),
[wallet_stdout, wallet_stderr],
)
def ws_new_client(client, server): def ws_new_client(client, server):
@@ -165,25 +224,29 @@ def ws_client_left(client, server):
def ws_message_received(client, server, message): def ws_message_received(client, server, message):
if len(message) > 200: if len(message) > 200:
message = message[:200] + '..' message = message[:200] + ".."
if swap_client: if swap_client:
swap_client.log.debug(f'ws_message_received {client["id"]} {message}') swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
def getCoreBinName(coin_id: int, coin_settings, default_name: str) -> str: def getCoreBinName(coin_id: int, coin_settings, default_name: str) -> str:
return coin_settings.get('core_binname', chainparams[coin_id].get('core_binname', default_name)) + ('.exe' if os.name == 'nt' else '') return coin_settings.get(
"core_binname", chainparams[coin_id].get("core_binname", default_name)
) + (".exe" if os.name == "nt" else "")
def getWalletBinName(coin_id: int, coin_settings, default_name: str) -> str: def getWalletBinName(coin_id: int, coin_settings, default_name: str) -> str:
return coin_settings.get('wallet_binname', chainparams[coin_id].get('wallet_binname', default_name)) + ('.exe' if os.name == 'nt' else '') return coin_settings.get(
"wallet_binname", chainparams[coin_id].get("wallet_binname", default_name)
) + (".exe" if os.name == "nt" else "")
def getCoreBinArgs(coin_id: int, coin_settings): def getCoreBinArgs(coin_id: int, coin_settings):
extra_args = [] extra_args = []
if 'config_filename' in coin_settings: if "config_filename" in coin_settings:
extra_args.append('--conf=' + coin_settings['config_filename']) extra_args.append("--conf=" + coin_settings["config_filename"])
if 'port' in coin_settings: if "port" in coin_settings:
extra_args.append('--port=' + str(int(coin_settings['port']))) extra_args.append("--port=" + str(int(coin_settings["port"])))
return extra_args return extra_args
@@ -193,17 +256,21 @@ def runClient(fp, data_dir, chain, start_only_coins):
pids = [] pids = []
threads = [] threads = []
settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME) settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
pids_path = os.path.join(data_dir, '.pids') pids_path = os.path.join(data_dir, ".pids")
if os.getenv('WALLET_ENCRYPTION_PWD', '') != '': if os.getenv("WALLET_ENCRYPTION_PWD", "") != "":
if 'decred' in start_only_coins: if "decred" in start_only_coins:
# Workaround for dcrwallet requiring password for initial startup # Workaround for dcrwallet requiring password for initial startup
logger.warning('Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred.') logger.warning(
"Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred."
)
else: else:
raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.') raise ValueError(
"Please unset the WALLET_ENCRYPTION_PWD environment variable."
)
if not os.path.exists(settings_path): if not os.path.exists(settings_path):
raise ValueError('Settings file not found: ' + str(settings_path)) raise ValueError("Settings file not found: " + str(settings_path))
with open(settings_path) as fs: with open(settings_path) as fs:
settings = json.load(fs) settings = json.load(fs)
@@ -215,7 +282,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
with open(pids_path) as fd: with open(pids_path) as fd:
for ln in fd: for ln in fd:
# TODO: try close # TODO: try close
logger.warning('Found pid for daemon {} '.format(ln.strip())) logger.warning("Found pid for daemon {} ".format(ln.strip()))
# Ensure daemons are stopped # Ensure daemons are stopped
swap_client.stopDaemons() swap_client.stopDaemons()
@@ -224,155 +291,227 @@ def runClient(fp, data_dir, chain, start_only_coins):
settings = swap_client.settings settings = swap_client.settings
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: if len(start_only_coins) > 0 and c not in start_only_coins:
continue 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)
except Exception as e: except Exception as e: # noqa: F841
logger.warning('Not starting unknown coin: {}'.format(c)) logger.warning("Not starting unknown coin: {}".format(c))
continue continue
if c in ('monero', 'wownero'): if c in ("monero", "wownero"):
if v['manage_daemon'] is True: if v["manage_daemon"] is True:
swap_client.log.info(f'Starting {display_name} daemon') swap_client.log.info(f"Starting {display_name} daemon")
filename: str = getCoreBinName(coin_id, v, c + 'd') filename: str = getCoreBinName(coin_id, v, c + "d")
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename)) daemons.append(startXmrDaemon(v["datadir"], v["bindir"], filename))
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info("Started {} {}".format(filename, pid))
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"])
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(coin_id, v['rpchost']) trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
opts = ['--daemon-address', daemon_addr, ] coin_id, v["rpchost"]
)
opts = [
"--daemon-address",
daemon_addr,
]
proxy_log_str = '' proxy_log_str = ""
proxy_host, proxy_port = swap_client.getXMRWalletProxy(coin_id, v['rpchost']) proxy_host, proxy_port = swap_client.getXMRWalletProxy(
coin_id, v["rpchost"]
)
if proxy_host: if proxy_host:
proxy_log_str = ' through proxy' proxy_log_str = " through proxy"
opts += ['--proxy', f'{proxy_host}:{proxy_port}', '--daemon-ssl-allow-any-cert', ] opts += [
"--proxy",
f"{proxy_host}:{proxy_port}",
"--daemon-ssl-allow-any-cert",
]
swap_client.log.info('daemon-address: {} ({}){}'.format(daemon_addr, 'trusted' if trusted_daemon else 'untrusted', proxy_log_str)) 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') opts.append(
filename: str = getWalletBinName(coin_id, v, c + '-wallet-rpc') "--trusted-daemon" if trusted_daemon else "--untrusted-daemon"
)
filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc")
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts)) daemons.append(
startXmrWalletDaemon(v["datadir"], v["bindir"], filename, opts)
)
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info("Started {} {}".format(filename, pid))
continue # /monero continue # /monero
if c == 'decred': if c == "decred":
appdata = v['datadir'] appdata = v["datadir"]
extra_opts = [f'--appdata="{appdata}"', ] extra_opts = [
use_shell: bool = True if os.name == 'nt' else False f'--appdata="{appdata}"',
if v['manage_daemon'] is True: ]
swap_client.log.info(f'Starting {display_name} daemon') use_shell: bool = True if os.name == "nt" else False
filename: str = getCoreBinName(coin_id, v, 'dcrd') if v["manage_daemon"] is True:
swap_client.log.info(f"Starting {display_name} daemon")
filename: str = getCoreBinName(coin_id, v, "dcrd")
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrd_stdout.log', 'use_shell': use_shell} extra_config = {
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config)) "add_datadir": False,
"stdout_to_file": True,
"stdout_filename": "dcrd_stdout.log",
"use_shell": use_shell,
}
daemons.append(
startDaemon(
appdata,
v["bindir"],
filename,
opts=extra_opts,
extra_config=extra_config,
)
)
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info("Started {} {}".format(filename, pid))
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")
filename: str = getWalletBinName(coin_id, v, 'dcrwallet') filename: str = getWalletBinName(coin_id, v, "dcrwallet")
wallet_pwd = v['wallet_pwd'] wallet_pwd = v["wallet_pwd"]
if wallet_pwd == '': if wallet_pwd == "":
# Only set when in startonlycoin mode # Only set when in startonlycoin mode
wallet_pwd = os.getenv('WALLET_ENCRYPTION_PWD', '') wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "")
if wallet_pwd != '': if wallet_pwd != "":
extra_opts.append(f'--pass="{wallet_pwd}"') extra_opts.append(f'--pass="{wallet_pwd}"')
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrwallet_stdout.log', 'use_shell': use_shell} extra_config = {
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config)) "add_datadir": False,
"stdout_to_file": True,
"stdout_filename": "dcrwallet_stdout.log",
"use_shell": use_shell,
}
daemons.append(
startDaemon(
appdata,
v["bindir"],
filename,
opts=extra_opts,
extra_config=extra_config,
)
)
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info("Started {} {}".format(filename, pid))
continue # /decred continue # /decred
if v['manage_daemon'] is True: if v["manage_daemon"] is True:
swap_client.log.info(f'Starting {display_name} daemon') swap_client.log.info(f"Starting {display_name} daemon")
filename: str = getCoreBinName(coin_id, v, c + 'd') filename: str = getCoreBinName(coin_id, v, c + "d")
extra_opts = getCoreBinArgs(coin_id, v) extra_opts = getCoreBinArgs(coin_id, v)
daemons.append(startDaemon(v['datadir'], v['bindir'], filename, opts=extra_opts)) daemons.append(
startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts)
)
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
pids.append((c, pid)) pids.append((c, pid))
swap_client.setDaemonPID(c, pid) swap_client.setDaemonPID(c, pid)
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info("Started {} {}".format(filename, pid))
if len(pids) > 0: if len(pids) > 0:
with open(pids_path, 'w') as fd: with open(pids_path, "w") as fd:
for p in pids: for p in pids:
fd.write('{}:{}\n'.format(*p)) fd.write("{}:{}\n".format(*p))
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
if len(start_only_coins) > 0: if len(start_only_coins) > 0:
logger.info(f'Only running {start_only_coins}. Manually exit with Ctrl + c when ready.') logger.info(
f"Only running {start_only_coins}. Manually exit with Ctrl + c when ready."
)
while not swap_client.delay_event.wait(0.5): while not swap_client.delay_event.wait(0.5):
pass pass
else: else:
swap_client.start() swap_client.start()
if 'htmlhost' in settings: if "htmlhost" in settings:
swap_client.log.info('Starting http server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport'])) swap_client.log.info(
allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS "Starting http server at http://%s:%d."
thread_http = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client) % (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) threads.append(thread_http)
thread_http.start() 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(
swap_client.ws_server.client_port = settings.get('wsclientport', settings['wsport']) host=settings["wshost"], port=settings["wsport"]
)
swap_client.ws_server.client_port = settings.get(
"wsclientport", 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 not swap_client.delay_event.wait(0.5): while not swap_client.delay_event.wait(0.5):
swap_client.update() swap_client.update()
except Exception as ex: except Exception as e: # noqa: F841
traceback.print_exc() traceback.print_exc()
if swap_client.ws_server: if swap_client.ws_server:
try: try:
swap_client.log.info('Stopping websocket server.') swap_client.log.info("Stopping websocket server.")
swap_client.ws_server.shutdown_gracefully() swap_client.ws_server.shutdown_gracefully()
except Exception as ex: except Exception as e: # noqa: F841
traceback.print_exc() traceback.print_exc()
swap_client.finalise() swap_client.finalise()
swap_client.log.info('Stopping HTTP threads.') swap_client.log.info("Stopping HTTP threads.")
for t in threads: for t in threads:
try: try:
t.stop() t.stop()
t.join() t.join()
except Exception as ex: except Exception as e: # noqa: F841
traceback.print_exc() traceback.print_exc()
closed_pids = [] closed_pids = []
for d in daemons: for d in daemons:
swap_client.log.info('Interrupting {}'.format(d.handle.pid)) swap_client.log.info("Interrupting {}".format(d.handle.pid))
try: try:
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT) d.handle.send_signal(
signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
)
except Exception as e: except Exception as e:
swap_client.log.info('Interrupting %d, error %s', d.handle.pid, str(e)) swap_client.log.info("Interrupting %d, error %s", d.handle.pid, str(e))
for d in daemons: for d in daemons:
try: try:
d.handle.wait(timeout=120) d.handle.wait(timeout=120)
@@ -381,96 +520,106 @@ def runClient(fp, data_dir, chain, start_only_coins):
fp.close() fp.close()
closed_pids.append(d.handle.pid) closed_pids.append(d.handle.pid)
except Exception as ex: except Exception as ex:
swap_client.log.error('Error: {}'.format(ex)) swap_client.log.error("Error: {}".format(ex))
if os.path.exists(pids_path): if os.path.exists(pids_path):
with open(pids_path) as fd: with open(pids_path) as fd:
lines = fd.read().split('\n') lines = fd.read().split("\n")
still_running = '' still_running = ""
for ln in lines: for ln in lines:
try: try:
if not int(ln.split(':')[1]) in closed_pids: if int(ln.split(":")[1]) not in closed_pids:
still_running += ln + '\n' still_running += ln + "\n"
except Exception: except Exception:
pass pass
with open(pids_path, 'w') as fd: with open(pids_path, "w") as fd:
fd.write(still_running) fd.write(still_running)
def printVersion(): def printVersion():
logger.info('Basicswap version: %s', __version__) logger.info("Basicswap version: %s", __version__)
def printHelp(): def printHelp():
print('Usage: basicswap-run ') print("Usage: basicswap-run ")
print('\n--help, -h Print help.') print("\n--help, -h Print help.")
print('--version, -v Print version.') print("--version, -v Print version.")
print('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR)) print(
print('--mainnet Run in mainnet mode.') "--datadir=PATH Path to basicswap data directory, default:{}.".format(
print('--testnet Run in testnet mode.') cfg.BASICSWAP_DATADIR
print('--regtest Run in regtest mode.') )
print('--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing.') )
print("--mainnet Run in mainnet mode.")
print("--testnet Run in testnet 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() 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] != "-":
logger.warning('Unknown argument %s', v) logger.warning("Unknown argument %s", v)
continue continue
s = v.split('=') s = v.split("=")
name = s[0].strip() name = s[0].strip()
for i in range(2): for i in range(2):
if name[0] == '-': if name[0] == "-":
name = name[1:] name = name[1:]
if name == 'v' or name == 'version': if name == "v" or name == "version":
printVersion() printVersion()
return 0 return 0
if name == 'h' or name == 'help': if name == "h" or name == "help":
printHelp() printHelp()
return 0 return 0
if name in ('mainnet', 'testnet', 'regtest'): if name in ("mainnet", "testnet", "regtest"):
chain = name chain = name
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': if name == "startonlycoin":
for coin in [s.lower() for s in s[1].split(',')]: for coin in [s.lower() for s in s[1].split(",")]:
if is_known_coin(coin) is False: if is_known_coin(coin) is False:
raise ValueError(f'Unknown coin: {coin}') raise ValueError(f"Unknown coin: {coin}")
start_only_coins.add(coin) start_only_coins.add(coin)
continue continue
logger.warning('Unknown argument %s', v) logger.warning("Unknown argument %s", v)
if os.name == 'nt': if os.name == "nt":
logger.warning('Running on windows is discouraged and windows support may be discontinued in the future. Please consider using the WSL docker setup instead.') logger.warning(
"Running on windows is discouraged and windows support may be discontinued in the future. Please consider using the WSL docker setup instead."
)
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))
logger.info('Using datadir: %s', data_dir) logger.info("Using datadir: %s", data_dir)
logger.info('Chain: %s', chain) logger.info("Chain: %s", chain)
if not os.path.exists(data_dir): if not os.path.exists(data_dir):
os.makedirs(data_dir) os.makedirs(data_dir)
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, start_only_coins) 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
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -35,459 +35,459 @@ class Coins(IntEnum):
chainparams = { chainparams = {
Coins.PART: { Coins.PART: {
'name': 'particl', "name": "particl",
'ticker': 'PART', "ticker": "PART",
'message_magic': 'Bitcoin Signed Message:\n', "message_magic": "Bitcoin Signed Message:\n",
'blocks_target': 60 * 2, "blocks_target": 60 * 2,
'decimal_places': 8, "decimal_places": 8,
'mainnet': { "mainnet": {
'rpcport': 51735, "rpcport": 51735,
'pubkey_address': 0x38, "pubkey_address": 0x38,
'script_address': 0x3c, "script_address": 0x3C,
'key_prefix': 0x6c, "key_prefix": 0x6C,
'stealth_key_prefix': 0x14, "stealth_key_prefix": 0x14,
'hrp': 'pw', "hrp": "pw",
'bip44': 44, "bip44": 44,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 51935, "rpcport": 51935,
'pubkey_address': 0x76, "pubkey_address": 0x76,
'script_address': 0x7a, "script_address": 0x7A,
'key_prefix': 0x2e, "key_prefix": 0x2E,
'stealth_key_prefix': 0x15, "stealth_key_prefix": 0x15,
'hrp': 'tpw', "hrp": "tpw",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
},
"regtest": {
"rpcport": 51936,
"pubkey_address": 0x76,
"script_address": 0x7A,
"key_prefix": 0x2E,
"stealth_key_prefix": 0x15,
"hrp": "rtpw",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 51936,
'pubkey_address': 0x76,
'script_address': 0x7a,
'key_prefix': 0x2e,
'stealth_key_prefix': 0x15,
'hrp': 'rtpw',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.BTC: { Coins.BTC: {
'name': 'bitcoin', "name": "bitcoin",
'ticker': 'BTC', "ticker": "BTC",
'message_magic': 'Bitcoin Signed Message:\n', "message_magic": "Bitcoin Signed Message:\n",
'blocks_target': 60 * 10, "blocks_target": 60 * 10,
'decimal_places': 8, "decimal_places": 8,
'mainnet': { "mainnet": {
'rpcport': 8332, "rpcport": 8332,
'pubkey_address': 0, "pubkey_address": 0,
'script_address': 5, "script_address": 5,
'key_prefix': 128, "key_prefix": 128,
'hrp': 'bc', "hrp": "bc",
'bip44': 0, "bip44": 0,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 18332, "rpcport": 18332,
'pubkey_address': 111, "pubkey_address": 111,
'script_address': 196, "script_address": 196,
'key_prefix': 239, "key_prefix": 239,
'hrp': 'tb', "hrp": "tb",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
'name': 'testnet3', "name": "testnet3",
},
"regtest": {
"rpcport": 18443,
"pubkey_address": 111,
"script_address": 196,
"key_prefix": 239,
"hrp": "bcrt",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 18443,
'pubkey_address': 111,
'script_address': 196,
'key_prefix': 239,
'hrp': 'bcrt',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.LTC: { Coins.LTC: {
'name': 'litecoin', "name": "litecoin",
'ticker': 'LTC', "ticker": "LTC",
'message_magic': 'Litecoin Signed Message:\n', "message_magic": "Litecoin Signed Message:\n",
'blocks_target': 60 * 1, "blocks_target": 60 * 1,
'decimal_places': 8, "decimal_places": 8,
'mainnet': { "mainnet": {
'rpcport': 9332, "rpcport": 9332,
'pubkey_address': 48, "pubkey_address": 48,
'script_address': 5, "script_address": 5,
'script_address2': 50, "script_address2": 50,
'key_prefix': 176, "key_prefix": 176,
'hrp': 'ltc', "hrp": "ltc",
'bip44': 2, "bip44": 2,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 19332, "rpcport": 19332,
'pubkey_address': 111, "pubkey_address": 111,
'script_address': 196, "script_address": 196,
'script_address2': 58, "script_address2": 58,
'key_prefix': 239, "key_prefix": 239,
'hrp': 'tltc', "hrp": "tltc",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
'name': 'testnet4', "name": "testnet4",
},
"regtest": {
"rpcport": 19443,
"pubkey_address": 111,
"script_address": 196,
"script_address2": 58,
"key_prefix": 239,
"hrp": "rltc",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 19443,
'pubkey_address': 111,
'script_address': 196,
'script_address2': 58,
'key_prefix': 239,
'hrp': 'rltc',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.DCR: { Coins.DCR: {
'name': 'decred', "name": "decred",
'ticker': 'DCR', "ticker": "DCR",
'message_magic': 'Decred Signed Message:\n', "message_magic": "Decred Signed Message:\n",
'blocks_target': 60 * 5, "blocks_target": 60 * 5,
'decimal_places': 8, "decimal_places": 8,
'mainnet': { "mainnet": {
'rpcport': 9109, "rpcport": 9109,
'pubkey_address': 0x073f, "pubkey_address": 0x073F,
'script_address': 0x071a, "script_address": 0x071A,
'key_prefix': 0x22de, "key_prefix": 0x22DE,
'bip44': 42, "bip44": 42,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 19109, "rpcport": 19109,
'pubkey_address': 0x0f21, "pubkey_address": 0x0F21,
'script_address': 0x0efc, "script_address": 0x0EFC,
'key_prefix': 0x230e, "key_prefix": 0x230E,
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
'name': 'testnet3', "name": "testnet3",
},
"regtest": { # simnet
"rpcport": 18656,
"pubkey_address": 0x0E91,
"script_address": 0x0E6C,
"key_prefix": 0x2307,
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': { # simnet
'rpcport': 18656,
'pubkey_address': 0x0e91,
'script_address': 0x0e6c,
'key_prefix': 0x2307,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.NMC: { Coins.NMC: {
'name': 'namecoin', "name": "namecoin",
'ticker': 'NMC', "ticker": "NMC",
'message_magic': 'Namecoin Signed Message:\n', "message_magic": "Namecoin Signed Message:\n",
'blocks_target': 60 * 10, "blocks_target": 60 * 10,
'decimal_places': 8, "decimal_places": 8,
'mainnet': { "mainnet": {
'rpcport': 8336, "rpcport": 8336,
'pubkey_address': 52, "pubkey_address": 52,
'script_address': 13, "script_address": 13,
'hrp': 'nc', "hrp": "nc",
'bip44': 7, "bip44": 7,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 18336, "rpcport": 18336,
'pubkey_address': 111, "pubkey_address": 111,
'script_address': 196, "script_address": 196,
'hrp': 'tn', "hrp": "tn",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
'name': 'testnet3', "name": "testnet3",
},
"regtest": {
"rpcport": 18443,
"pubkey_address": 111,
"script_address": 196,
"hrp": "ncrt",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 18443,
'pubkey_address': 111,
'script_address': 196,
'hrp': 'ncrt',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.XMR: { Coins.XMR: {
'name': 'monero', "name": "monero",
'ticker': 'XMR', "ticker": "XMR",
'client': 'xmr', "client": "xmr",
'decimal_places': 12, "decimal_places": 12,
'mainnet': { "mainnet": {
'rpcport': 18081, "rpcport": 18081,
'walletrpcport': 18082, "walletrpcport": 18082,
'min_amount': 100000, "min_amount": 100000,
'max_amount': 10000 * XMR_COIN, "max_amount": 10000 * XMR_COIN,
'address_prefix': 18, "address_prefix": 18,
}, },
'testnet': { "testnet": {
'rpcport': 28081, "rpcport": 28081,
'walletrpcport': 28082, "walletrpcport": 28082,
'min_amount': 100000, "min_amount": 100000,
'max_amount': 10000 * XMR_COIN, "max_amount": 10000 * XMR_COIN,
'address_prefix': 18, "address_prefix": 18,
},
"regtest": {
"rpcport": 18081,
"walletrpcport": 18082,
"min_amount": 100000,
"max_amount": 10000 * XMR_COIN,
"address_prefix": 18,
}, },
'regtest': {
'rpcport': 18081,
'walletrpcport': 18082,
'min_amount': 100000,
'max_amount': 10000 * XMR_COIN,
'address_prefix': 18,
}
}, },
Coins.WOW: { Coins.WOW: {
'name': 'wownero', "name": "wownero",
'ticker': 'WOW', "ticker": "WOW",
'client': 'wow', "client": "wow",
'decimal_places': 11, "decimal_places": 11,
'mainnet': { "mainnet": {
'rpcport': 34568, "rpcport": 34568,
'walletrpcport': 34572, # todo "walletrpcport": 34572, # todo
'min_amount': 100000, "min_amount": 100000,
'max_amount': 10000 * WOW_COIN, "max_amount": 10000 * WOW_COIN,
'address_prefix': 4146, "address_prefix": 4146,
}, },
'testnet': { "testnet": {
'rpcport': 44568, "rpcport": 44568,
'walletrpcport': 44572, "walletrpcport": 44572,
'min_amount': 100000, "min_amount": 100000,
'max_amount': 10000 * WOW_COIN, "max_amount": 10000 * WOW_COIN,
'address_prefix': 4146, "address_prefix": 4146,
},
"regtest": {
"rpcport": 54568,
"walletrpcport": 54572,
"min_amount": 100000,
"max_amount": 10000 * WOW_COIN,
"address_prefix": 4146,
}, },
'regtest': {
'rpcport': 54568,
'walletrpcport': 54572,
'min_amount': 100000,
'max_amount': 10000 * WOW_COIN,
'address_prefix': 4146,
}
}, },
Coins.PIVX: { Coins.PIVX: {
'name': 'pivx', "name": "pivx",
'ticker': 'PIVX', "ticker": "PIVX",
'display_name': 'PIVX', "display_name": "PIVX",
'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_cltv": True,
'has_csv': False, "has_csv": False,
'has_segwit': False, "has_segwit": False,
'mainnet': { "mainnet": {
'rpcport': 51473, "rpcport": 51473,
'pubkey_address': 30, "pubkey_address": 30,
'script_address': 13, "script_address": 13,
'key_prefix': 212, "key_prefix": 212,
'bip44': 119, "bip44": 119,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 51475, "rpcport": 51475,
'pubkey_address': 139, "pubkey_address": 139,
'script_address': 19, "script_address": 19,
'key_prefix': 239, "key_prefix": 239,
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
'name': 'testnet4', "name": "testnet4",
},
"regtest": {
"rpcport": 51477,
"pubkey_address": 139,
"script_address": 19,
"key_prefix": 239,
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 51477,
'pubkey_address': 139,
'script_address': 19,
'key_prefix': 239,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.DASH: { Coins.DASH: {
'name': 'dash', "name": "dash",
'ticker': 'DASH', "ticker": "DASH",
'message_magic': 'DarkCoin Signed Message:\n', "message_magic": "DarkCoin Signed Message:\n",
'blocks_target': 60 * 2.5, "blocks_target": 60 * 2.5,
'decimal_places': 8, "decimal_places": 8,
'has_csv': True, "has_csv": True,
'has_segwit': False, "has_segwit": False,
'mainnet': { "mainnet": {
'rpcport': 9998, "rpcport": 9998,
'pubkey_address': 76, "pubkey_address": 76,
'script_address': 16, "script_address": 16,
'key_prefix': 204, "key_prefix": 204,
'hrp': '', "hrp": "",
'bip44': 5, "bip44": 5,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 19998, "rpcport": 19998,
'pubkey_address': 140, "pubkey_address": 140,
'script_address': 19, "script_address": 19,
'key_prefix': 239, "key_prefix": 239,
'hrp': '', "hrp": "",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
},
"regtest": {
"rpcport": 18332,
"pubkey_address": 140,
"script_address": 19,
"key_prefix": 239,
"hrp": "",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 18332,
'pubkey_address': 140,
'script_address': 19,
'key_prefix': 239,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.FIRO: { Coins.FIRO: {
'name': 'firo', "name": "firo",
'ticker': 'FIRO', "ticker": "FIRO",
'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_cltv': False, "has_cltv": False,
'has_csv': False, "has_csv": False,
'has_segwit': False, "has_segwit": False,
'mainnet': { "mainnet": {
'rpcport': 8888, "rpcport": 8888,
'pubkey_address': 82, "pubkey_address": 82,
'script_address': 7, "script_address": 7,
'key_prefix': 210, "key_prefix": 210,
'hrp': '', "hrp": "",
'bip44': 136, "bip44": 136,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 18888, "rpcport": 18888,
'pubkey_address': 65, "pubkey_address": 65,
'script_address': 178, "script_address": 178,
'key_prefix': 185, "key_prefix": 185,
'hrp': '', "hrp": "",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
},
"regtest": {
"rpcport": 28888,
"pubkey_address": 65,
"script_address": 178,
"key_prefix": 239,
"hrp": "",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 28888,
'pubkey_address': 65,
'script_address': 178,
'key_prefix': 239,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.NAV: { Coins.NAV: {
'name': 'navcoin', "name": "navcoin",
'ticker': 'NAV', "ticker": "NAV",
'message_magic': 'Navcoin Signed Message:\n', "message_magic": "Navcoin Signed Message:\n",
'blocks_target': 30, "blocks_target": 30,
'decimal_places': 8, "decimal_places": 8,
'has_csv': True, "has_csv": True,
'has_segwit': True, "has_segwit": True,
'mainnet': { "mainnet": {
'rpcport': 44444, "rpcport": 44444,
'pubkey_address': 53, "pubkey_address": 53,
'script_address': 85, "script_address": 85,
'key_prefix': 150, "key_prefix": 150,
'hrp': '', "hrp": "",
'bip44': 130, "bip44": 130,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 44445, "rpcport": 44445,
'pubkey_address': 111, "pubkey_address": 111,
'script_address': 196, "script_address": 196,
'key_prefix': 239, "key_prefix": 239,
'hrp': '', "hrp": "",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "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,
}, },
'regtest': {
'rpcport': 44446,
'pubkey_address': 111,
'script_address': 196,
'key_prefix': 239,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
Coins.BCH: { Coins.BCH: {
'name': 'bitcoincash', "name": "bitcoincash",
'ticker': 'BCH', "ticker": "BCH",
'display_name': 'Bitcoin Cash', "display_name": "Bitcoin Cash",
'message_magic': 'Bitcoin Signed Message:\n', "message_magic": "Bitcoin Signed Message:\n",
'blocks_target': 60 * 2, "blocks_target": 60 * 2,
'decimal_places': 8, "decimal_places": 8,
'has_cltv': True, "has_cltv": True,
'has_csv': True, "has_csv": True,
'has_segwit': False, "has_segwit": False,
'cli_binname': 'bitcoin-cli', "cli_binname": "bitcoin-cli",
'core_binname': 'bitcoind', "core_binname": "bitcoind",
'mainnet': { "mainnet": {
'rpcport': 8332, "rpcport": 8332,
'pubkey_address': 0, "pubkey_address": 0,
'script_address': 5, "script_address": 5,
'key_prefix': 128, "key_prefix": 128,
'hrp': 'bitcoincash', "hrp": "bitcoincash",
'bip44': 0, "bip44": 0,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
}, },
'testnet': { "testnet": {
'rpcport': 18332, "rpcport": 18332,
'pubkey_address': 111, "pubkey_address": 111,
'script_address': 196, "script_address": 196,
'key_prefix': 239, "key_prefix": 239,
'hrp': 'bchtest', "hrp": "bchtest",
'bip44': 1, "bip44": 1,
'min_amount': 1000, "min_amount": 1000,
'max_amount': 100000 * COIN, "max_amount": 100000 * COIN,
'name': 'testnet3', "name": "testnet3",
},
"regtest": {
"rpcport": 18443,
"pubkey_address": 111,
"script_address": 196,
"key_prefix": 239,
"hrp": "bchreg",
"bip44": 1,
"min_amount": 1000,
"max_amount": 100000 * COIN,
}, },
'regtest': {
'rpcport': 18443,
'pubkey_address': 111,
'script_address': 196,
'key_prefix': 239,
'hrp': 'bchreg',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}, },
} }
ticker_map = {} ticker_map = {}
for c, params in chainparams.items(): for c, params in chainparams.items():
ticker_map[params['ticker'].lower()] = c ticker_map[params["ticker"].lower()] = c
def getCoinIdFromTicker(ticker: str) -> str: def getCoinIdFromTicker(ticker: str) -> str:
try: try:
return ticker_map[ticker.lower()] return ticker_map[ticker.lower()]
except Exception: except Exception:
raise ValueError('Unknown coin') raise ValueError("Unknown coin")

View File

@@ -6,35 +6,47 @@
import os import os
CONFIG_FILENAME = 'basicswap.json' CONFIG_FILENAME = "basicswap.json"
BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', os.path.join('~', '.basicswap')) BASICSWAP_DATADIR = os.getenv("BASICSWAP_DATADIR", os.path.join("~", ".basicswap"))
DEFAULT_ALLOW_CORS = False DEFAULT_ALLOW_CORS = False
TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap')) TEST_DATADIRS = os.path.expanduser(os.getenv("DATADIRS", "/tmp/basicswap"))
DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', os.path.join('~', '.basicswap', 'bin'))) DEFAULT_TEST_BINDIR = os.path.expanduser(
os.getenv("DEFAULT_TEST_BINDIR", os.path.join("~", ".basicswap", "bin"))
)
bin_suffix = ('.exe' if os.name == 'nt' else '') bin_suffix = ".exe" if os.name == "nt" else ""
PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'particl'))) PARTICL_BINDIR = os.path.expanduser(
PARTICLD = os.getenv('PARTICLD', 'particld' + bin_suffix) os.getenv("PARTICL_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "particl"))
PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + bin_suffix) )
PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx' + bin_suffix) PARTICLD = os.getenv("PARTICLD", "particld" + bin_suffix)
PARTICL_CLI = os.getenv("PARTICL_CLI", "particl-cli" + bin_suffix)
PARTICL_TX = os.getenv("PARTICL_TX", "particl-tx" + bin_suffix)
BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'bitcoin'))) BITCOIN_BINDIR = os.path.expanduser(
BITCOIND = os.getenv('BITCOIND', 'bitcoind' + bin_suffix) os.getenv("BITCOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "bitcoin"))
BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + bin_suffix) )
BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx' + bin_suffix) BITCOIND = os.getenv("BITCOIND", "bitcoind" + bin_suffix)
BITCOIN_CLI = os.getenv("BITCOIN_CLI", "bitcoin-cli" + bin_suffix)
BITCOIN_TX = os.getenv("BITCOIN_TX", "bitcoin-tx" + bin_suffix)
LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'litecoin'))) LITECOIN_BINDIR = os.path.expanduser(
LITECOIND = os.getenv('LITECOIND', 'litecoind' + bin_suffix) os.getenv("LITECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "litecoin"))
LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + bin_suffix) )
LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + bin_suffix) LITECOIND = os.getenv("LITECOIND", "litecoind" + bin_suffix)
LITECOIN_CLI = os.getenv("LITECOIN_CLI", "litecoin-cli" + bin_suffix)
LITECOIN_TX = os.getenv("LITECOIN_TX", "litecoin-tx" + bin_suffix)
NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'namecoin'))) NAMECOIN_BINDIR = os.path.expanduser(
NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix) os.getenv("NAMECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "namecoin"))
NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix) )
NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix) NAMECOIND = os.getenv("NAMECOIND", "namecoind" + bin_suffix)
NAMECOIN_CLI = os.getenv("NAMECOIN_CLI", "namecoin-cli" + bin_suffix)
NAMECOIN_TX = os.getenv("NAMECOIN_TX", "namecoin-tx" + bin_suffix)
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero'))) XMR_BINDIR = os.path.expanduser(
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix) os.getenv("XMR_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "monero"))
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) )
XMRD = os.getenv("XMRD", "monerod" + bin_suffix)
XMR_WALLET_RPC = os.getenv("XMR_WALLET_RPC", "monero-wallet-rpc" + bin_suffix)
# NOTE: Adding coin definitions here is deprecated. Please add in coin test file. # NOTE: Adding coin definitions here is deprecated. Please add in coin test file.

View File

@@ -25,34 +25,34 @@ class Concepts(IntEnum):
def strConcepts(state): def strConcepts(state):
if state == Concepts.OFFER: if state == Concepts.OFFER:
return 'Offer' return "Offer"
if state == Concepts.BID: if state == Concepts.BID:
return 'Bid' return "Bid"
if state == Concepts.NETWORK_MESSAGE: if state == Concepts.NETWORK_MESSAGE:
return 'Network Message' return "Network Message"
return 'Unknown' return "Unknown"
def pack_state(new_state: int, now: int) -> bytes: def pack_state(new_state: int, now: int) -> bytes:
return int(new_state).to_bytes(4, 'little') + now.to_bytes(8, 'little') return int(new_state).to_bytes(4, "little") + now.to_bytes(8, "little")
class DBKVInt(Base): class DBKVInt(Base):
__tablename__ = 'kv_int' __tablename__ = "kv_int"
key = sa.Column(sa.String, primary_key=True) key = sa.Column(sa.String, primary_key=True)
value = sa.Column(sa.Integer) value = sa.Column(sa.Integer)
class DBKVString(Base): class DBKVString(Base):
__tablename__ = 'kv_string' __tablename__ = "kv_string"
key = sa.Column(sa.String, primary_key=True) key = sa.Column(sa.String, primary_key=True)
value = sa.Column(sa.String) value = sa.Column(sa.String)
class Offer(Base): class Offer(Base):
__tablename__ = 'offers' __tablename__ = "offers"
offer_id = sa.Column(sa.LargeBinary, primary_key=True) offer_id = sa.Column(sa.LargeBinary, primary_key=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -89,7 +89,9 @@ class Offer(Base):
# Local fields # Local fields
auto_accept_bids = sa.Column(sa.Boolean) auto_accept_bids = sa.Column(sa.Boolean)
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
security_token = sa.Column(sa.LargeBinary) security_token = sa.Column(sa.LargeBinary)
bid_reversed = sa.Column(sa.Boolean) bid_reversed = sa.Column(sa.Boolean)
@@ -106,10 +108,10 @@ class Offer(Base):
class Bid(Base): class Bid(Base):
__tablename__ = 'bids' __tablename__ = "bids"
bid_id = sa.Column(sa.LargeBinary, primary_key=True) bid_id = sa.Column(sa.LargeBinary, primary_key=True)
offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id')) offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey("offers.offer_id"))
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
protocol_version = sa.Column(sa.Integer) protocol_version = sa.Column(sa.Integer)
@@ -121,13 +123,17 @@ class Bid(Base):
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) 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)
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
pkhash_buyer = sa.Column(sa.LargeBinary) pkhash_buyer = sa.Column(sa.LargeBinary)
pkhash_buyer_to = sa.Column(sa.LargeBinary) # Used for the ptx if coin pubkey hashes differ pkhash_buyer_to = sa.Column(
sa.LargeBinary
) # Used for the ptx if coin pubkey hashes differ
amount = sa.Column(sa.BigInteger) amount = sa.Column(sa.BigInteger)
rate = sa.Column(sa.BigInteger) rate = sa.Column(sa.BigInteger)
@@ -149,8 +155,12 @@ class Bid(Base):
debug_ind = sa.Column(sa.Integer) debug_ind = sa.Column(sa.Integer)
security_token = sa.Column(sa.LargeBinary) security_token = sa.Column(sa.LargeBinary)
chain_a_height_start = sa.Column(sa.Integer) # Height of script chain before the swap chain_a_height_start = sa.Column(
chain_b_height_start = sa.Column(sa.Integer) # Height of scriptless chain before the swap sa.Integer
) # Height of script chain before the swap
chain_b_height_start = sa.Column(
sa.Integer
) # Height of scriptless chain before the swap
reject_code = sa.Column(sa.Integer) reject_code = sa.Column(sa.Integer)
@@ -199,12 +209,12 @@ class Bid(Base):
class SwapTx(Base): class SwapTx(Base):
__tablename__ = 'transactions' __tablename__ = "transactions"
bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey('bids.bid_id')) bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey("bids.bid_id"))
tx_type = sa.Column(sa.Integer) # TxTypes tx_type = sa.Column(sa.Integer) # TxTypes
__table_args__ = ( __table_args__ = (
sa.PrimaryKeyConstraint('bid_id', 'tx_type'), sa.PrimaryKeyConstraint("bid_id", "tx_type"),
{}, {},
) )
@@ -240,7 +250,7 @@ class SwapTx(Base):
class PrefundedTx(Base): class PrefundedTx(Base):
__tablename__ = 'prefunded_transactions' __tablename__ = "prefunded_transactions"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -253,7 +263,7 @@ class PrefundedTx(Base):
class PooledAddress(Base): class PooledAddress(Base):
__tablename__ = 'addresspool' __tablename__ = "addresspool"
addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
coin_type = sa.Column(sa.Integer) coin_type = sa.Column(sa.Integer)
@@ -263,13 +273,13 @@ class PooledAddress(Base):
class SentOffer(Base): class SentOffer(Base):
__tablename__ = 'sentoffers' __tablename__ = "sentoffers"
offer_id = sa.Column(sa.LargeBinary, primary_key=True) offer_id = sa.Column(sa.LargeBinary, primary_key=True)
class SmsgAddress(Base): class SmsgAddress(Base):
__tablename__ = 'smsgaddresses' __tablename__ = "smsgaddresses"
addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -281,7 +291,7 @@ class SmsgAddress(Base):
class Action(Base): class Action(Base):
__tablename__ = 'actions' __tablename__ = "actions"
action_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) action_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -293,7 +303,7 @@ class Action(Base):
class EventLog(Base): class EventLog(Base):
__tablename__ = 'eventlog' __tablename__ = "eventlog"
event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -303,32 +313,38 @@ class EventLog(Base):
event_type = sa.Column(sa.Integer) event_type = sa.Column(sa.Integer)
event_msg = sa.Column(sa.String) event_msg = sa.Column(sa.String)
__table_args__ = (sa.Index('main_index', 'linked_type', 'linked_id'), ) __table_args__ = (sa.Index("main_index", "linked_type", "linked_id"),)
class XmrOffer(Base): class XmrOffer(Base):
__tablename__ = 'xmr_offers' __tablename__ = "xmr_offers"
# TODO: Merge to Offer # TODO: Merge to Offer
swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id')) offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey("offers.offer_id"))
a_fee_rate = sa.Column(sa.BigInteger) # Chain a fee rate a_fee_rate = sa.Column(sa.BigInteger) # Chain a fee rate
b_fee_rate = sa.Column(sa.BigInteger) # Chain b fee rate b_fee_rate = sa.Column(sa.BigInteger) # Chain b fee rate
lock_time_1 = sa.Column(sa.Integer) # Delay before the chain a lock refund tx can be mined lock_time_1 = sa.Column(
lock_time_2 = sa.Column(sa.Integer) # Delay before the follower can spend from the chain a lock refund tx sa.Integer
) # Delay before the chain a lock refund tx can be mined
lock_time_2 = sa.Column(
sa.Integer
) # Delay before the follower can spend from the chain a lock refund tx
class XmrSwap(Base): class XmrSwap(Base):
__tablename__ = 'xmr_swaps' __tablename__ = "xmr_swaps"
swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey('bids.bid_id')) bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey("bids.bid_id"))
contract_count = sa.Column(sa.Integer) contract_count = sa.Column(sa.Integer)
dest_af = sa.Column(sa.LargeBinary) # Destination for coin A amount to follower when swap completes successfully dest_af = sa.Column(
sa.LargeBinary
) # Destination for coin A amount to follower when swap completes successfully
pkal = sa.Column(sa.LargeBinary) pkal = sa.Column(sa.LargeBinary)
pkasl = sa.Column(sa.LargeBinary) pkasl = sa.Column(sa.LargeBinary)
@@ -376,13 +392,15 @@ class XmrSwap(Base):
al_lock_spend_tx_esig = sa.Column(sa.LargeBinary) al_lock_spend_tx_esig = sa.Column(sa.LargeBinary)
kal_sig = sa.Column(sa.LargeBinary) kal_sig = sa.Column(sa.LargeBinary)
a_lock_refund_swipe_tx = sa.Column(sa.LargeBinary) # Follower spends script coin lock refund tx a_lock_refund_swipe_tx = sa.Column(
sa.LargeBinary
) # Follower spends script coin lock refund tx
b_lock_tx_id = sa.Column(sa.LargeBinary) b_lock_tx_id = sa.Column(sa.LargeBinary)
class XmrSplitData(Base): class XmrSplitData(Base):
__tablename__ = 'xmr_split_data' __tablename__ = "xmr_split_data"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
addr_from = sa.Column(sa.String) addr_from = sa.Column(sa.String)
@@ -393,11 +411,13 @@ class XmrSplitData(Base):
dleag = sa.Column(sa.LargeBinary) dleag = sa.Column(sa.LargeBinary)
created_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger)
__table_args__ = (sa.UniqueConstraint('bid_id', 'msg_type', 'msg_sequence', name='uc_1'),) __table_args__ = (
sa.UniqueConstraint("bid_id", "msg_type", "msg_sequence", name="uc_1"),
)
class RevokedMessage(Base): class RevokedMessage(Base):
__tablename__ = 'revoked_messages' __tablename__ = "revoked_messages"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -407,7 +427,7 @@ class RevokedMessage(Base):
class Wallets(Base): class Wallets(Base):
__tablename__ = 'wallets' __tablename__ = "wallets"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -419,7 +439,7 @@ class Wallets(Base):
class KnownIdentity(Base): class KnownIdentity(Base):
__tablename__ = 'knownidentities' __tablename__ = "knownidentities"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -441,7 +461,7 @@ class KnownIdentity(Base):
class AutomationStrategy(Base): class AutomationStrategy(Base):
__tablename__ = 'automationstrategies' __tablename__ = "automationstrategies"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -457,7 +477,7 @@ class AutomationStrategy(Base):
class AutomationLink(Base): class AutomationLink(Base):
__tablename__ = 'automationlinks' __tablename__ = "automationlinks"
# Contains per order/bid options # Contains per order/bid options
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
@@ -474,11 +494,11 @@ class AutomationLink(Base):
note = sa.Column(sa.String) note = sa.Column(sa.String)
created_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger)
__table_args__ = (sa.Index('linked_index', 'linked_type', 'linked_id'), ) __table_args__ = (sa.Index("linked_index", "linked_type", "linked_id"),)
class History(Base): class History(Base):
__tablename__ = 'history' __tablename__ = "history"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
concept_type = sa.Column(sa.Integer) concept_type = sa.Column(sa.Integer)
@@ -489,7 +509,7 @@ class History(Base):
class BidState(Base): class BidState(Base):
__tablename__ = 'bidstates' __tablename__ = "bidstates"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -505,7 +525,7 @@ class BidState(Base):
class Notification(Base): class Notification(Base):
__tablename__ = 'notifications' __tablename__ = "notifications"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -515,7 +535,7 @@ class Notification(Base):
class MessageLink(Base): class MessageLink(Base):
__tablename__ = 'message_links' __tablename__ = "message_links"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
@@ -531,7 +551,7 @@ class MessageLink(Base):
class CheckedBlock(Base): class CheckedBlock(Base):
__tablename__ = 'checkedblocks' __tablename__ = "checkedblocks"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
created_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger)

View File

@@ -14,7 +14,8 @@ from .db import (
Concepts, Concepts,
AutomationStrategy, AutomationStrategy,
CURRENT_DB_VERSION, CURRENT_DB_VERSION,
CURRENT_DB_DATA_VERSION) CURRENT_DB_DATA_VERSION,
)
from .basicswap_util import ( from .basicswap_util import (
BidStates, BidStates,
@@ -30,7 +31,11 @@ def upgradeDatabaseData(self, data_version):
if data_version >= CURRENT_DB_DATA_VERSION: if data_version >= CURRENT_DB_DATA_VERSION:
return return
self.log.info('Upgrading database records from version %d to %d.', data_version, CURRENT_DB_DATA_VERSION) self.log.info(
"Upgrading database records from version %d to %d.",
data_version,
CURRENT_DB_DATA_VERSION,
)
with self.mxDB: with self.mxDB:
try: try:
session = scoped_session(self.session_factory) session = scoped_session(self.session_factory)
@@ -38,26 +43,35 @@ def upgradeDatabaseData(self, data_version):
now = int(time.time()) now = int(time.time())
if data_version < 1: if data_version < 1:
session.add(AutomationStrategy( session.add(
AutomationStrategy(
active_ind=1, active_ind=1,
label='Accept All', label="Accept All",
type_ind=Concepts.OFFER, type_ind=Concepts.OFFER,
data=json.dumps({'exact_rate_only': True, data=json.dumps(
'max_concurrent_bids': 5}).encode('utf-8'), {"exact_rate_only": True, "max_concurrent_bids": 5}
).encode("utf-8"),
only_known_identities=False, only_known_identities=False,
created_at=now)) created_at=now,
session.add(AutomationStrategy( )
)
session.add(
AutomationStrategy(
active_ind=1, active_ind=1,
label='Accept Known', label="Accept Known",
type_ind=Concepts.OFFER, type_ind=Concepts.OFFER,
data=json.dumps({'exact_rate_only': True, data=json.dumps(
'max_concurrent_bids': 5}).encode('utf-8'), {"exact_rate_only": True, "max_concurrent_bids": 5}
).encode("utf-8"),
only_known_identities=True, only_known_identities=True,
note='Accept bids from identities with previously successful swaps only', note="Accept bids from identities with previously successful swaps only",
created_at=now)) created_at=now,
)
)
for state in BidStates: for state in BidStates:
session.add(BidState( session.add(
BidState(
active_ind=1, active_ind=1,
state_id=int(state), state_id=int(state),
in_progress=isActiveBidState(state), in_progress=isActiveBidState(state),
@@ -65,25 +79,47 @@ def upgradeDatabaseData(self, data_version):
swap_failed=isFailingBidState(state), swap_failed=isFailingBidState(state),
swap_ended=isFinalBidState(state), swap_ended=isFinalBidState(state),
label=strBidState(state), label=strBidState(state),
created_at=now)) created_at=now,
)
)
if data_version > 0 and data_version < 2: if data_version > 0 and data_version < 2:
for state in (BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX): for state in (
session.add(BidState( BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS,
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
):
session.add(
BidState(
active_ind=1, active_ind=1,
state_id=int(state), state_id=int(state),
in_progress=isActiveBidState(state), in_progress=isActiveBidState(state),
label=strBidState(state), label=strBidState(state),
created_at=now)) created_at=now,
)
)
if data_version > 0 and data_version < 3: if data_version > 0 and data_version < 3:
for state in BidStates: for state in BidStates:
in_error = isErrorBidState(state) in_error = isErrorBidState(state)
swap_failed = isFailingBidState(state) swap_failed = isFailingBidState(state)
swap_ended = isFinalBidState(state) swap_ended = isFinalBidState(state)
session.execute(text('UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id', {'in_error': in_error, 'swap_failed': swap_failed, 'swap_ended': swap_ended, 'state_id': int(state)})) session.execute(
text(
"UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id",
{
"in_error": in_error,
"swap_failed": swap_failed,
"swap_ended": swap_ended,
"state_id": int(state),
},
)
)
if data_version > 0 and data_version < 4: if data_version > 0 and data_version < 4:
for state in (BidStates.BID_REQUEST_SENT, BidStates.BID_REQUEST_ACCEPTED): for state in (
session.add(BidState( BidStates.BID_REQUEST_SENT,
BidStates.BID_REQUEST_ACCEPTED,
):
session.add(
BidState(
active_ind=1, active_ind=1,
state_id=int(state), state_id=int(state),
in_progress=isActiveBidState(state), in_progress=isActiveBidState(state),
@@ -91,12 +127,16 @@ def upgradeDatabaseData(self, data_version):
swap_failed=isFailingBidState(state), swap_failed=isFailingBidState(state),
swap_ended=isFinalBidState(state), swap_ended=isFinalBidState(state),
label=strBidState(state), label=strBidState(state),
created_at=now)) created_at=now,
)
)
self.db_data_version = CURRENT_DB_DATA_VERSION self.db_data_version = CURRENT_DB_DATA_VERSION
self.setIntKV('db_data_version', self.db_data_version, session) self.setIntKV("db_data_version", self.db_data_version, session)
session.commit() session.commit()
self.log.info('Upgraded database records to version {}'.format(self.db_data_version)) self.log.info(
"Upgraded database records to version {}".format(self.db_data_version)
)
finally: finally:
session.close() session.close()
session.remove() session.remove()
@@ -106,23 +146,31 @@ def upgradeDatabase(self, db_version):
if db_version >= CURRENT_DB_VERSION: if db_version >= CURRENT_DB_VERSION:
return return
self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION) self.log.info(
"Upgrading database from version %d to %d.", db_version, CURRENT_DB_VERSION
)
while True: while True:
session = scoped_session(self.session_factory) session = scoped_session(self.session_factory)
current_version = db_version current_version = db_version
if current_version == 6: if current_version == 6:
session.execute(text('ALTER TABLE bids ADD COLUMN security_token BLOB')) session.execute(text("ALTER TABLE bids ADD COLUMN security_token BLOB"))
session.execute(text('ALTER TABLE offers ADD COLUMN security_token BLOB')) session.execute(text("ALTER TABLE offers ADD COLUMN security_token BLOB"))
db_version += 1 db_version += 1
elif current_version == 7: elif current_version == 7:
session.execute(text('ALTER TABLE transactions ADD COLUMN block_hash BLOB')) session.execute(text("ALTER TABLE transactions ADD COLUMN block_hash BLOB"))
session.execute(text('ALTER TABLE transactions ADD COLUMN block_height INTEGER')) session.execute(
session.execute(text('ALTER TABLE transactions ADD COLUMN block_time INTEGER')) text("ALTER TABLE transactions ADD COLUMN block_height INTEGER")
)
session.execute(
text("ALTER TABLE transactions ADD COLUMN block_time INTEGER")
)
db_version += 1 db_version += 1
elif current_version == 8: elif current_version == 8:
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE wallets ( CREATE TABLE wallets (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
coin_id INTEGER, coin_id INTEGER,
@@ -130,30 +178,48 @@ def upgradeDatabase(self, db_version):
wallet_data VARCHAR, wallet_data VARCHAR,
balance_type INTEGER, balance_type INTEGER,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
db_version += 1 db_version += 1
elif current_version == 9: elif current_version == 9:
session.execute(text('ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR')) session.execute(text("ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR"))
db_version += 1 db_version += 1
elif current_version == 10: elif current_version == 10:
session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER')) session.execute(
session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER')) text("ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER")
session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR')) )
session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR')) session.execute(
session.execute(text('UPDATE smsgaddresses SET active_ind = 1, created_at = 1')) text("ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER")
)
session.execute(text("ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR"))
session.execute(text("ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR"))
session.execute(
text("UPDATE smsgaddresses SET active_ind = 1, created_at = 1")
)
session.execute(text('ALTER TABLE offers ADD COLUMN addr_to VARCHAR')) session.execute(text("ALTER TABLE offers ADD COLUMN addr_to VARCHAR"))
session.execute(text(f'UPDATE offers SET addr_to = "{self.network_addr}"')) session.execute(text(f'UPDATE offers SET addr_to = "{self.network_addr}"'))
db_version += 1 db_version += 1
elif current_version == 11: elif current_version == 11:
session.execute(text('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER')) session.execute(
session.execute(text('ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER')) text("ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER")
session.execute(text('ALTER TABLE bids ADD COLUMN protocol_version INTEGER')) )
session.execute(text('ALTER TABLE offers ADD COLUMN protocol_version INTEGER')) session.execute(
session.execute(text('ALTER TABLE transactions ADD COLUMN tx_data BLOB')) text("ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER")
)
session.execute(
text("ALTER TABLE bids ADD COLUMN protocol_version INTEGER")
)
session.execute(
text("ALTER TABLE offers ADD COLUMN protocol_version INTEGER")
)
session.execute(text("ALTER TABLE transactions ADD COLUMN tx_data BLOB"))
db_version += 1 db_version += 1
elif current_version == 12: elif current_version == 12:
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE knownidentities ( CREATE TABLE knownidentities (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
address VARCHAR, address VARCHAR,
@@ -168,15 +234,23 @@ def upgradeDatabase(self, db_version):
note VARCHAR, note VARCHAR,
updated_at BIGINT, updated_at BIGINT,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
session.execute(text('ALTER TABLE bids ADD COLUMN reject_code INTEGER')) )
session.execute(text('ALTER TABLE bids ADD COLUMN rate INTEGER')) )
session.execute(text('ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER')) session.execute(text("ALTER TABLE bids ADD COLUMN reject_code INTEGER"))
session.execute(text('ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER')) session.execute(text("ALTER TABLE bids ADD COLUMN rate INTEGER"))
session.execute(
text("ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER")
)
session.execute(
text("ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER")
)
db_version += 1 db_version += 1
elif current_version == 13: elif current_version == 13:
db_version += 1 db_version += 1
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE automationstrategies ( CREATE TABLE automationstrategies (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
active_ind INTEGER, active_ind INTEGER,
@@ -188,9 +262,13 @@ def upgradeDatabase(self, db_version):
note VARCHAR, note VARCHAR,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE automationlinks ( CREATE TABLE automationlinks (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
active_ind INTEGER, active_ind INTEGER,
@@ -205,9 +283,13 @@ def upgradeDatabase(self, db_version):
note VARCHAR, note VARCHAR,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE history ( CREATE TABLE history (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
concept_type INTEGER, concept_type INTEGER,
@@ -216,9 +298,13 @@ def upgradeDatabase(self, db_version):
note VARCHAR, note VARCHAR,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE bidstates ( CREATE TABLE bidstates (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
active_ind INTEGER, active_ind INTEGER,
@@ -228,31 +314,53 @@ def upgradeDatabase(self, db_version):
note VARCHAR, note VARCHAR,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
session.execute(text('ALTER TABLE wallets ADD COLUMN active_ind INTEGER')) session.execute(text("ALTER TABLE wallets ADD COLUMN active_ind INTEGER"))
session.execute(text('ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER')) session.execute(
session.execute(text('ALTER TABLE eventqueue RENAME TO actions')) text("ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER")
session.execute(text('ALTER TABLE actions RENAME COLUMN event_id TO action_id')) )
session.execute(text('ALTER TABLE actions RENAME COLUMN event_type TO action_type')) session.execute(text("ALTER TABLE eventqueue RENAME TO actions"))
session.execute(text('ALTER TABLE actions RENAME COLUMN event_data TO action_data')) session.execute(
text("ALTER TABLE actions RENAME COLUMN event_id TO action_id")
)
session.execute(
text("ALTER TABLE actions RENAME COLUMN event_type TO action_type")
)
session.execute(
text("ALTER TABLE actions RENAME COLUMN event_data TO action_data")
)
elif current_version == 14: elif current_version == 14:
db_version += 1 db_version += 1
session.execute(text('ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB')) session.execute(
session.execute(text('ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id')) text("ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB")
)
session.execute(
text(
"ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id"
)
)
elif current_version == 15: elif current_version == 15:
db_version += 1 db_version += 1
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE notifications ( CREATE TABLE notifications (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
active_ind INTEGER, active_ind INTEGER,
event_type INTEGER, event_type INTEGER,
event_data BLOB, event_data BLOB,
created_at BIGINT, created_at BIGINT,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
elif current_version == 16: elif current_version == 16:
db_version += 1 db_version += 1
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE prefunded_transactions ( CREATE TABLE prefunded_transactions (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
active_ind INTEGER, active_ind INTEGER,
@@ -262,25 +370,43 @@ def upgradeDatabase(self, db_version):
tx_type INTEGER, tx_type INTEGER,
tx_data BLOB, tx_data BLOB,
used_by BLOB, used_by BLOB,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
)
)
elif current_version == 17: elif current_version == 17:
db_version += 1 db_version += 1
session.execute(text('ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER')) session.execute(
session.execute(text('ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER')) text(
session.execute(text('ALTER TABLE knownidentities ADD COLUMN data BLOB')) "ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER"
session.execute(text('UPDATE knownidentities SET active_ind = 1')) )
)
session.execute(
text(
"ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER"
)
)
session.execute(text("ALTER TABLE knownidentities ADD COLUMN data BLOB"))
session.execute(text("UPDATE knownidentities SET active_ind = 1"))
elif current_version == 18: elif current_version == 18:
db_version += 1 db_version += 1
session.execute(text('ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING')) session.execute(
session.execute(text('ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING')) text("ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING")
)
session.execute(
text("ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING")
)
elif current_version == 19: elif current_version == 19:
db_version += 1 db_version += 1
session.execute(text('ALTER TABLE bidstates ADD COLUMN in_error INTEGER')) session.execute(text("ALTER TABLE bidstates ADD COLUMN in_error INTEGER"))
session.execute(text('ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER')) session.execute(
session.execute(text('ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER')) text("ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER")
)
session.execute(text("ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER"))
elif current_version == 20: elif current_version == 20:
db_version += 1 db_version += 1
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE message_links ( CREATE TABLE message_links (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
active_ind INTEGER, active_ind INTEGER,
@@ -292,18 +418,22 @@ def upgradeDatabase(self, db_version):
msg_type INTEGER, msg_type INTEGER,
msg_sequence INTEGER, msg_sequence INTEGER,
msg_id BLOB, msg_id BLOB,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
session.execute(text('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER')) )
)
session.execute(text("ALTER TABLE offers ADD COLUMN bid_reversed INTEGER"))
elif current_version == 21: elif current_version == 21:
db_version += 1 db_version += 1
session.execute(text('ALTER TABLE offers ADD COLUMN proof_utxos BLOB')) session.execute(text("ALTER TABLE offers ADD COLUMN proof_utxos BLOB"))
session.execute(text('ALTER TABLE bids ADD COLUMN proof_utxos BLOB')) session.execute(text("ALTER TABLE bids ADD COLUMN proof_utxos BLOB"))
elif current_version == 22: elif current_version == 22:
db_version += 1 db_version += 1
session.execute(text('ALTER TABLE offers ADD COLUMN amount_to INTEGER')) session.execute(text("ALTER TABLE offers ADD COLUMN amount_to INTEGER"))
elif current_version == 23: elif current_version == 23:
db_version += 1 db_version += 1
session.execute(text(''' session.execute(
text(
"""
CREATE TABLE checkedblocks ( CREATE TABLE checkedblocks (
record_id INTEGER NOT NULL, record_id INTEGER NOT NULL,
created_at BIGINT, created_at BIGINT,
@@ -311,17 +441,19 @@ def upgradeDatabase(self, db_version):
block_height INTEGER, block_height INTEGER,
block_hash BLOB, block_hash BLOB,
block_time INTEGER, block_time INTEGER,
PRIMARY KEY (record_id))''')) PRIMARY KEY (record_id))"""
session.execute(text('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB')) )
)
session.execute(text("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB"))
if current_version != db_version: if current_version != db_version:
self.db_version = db_version self.db_version = db_version
self.setIntKV('db_version', db_version, session) self.setIntKV("db_version", db_version, session)
session.commit() session.commit()
session.close() session.close()
session.remove() session.remove()
self.log.info('Upgraded database to version {}'.format(self.db_version)) self.log.info("Upgraded database to version {}".format(self.db_version))
continue continue
break break
if db_version != CURRENT_DB_VERSION: if db_version != CURRENT_DB_VERSION:
raise ValueError('Unable to upgrade database.') raise ValueError("Unable to upgrade database.")

View File

@@ -15,45 +15,142 @@ def remove_expired_data(self, time_offset: int = 0):
try: try:
session = self.openSession() session = self.openSession()
active_bids_insert = self.activeBidsQueryStr(now, '', 'b2') active_bids_insert = self.activeBidsQueryStr(now, "", "b2")
query_str = f''' query_str = f"""
SELECT o.offer_id FROM offers o SELECT o.offer_id FROM offers o
WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert}) WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert})
''' """
num_offers = 0 num_offers = 0
num_bids = 0 num_bids = 0
offer_rows = session.execute(text(query_str), {'expired_at': now - time_offset}) offer_rows = session.execute(text(query_str), {"expired_at": now - time_offset})
for offer_row in offer_rows: for offer_row in offer_rows:
num_offers += 1 num_offers += 1
bid_rows = session.execute(text('SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id'), {'offer_id': offer_row[0]}) bid_rows = session.execute(
text("SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id"),
{"offer_id": offer_row[0]},
)
for bid_row in bid_rows: for bid_row in bid_rows:
num_bids += 1 num_bids += 1
session.execute(text('DELETE FROM transactions WHERE transactions.bid_id = :bid_id'), {'bid_id': bid_row[0]}) session.execute(
session.execute(text('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) text(
session.execute(text('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) "DELETE FROM transactions WHERE transactions.bid_id = :bid_id"
session.execute(text('DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) ),
session.execute(text('DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) {"bid_id": bid_row[0]},
session.execute(text('DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id'), {'bid_id': bid_row[0]}) )
session.execute(text('DELETE FROM actions WHERE actions.linked_id = :bid_id'), {'bid_id': bid_row[0]}) session.execute(
session.execute(text('DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id'), {'bid_id': bid_row[0]}) text(
session.execute(text('DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id'), {'bid_id': bid_row[0]}) "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id"
session.execute(text('DELETE FROM bids WHERE bids.bid_id = :bid_id'), {'bid_id': bid_row[0]}) ),
session.execute(text('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id'), {'type_ind': int(Concepts.BID), 'linked_id': bid_row[0]}) {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
)
session.execute(
text(
"DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id"
),
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
)
session.execute(
text(
"DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id"
),
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
)
session.execute(
text(
"DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id"
),
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
)
session.execute(
text("DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id"),
{"bid_id": bid_row[0]},
)
session.execute(
text("DELETE FROM actions WHERE actions.linked_id = :bid_id"),
{"bid_id": bid_row[0]},
)
session.execute(
text("DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id"),
{"bid_id": bid_row[0]},
)
session.execute(
text(
"DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id"
),
{"bid_id": bid_row[0]},
)
session.execute(
text("DELETE FROM bids WHERE bids.bid_id = :bid_id"),
{"bid_id": bid_row[0]},
)
session.execute(
text(
"DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id"
),
{"type_ind": int(Concepts.BID), "linked_id": bid_row[0]},
)
session.execute(text('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) session.execute(
session.execute(text('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) text(
session.execute(text('DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id"
session.execute(text('DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) ),
session.execute(text('DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id'), {'offer_id': offer_row[0]}) {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
session.execute(text('DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id'), {'offer_id': offer_row[0]}) )
session.execute(text('DELETE FROM actions WHERE actions.linked_id = :offer_id'), {'offer_id': offer_row[0]}) session.execute(
session.execute(text('DELETE FROM offers WHERE offers.offer_id = :offer_id'), {'offer_id': offer_row[0]}) text(
session.execute(text('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id"
),
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
)
session.execute(
text(
"DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id"
),
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
)
session.execute(
text(
"DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id"
),
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
)
session.execute(
text("DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id"),
{"offer_id": offer_row[0]},
)
session.execute(
text("DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id"),
{"offer_id": offer_row[0]},
)
session.execute(
text("DELETE FROM actions WHERE actions.linked_id = :offer_id"),
{"offer_id": offer_row[0]},
)
session.execute(
text("DELETE FROM offers WHERE offers.offer_id = :offer_id"),
{"offer_id": offer_row[0]},
)
session.execute(
text(
"DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id"
),
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
)
if num_offers > 0 or num_bids > 0: if num_offers > 0 or num_bids > 0:
self.log.info('Removed data for {} expired offer{} and {} bid{}.'.format(num_offers, 's' if num_offers != 1 else '', num_bids, 's' if num_bids != 1 else '')) self.log.info(
"Removed data for {} expired offer{} and {} bid{}.".format(
num_offers,
"s" if num_offers != 1 else "",
num_bids,
"s" if num_bids != 1 else "",
)
)
session.execute(text('DELETE FROM checkedblocks WHERE created_at <= :expired_at'), {'expired_at': now - time_offset}) session.execute(
text("DELETE FROM checkedblocks WHERE created_at <= :expired_at"),
{"expired_at": now - time_offset},
)
finally: finally:
self.closeSession(session) self.closeSession(session)

View File

@@ -13,8 +13,8 @@ def encodepoint(P):
zi = edf.inv(P[2]) zi = edf.inv(P[2])
x = (P[0] * zi) % edf.q x = (P[0] * zi) % edf.q
y = (P[1] * zi) % edf.q y = (P[1] * zi) % edf.q
y += ((x & 1) << 255) y += (x & 1) << 255
return y.to_bytes(32, byteorder='little') return y.to_bytes(32, byteorder="little")
def hashToEd25519(bytes_in): def hashToEd25519(bytes_in):
@@ -22,8 +22,8 @@ def hashToEd25519(bytes_in):
for i in range(1000): for i in range(1000):
h255 = bytearray(hashed) h255 = bytearray(hashed)
x_sign = 0 if h255[31] & 0x80 == 0 else 1 x_sign = 0 if h255[31] & 0x80 == 0 else 1
h255[31] &= 0x7f # Clear top bit h255[31] &= 0x7F # Clear top bit
y = int.from_bytes(h255, byteorder='little') y = int.from_bytes(h255, byteorder="little")
x = edf.xrecover(y, x_sign) x = edf.xrecover(y, x_sign)
if x == 0 and y == 1: # Skip infinity point if x == 0 and y == 1: # Skip infinity point
continue continue
@@ -33,4 +33,4 @@ def hashToEd25519(bytes_in):
if edf.isoncurve(P) and edf.is_identity(edf.scalarmult(P, edf.l)): if edf.isoncurve(P) and edf.is_identity(edf.scalarmult(P, edf.l)):
return P return P
hashed = hashlib.sha256(hashed).digest() hashed = hashlib.sha256(hashed).digest()
raise ValueError('hashToEd25519 failed') raise ValueError("hashToEd25519 failed")

View File

@@ -7,7 +7,7 @@
import json import json
class Explorer(): class Explorer:
def __init__(self, swapclient, coin_type, base_url): def __init__(self, swapclient, coin_type, base_url):
self.swapclient = swapclient self.swapclient = swapclient
self.coin_type = coin_type self.coin_type = coin_type
@@ -15,82 +15,94 @@ class Explorer():
self.log = self.swapclient.log self.log = self.swapclient.log
def readURL(self, url): def readURL(self, url):
self.log.debug('Explorer url: {}'.format(url)) self.log.debug("Explorer url: {}".format(url))
return self.swapclient.readURL(url) return self.swapclient.readURL(url)
class ExplorerInsight(Explorer): class ExplorerInsight(Explorer):
def getChainHeight(self): def getChainHeight(self):
return json.loads(self.readURL(self.base_url + '/sync'))['blockChainHeight'] return json.loads(self.readURL(self.base_url + "/sync"))["blockChainHeight"]
def getBlock(self, block_hash): def getBlock(self, block_hash):
data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash))) data = json.loads(self.readURL(self.base_url + "/block/{}".format(block_hash)))
return data return data
def getTransaction(self, txid): def getTransaction(self, txid):
data = json.loads(self.readURL(self.base_url + '/tx/{}'.format(txid))) data = json.loads(self.readURL(self.base_url + "/tx/{}".format(txid)))
return data return data
def getBalance(self, address): def getBalance(self, address):
data = json.loads(self.readURL(self.base_url + '/addr/{}/balance'.format(address))) data = json.loads(
self.readURL(self.base_url + "/addr/{}/balance".format(address))
)
return data return data
def lookupUnspentByAddress(self, address): def lookupUnspentByAddress(self, address):
data = json.loads(self.readURL(self.base_url + '/addr/{}/utxo'.format(address))) data = json.loads(self.readURL(self.base_url + "/addr/{}/utxo".format(address)))
rv = [] rv = []
for utxo in data: for utxo in data:
rv.append({ rv.append(
'txid': utxo['txid'], {
'index': utxo['vout'], "txid": utxo["txid"],
'height': utxo['height'], "index": utxo["vout"],
'n_conf': utxo['confirmations'], "height": utxo["height"],
'value': utxo['satoshis'], "n_conf": utxo["confirmations"],
}) "value": utxo["satoshis"],
}
)
return rv return rv
class ExplorerBitAps(Explorer): class ExplorerBitAps(Explorer):
def getChainHeight(self): def getChainHeight(self):
return json.loads(self.readURL(self.base_url + '/block/last'))['data']['block']['height'] return json.loads(self.readURL(self.base_url + "/block/last"))["data"]["block"][
"height"
]
def getBlock(self, block_hash): def getBlock(self, block_hash):
data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash))) data = json.loads(self.readURL(self.base_url + "/block/{}".format(block_hash)))
return data return data
def getTransaction(self, txid): def getTransaction(self, txid):
data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(txid))) data = json.loads(self.readURL(self.base_url + "/transaction/{}".format(txid)))
return data return data
def getBalance(self, address): def getBalance(self, address):
data = json.loads(self.readURL(self.base_url + '/address/state/' + address)) data = json.loads(self.readURL(self.base_url + "/address/state/" + address))
return data['data']['balance'] return data["data"]["balance"]
def lookupUnspentByAddress(self, address): def lookupUnspentByAddress(self, address):
# Can't get unspents return only if exactly one transaction exists # Can't get unspents return only if exactly one transaction exists
data = json.loads(self.readURL(self.base_url + '/address/transactions/' + address)) data = json.loads(
self.readURL(self.base_url + "/address/transactions/" + address)
)
try: try:
assert data['data']['list'] == 1 assert data["data"]["list"] == 1
except Exception as ex: except Exception as ex:
self.log.debug('Explorer error: {}'.format(str(ex))) self.log.debug("Explorer error: {}".format(str(ex)))
return None return None
tx = data['data']['list'][0] tx = data["data"]["list"][0]
tx_data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(tx['txId'])))['data'] tx_data = json.loads(
self.readURL(self.base_url + "/transaction/{}".format(tx["txId"]))
)["data"]
for i, vout in tx_data['vOut'].items(): for i, vout in tx_data["vOut"].items():
if vout['address'] == address: if vout["address"] == address:
return [{ return [
'txid': tx_data['txId'], {
'index': int(i), "txid": tx_data["txId"],
'height': tx_data['blockHeight'], "index": int(i),
'n_conf': tx_data['confirmations'], "height": tx_data["blockHeight"],
'value': vout['value'], "n_conf": tx_data["confirmations"],
}] "value": vout["value"],
}
]
class ExplorerChainz(Explorer): class ExplorerChainz(Explorer):
def getChainHeight(self): def getChainHeight(self):
return int(self.readURL(self.base_url + '?q=getblockcount')) return int(self.readURL(self.base_url + "?q=getblockcount"))
def lookupUnspentByAddress(self, address): def lookupUnspentByAddress(self, address):
chain_height = self.getChainHeight() chain_height = self.getChainHeight()
self.log.debug('[rm] chain_height %d', chain_height) self.log.debug("[rm] chain_height %d", chain_height)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert # Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -56,12 +57,12 @@ from .ui.page_identity import page_identity
from .ui.page_smsgaddresses import page_smsgaddresses from .ui.page_smsgaddresses import page_smsgaddresses
from .ui.page_debug import page_debug from .ui.page_debug import page_debug
env = Environment(loader=PackageLoader('basicswap', 'templates')) env = Environment(loader=PackageLoader("basicswap", "templates"))
env.filters['formatts'] = format_timestamp env.filters["formatts"] = format_timestamp
def extractDomain(url): def extractDomain(url):
return url.split('://', 1)[1].split('/', 1)[0] return url.split("://", 1)[1].split("/", 1)[0]
def listAvailableExplorers(swap_client): def listAvailableExplorers(swap_client):
@@ -69,40 +70,47 @@ def listAvailableExplorers(swap_client):
for c in Coins: for c in Coins:
if c not in chainparams: if c not in chainparams:
continue continue
for i, e in enumerate(swap_client.coin_clients[c]['explorers']): for i, e in enumerate(swap_client.coin_clients[c]["explorers"]):
explorers.append(('{}_{}'.format(int(c), i), getCoinName(c) + ' - ' + extractDomain(e.base_url))) explorers.append(
(
"{}_{}".format(int(c), i),
getCoinName(c) + " - " + extractDomain(e.base_url),
)
)
return explorers return explorers
def listExplorerActions(swap_client): def listExplorerActions(swap_client):
actions = [('height', 'Chain Height'), actions = [
('block', 'Get Block'), ("height", "Chain Height"),
('tx', 'Get Transaction'), ("block", "Get Block"),
('balance', 'Address Balance'), ("tx", "Get Transaction"),
('unspent', 'List Unspent')] ("balance", "Address Balance"),
("unspent", "List Unspent"),
]
return actions return actions
def parse_cmd(cmd: str, type_map: str): def parse_cmd(cmd: str, type_map: str):
params = shlex.split(cmd) params = shlex.split(cmd)
if len(params) < 1: if len(params) < 1:
return '', [] return "", []
method = params[0] method = params[0]
typed_params = [] typed_params = []
params = params[1:] params = params[1:]
for i, param in enumerate(params): for i, param in enumerate(params):
if i >= len(type_map): if i >= len(type_map):
type_ind = 's' type_ind = "s"
else: else:
type_ind = type_map[i] type_ind = type_map[i]
if type_ind == 'i': if type_ind == "i":
typed_params.append(int(param)) typed_params.append(int(param))
elif type_ind == 'f': elif type_ind == "f":
typed_params.append(float(param)) typed_params.append(float(param))
elif type_ind == 'b': elif type_ind == "b":
typed_params.append(toBool(param)) typed_params.append(toBool(param))
elif type_ind == 'j': elif type_ind == "j":
typed_params.append(json.loads(param)) typed_params.append(json.loads(param))
else: else:
typed_params.append(param) typed_params.append(param)
@@ -122,99 +130,112 @@ class HttpHandler(BaseHTTPRequestHandler):
return os.urandom(8).hex() return os.urandom(8).hex()
def checkForm(self, post_string, name, messages): def checkForm(self, post_string, name, messages):
if post_string == '': if post_string == "":
return None return None
form_data = parse.parse_qs(post_string) form_data = parse.parse_qs(post_string)
form_id = form_data[b'formid'][0].decode('utf-8') form_id = form_data[b"formid"][0].decode("utf-8")
if self.server.last_form_id.get(name, None) == form_id: if self.server.last_form_id.get(name, None) == form_id:
messages.append('Prevented double submit for form {}.'.format(form_id)) messages.append("Prevented double submit for form {}.".format(form_id))
return None return None
self.server.last_form_id[name] = form_id self.server.last_form_id[name] = form_id
return form_data return form_data
def render_template(self, template, args_dict, status_code=200, version=__version__): def render_template(
self, template, args_dict, status_code=200, version=__version__
):
swap_client = self.server.swap_client swap_client = self.server.swap_client
if swap_client.ws_server: if swap_client.ws_server:
args_dict['ws_port'] = swap_client.ws_server.client_port args_dict["ws_port"] = swap_client.ws_server.client_port
if swap_client.debug: if swap_client.debug:
args_dict['debug_mode'] = True args_dict["debug_mode"] = True
if swap_client.debug_ui: if swap_client.debug_ui:
args_dict['debug_ui_mode'] = True args_dict["debug_ui_mode"] = True
if swap_client.use_tor_proxy: if swap_client.use_tor_proxy:
args_dict['use_tor_proxy'] = True args_dict["use_tor_proxy"] = True
# TODO: Cache value? # TODO: Cache value?
try: try:
tor_state = get_tor_established_state(swap_client) tor_state = get_tor_established_state(swap_client)
args_dict['tor_established'] = True if tor_state == '1' else False args_dict["tor_established"] = True if tor_state == "1" else False
except Exception as e: except Exception as e:
args_dict['tor_established'] = False args_dict["tor_established"] = False
if swap_client.debug: if swap_client.debug:
swap_client.log.error(f"Error getting Tor state: {str(e)}") swap_client.log.error(f"Error getting Tor state: {str(e)}")
swap_client.log.error(traceback.format_exc()) swap_client.log.error(traceback.format_exc())
if swap_client._show_notifications: if swap_client._show_notifications:
args_dict['notifications'] = swap_client.getNotifications() args_dict["notifications"] = swap_client.getNotifications()
if 'messages' in args_dict: if "messages" in args_dict:
messages_with_ids = [] messages_with_ids = []
for msg in args_dict['messages']: for msg in args_dict["messages"]:
messages_with_ids.append((self.server.msg_id_counter, msg)) messages_with_ids.append((self.server.msg_id_counter, msg))
self.server.msg_id_counter += 1 self.server.msg_id_counter += 1
args_dict['messages'] = messages_with_ids args_dict["messages"] = messages_with_ids
if 'err_messages' in args_dict: if "err_messages" in args_dict:
err_messages_with_ids = [] err_messages_with_ids = []
for msg in args_dict['err_messages']: for msg in args_dict["err_messages"]:
err_messages_with_ids.append((self.server.msg_id_counter, msg)) err_messages_with_ids.append((self.server.msg_id_counter, msg))
self.server.msg_id_counter += 1 self.server.msg_id_counter += 1
args_dict['err_messages'] = err_messages_with_ids args_dict["err_messages"] = err_messages_with_ids
shutdown_token = os.urandom(8).hex() shutdown_token = os.urandom(8).hex()
self.server.session_tokens['shutdown'] = shutdown_token self.server.session_tokens["shutdown"] = shutdown_token
args_dict['shutdown_token'] = shutdown_token args_dict["shutdown_token"] = shutdown_token
encrypted, locked = swap_client.getLockedState() encrypted, locked = swap_client.getLockedState()
args_dict['encrypted'] = encrypted args_dict["encrypted"] = encrypted
args_dict['locked'] = locked args_dict["locked"] = locked
if self.server.msg_id_counter >= 0x7FFFFFFF: if self.server.msg_id_counter >= 0x7FFFFFFF:
self.server.msg_id_counter = 0 self.server.msg_id_counter = 0
args_dict['version'] = version args_dict["version"] = version
self.putHeaders(status_code, 'text/html') self.putHeaders(status_code, "text/html")
return bytes(template.render( return bytes(
template.render(
title=self.server.title, title=self.server.title,
h2=self.server.title, h2=self.server.title,
form_id=self.generate_form_id(), form_id=self.generate_form_id(),
**args_dict, **args_dict,
), 'UTF-8') ),
"UTF-8",
)
def render_simple_template(self, template, args_dict): def render_simple_template(self, template, args_dict):
swap_client = self.server.swap_client return bytes(
return bytes(template.render( template.render(
title=self.server.title, title=self.server.title,
**args_dict, **args_dict,
), 'UTF-8') ),
"UTF-8",
)
def page_info(self, info_str, post_string=None): def page_info(self, info_str, post_string=None):
template = env.get_template('info.html') template = env.get_template("info.html")
swap_client = self.server.swap_client swap_client = self.server.swap_client
summary = swap_client.getSummary() summary = swap_client.getSummary()
return self.render_template(template, { return self.render_template(
'title_str': 'BasicSwap Info', template,
'message_str': info_str, {
'summary': summary, "title_str": "BasicSwap Info",
}) "message_str": info_str,
"summary": summary,
},
)
def page_error(self, error_str, post_string=None): def page_error(self, error_str, post_string=None):
template = env.get_template('error.html') template = env.get_template("error.html")
swap_client = self.server.swap_client swap_client = self.server.swap_client
summary = swap_client.getSummary() summary = swap_client.getSummary()
return self.render_template(template, { return self.render_template(
'title_str': 'BasicSwap Error', template,
'message_str': error_str, {
'summary': summary, "title_str": "BasicSwap Error",
}) "message_str": error_str,
"summary": summary,
},
)
def page_explorers(self, url_split, post_string): def page_explorers(self, url_split, post_string):
swap_client = self.server.swap_client swap_client = self.server.swap_client
@@ -226,42 +247,49 @@ class HttpHandler(BaseHTTPRequestHandler):
action = -1 action = -1
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'explorers', err_messages) form_data = self.checkForm(post_string, "explorers", err_messages)
if form_data: if form_data:
explorer = form_data[b'explorer'][0].decode('utf-8') explorer = form_data[b"explorer"][0].decode("utf-8")
action = form_data[b'action'][0].decode('utf-8') action = form_data[b"action"][0].decode("utf-8")
args = '' if b'args' not in form_data else form_data[b'args'][0].decode('utf-8') args = (
""
if b"args" not in form_data
else form_data[b"args"][0].decode("utf-8")
)
try: try:
c, e = explorer.split('_') c, e = explorer.split("_")
exp = swap_client.coin_clients[Coins(int(c))]['explorers'][int(e)] exp = swap_client.coin_clients[Coins(int(c))]["explorers"][int(e)]
if action == 'height': if action == "height":
result = str(exp.getChainHeight()) result = str(exp.getChainHeight())
elif action == 'block': elif action == "block":
result = dumpj(exp.getBlock(args)) result = dumpj(exp.getBlock(args))
elif action == 'tx': elif action == "tx":
result = dumpj(exp.getTransaction(args)) result = dumpj(exp.getTransaction(args))
elif action == 'balance': elif action == "balance":
result = dumpj(exp.getBalance(args)) result = dumpj(exp.getBalance(args))
elif action == 'unspent': elif action == "unspent":
result = dumpj(exp.lookupUnspentByAddress(args)) result = dumpj(exp.lookupUnspentByAddress(args))
else: else:
result = 'Unknown action' result = "Unknown action"
except Exception as ex: except Exception as ex:
result = str(ex) result = str(ex)
template = env.get_template('explorers.html') template = env.get_template("explorers.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'explorers': listAvailableExplorers(swap_client), "messages": messages,
'explorer': explorer, "err_messages": err_messages,
'actions': listExplorerActions(swap_client), "explorers": listAvailableExplorers(swap_client),
'action': action, "explorer": explorer,
'result': result, "actions": listExplorerActions(swap_client),
'summary': summary, "action": action,
}) "result": result,
"summary": summary,
},
)
def page_rpc(self, url_split, post_string): def page_rpc(self, url_split, post_string):
swap_client = self.server.swap_client swap_client = self.server.swap_client
@@ -269,34 +297,33 @@ class HttpHandler(BaseHTTPRequestHandler):
summary = swap_client.getSummary() summary = swap_client.getSummary()
result = None result = None
cmd = '' cmd = ""
coin_type_selected = -1 coin_type_selected = -1
coin_type = -1 coin_type = -1
coin_id = -1 call_type = "cli"
call_type = 'cli' type_map = ""
type_map = ''
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'rpc', err_messages) form_data = self.checkForm(post_string, "rpc", err_messages)
if form_data: if form_data:
try: try:
call_type = get_data_entry_or(form_data, 'call_type', 'cli') call_type = get_data_entry_or(form_data, "call_type", "cli")
type_map = get_data_entry_or(form_data, 'type_map', '') type_map = get_data_entry_or(form_data, "type_map", "")
try: try:
coin_type_selected = get_data_entry(form_data, 'coin_type') coin_type_selected = get_data_entry(form_data, "coin_type")
coin_type_split = coin_type_selected.split(',') coin_type_split = coin_type_selected.split(",")
coin_type = Coins(int(coin_type_split[0])) coin_type = Coins(int(coin_type_split[0]))
coin_variant = int(coin_type_split[1]) coin_variant = int(coin_type_split[1])
except Exception: except Exception:
raise ValueError('Unknown Coin Type') raise ValueError("Unknown Coin Type")
if coin_type in (Coins.DCR,): if coin_type in (Coins.DCR,):
call_type = 'http' call_type = "http"
try: try:
cmd = get_data_entry(form_data, 'cmd') cmd = get_data_entry(form_data, "cmd")
except Exception: except Exception:
raise ValueError('Invalid command') raise ValueError("Invalid command")
if coin_type in (Coins.XMR, Coins.WOW): if coin_type in (Coins.XMR, Coins.WOW):
ci = swap_client.ci(coin_type) ci = swap_client.ci(coin_type)
arr = cmd.split(None, 1) arr = cmd.split(None, 1)
@@ -311,10 +338,10 @@ class HttpHandler(BaseHTTPRequestHandler):
params = None params = None
rv = ci.rpc2(method, params) rv = ci.rpc2(method, params)
else: else:
raise ValueError('Unknown RPC variant') raise ValueError("Unknown RPC variant")
result = json.dumps(rv, indent=4) result = json.dumps(rv, indent=4)
else: else:
if call_type == 'http': if call_type == "http":
ci = swap_client.ci(coin_type) ci = swap_client.ci(coin_type)
method, params = parse_cmd(cmd, type_map) method, params = parse_cmd(cmd, type_map)
if coin_variant == 1: if coin_variant == 1:
@@ -328,44 +355,50 @@ class HttpHandler(BaseHTTPRequestHandler):
rv = ci.rpc_wallet(method, params) rv = ci.rpc_wallet(method, params)
if not isinstance(rv, str): if not isinstance(rv, str):
rv = json.dumps(rv, indent=4) rv = json.dumps(rv, indent=4)
result = cmd + '\n' + rv 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:
result = cmd + '\n' + str(ex) result = cmd + "\n" + str(ex)
if self.server.swap_client.debug is True: if self.server.swap_client.debug is True:
self.server.swap_client.log.error(traceback.format_exc()) self.server.swap_client.log.error(traceback.format_exc())
template = env.get_template('rpc.html') template = env.get_template("rpc.html")
coin_available = listAvailableCoins(swap_client, with_variants=False) coin_available = listAvailableCoins(swap_client, with_variants=False)
with_xmr: bool = any(c[0] == Coins.XMR for c in coin_available) with_xmr: bool = any(c[0] == Coins.XMR for c in coin_available)
with_wow: bool = any(c[0] == Coins.WOW for c in coin_available) with_wow: bool = any(c[0] == Coins.WOW for c in coin_available)
coins = [(str(c[0]) + ',0', c[1]) for c in coin_available if c[0] not in (Coins.XMR, Coins.WOW)] coins = [
(str(c[0]) + ",0", c[1])
for c in coin_available
if c[0] not in (Coins.XMR, Coins.WOW)
]
if any(c[0] == Coins.DCR for c in coin_available): if any(c[0] == Coins.DCR for c in coin_available):
coins.append((str(int(Coins.DCR)) + ',1', 'Decred Wallet')) coins.append((str(int(Coins.DCR)) + ",1", "Decred Wallet"))
if any(c[0] == Coins.LTC for c in coin_available): if any(c[0] == Coins.LTC for c in coin_available):
coins.append((str(int(Coins.LTC)) + ',2', 'Litecoin MWEB Wallet')) coins.append((str(int(Coins.LTC)) + ",2", "Litecoin MWEB Wallet"))
if with_xmr: if with_xmr:
coins.append((str(int(Coins.XMR)) + ',0', 'Monero')) coins.append((str(int(Coins.XMR)) + ",0", "Monero"))
coins.append((str(int(Coins.XMR)) + ',1', 'Monero JSON')) coins.append((str(int(Coins.XMR)) + ",1", "Monero JSON"))
coins.append((str(int(Coins.XMR)) + ',2', 'Monero Wallet')) coins.append((str(int(Coins.XMR)) + ",2", "Monero Wallet"))
if with_wow: if with_wow:
coins.append((str(int(Coins.WOW)) + ',0', 'Wownero')) coins.append((str(int(Coins.WOW)) + ",0", "Wownero"))
coins.append((str(int(Coins.WOW)) + ',1', 'Wownero JSON')) coins.append((str(int(Coins.WOW)) + ",1", "Wownero JSON"))
coins.append((str(int(Coins.WOW)) + ',2', 'Wownero Wallet')) coins.append((str(int(Coins.WOW)) + ",2", "Wownero Wallet"))
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'coins': coins, "messages": messages,
'coin_type': coin_type_selected, "err_messages": err_messages,
'call_type': call_type, "coins": coins,
'result': result, "coin_type": coin_type_selected,
'messages': messages, "call_type": call_type,
'summary': summary, "result": result,
}) "summary": summary,
},
)
def page_active(self, url_split, post_string): def page_active(self, url_split, post_string):
swap_client = self.server.swap_client swap_client = self.server.swap_client
@@ -373,12 +406,24 @@ class HttpHandler(BaseHTTPRequestHandler):
active_swaps = swap_client.listSwapsInProgress() active_swaps = swap_client.listSwapsInProgress()
summary = swap_client.getSummary() summary = swap_client.getSummary()
template = env.get_template('active.html') template = env.get_template("active.html")
return self.render_template(template, { return self.render_template(
'refresh': 30, template,
'active_swaps': [(s[0].hex(), s[1], strBidState(s[2]), strTxState(s[3]), strTxState(s[4])) for s in active_swaps], {
'summary': summary, "refresh": 30,
}) "active_swaps": [
(
s[0].hex(),
s[1],
strBidState(s[2]),
strTxState(s[3]),
strTxState(s[4]),
)
for s in active_swaps
],
"summary": summary,
},
)
def page_watched(self, url_split, post_string): def page_watched(self, url_split, post_string):
swap_client = self.server.swap_client swap_client = self.server.swap_client
@@ -386,60 +431,68 @@ class HttpHandler(BaseHTTPRequestHandler):
watched_outputs, last_scanned = swap_client.listWatchedOutputs() watched_outputs, last_scanned = swap_client.listWatchedOutputs()
summary = swap_client.getSummary() summary = swap_client.getSummary()
template = env.get_template('watched.html') template = env.get_template("watched.html")
return self.render_template(template, { return self.render_template(
'refresh': 30, template,
'last_scanned': [(getCoinName(ls[0]), ls[1]) for ls in last_scanned], {
'watched_outputs': [(wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4])) for wo in watched_outputs], "refresh": 30,
'summary': summary, "last_scanned": [(getCoinName(ls[0]), ls[1]) for ls in last_scanned],
}) "watched_outputs": [
(wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4]))
for wo in watched_outputs
],
"summary": summary,
},
)
def page_shutdown(self, url_split, post_string): def page_shutdown(self, url_split, post_string):
swap_client = self.server.swap_client swap_client = self.server.swap_client
if len(url_split) > 2: if len(url_split) > 2:
token = url_split[2] token = url_split[2]
expect_token = self.server.session_tokens.get('shutdown', None) expect_token = self.server.session_tokens.get("shutdown", None)
if token != expect_token: if token != expect_token:
return self.page_info('Unexpected token, still running.') return self.page_info("Unexpected token, still running.")
swap_client.stopRunning() swap_client.stopRunning()
return self.page_info('Shutting down') return self.page_info("Shutting down")
def page_index(self, url_split): def page_index(self, url_split):
swap_client = self.server.swap_client swap_client = self.server.swap_client
swap_client.checkSystemStatus() swap_client.checkSystemStatus()
summary = swap_client.getSummary()
self.send_response(302) self.send_response(302)
self.send_header('Location', '/offers') self.send_header("Location", "/offers")
self.end_headers() self.end_headers()
return b'' return b""
def page_404(self, url_split): def page_404(self, url_split):
swap_client = self.server.swap_client swap_client = self.server.swap_client
summary = swap_client.getSummary() summary = swap_client.getSummary()
template = env.get_template('404.html') template = env.get_template("404.html")
return self.render_template(template, { return self.render_template(
'summary': summary, template,
}) {
"summary": summary,
},
)
def putHeaders(self, status_code, content_type): def putHeaders(self, status_code, content_type):
self.send_response(status_code) self.send_response(status_code)
if self.server.allow_cors: if self.server.allow_cors:
self.send_header('Access-Control-Allow-Origin', '*') self.send_header("Access-Control-Allow-Origin", "*")
self.send_header('Content-Type', content_type) self.send_header("Content-Type", content_type)
self.end_headers() self.end_headers()
def handle_http(self, status_code, path, post_string='', is_json=False): def handle_http(self, status_code, path, post_string="", is_json=False):
swap_client = self.server.swap_client swap_client = self.server.swap_client
parsed = parse.urlparse(self.path) parsed = parse.urlparse(self.path)
url_split = parsed.path.split('/') url_split = parsed.path.split("/")
if post_string == '' and len(parsed.query) > 0: if post_string == "" and len(parsed.query) > 0:
post_string = parsed.query post_string = parsed.query
if len(url_split) > 1 and url_split[1] == 'json': if len(url_split) > 1 and url_split[1] == "json":
try: try:
self.putHeaders(status_code, 'text/plain') self.putHeaders(status_code, "text/plain")
func = js_url_to_function(url_split) func = js_url_to_function(url_split)
return func(self, url_split, post_string, is_json) return func(self, url_split, post_string, is_json)
except Exception as ex: except Exception as ex:
@@ -447,37 +500,42 @@ class HttpHandler(BaseHTTPRequestHandler):
swap_client.log.error(traceback.format_exc()) swap_client.log.error(traceback.format_exc())
return js_error(self, str(ex)) return js_error(self, str(ex))
if len(url_split) > 1 and url_split[1] == 'static': if len(url_split) > 1 and url_split[1] == "static":
try: try:
static_path = os.path.join(os.path.dirname(__file__), 'static') static_path = os.path.join(os.path.dirname(__file__), "static")
if len(url_split) > 3 and url_split[2] == 'sequence_diagrams': if len(url_split) > 3 and url_split[2] == "sequence_diagrams":
with open(os.path.join(static_path, 'sequence_diagrams', url_split[3]), 'rb') as fp: with open(
self.putHeaders(status_code, 'image/svg+xml') os.path.join(static_path, "sequence_diagrams", url_split[3]),
"rb",
) as fp:
self.putHeaders(status_code, "image/svg+xml")
return fp.read() return fp.read()
elif len(url_split) > 3 and url_split[2] == 'images': elif len(url_split) > 3 and url_split[2] == "images":
filename = os.path.join(*url_split[3:]) filename = os.path.join(*url_split[3:])
_, extension = os.path.splitext(filename) _, extension = os.path.splitext(filename)
mime_type = { mime_type = {
'.svg': 'image/svg+xml', ".svg": "image/svg+xml",
'.png': 'image/png', ".png": "image/png",
'.jpg': 'image/jpeg', ".jpg": "image/jpeg",
'.gif': 'image/gif', ".gif": "image/gif",
'.ico': 'image/x-icon', ".ico": "image/x-icon",
}.get(extension, '') }.get(extension, "")
if mime_type == '': if mime_type == "":
raise ValueError('Unknown file type ' + filename) raise ValueError("Unknown file type " + filename)
with open(os.path.join(static_path, 'images', filename), 'rb') as fp: with open(
os.path.join(static_path, "images", filename), "rb"
) as fp:
self.putHeaders(status_code, mime_type) self.putHeaders(status_code, mime_type)
return fp.read() return fp.read()
elif len(url_split) > 3 and url_split[2] == 'css': elif len(url_split) > 3 and url_split[2] == "css":
filename = os.path.join(*url_split[3:]) filename = os.path.join(*url_split[3:])
with open(os.path.join(static_path, 'css', filename), 'rb') as fp: with open(os.path.join(static_path, "css", filename), "rb") as fp:
self.putHeaders(status_code, 'text/css; charset=utf-8') self.putHeaders(status_code, "text/css; charset=utf-8")
return fp.read() return fp.read()
elif len(url_split) > 3 and url_split[2] == 'js': elif len(url_split) > 3 and url_split[2] == "js":
filename = os.path.join(*url_split[3:]) filename = os.path.join(*url_split[3:])
with open(os.path.join(static_path, 'js', filename), 'rb') as fp: with open(os.path.join(static_path, "js", filename), "rb") as fp:
self.putHeaders(status_code, 'application/javascript') self.putHeaders(status_code, "application/javascript")
return fp.read() return fp.read()
else: else:
return self.page_404(url_split) return self.page_404(url_split)
@@ -492,63 +550,63 @@ class HttpHandler(BaseHTTPRequestHandler):
if len(url_split) > 1: if len(url_split) > 1:
page = url_split[1] page = url_split[1]
if page == 'active': if page == "active":
return self.page_active(url_split, post_string) return self.page_active(url_split, post_string)
if page == 'wallets': if page == "wallets":
return page_wallets(self, url_split, post_string) return page_wallets(self, url_split, post_string)
if page == 'wallet': if page == "wallet":
return page_wallet(self, url_split, post_string) return page_wallet(self, url_split, post_string)
if page == 'settings': if page == "settings":
return page_settings(self, url_split, post_string) return page_settings(self, url_split, post_string)
if page == 'error': if page == "error":
return self.page_error(url_split, post_string) return self.page_error(url_split, post_string)
if page == 'info': if page == "info":
return self.page_info(url_split, post_string) return self.page_info(url_split, post_string)
if page == 'rpc': if page == "rpc":
return self.page_rpc(url_split, post_string) return self.page_rpc(url_split, post_string)
if page == 'debug': if page == "debug":
return page_debug(self, url_split, post_string) return page_debug(self, url_split, post_string)
if page == 'explorers': if page == "explorers":
return self.page_explorers(url_split, post_string) return self.page_explorers(url_split, post_string)
if page == 'offer': if page == "offer":
return page_offer(self, url_split, post_string) return page_offer(self, url_split, post_string)
if page == 'offers': if page == "offers":
return page_offers(self, url_split, post_string) return page_offers(self, url_split, post_string)
if page == 'newoffer': if page == "newoffer":
return page_newoffer(self, url_split, post_string) return page_newoffer(self, url_split, post_string)
if page == 'sentoffers': if page == "sentoffers":
return page_offers(self, url_split, post_string, sent=True) return page_offers(self, url_split, post_string, sent=True)
if page == 'bid': if page == "bid":
return page_bid(self, url_split, post_string) return page_bid(self, url_split, post_string)
if page == 'receivedbids': if page == "receivedbids":
return page_bids(self, url_split, post_string, received=True) return page_bids(self, url_split, post_string, received=True)
if page == 'sentbids': if page == "sentbids":
return page_bids(self, url_split, post_string, sent=True) return page_bids(self, url_split, post_string, sent=True)
if page == 'availablebids': if page == "availablebids":
return page_bids(self, url_split, post_string, available=True) return page_bids(self, url_split, post_string, available=True)
if page == 'watched': if page == "watched":
return self.page_watched(url_split, post_string) return self.page_watched(url_split, post_string)
if page == 'smsgaddresses': if page == "smsgaddresses":
return page_smsgaddresses(self, url_split, post_string) return page_smsgaddresses(self, url_split, post_string)
if page == 'identity': if page == "identity":
return page_identity(self, url_split, post_string) return page_identity(self, url_split, post_string)
if page == 'tor': if page == "tor":
return page_tor(self, url_split, post_string) return page_tor(self, url_split, post_string)
if page == 'automation': if page == "automation":
return page_automation_strategies(self, url_split, post_string) return page_automation_strategies(self, url_split, post_string)
if page == 'automationstrategy': if page == "automationstrategy":
return page_automation_strategy(self, url_split, post_string) return page_automation_strategy(self, url_split, post_string)
if page == 'newautomationstrategy': if page == "newautomationstrategy":
return page_automation_strategy_new(self, url_split, post_string) return page_automation_strategy_new(self, url_split, post_string)
if page == 'shutdown': if page == "shutdown":
return self.page_shutdown(url_split, post_string) return self.page_shutdown(url_split, post_string)
if page == 'changepassword': if page == "changepassword":
return page_changepassword(self, url_split, post_string) return page_changepassword(self, url_split, post_string)
if page == 'unlock': if page == "unlock":
return page_unlock(self, url_split, post_string) return page_unlock(self, url_split, post_string)
if page == 'lock': if page == "lock":
return page_lock(self, url_split, post_string) return page_lock(self, url_split, post_string)
if page != '': if page != "":
return self.page_404(url_split) return self.page_404(url_split)
return self.page_index(url_split) return self.page_index(url_split)
except LockedCoinError: except LockedCoinError:
@@ -563,20 +621,20 @@ class HttpHandler(BaseHTTPRequestHandler):
self.wfile.write(response) self.wfile.write(response)
def do_POST(self): def do_POST(self):
post_string = self.rfile.read(int(self.headers.get('Content-Length'))) post_string = self.rfile.read(int(self.headers.get("Content-Length")))
is_json = True if 'json' in self.headers.get('Content-Type', '') else False is_json = True if "json" in self.headers.get("Content-Type", "") else False
response = self.handle_http(200, self.path, post_string, is_json) response = self.handle_http(200, self.path, post_string, is_json)
self.wfile.write(response) self.wfile.write(response)
def do_HEAD(self): def do_HEAD(self):
self.putHeaders(200, 'text/html') self.putHeaders(200, "text/html")
def do_OPTIONS(self): def do_OPTIONS(self):
self.send_response(200, 'ok') self.send_response(200, "ok")
if self.server.allow_cors: if self.server.allow_cors:
self.send_header('Access-Control-Allow-Origin', '*') self.send_header("Access-Control-Allow-Origin", "*")
self.send_header('Access-Control-Allow-Headers', '*') self.send_header("Access-Control-Allow-Headers", "*")
self.end_headers() self.end_headers()
@@ -590,7 +648,7 @@ class HttpThread(threading.Thread, HTTPServer):
self.port_no = port_no self.port_no = port_no
self.allow_cors = allow_cors self.allow_cors = allow_cors
self.swap_client = swap_client self.swap_client = swap_client
self.title = 'BasicSwap - ' + __version__ self.title = "BasicSwap - " + __version__
self.last_form_id = dict() self.last_form_id = dict()
self.session_tokens = dict() self.session_tokens = dict()
self.env = env self.env = env
@@ -605,9 +663,9 @@ class HttpThread(threading.Thread, HTTPServer):
# Send fake request # Send fake request
conn = http.client.HTTPConnection(self.host_name, self.port_no) conn = http.client.HTTPConnection(self.host_name, self.port_no)
conn.connect() conn.connect()
conn.request('GET', '/none') conn.request("GET", "/none")
response = conn.getresponse() response = conn.getresponse()
data = response.read() _ = response.read()
conn.close() conn.close()
def serve_forever(self): def serve_forever(self):

View File

@@ -14,7 +14,8 @@ from basicswap.chainparams import (
) )
from basicswap.util import ( from basicswap.util import (
ensure, ensure,
i2b, b2i, i2b,
b2i,
make_int, make_int,
format_amount, format_amount,
TemporaryError, TemporaryError,
@@ -26,9 +27,7 @@ from basicswap.util.ecc import (
ep, ep,
getSecretInt, getSecretInt,
) )
from coincurve.dleag import ( from coincurve.dleag import verify_secp256k1_point
verify_secp256k1_point
)
from coincurve.keys import ( from coincurve.keys import (
PublicKey, PublicKey,
) )
@@ -67,33 +66,33 @@ class CoinInterface:
def coin_name(self) -> str: def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()] coin_chainparams = chainparams[self.coin_type()]
if 'display_name' in coin_chainparams: if "display_name" in coin_chainparams:
return coin_chainparams['display_name'] return coin_chainparams["display_name"]
return coin_chainparams['name'].capitalize() return coin_chainparams["name"].capitalize()
def ticker(self) -> str: def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker'] ticker = chainparams[self.coin_type()]["ticker"]
if self._network == 'testnet': if self._network == "testnet":
ticker = 't' + ticker ticker = "t" + ticker
elif self._network == 'regtest': elif self._network == "regtest":
ticker = 'rt' + ticker ticker = "rt" + ticker
return ticker return ticker
def getExchangeTicker(self, exchange_name: str) -> str: def getExchangeTicker(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['ticker'] return chainparams[self.coin_type()]["ticker"]
def getExchangeName(self, exchange_name: str) -> str: def getExchangeName(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['name'] return chainparams[self.coin_type()]["name"]
def ticker_mainnet(self) -> str: def ticker_mainnet(self) -> str:
ticker = chainparams[self.coin_type()]['ticker'] ticker = chainparams[self.coin_type()]["ticker"]
return ticker return ticker
def min_amount(self) -> int: def min_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['min_amount'] return chainparams[self.coin_type()][self._network]["min_amount"]
def max_amount(self) -> int: def max_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['max_amount'] return chainparams[self.coin_type()][self._network]["max_amount"]
def setWalletSeedWarning(self, value: bool) -> None: def setWalletSeedWarning(self, value: bool) -> None:
self._unknown_wallet_seed = value self._unknown_wallet_seed = value
@@ -111,7 +110,7 @@ class CoinInterface:
return chainparams[self.coin_type()][self._network] return chainparams[self.coin_type()][self._network]
def has_segwit(self) -> bool: def has_segwit(self) -> bool:
return chainparams[self.coin_type()].get('has_segwit', True) return chainparams[self.coin_type()].get("has_segwit", True)
def use_p2shp2wsh(self) -> bool: def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh # p2sh-p2wsh
@@ -121,24 +120,26 @@ class CoinInterface:
if isinstance(ex, TemporaryError): if isinstance(ex, TemporaryError):
return True return True
str_error: str = str(ex).lower() str_error: str = str(ex).lower()
if 'not enough unlocked money' in str_error: if "not enough unlocked money" in str_error:
return True return True
if 'no unlocked balance' in str_error: if "no unlocked balance" in str_error:
return True return True
if 'transaction was rejected by daemon' in str_error: if "transaction was rejected by daemon" in str_error:
return True return True
if 'invalid unlocked_balance' in str_error: if "invalid unlocked_balance" in str_error:
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: if "timed out" in str_error:
return True return True
if 'request-sent' in str_error: if "request-sent" in str_error:
return True return True
return False return False
def setConfTarget(self, new_conf_target: int) -> None: def setConfTarget(self, new_conf_target: int) -> None:
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value') ensure(
new_conf_target >= 1 and new_conf_target < 33, "Invalid conf_target value"
)
self._conf_target = new_conf_target self._conf_target = new_conf_target
def walletRestoreHeight(self) -> int: def walletRestoreHeight(self) -> int:
@@ -171,30 +172,15 @@ class CoinInterface:
return self._altruistic return self._altruistic
class AdaptorSigInterface(): class AdaptorSigInterface:
def getScriptLockTxDummyWitness(self, script: bytes): def getScriptLockTxDummyWitness(self, script: bytes):
return [ return [b"", bytes(72), bytes(72), bytes(len(script))]
b'',
bytes(72),
bytes(72),
bytes(len(script))
]
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes): def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
return [ return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))]
b'',
bytes(72),
bytes(72),
bytes((1,)),
bytes(len(script))
]
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes): def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
return [ return [bytes(72), b"", bytes(len(script))]
bytes(72),
b'',
bytes(len(script))
]
class Secp256k1Interface(CoinInterface, AdaptorSigInterface): class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
@@ -213,7 +199,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def verifyKey(self, k: bytes) -> bool: def verifyKey(self, k: bytes) -> bool:
i = b2i(k) i = b2i(k)
return (i < ep.o and i > 0) return i < ep.o and i > 0
def verifyPubkey(self, pubkey_bytes: bytes) -> bool: def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
return verify_secp256k1_point(pubkey_bytes) return verify_secp256k1_point(pubkey_bytes)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert # Copyright (c) 2022-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -11,7 +12,10 @@ from basicswap.util.address import decodeAddress
from basicswap.contrib.mnemonic import Mnemonic from basicswap.contrib.mnemonic import Mnemonic
from basicswap.contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import (
CScript, CScript,
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG OP_DUP,
OP_HASH160,
OP_EQUALVERIFY,
OP_CHECKSIG,
) )
@@ -22,41 +26,49 @@ class DASHInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super().__init__(coin_settings, network, swap_client) super().__init__(coin_settings, network, swap_client)
self._wallet_passphrase = '' self._wallet_passphrase = ""
self._have_checked_seed = False self._have_checked_seed = False
self._wallet_v20_compatible = False if not swap_client else swap_client.getChainClientSettings(self.coin_type()).get('wallet_v20_compatible', False) self._wallet_v20_compatible = (
False
if not swap_client
else swap_client.getChainClientSettings(self.coin_type()).get(
"wallet_v20_compatible", False
)
)
def decodeAddress(self, address: str) -> bytes: def decodeAddress(self, address: str) -> bytes:
return decodeAddress(address)[1:] return decodeAddress(address)[1:]
def getWalletSeedID(self) -> str: def getWalletSeedID(self) -> str:
hdseed: str = self.rpc_wallet('dumphdinfo')['hdseed'] hdseed: str = self.rpc_wallet("dumphdinfo")["hdseed"]
return self.getSeedHash(bytes.fromhex(hdseed)).hex() return self.getSeedHash(bytes.fromhex(hdseed)).hex()
def entropyToMnemonic(self, key: bytes) -> None: def entropyToMnemonic(self, key: bytes) -> None:
return Mnemonic('english').to_mnemonic(key) return Mnemonic("english").to_mnemonic(key)
def initialiseWallet(self, key_bytes: bytes) -> None: def initialiseWallet(self, key_bytes: bytes) -> None:
self._have_checked_seed = False self._have_checked_seed = False
if self._wallet_v20_compatible: if self._wallet_v20_compatible:
self._log.warning('Generating wallet compatible with v20 seed.') self._log.warning("Generating wallet compatible with v20 seed.")
words = self.entropyToMnemonic(key_bytes) words = self.entropyToMnemonic(key_bytes)
mnemonic_passphrase = '' mnemonic_passphrase = ""
self.rpc_wallet('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)
return return
key_wif = self.encodeKey(key_bytes) key_wif = self.encodeKey(key_bytes)
self.rpc_wallet('sethdseed', [True, key_wif]) self.rpc_wallet("sethdseed", [True, key_wif])
def checkExpectedSeed(self, expect_seedid: str) -> bool: def checkExpectedSeed(self, expect_seedid: str) -> bool:
self._expect_seedid_hex = expect_seedid self._expect_seedid_hex = expect_seedid
rv = self.rpc_wallet('dumphdinfo') rv = self.rpc_wallet("dumphdinfo")
if rv['mnemonic'] != '': if rv["mnemonic"] != "":
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()
have_expected_seed: bool = expect_seedid == entropy_hash have_expected_seed: bool = expect_seedid == entropy_hash
else: else:
@@ -65,11 +77,11 @@ class DASHInterface(BTCInterface):
return have_expected_seed return have_expected_seed
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_wallet('sendtoaddress', params) return self.rpc_wallet("sendtoaddress", params)
def getSpendableBalance(self) -> int: def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_wallet('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
@@ -79,19 +91,23 @@ class DASHInterface(BTCInterface):
add_bytes = 107 add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000) pay_fee = round(fee_rate * size / 1000)
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') self._log.info(
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
)
return pay_fee return pay_fee
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_wallet('gettransaction', [txid_hex]) rv = self.rpc_wallet("gettransaction", [txid_hex])
except Exception as ex: except Exception as e: # noqa: F841
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
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
@@ -105,8 +121,8 @@ class DASHInterface(BTCInterface):
self._sc.checkWalletSeed(self.coin_type()) self._sc.checkWalletSeed(self.coin_type())
except Exception as ex: except Exception as ex:
# dumphdinfo can fail if the wallet is not initialised # dumphdinfo can fail if the wallet is not initialised
self._log.debug(f'DASH checkWalletSeed failed: {ex}.') self._log.debug(f"DASH checkWalletSeed failed: {ex}.")
def lockWallet(self): def lockWallet(self):
super().lockWallet() super().lockWallet()
self._wallet_passphrase = '' self._wallet_passphrase = ""

View File

@@ -1,4 +1,5 @@
from .dcr import DCRInterface from .dcr import DCRInterface
__all__ = ['DCRInterface',] __all__ = [
"DCRInterface",
]

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ class SigHashType(IntEnum):
SigHashSingle = 0x3 SigHashSingle = 0x3
SigHashAnyOneCanPay = 0x80 SigHashAnyOneCanPay = 0x80
SigHashMask = 0x1f SigHashMask = 0x1F
class SignatureType(IntEnum): class SignatureType(IntEnum):
@@ -33,7 +33,7 @@ class SignatureType(IntEnum):
class COutPoint: class COutPoint:
__slots__ = ('hash', 'n', 'tree') __slots__ = ("hash", "n", "tree")
def __init__(self, hash=0, n=0, tree=0): def __init__(self, hash=0, n=0, tree=0):
self.hash = hash self.hash = hash
@@ -41,24 +41,30 @@ class COutPoint:
self.tree = tree self.tree = tree
def get_hash(self) -> bytes: def get_hash(self) -> bytes:
return self.hash.to_bytes(32, 'big') return self.hash.to_bytes(32, "big")
class CTxIn: class CTxIn:
__slots__ = ('prevout', 'sequence', __slots__ = (
'value_in', 'block_height', 'block_index', 'signature_script') # Witness "prevout",
"sequence",
"value_in",
"block_height",
"block_index",
"signature_script",
) # Witness
def __init__(self, prevout=COutPoint(), sequence=0): def __init__(self, prevout=COutPoint(), sequence=0):
self.prevout = prevout self.prevout = prevout
self.sequence = sequence self.sequence = sequence
self.value_in = -1 self.value_in = -1
self.block_height = 0 self.block_height = 0
self.block_index = 0xffffffff self.block_index = 0xFFFFFFFF
self.signature_script = bytes() self.signature_script = bytes()
class CTxOut: class CTxOut:
__slots__ = ('value', 'version', 'script_pubkey') __slots__ = ("value", "version", "script_pubkey")
def __init__(self, value=0, script_pubkey=bytes()): def __init__(self, value=0, script_pubkey=bytes()):
self.value = value self.value = value
@@ -67,7 +73,7 @@ class CTxOut:
class CTransaction: class CTransaction:
__slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry') __slots__ = ("hash", "version", "vin", "vout", "locktime", "expiry")
def __init__(self, tx=None): def __init__(self, tx=None):
if tx is None: if tx is None:
@@ -85,8 +91,8 @@ class CTransaction:
def deserialize(self, data: bytes) -> None: def deserialize(self, data: bytes) -> None:
version = int.from_bytes(data[:4], 'little') version = int.from_bytes(data[:4], "little")
self.version = version & 0xffff self.version = version & 0xFFFF
ser_type: int = version >> 16 ser_type: int = version >> 16
o = 4 o = 4
@@ -97,13 +103,13 @@ class CTransaction:
for i in range(num_txin): for i in range(num_txin):
txi = CTxIn() txi = CTxIn()
txi.prevout = COutPoint() txi.prevout = COutPoint()
txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little') txi.prevout.hash = int.from_bytes(data[o : o + 32], "little")
o += 32 o += 32
txi.prevout.n = int.from_bytes(data[o:o + 4], 'little') txi.prevout.n = int.from_bytes(data[o : o + 4], "little")
o += 4 o += 4
txi.prevout.tree = data[o] txi.prevout.tree = data[o]
o += 1 o += 1
txi.sequence = int.from_bytes(data[o:o + 4], 'little') txi.sequence = int.from_bytes(data[o : o + 4], "little")
o += 4 o += 4
self.vin.append(txi) self.vin.append(txi)
@@ -112,9 +118,9 @@ class CTransaction:
for i in range(num_txout): for i in range(num_txout):
txo = CTxOut() txo = CTxOut()
txo.value = int.from_bytes(data[o:o + 8], 'little') txo.value = int.from_bytes(data[o : o + 8], "little")
o += 8 o += 8
txo.version = int.from_bytes(data[o:o + 2], 'little') txo.version = int.from_bytes(data[o : o + 2], "little")
o += 2 o += 2
script_bytes, nb = decode_compactsize(data, o) script_bytes, nb = decode_compactsize(data, o)
o += nb o += nb
@@ -122,9 +128,9 @@ class CTransaction:
o += script_bytes o += script_bytes
self.vout.append(txo) self.vout.append(txo)
self.locktime = int.from_bytes(data[o:o + 4], 'little') self.locktime = int.from_bytes(data[o : o + 4], "little")
o += 4 o += 4
self.expiry = int.from_bytes(data[o:o + 4], 'little') self.expiry = int.from_bytes(data[o : o + 4], "little")
o += 4 o += 4
if ser_type == TxSerializeType.NoWitness: if ser_type == TxSerializeType.NoWitness:
@@ -137,15 +143,15 @@ class CTransaction:
self.vin = [CTxIn() for _ in range(num_wit_scripts)] self.vin = [CTxIn() for _ in range(num_wit_scripts)]
else: else:
if num_wit_scripts != len(self.vin): if num_wit_scripts != len(self.vin):
raise ValueError('non equal witness and prefix txin quantities') raise ValueError("non equal witness and prefix txin quantities")
for i in range(num_wit_scripts): for i in range(num_wit_scripts):
txi = self.vin[i] txi = self.vin[i]
txi.value_in = int.from_bytes(data[o:o + 8], 'little') txi.value_in = int.from_bytes(data[o : o + 8], "little")
o += 8 o += 8
txi.block_height = int.from_bytes(data[o:o + 4], 'little') txi.block_height = int.from_bytes(data[o : o + 4], "little")
o += 4 o += 4
txi.block_index = int.from_bytes(data[o:o + 4], 'little') txi.block_index = int.from_bytes(data[o : o + 4], "little")
o += 4 o += 4
script_bytes, nb = decode_compactsize(data, o) script_bytes, nb = decode_compactsize(data, o)
o += nb o += nb
@@ -154,34 +160,36 @@ class CTransaction:
def serialize(self, ser_type=TxSerializeType.Full) -> bytes: def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
data = bytes() data = bytes()
version = (self.version & 0xffff) | (ser_type << 16) version = (self.version & 0xFFFF) | (ser_type << 16)
data += version.to_bytes(4, 'little') data += version.to_bytes(4, "little")
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness: if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
data += encode_compactsize(len(self.vin)) data += encode_compactsize(len(self.vin))
for txi in self.vin: for txi in self.vin:
data += txi.prevout.hash.to_bytes(32, 'little') data += txi.prevout.hash.to_bytes(32, "little")
data += txi.prevout.n.to_bytes(4, 'little') data += txi.prevout.n.to_bytes(4, "little")
data += txi.prevout.tree.to_bytes(1, 'little') data += txi.prevout.tree.to_bytes(1, "little")
data += txi.sequence.to_bytes(4, 'little') data += txi.sequence.to_bytes(4, "little")
data += encode_compactsize(len(self.vout)) data += encode_compactsize(len(self.vout))
for txo in self.vout: for txo in self.vout:
data += txo.value.to_bytes(8, 'little') data += txo.value.to_bytes(8, "little")
data += txo.version.to_bytes(2, 'little') data += txo.version.to_bytes(2, "little")
data += encode_compactsize(len(txo.script_pubkey)) data += encode_compactsize(len(txo.script_pubkey))
data += txo.script_pubkey data += txo.script_pubkey
data += self.locktime.to_bytes(4, 'little') data += self.locktime.to_bytes(4, "little")
data += self.expiry.to_bytes(4, 'little') data += self.expiry.to_bytes(4, "little")
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness: if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
data += encode_compactsize(len(self.vin)) data += encode_compactsize(len(self.vin))
for txi in self.vin: for txi in self.vin:
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values tc_value_in = (
data += tc_value_in.to_bytes(8, 'little') txi.value_in & 0xFFFFFFFFFFFFFFFF
data += txi.block_height.to_bytes(4, 'little') ) # Convert negative values
data += txi.block_index.to_bytes(4, 'little') data += tc_value_in.to_bytes(8, "little")
data += txi.block_height.to_bytes(4, "little")
data += txi.block_index.to_bytes(4, "little")
data += encode_compactsize(len(txi.signature_script)) data += encode_compactsize(len(txi.signature_script))
data += txi.signature_script data += txi.signature_script
@@ -191,10 +199,10 @@ class CTransaction:
return blake256(self.serialize(TxSerializeType.NoWitness))[::-1] return blake256(self.serialize(TxSerializeType.NoWitness))[::-1]
def TxHashWitness(self) -> bytes: def TxHashWitness(self) -> bytes:
raise ValueError('todo') raise ValueError("todo")
def TxHashFull(self) -> bytes: def TxHashFull(self) -> bytes:
raise ValueError('todo') raise ValueError("todo")
def findOutput(tx, script_pk: bytes): def findOutput(tx, script_pk: bytes):

View File

@@ -9,34 +9,34 @@ import traceback
from basicswap.rpc import Jsonrpc from basicswap.rpc import Jsonrpc
def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'): def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1"):
try: try:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) url = "http://{}@{}:{}/".format(auth, host, rpc_port)
x = Jsonrpc(url) x = Jsonrpc(url)
x.__handler = None x.__handler = None
v = x.json_request(method, params) v = x.json_request(method, params)
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:
traceback.print_exc() traceback.print_exc()
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method) 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"]))
return r['result'] return r["result"]
def openrpc(rpc_port, auth, host='127.0.0.1'): def openrpc(rpc_port, auth, host="127.0.0.1"):
try: try:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) url = "http://{}@{}:{}/".format(auth, host, rpc_port)
return Jsonrpc(url) return Jsonrpc(url)
except Exception as ex: except Exception as ex:
traceback.print_exc() traceback.print_exc()
raise ValueError('RPC error ' + str(ex)) raise ValueError("RPC error " + str(ex))
def make_rpc_func(port, auth, host='127.0.0.1'): def make_rpc_func(port, auth, host="127.0.0.1"):
port = port port = port
auth = auth auth = auth
host = host host = host
@@ -44,4 +44,5 @@ def make_rpc_func(port, auth, host='127.0.0.1'):
def rpc_func(method, params=None): def rpc_func(method, params=None):
nonlocal port, auth, host nonlocal port, auth, host
return callrpc(port, auth, method, params, host) return callrpc(port, auth, method, params, host)
return rpc_func return rpc_func

View File

@@ -7,7 +7,7 @@
OP_0 = 0x00 OP_0 = 0x00
OP_DATA_1 = 0x01 OP_DATA_1 = 0x01
OP_1NEGATE = 0x4f OP_1NEGATE = 0x4F
OP_1 = 0x51 OP_1 = 0x51
OP_IF = 0x63 OP_IF = 0x63
OP_ELSE = 0x67 OP_ELSE = 0x67
@@ -16,13 +16,13 @@ OP_DROP = 0x75
OP_DUP = 0x76 OP_DUP = 0x76
OP_EQUAL = 0x87 OP_EQUAL = 0x87
OP_EQUALVERIFY = 0x88 OP_EQUALVERIFY = 0x88
OP_PUSHDATA1 = 0x4c OP_PUSHDATA1 = 0x4C
OP_PUSHDATA2 = 0x4d OP_PUSHDATA2 = 0x4D
OP_PUSHDATA4 = 0x4e OP_PUSHDATA4 = 0x4E
OP_HASH160 = 0xa9 OP_HASH160 = 0xA9
OP_CHECKSIG = 0xac OP_CHECKSIG = 0xAC
OP_CHECKMULTISIG = 0xae OP_CHECKMULTISIG = 0xAE
OP_CHECKSEQUENCEVERIFY = 0xb2 OP_CHECKSEQUENCEVERIFY = 0xB2
def push_script_data(data_array: bytearray, data: bytes) -> None: def push_script_data(data_array: bytearray, data: bytes) -> None:
@@ -39,12 +39,12 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
return return
if len_data < OP_PUSHDATA1: if len_data < OP_PUSHDATA1:
data_array += len_data.to_bytes(1, 'little') data_array += len_data.to_bytes(1, "little")
elif len_data <= 0xff: elif len_data <= 0xFF:
data_array += bytes((OP_PUSHDATA1, len_data)) data_array += bytes((OP_PUSHDATA1, len_data))
elif len_data <= 0xffff: elif len_data <= 0xFFFF:
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little') data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, "little")
else: else:
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little') data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, "little")
data_array += data data_array += data

View File

@@ -10,46 +10,48 @@ import subprocess
def createDCRWallet(args, hex_seed, logging, delay_event): def createDCRWallet(args, hex_seed, logging, delay_event):
logging.info('Creating DCR wallet') logging.info("Creating DCR wallet")
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read (pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
if os.name == 'nt': if os.name == "nt":
str_args = ' '.join(args) str_args = " ".join(args)
p = subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) p = subprocess.Popen(
str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w
)
else: else:
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w)
def readOutput(): def readOutput():
buf = os.read(pipe_r, 1024).decode('utf-8') buf = os.read(pipe_r, 1024).decode("utf-8")
response = None response = None
if 'Opened wallet' in buf: if "Opened wallet" in buf:
pass pass
elif 'Use the existing configured private passphrase' in buf: elif "Use the existing configured private passphrase" in buf:
response = b'y\n' response = b"y\n"
elif 'Do you want to add an additional layer of encryption' in buf: elif "Do you want to add an additional layer of encryption" in buf:
response = b'n\n' response = b"n\n"
elif 'Do you have an existing wallet seed' in buf: elif "Do you have an existing wallet seed" in buf:
response = b'y\n' response = b"y\n"
elif 'Enter existing wallet seed' in buf: elif "Enter existing wallet seed" in buf:
response = (hex_seed + '\n').encode('utf-8') response = (hex_seed + "\n").encode("utf-8")
elif 'Seed input successful' in buf: elif "Seed input successful" in buf:
pass pass
elif 'Upgrading database from version' in buf: elif "Upgrading database from version" in buf:
pass pass
elif 'Ticket commitments db upgrade done' in buf: elif "Ticket commitments db upgrade done" in buf:
pass pass
elif 'The wallet has been created successfully' in buf: elif "The wallet has been created successfully" in buf:
pass pass
else: else:
raise ValueError(f'Unexpected output: {buf}') raise ValueError(f"Unexpected output: {buf}")
if response is not None: if response is not None:
p.stdin.write(response) p.stdin.write(response)
p.stdin.flush() p.stdin.flush()
try: try:
while p.poll() is None: while p.poll() is None:
if os.name == 'nt': if os.name == "nt":
readOutput() readOutput()
delay_event.wait(0.1) delay_event.wait(0.1)
continue continue
@@ -57,7 +59,7 @@ def createDCRWallet(args, hex_seed, logging, delay_event):
readOutput() readOutput()
delay_event.wait(0.1) delay_event.wait(0.1)
except Exception as e: except Exception as e:
logging.error(f'dcrwallet --create failed: {e}') logging.error(f"dcrwallet --create failed: {e}")
finally: finally:
if p.poll() is None: if p.poll() is None:
p.terminate() p.terminate()

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert # Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -40,10 +41,12 @@ class FIROInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super(FIROInterface, self).__init__(coin_settings, network, swap_client) super(FIROInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host
)
def getExchangeName(self, exchange_name: str) -> str: def getExchangeName(self, exchange_name: str) -> str:
return 'zcoin' return "zcoin"
def initialiseWallet(self, key): def initialiseWallet(self, key):
# load with -hdseed= parameter # load with -hdseed= parameter
@@ -52,8 +55,8 @@ class FIROInterface(BTCInterface):
def checkWallets(self) -> int: def checkWallets(self) -> int:
return 1 return 1
def getNewAddress(self, use_segwit, label='swap_receive'): def getNewAddress(self, use_segwit, label="swap_receive"):
return self.rpc('getnewaddress', [label]) return self.rpc("getnewaddress", [label])
# addr_plain = self.rpc('getnewaddress', [label]) # addr_plain = self.rpc('getnewaddress', [label])
# return self.rpc('addwitnessaddress', [addr_plain]) # return self.rpc('addwitnessaddress', [addr_plain])
@@ -61,20 +64,20 @@ class FIROInterface(BTCInterface):
return decodeAddress(address)[1:] return decodeAddress(address)[1:]
def encodeSegwitAddress(self, script): def encodeSegwitAddress(self, script):
raise ValueError('TODO') raise ValueError("TODO")
def decodeSegwitAddress(self, addr): def decodeSegwitAddress(self, addr):
raise ValueError('TODO') raise ValueError("TODO")
def isWatchOnlyAddress(self, address): def isWatchOnlyAddress(self, address):
addr_info = self.rpc('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('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"]
def getSCLockScriptAddress(self, lock_script: bytes) -> str: def getSCLockScriptAddress(self, lock_script: bytes) -> str:
lock_tx_dest = self.getScriptDest(lock_script) lock_tx_dest = self.getScriptDest(lock_script)
@@ -82,58 +85,83 @@ 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('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True]) self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True])
addr_info = self.rpc('validateaddress', [address])
return address return address
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): def getLockTxHeight(
self,
txid,
dest_address,
bid_amount,
rescan_from,
find_index: bool = False,
vout: int = -1,
):
# Add watchonly address and rescan if required # Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True): if not self.isAddressMine(dest_address, or_watch_only=True):
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.rescanBlockchainForAddress(rescan_from, dest_address) 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('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:
txid = bytes.fromhex(tx['txid']) txid = bytes.fromhex(tx["txid"])
break break
if txid is None: if txid is None:
return None return None
try: try:
tx = self.rpc('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('getblockheader', [tx['blockhash']]) block_header = self.rpc("getblockheader", [tx["blockhash"]])
block_height = block_header['height'] block_height = block_header["height"]
rv = { rv = {
'depth': 0 if 'confirmations' not in tx else tx['confirmations'], "depth": 0 if "confirmations" not in tx else tx["confirmations"],
'height': block_height} "height": block_height,
}
except Exception as e: except Exception as e:
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e)) self._log.debug(
"getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e)
)
return None return None
if find_index: if find_index:
tx_obj = self.rpc('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:
rv['txid'] = txid.hex() rv["txid"] = txid.hex()
return rv return rv
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: def createSCLockTx(
self, value: int, script: bytearray, vkbv: bytes = None
) -> bytes:
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script))) tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
@@ -144,24 +172,36 @@ 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('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(
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) 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) 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 = {
'lockUnspents': lock_unspents, "lockUnspents": lock_unspents,
'feeRate': fee_rate, "feeRate": fee_rate,
} }
if sub_fee: if sub_fee:
options['subtractFeeFromOutputs'] = [0,] options["subtractFeeFromOutputs"] = [
return self.rpc('fundrawtransaction', [txn, options])['hex'] 0,
]
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('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
@@ -188,60 +228,75 @@ class FIROInterface(BTCInterface):
return CScript([OP_HASH160, script_hash, OP_EQUAL]) 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('sendtoaddress', params) return self.rpc("sendtoaddress", params)
def getWalletSeedID(self): def getWalletSeedID(self):
return self.rpc('getwalletinfo')['hdmasterkeyid'] return self.rpc("getwalletinfo")["hdmasterkeyid"]
def getSpendableBalance(self) -> int: def getSpendableBalance(self) -> int:
return self.make_int(self.rpc('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
size = len(tx.serialize_with_witness()) + add_bytes size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000) pay_fee = round(fee_rate * size / 1000)
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') self._log.info(
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
)
return pay_fee return pay_fee
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('signrawtransaction', [tx.hex(), [], [key_wif, ]]) rv = self.rpc(
return bytes.fromhex(rv['hex']) "signrawtransaction",
[
tx.hex(),
[],
[
key_wif,
],
],
)
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('gettransaction', [txid_hex]) rv = self.rpc("gettransaction", [txid_hex])
except Exception as ex: except Exception as e: # noqa: F841
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
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): def getProofOfFunds(self, amount_for, extra_commit_bytes):
# TODO: Lock unspent and use same output/s to fund bid # TODO: Lock unspent and use same output/s to fund bid
unspents_by_addr = dict() unspents_by_addr = dict()
unspents = self.rpc('listunspent') unspents = self.rpc("listunspent")
for u in unspents: for u in unspents:
if u['spendable'] is not True: if u["spendable"] is not True:
continue continue
if u['address'] not in unspents_by_addr: if u["address"] not in unspents_by_addr:
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []} unspents_by_addr[u["address"]] = {"total": 0, "utxos": []}
utxo_amount: int = self.make_int(u['amount'], r=1) utxo_amount: int = self.make_int(u["amount"], r=1)
unspents_by_addr[u['address']]['total'] += utxo_amount unspents_by_addr[u["address"]]["total"] += utxo_amount
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'])) unspents_by_addr[u["address"]]["utxos"].append(
(utxo_amount, u["txid"], u["vout"])
)
max_utxos: int = 4 max_utxos: int = 4
viable_addrs = [] viable_addrs = []
for addr, data in unspents_by_addr.items(): for addr, data in unspents_by_addr.items():
if data['total'] >= amount_for: if data["total"] >= amount_for:
# Sort from largest to smallest amount # Sort from largest to smallest amount
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0]) sorted_utxos = sorted(data["utxos"], key=lambda x: x[0])
# Max outputs required to reach amount_for # Max outputs required to reach amount_for
utxos_req: int = 0 utxos_req: int = 0
@@ -256,13 +311,17 @@ class FIROInterface(BTCInterface):
viable_addrs.append(addr) viable_addrs.append(addr)
continue continue
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof') ensure(
len(viable_addrs) > 0, "Could not find address with enough funds for proof"
)
sign_for_addr: str = random.choice(viable_addrs) sign_for_addr: str = random.choice(viable_addrs)
self._log.debug('sign_for_addr %s', sign_for_addr) self._log.debug("sign_for_addr %s", sign_for_addr)
prove_utxos = [] prove_utxos = []
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0]) sorted_utxos = sorted(
unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0]
)
hasher = hashlib.sha256() hasher = hashlib.sha256()
@@ -272,18 +331,29 @@ class FIROInterface(BTCInterface):
outpoint = (bytes.fromhex(utxo[1]), utxo[2]) outpoint = (bytes.fromhex(utxo[1]), utxo[2])
prove_utxos.append(outpoint) prove_utxos.append(outpoint)
hasher.update(outpoint[0]) hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big')) hasher.update(outpoint[1].to_bytes(2, "big"))
if sum_value >= amount_for: if sum_value >= amount_for:
break break
utxos_hash = hasher.digest() utxos_hash = hasher.digest()
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo if (
self.using_segwit()
): # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh # 'Address does not refer to key' for non p2pkh
pkh = self.decodeAddress(sign_for_addr) pkh = self.decodeAddress(sign_for_addr)
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('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()]) 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) return (sign_for_addr, signature, prove_utxos)
@@ -292,19 +362,23 @@ class FIROInterface(BTCInterface):
sum_value: int = 0 sum_value: int = 0
for outpoint in utxos: for outpoint in utxos:
hasher.update(outpoint[0]) hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big')) hasher.update(outpoint[1].to_bytes(2, "big"))
utxos_hash = hasher.digest() utxos_hash = hasher.digest()
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) passed = self.verifyMessage(
ensure(passed is True, 'Proof of funds signature invalid') 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(): if self.using_segwit():
address = self.encodeSegwitAddress(decodeAddress(address)[1:]) address = self.encodeSegwitAddress(decodeAddress(address)[1:])
sum_value: int = 0 sum_value: int = 0
for outpoint in utxos: for outpoint in utxos:
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]]) txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1]])
sum_value += self.make_int(txout['value']) sum_value += self.make_int(txout["value"])
return sum_value return sum_value
@@ -314,15 +388,15 @@ class FIROInterface(BTCInterface):
chain_blocks: int = self.getChainHeight() chain_blocks: int = self.getChainHeight()
current_height: int = chain_blocks current_height: int = chain_blocks
block_hash = self.rpc('getblockhash', [current_height]) block_hash = self.rpc("getblockhash", [current_height])
script_hash: bytes = self.decodeAddress(addr_find) script_hash: bytes = self.decodeAddress(addr_find)
find_scriptPubKey = self.getDestForScriptHash(script_hash) find_scriptPubKey = self.getDestForScriptHash(script_hash)
while current_height > height_start: while current_height > height_start:
block_hash = self.rpc('getblockhash', [current_height]) block_hash = self.rpc("getblockhash", [current_height])
block = self.rpc('getblock', [block_hash, False]) block = self.rpc("getblock", [block_hash, False])
decoded_block = CBlock() decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block) decoded_block = FromHex(decoded_block, block)
for tx in decoded_block.vtx: for tx in decoded_block.vtx:
@@ -330,38 +404,46 @@ class FIROInterface(BTCInterface):
if txo.scriptPubKey == find_scriptPubKey: if txo.scriptPubKey == find_scriptPubKey:
tx.rehash() tx.rehash()
txid = i2b(tx.sha256) 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(
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash)) "Found output to addr: {} in tx {} in block {}".format(
self.rpc('invalidateblock', [block_hash]) addr_find, txid.hex(), block_hash
self.rpc('reconsiderblock', [block_hash]) )
)
self._log.info(
"rescanblockchain hack invalidateblock {}".format(
block_hash
)
)
self.rpc("invalidateblock", [block_hash])
self.rpc("reconsiderblock", [block_hash])
return return
current_height -= 1 current_height -= 1
def getBlockWithTxns(self, block_hash: str): def getBlockWithTxns(self, block_hash: str):
# TODO: Bypass decoderawtransaction and getblockheader # TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc('getblock', [block_hash, False]) block = self.rpc("getblock", [block_hash, False])
block_header = self.rpc('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_hex = tx.serialize_with_witness().hex() tx_hex = tx.serialize_with_witness().hex()
tx_dec = self.rpc('decoderawtransaction', [tx_hex]) tx_dec = self.rpc("decoderawtransaction", [tx_hex])
if 'hex' not in tx_dec: if "hex" not in tx_dec:
tx_dec['hex'] = tx_hex tx_dec["hex"] = tx_hex
tx_rv.append(tx_dec) tx_rv.append(tx_dec)
block_rv = { block_rv = {
'hash': block_hash, "hash": block_hash,
'previousblockhash': block_header['previousblockhash'], "previousblockhash": block_header["previousblockhash"],
'tx': tx_rv, "tx": tx_rv,
'confirmations': block_header['confirmations'], "confirmations": block_header["confirmations"],
'height': block_header['height'], "height": block_header["height"],
'time': block_header['time'], "time": block_header["time"],
'version': block_header['version'], "version": block_header["version"],
'merkleroot': block_header['merkleroot'], "merkleroot": block_header["merkleroot"],
} }
return block_rv return block_rv

View File

@@ -17,63 +17,73 @@ class LTCInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super(LTCInterface, self).__init__(coin_settings, network, swap_client) super(LTCInterface, self).__init__(coin_settings, network, swap_client)
self._rpc_wallet_mweb = 'mweb' 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) 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: def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb']) return self.rpc_wallet_mweb("getnewaddress", [label, "mweb"])
def getNewStealthAddress(self, label=''): def getNewStealthAddress(self, label=""):
return self.getNewMwebAddress(False, label) return self.getNewMwebAddress(False, label)
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str: def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
params = [addr_to, value, '', '', subfee, True, self._conf_target] params = [addr_to, value, "", "", subfee, True, self._conf_target]
if type_from == 'mweb': if type_from == "mweb":
return self.rpc_wallet_mweb('sendtoaddress', params) return self.rpc_wallet_mweb("sendtoaddress", params)
return self.rpc_wallet('sendtoaddress', params) return self.rpc_wallet("sendtoaddress", params)
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
spendable_balance = self.getSpendableBalance() spendable_balance = self.getSpendableBalance()
if spendable_balance < value_sats: if spendable_balance < value_sats:
raise ValueError('Balance too low') raise ValueError("Balance too low")
address = self.getNewAddress(self._use_segwit, 'create_utxo') address = self.getNewAddress(self._use_segwit, "create_utxo")
return self.withdrawCoin(self.format_amount(value_sats), 'plain', address, False), address return (
self.withdrawCoin(self.format_amount(value_sats), "plain", address, False),
address,
)
def getWalletInfo(self): def getWalletInfo(self):
rv = super(LTCInterface, self).getWalletInfo() rv = super(LTCInterface, self).getWalletInfo()
mweb_info = self.rpc_wallet_mweb('getwalletinfo') mweb_info = self.rpc_wallet_mweb("getwalletinfo")
rv['mweb_balance'] = mweb_info['balance'] rv["mweb_balance"] = mweb_info["balance"]
rv['mweb_unconfirmed'] = mweb_info['unconfirmed_balance'] rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"]
rv['mweb_immature'] = mweb_info['immature_balance'] rv["mweb_immature"] = mweb_info["immature_balance"]
return rv return rv
def getUnspentsByAddr(self): def getUnspentsByAddr(self):
unspent_addr = dict() unspent_addr = dict()
unspent = self.rpc_wallet('listunspent') unspent = self.rpc_wallet("listunspent")
for u in unspent: for u in unspent:
if u.get('spendable', False) is False: if u.get("spendable", False) is False:
continue continue
if u.get('solvable', False) is False: # Filter out mweb outputs if u.get("solvable", False) is False: # Filter out mweb outputs
continue continue
if 'address' not in u: if "address" not in u:
continue continue
if 'desc' in u: if "desc" in u:
desc = u['desc'] desc = u["desc"]
if self.using_segwit: if self.using_segwit:
if self.use_p2shp2wsh(): if self.use_p2shp2wsh():
if not desc.startswith('sh(wpkh'): if not desc.startswith("sh(wpkh"):
continue continue
else: else:
if not desc.startswith('wpkh'): if not desc.startswith("wpkh"):
continue continue
else: else:
if not desc.startswith('pkh'): if not desc.startswith("pkh"):
continue 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
@@ -84,8 +94,10 @@ class LTCInterfaceMWEB(LTCInterface):
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client) super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
self._rpc_wallet = 'mweb' self._rpc_wallet = "mweb"
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet) self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
)
def chainparams(self): def chainparams(self):
return chainparams[Coins.LTC] return chainparams[Coins.LTC]
@@ -95,54 +107,54 @@ class LTCInterfaceMWEB(LTCInterface):
def coin_name(self) -> str: def coin_name(self) -> str:
coin_chainparams = chainparams[Coins.LTC] coin_chainparams = chainparams[Coins.LTC]
return coin_chainparams['name'].capitalize() + ' MWEB' return coin_chainparams["name"].capitalize() + " MWEB"
def ticker(self) -> str: def ticker(self) -> str:
ticker = chainparams[Coins.LTC]['ticker'] ticker = chainparams[Coins.LTC]["ticker"]
if self._network == 'testnet': if self._network == "testnet":
ticker = 't' + ticker ticker = "t" + ticker
elif self._network == 'regtest': elif self._network == "regtest":
ticker = 'rt' + ticker ticker = "rt" + ticker
return ticker + '_MWEB' return ticker + "_MWEB"
def getNewAddress(self, use_segwit=False, label='swap_receive') -> str: def getNewAddress(self, use_segwit=False, label="swap_receive") -> str:
return self.getNewMwebAddress() return self.getNewMwebAddress()
def has_mweb_wallet(self) -> bool: def has_mweb_wallet(self) -> bool:
return 'mweb' in self.rpc('listwallets') return "mweb" in self.rpc("listwallets")
def init_wallet(self, password=None): def init_wallet(self, password=None):
# If system is encrypted mweb wallet will be created at first unlock # If system is encrypted mweb wallet will be created at first unlock
self._log.info('init_wallet - {}'.format(self.ticker())) self._log.info("init_wallet - {}".format(self.ticker()))
self._log.info('Creating mweb wallet for {}.'.format(self.coin_name())) self._log.info("Creating mweb wallet for {}.".format(self.coin_name()))
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
self.rpc('createwallet', ['mweb', False, True, password, False, False, True]) self.rpc("createwallet", ["mweb", False, True, password, False, False, True])
if password is not None: if password is not None:
# Max timeout value, ~3 years # Max timeout value, ~3 years
self.rpc_wallet('walletpassphrase', [password, 100000000]) self.rpc_wallet("walletpassphrase", [password, 100000000])
if self.getWalletSeedID() == 'Not found': if self.getWalletSeedID() == "Not found":
self._sc.initialiseWallet(self.coin_type()) self._sc.initialiseWallet(self.coin_type())
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain() # Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
self.rpc('unloadwallet', ['mweb']) self.rpc("unloadwallet", ["mweb"])
self.rpc('loadwallet', ['mweb']) self.rpc("loadwallet", ["mweb"])
if password is not None: if password is not None:
self.rpc_wallet('walletpassphrase', [password, 100000000]) self.rpc_wallet("walletpassphrase", [password, 100000000])
self.rpc_wallet('keypoolrefill') self.rpc_wallet("keypoolrefill")
def unlockWallet(self, password: str): def unlockWallet(self, password: str):
if password == '': if password == "":
return return
self._log.info('unlockWallet - {}'.format(self.ticker())) self._log.info("unlockWallet - {}".format(self.ticker()))
if not self.has_mweb_wallet(): if not self.has_mweb_wallet():
self.init_wallet(password) self.init_wallet(password)
else: else:
# Max timeout value, ~3 years # Max timeout value, ~3 years
self.rpc_wallet('walletpassphrase', [password, 100000000]) self.rpc_wallet("walletpassphrase", [password, 100000000])
self._sc.checkWalletSeed(self.coin_type()) self._sc.checkWalletSeed(self.coin_type())

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert # Copyright (c) 2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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,7 +39,9 @@ from basicswap.util.address import (
encodeAddress, encodeAddress,
) )
from basicswap.util import ( from basicswap.util import (
b2i, i2b, i2h, b2i,
i2b,
i2h,
ensure, ensure,
) )
from basicswap.basicswap_util import ( from basicswap.basicswap_util import (
@@ -49,7 +52,10 @@ from basicswap.interface.contrib.nav_test_framework.script import (
CScript, CScript,
OP_0, OP_0,
OP_EQUAL, OP_EQUAL,
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, OP_DUP,
OP_HASH160,
OP_EQUALVERIFY,
OP_CHECKSIG,
SIGHASH_ALL, SIGHASH_ALL,
SegwitVersion1SignatureHash, SegwitVersion1SignatureHash,
) )
@@ -71,7 +77,9 @@ class NAVInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super(NAVInterface, self).__init__(coin_settings, network, swap_client) super(NAVInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host
)
def use_p2shp2wsh(self) -> bool: def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh # p2sh-p2wsh
@@ -85,31 +93,31 @@ class NAVInterface(BTCInterface):
return 1 return 1
def getWalletSeedID(self): def getWalletSeedID(self):
return self.rpc('getwalletinfo')['hdmasterkeyid'] return self.rpc("getwalletinfo")["hdmasterkeyid"]
def withdrawCoin(self, value, addr_to: str, subfee: bool): def withdrawCoin(self, value, addr_to: str, subfee: bool):
strdzeel = '' strdzeel = ""
params = [addr_to, value, '', '', strdzeel, subfee] params = [addr_to, value, "", "", strdzeel, subfee]
return self.rpc('sendtoaddress', params) return self.rpc("sendtoaddress", params)
def getSpendableBalance(self) -> int: def getSpendableBalance(self) -> int:
return self.make_int(self.rpc('getwalletinfo')['balance']) return self.make_int(self.rpc("getwalletinfo")["balance"])
def signTxWithWallet(self, tx: bytes) -> bytes: def signTxWithWallet(self, tx: bytes) -> bytes:
rv = self.rpc('signrawtransaction', [tx.hex()]) rv = self.rpc("signrawtransaction", [tx.hex()])
return bytes.fromhex(rv['hex']) return bytes.fromhex(rv["hex"])
def checkExpectedSeed(self, key_hash: str): def checkExpectedSeed(self, key_hash: str):
try: try:
rv = self.rpc('dumpmnemonic') rv = self.rpc("dumpmnemonic")
entropy = Mnemonic('english').to_entropy(rv.split(' ')) entropy = Mnemonic("english").to_entropy(rv.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
return entropy_hash == key_hash return entropy_hash == key_hash
except Exception as e: except Exception as e:
self._log.warning('checkExpectedSeed failed: {}'.format(str(e))) self._log.warning("checkExpectedSeed failed: {}".format(str(e)))
return False return False
def getScriptForP2PKH(self, pkh: bytes) -> bytearray: def getScriptForP2PKH(self, pkh: bytes) -> bytearray:
@@ -134,13 +142,22 @@ class NAVInterface(BTCInterface):
script = CScript([OP_0, pkh]) script = CScript([OP_0, pkh])
script_hash = hash160(script) script_hash = hash160(script)
assert len(script_hash) == 20 assert len(script_hash) == 20
return encodeAddress(bytes((self.chainparams_network()['script_address'],)) + script_hash) return encodeAddress(
bytes((self.chainparams_network()["script_address"],)) + script_hash
)
def encodeSegwitAddressScript(self, script: bytes) -> str: def encodeSegwitAddressScript(self, script: bytes) -> str:
if len(script) == 23 and script[0] == OP_HASH160 and script[1] == 20 and script[22] == OP_EQUAL: if (
len(script) == 23
and script[0] == OP_HASH160
and script[1] == 20
and script[22] == OP_EQUAL
):
script_hash = script[2:22] script_hash = script[2:22]
return encodeAddress(bytes((self.chainparams_network()['script_address'],)) + script_hash) return encodeAddress(
raise ValueError('Unknown Script') bytes((self.chainparams_network()["script_address"],)) + script_hash
)
raise ValueError("Unknown Script")
def loadTx(self, tx_bytes: bytes) -> CTransaction: def loadTx(self, tx_bytes: bytes) -> CTransaction:
# Load tx from bytes to internal representation # Load tx from bytes to internal representation
@@ -148,9 +165,18 @@ class NAVInterface(BTCInterface):
tx.deserialize(BytesIO(tx_bytes)) tx.deserialize(BytesIO(tx_bytes))
return tx return tx
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script, prevout_value: int): def signTx(
self,
key_bytes: bytes,
tx_bytes: bytes,
input_n: int,
prevout_script,
prevout_value: int,
):
tx = self.loadTx(tx_bytes) tx = self.loadTx(tx_bytes)
sig_hash = SegwitVersion1SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) sig_hash = SegwitVersion1SignatureHash(
prevout_script, tx, input_n, SIGHASH_ALL, prevout_value
)
eck = PrivateKey(key_bytes) eck = PrivateKey(key_bytes)
return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,)) return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,))
@@ -165,23 +191,25 @@ class NAVInterface(BTCInterface):
# TODO: Lock unspent and use same output/s to fund bid # TODO: Lock unspent and use same output/s to fund bid
unspents_by_addr = dict() unspents_by_addr = dict()
unspents = self.rpc('listunspent') unspents = self.rpc("listunspent")
for u in unspents: for u in unspents:
if u['spendable'] is not True: if u["spendable"] is not True:
continue continue
if u['address'] not in unspents_by_addr: if u["address"] not in unspents_by_addr:
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []} unspents_by_addr[u["address"]] = {"total": 0, "utxos": []}
utxo_amount: int = self.make_int(u['amount'], r=1) utxo_amount: int = self.make_int(u["amount"], r=1)
unspents_by_addr[u['address']]['total'] += utxo_amount unspents_by_addr[u["address"]]["total"] += utxo_amount
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'])) unspents_by_addr[u["address"]]["utxos"].append(
(utxo_amount, u["txid"], u["vout"])
)
max_utxos: int = 4 max_utxos: int = 4
viable_addrs = [] viable_addrs = []
for addr, data in unspents_by_addr.items(): for addr, data in unspents_by_addr.items():
if data['total'] >= amount_for: if data["total"] >= amount_for:
# Sort from largest to smallest amount # Sort from largest to smallest amount
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0]) sorted_utxos = sorted(data["utxos"], key=lambda x: x[0])
# Max outputs required to reach amount_for # Max outputs required to reach amount_for
utxos_req: int = 0 utxos_req: int = 0
@@ -196,13 +224,17 @@ class NAVInterface(BTCInterface):
viable_addrs.append(addr) viable_addrs.append(addr)
continue continue
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof') ensure(
len(viable_addrs) > 0, "Could not find address with enough funds for proof"
)
sign_for_addr: str = random.choice(viable_addrs) sign_for_addr: str = random.choice(viable_addrs)
self._log.debug('sign_for_addr %s', sign_for_addr) self._log.debug("sign_for_addr %s", sign_for_addr)
prove_utxos = [] prove_utxos = []
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0]) sorted_utxos = sorted(
unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0]
)
hasher = hashlib.sha256() hasher = hashlib.sha256()
@@ -212,20 +244,36 @@ class NAVInterface(BTCInterface):
outpoint = (bytes.fromhex(utxo[1]), utxo[2]) outpoint = (bytes.fromhex(utxo[1]), utxo[2])
prove_utxos.append(outpoint) prove_utxos.append(outpoint)
hasher.update(outpoint[0]) hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big')) hasher.update(outpoint[1].to_bytes(2, "big"))
if sum_value >= amount_for: if sum_value >= amount_for:
break break
utxos_hash = hasher.digest() utxos_hash = hasher.digest()
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo if (
self.using_segwit()
): # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh # 'Address does not refer to key' for non p2pkh
addr_info = self.rpc('validateaddress', [addr, ]) addr_info = self.rpc(
if 'isscript' in addr_info and addr_info['isscript'] and 'hex' in addr_info: "validateaddress",
pkh = bytes.fromhex(addr_info['hex'])[2:] [
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) 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('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()]) 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) return (sign_for_addr, signature, prove_utxos)
@@ -234,48 +282,64 @@ class NAVInterface(BTCInterface):
sum_value: int = 0 sum_value: int = 0
for outpoint in utxos: for outpoint in utxos:
hasher.update(outpoint[0]) hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big')) hasher.update(outpoint[1].to_bytes(2, "big"))
utxos_hash = hasher.digest() utxos_hash = hasher.digest()
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) passed = self.verifyMessage(
ensure(passed is True, 'Proof of funds signature invalid') 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(): if self.using_segwit():
address = self.encodeSegwitAddress(self.decodeAddress(address)[1:]) address = self.encodeSegwitAddress(self.decodeAddress(address)[1:])
sum_value: int = 0 sum_value: int = 0
for outpoint in utxos: for outpoint in utxos:
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]]) txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1]])
sum_value += self.make_int(txout['value']) sum_value += self.make_int(txout["value"])
return sum_value return sum_value
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: def createRawFundedTransaction(
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) 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) 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}"
)
if sub_fee: if sub_fee:
raise ValueError('Navcoin fundrawtransaction is missing the subtractFeeFromOutputs parameter') raise ValueError(
"Navcoin fundrawtransaction is missing the subtractFeeFromOutputs parameter"
)
# options['subtractFeeFromOutputs'] = [0,] # options['subtractFeeFromOutputs'] = [0,]
fee_rate = self.make_int(fee_rate, r=1) fee_rate = self.make_int(fee_rate, r=1)
return self.fundTx(txn, fee_rate, lock_unspents).hex() return self.fundTx(txn, fee_rate, lock_unspents).hex()
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('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"]
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('signrawtransaction', [txn_funded])['hex'] return self.rpc("signrawtransaction", [txn_funded])["hex"]
def getBlockchainInfo(self): def getBlockchainInfo(self):
rv = self.rpc('getblockchaininfo') rv = self.rpc("getblockchaininfo")
synced = round(rv['verificationprogress'], 3) synced = round(rv["verificationprogress"], 3)
if synced >= 0.997: if synced >= 0.997:
rv['verificationprogress'] = 1.0 rv["verificationprogress"] = 1.0
return rv return rv
def encodeScriptDest(self, script_dest: bytes) -> str: def encodeScriptDest(self, script_dest: bytes) -> str:
@@ -283,47 +347,75 @@ class NAVInterface(BTCInterface):
return self.sh_to_address(script_hash) return self.sh_to_address(script_hash)
def encode_p2wsh(self, script: bytes) -> str: def encode_p2wsh(self, script: bytes) -> str:
return pubkeyToAddress(self.chainparams_network()['script_address'], script) return pubkeyToAddress(self.chainparams_network()["script_address"], script)
def find_prevout_info(self, txn_hex: str, txn_script: bytes): def find_prevout_info(self, txn_hex: str, txn_script: bytes):
txjs = self.rpc('decoderawtransaction', [txn_hex]) txjs = self.rpc("decoderawtransaction", [txn_hex])
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex()) n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
return { return {
'txid': txjs['txid'], "txid": txjs["txid"],
'vout': n, "vout": n,
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'], "scriptPubKey": txjs["vout"][n]["scriptPubKey"]["hex"],
'redeemScript': txn_script.hex(), "redeemScript": txn_script.hex(),
'amount': txjs['vout'][n]['value'] "amount": txjs["vout"][n]["value"],
} }
def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str: def getNewAddress(self, use_segwit: bool, label: str = "swap_receive") -> str:
address: str = self.rpc('getnewaddress', [label,]) address: str = self.rpc(
"getnewaddress",
[
label,
],
)
if use_segwit: if use_segwit:
return self.rpc('addwitnessaddress', [address,]) return self.rpc(
"addwitnessaddress",
[
address,
],
)
return address return address
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str: def createRedeemTxn(
self, prevout, output_addr: str, output_value: int, txn_script: bytes
) -> str:
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
prev_txid = b2i(bytes.fromhex(prevout['txid'])) prev_txid = b2i(bytes.fromhex(prevout["txid"]))
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), tx.vin.append(
scriptSig=self.getScriptScriptSig(txn_script))) CTxIn(
COutPoint(prev_txid, prevout["vout"]),
scriptSig=self.getScriptScriptSig(txn_script),
)
)
pkh = self.decodeAddress(output_addr) pkh = self.decodeAddress(output_addr)
script = self.getScriptForPubkeyHash(pkh) script = self.getScriptForPubkeyHash(pkh)
tx.vout.append(self.txoType()(output_value, script)) tx.vout.append(self.txoType()(output_value, script))
tx.rehash() tx.rehash()
return tx.serialize().hex() return tx.serialize().hex()
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes) -> str: def createRefundTxn(
self,
prevout,
output_addr: str,
output_value: int,
locktime: int,
sequence: int,
txn_script: bytes,
) -> str:
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.nLockTime = locktime tx.nLockTime = locktime
prev_txid = b2i(bytes.fromhex(prevout['txid'])) prev_txid = b2i(bytes.fromhex(prevout["txid"]))
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), tx.vin.append(
CTxIn(
COutPoint(prev_txid, prevout["vout"]),
nSequence=sequence, nSequence=sequence,
scriptSig=self.getScriptScriptSig(txn_script))) scriptSig=self.getScriptScriptSig(txn_script),
)
)
pkh = self.decodeAddress(output_addr) pkh = self.decodeAddress(output_addr)
script = self.getScriptForPubkeyHash(pkh) script = self.getScriptForPubkeyHash(pkh)
tx.vout.append(self.txoType()(output_value, script)) tx.vout.append(self.txoType()(output_value, script))
@@ -332,14 +424,30 @@ class NAVInterface(BTCInterface):
def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str: def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str:
key = decodeWif(key_wif) key = decodeWif(key_wif)
redeem_script = bytes.fromhex(prevout_data['redeemScript']) redeem_script = bytes.fromhex(prevout_data["redeemScript"])
sig = self.signTx(key, bytes.fromhex(tx_hex), 0, redeem_script, self.make_int(prevout_data['amount'])) sig = self.signTx(
key,
bytes.fromhex(tx_hex),
0,
redeem_script,
self.make_int(prevout_data["amount"]),
)
return sig.hex() return sig.hex()
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool: 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 = SegwitVersion1SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) sig_hash = SegwitVersion1SignatureHash(
prevout_script, tx, input_n, SIGHASH_ALL, prevout_value
)
pubkey = PublicKey(K) pubkey = PublicKey(K)
return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte
@@ -347,7 +455,7 @@ class NAVInterface(BTCInterface):
def verifyRawTransaction(self, tx_hex: str, prevouts): def verifyRawTransaction(self, tx_hex: str, prevouts):
# Only checks signature # Only checks signature
# verifyrawtransaction # verifyrawtransaction
self._log.warning('NAV verifyRawTransaction only checks signature') self._log.warning("NAV verifyRawTransaction only checks signature")
inputs_valid: bool = False inputs_valid: bool = False
validscripts: int = 0 validscripts: int = 0
@@ -359,22 +467,26 @@ class NAVInterface(BTCInterface):
input_n: int = 0 input_n: int = 0
prevout_data = prevouts[input_n] prevout_data = prevouts[input_n]
redeem_script = bytes.fromhex(prevout_data['redeemScript']) redeem_script = bytes.fromhex(prevout_data["redeemScript"])
prevout_value = self.make_int(prevout_data['amount']) prevout_value = self.make_int(prevout_data["amount"])
if self.verifyTxSig(tx_bytes, signature, pubkey, input_n, redeem_script, prevout_value): if self.verifyTxSig(
tx_bytes, signature, pubkey, input_n, redeem_script, prevout_value
):
validscripts += 1 validscripts += 1
# TODO: validate inputs # TODO: validate inputs
inputs_valid = True inputs_valid = True
return { return {
'inputs_valid': inputs_valid, "inputs_valid": inputs_valid,
'validscripts': validscripts, "validscripts": validscripts,
} }
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int: def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes tx_vsize = (
5 # Add a few bytes, sequence in script takes variable amount of bytes
)
tx_vsize += 184 if redeem else 187 tx_vsize += 184 if redeem else 187
return tx_vsize return tx_vsize
@@ -393,15 +505,15 @@ class NAVInterface(BTCInterface):
chain_blocks: int = self.getChainHeight() chain_blocks: int = self.getChainHeight()
current_height: int = chain_blocks current_height: int = chain_blocks
block_hash = self.rpc('getblockhash', [current_height]) block_hash = self.rpc("getblockhash", [current_height])
script_hash: bytes = self.decodeAddress(addr_find) script_hash: bytes = self.decodeAddress(addr_find)
find_scriptPubKey = self.getDestForScriptHash(script_hash) find_scriptPubKey = self.getDestForScriptHash(script_hash)
while current_height > height_start: while current_height > height_start:
block_hash = self.rpc('getblockhash', [current_height]) block_hash = self.rpc("getblockhash", [current_height])
block = self.rpc('getblock', [block_hash, False]) block = self.rpc("getblock", [block_hash, False])
decoded_block = CBlock() decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block) decoded_block = FromHex(decoded_block, block)
for tx in decoded_block.vtx: for tx in decoded_block.vtx:
@@ -409,84 +521,116 @@ class NAVInterface(BTCInterface):
if txo.scriptPubKey == find_scriptPubKey: if txo.scriptPubKey == find_scriptPubKey:
tx.rehash() tx.rehash()
txid = i2b(tx.sha256) 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(
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash)) "Found output to addr: {} in tx {} in block {}".format(
self.rpc('invalidateblock', [block_hash]) addr_find, txid.hex(), block_hash
self.rpc('reconsiderblock', [block_hash]) )
)
self._log.info(
"rescanblockchain hack invalidateblock {}".format(
block_hash
)
)
self.rpc("invalidateblock", [block_hash])
self.rpc("reconsiderblock", [block_hash])
return return
current_height -= 1 current_height -= 1
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): def getLockTxHeight(
self,
txid,
dest_address,
bid_amount,
rescan_from,
find_index: bool = False,
vout: int = -1,
):
# Add watchonly address and rescan if required # Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True): if not self.isAddressMine(dest_address, or_watch_only=True):
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.rescanBlockchainForAddress(rescan_from, dest_address) 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('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:
txid = bytes.fromhex(tx['txid']) txid = bytes.fromhex(tx["txid"])
break break
if txid is None: if txid is None:
return None return None
try: try:
tx = self.rpc('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('getblockheader', [tx['blockhash']]) block_header = self.rpc("getblockheader", [tx["blockhash"]])
block_height = block_header['height'] block_height = block_header["height"]
rv = { rv = {
'depth': 0 if 'confirmations' not in tx else tx['confirmations'], "depth": 0 if "confirmations" not in tx else tx["confirmations"],
'height': block_height} "height": block_height,
}
except Exception as e: except Exception as e:
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e)) self._log.debug(
"getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e)
)
return None return None
if find_index: if find_index:
tx_obj = self.rpc('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:
rv['txid'] = txid.hex() rv["txid"] = txid.hex()
return rv return rv
def getBlockWithTxns(self, block_hash): def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader # TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc('getblock', [block_hash, False]) block = self.rpc("getblock", [block_hash, False])
block_header = self.rpc('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_hex = tx.serialize_with_witness().hex() tx_hex = tx.serialize_with_witness().hex()
tx_dec = self.rpc('decoderawtransaction', [tx_hex]) tx_dec = self.rpc("decoderawtransaction", [tx_hex])
if 'hex' not in tx_dec: if "hex" not in tx_dec:
tx_dec['hex'] = tx_hex tx_dec["hex"] = tx_hex
tx_rv.append(tx_dec) tx_rv.append(tx_dec)
block_rv = { block_rv = {
'hash': block_hash, "hash": block_hash,
'previousblockhash': block_header['previousblockhash'], "previousblockhash": block_header["previousblockhash"],
'tx': tx_rv, "tx": tx_rv,
'confirmations': block_header['confirmations'], "confirmations": block_header["confirmations"],
'height': block_header['height'], "height": block_header["height"],
'time': block_header['time'], "time": block_header["time"],
'version': block_header['version'], "version": block_header["version"],
'merkleroot': block_header['merkleroot'], "merkleroot": block_header["merkleroot"],
} }
return block_rv return block_rv
@@ -513,15 +657,30 @@ class NAVInterface(BTCInterface):
tx.vout.append(self.txoType()(output_amount, script_pk)) tx.vout.append(self.txoType()(output_amount, script_pk))
return tx.serialize() 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, lock_tx_vout=None) -> bytes: def spendBLockTx(
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex()) self,
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ]) chain_b_lock_txid: bytes,
lock_tx = self.loadTx(bytes.fromhex(wtx['hex'])) address_to: str,
kbv: bytes,
kbs: bytes,
cb_swap_value: int,
b_fee: int,
restore_height: int,
lock_tx_vout=None,
) -> 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) Kbs = self.getPubkey(kbs)
script_pk = self.getPkDest(Kbs) script_pk = self.getPkDest(Kbs)
locked_n = findOutput(lock_tx, script_pk) locked_n = findOutput(lock_tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, "Output not found in tx")
pkh_to = self.decodeAddress(address_to) pkh_to = self.decodeAddress(address_to)
tx = CTransaction() tx = CTransaction()
@@ -531,10 +690,16 @@ class NAVInterface(BTCInterface):
script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs)) script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs))
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n), tx.vin.append(
CTxIn(
COutPoint(chain_b_lock_txid_int, locked_n),
nSequence=0, nSequence=0,
scriptSig=script_sig)) scriptSig=script_sig,
tx.vout.append(self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to))) )
)
tx.vout.append(
self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to))
)
pay_fee = self.getBLockSpendTxFee(tx, b_fee) pay_fee = self.getBLockSpendTxFee(tx, b_fee)
tx.vout[0].nValue = cb_swap_value - pay_fee tx.vout[0].nValue = cb_swap_value - pay_fee
@@ -560,16 +725,20 @@ class NAVInterface(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('gettransaction', [txid_hex]) rv = self.rpc("gettransaction", [txid_hex])
except Exception as ex: except Exception as e: # noqa: F841
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
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 createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: def createSCLockTx(
self, value: int, script: bytearray, vkbv: bytes = None
) -> bytes:
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script))) tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
@@ -580,20 +749,20 @@ class NAVInterface(BTCInterface):
feerate_str = self.format_amount(feerate) feerate_str = self.format_amount(feerate)
# TODO: unlock unspents if bid cancelled # TODO: unlock unspents if bid cancelled
options = { options = {
'lockUnspents': lock_unspents, "lockUnspents": lock_unspents,
'feeRate': feerate_str, "feeRate": feerate_str,
} }
rv = self.rpc('fundrawtransaction', [tx_hex, options]) rv = self.rpc("fundrawtransaction", [tx_hex, options])
# Sign transaction then strip witness data to fill scriptsig # Sign transaction then strip witness data to fill scriptsig
rv = self.rpc('signrawtransaction', [rv['hex']]) rv = self.rpc("signrawtransaction", [rv["hex"]])
tx_signed = self.loadTx(bytes.fromhex(rv['hex'])) tx_signed = self.loadTx(bytes.fromhex(rv["hex"]))
if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit): if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit):
raise ValueError('txn has non segwit input') raise ValueError("txn has non segwit input")
for witness_data in tx_signed.wit.vtxinwit: for witness_data in tx_signed.wit.vtxinwit:
if len(witness_data.scriptWitness.stack) < 2: if len(witness_data.scriptWitness.stack) < 2:
raise ValueError('txn has non segwit input') raise ValueError("txn has non segwit input")
return tx_signed.serialize_without_witness() return tx_signed.serialize_without_witness()
@@ -601,13 +770,23 @@ class NAVInterface(BTCInterface):
tx_funded = self.fundTx(tx_bytes.hex(), feerate) tx_funded = self.fundTx(tx_bytes.hex(), feerate)
return tx_funded return tx_funded
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 = self.loadTx(tx_lock_bytes) 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)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, "Output not found in tx")
locked_coin = tx_lock.vout[locked_n].nValue locked_coin = tx_lock.vout[locked_n].nValue
tx_lock.rehash() tx_lock.rehash()
@@ -616,9 +795,13 @@ class NAVInterface(BTCInterface):
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), tx.vin.append(
CTxIn(
COutPoint(tx_lock_id_int, locked_n),
nSequence=lock1_value, nSequence=lock1_value,
scriptSig=self.getScriptScriptSig(script_lock))) scriptSig=self.getScriptScriptSig(script_lock),
)
)
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script))) tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
@@ -628,12 +811,24 @@ class NAVInterface(BTCInterface):
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() tx.rehash()
self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', self._log.info(
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) "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 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): 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 # 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 # 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
@@ -642,7 +837,7 @@ class NAVInterface(BTCInterface):
output_script = self.getScriptDest(script_lock_refund) output_script = self.getScriptDest(script_lock_refund)
locked_n = findOutput(tx_lock_refund, output_script) locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, "Output not found in tx")
locked_coin = tx_lock_refund.vout[locked_n].nValue locked_coin = tx_lock_refund.vout[locked_n].nValue
tx_lock_refund.rehash() tx_lock_refund.rehash()
@@ -650,25 +845,46 @@ class NAVInterface(BTCInterface):
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), tx.vin.append(
CTxIn(
COutPoint(tx_lock_refund_hash_int, locked_n),
nSequence=0, nSequence=0,
scriptSig=self.getScriptScriptSig(script_lock_refund))) scriptSig=self.getScriptScriptSig(script_lock_refund),
)
)
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))) tx.vout.append(
self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))
)
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund) dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(
script_lock_refund
)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
pay_fee = round(tx_fee_rate * vsize / 1000) pay_fee = round(tx_fee_rate * vsize / 1000)
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() tx.rehash()
self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', self._log.info(
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) "createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
)
return tx.serialize() return tx.serialize()
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None): def createSCLockRefundSpendToFTx(
self,
tx_lock_refund_bytes,
script_lock_refund,
pkh_dest,
tx_fee_rate,
vkbv=None,
kbsf=None,
):
# lock refund swipe tx # lock refund swipe tx
# Sends the coinA locked coin to the follower # Sends the coinA locked coin to the follower
@@ -676,7 +892,7 @@ class NAVInterface(BTCInterface):
output_script = self.getScriptDest(script_lock_refund) output_script = self.getScriptDest(script_lock_refund)
locked_n = findOutput(tx_lock_refund, output_script) locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, "Output not found in tx")
locked_coin = tx_lock_refund.vout[locked_n].nValue locked_coin = tx_lock_refund.vout[locked_n].nValue
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund) A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
@@ -686,29 +902,44 @@ class NAVInterface(BTCInterface):
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), tx.vin.append(
CTxIn(
COutPoint(tx_lock_refund_hash_int, locked_n),
nSequence=lock2_value, nSequence=lock2_value,
scriptSig=self.getScriptScriptSig(script_lock_refund))) scriptSig=self.getScriptScriptSig(script_lock_refund),
)
)
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) tx.vout.append(
self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))
)
dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund) dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(
script_lock_refund
)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
pay_fee = round(tx_fee_rate * vsize / 1000) pay_fee = round(tx_fee_rate * vsize / 1000)
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() tx.rehash()
self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', self._log.info(
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) "createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
)
return tx.serialize() return tx.serialize()
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}): def createSCLockSpendTx(
self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}
):
tx_lock = self.loadTx(tx_lock_bytes) 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)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, "Output not found in tx")
locked_coin = tx_lock.vout[locked_n].nValue locked_coin = tx_lock.vout[locked_n].nValue
tx_lock.rehash() tx_lock.rehash()
@@ -716,10 +947,16 @@ class NAVInterface(BTCInterface):
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), tx.vin.append(
scriptSig=self.getScriptScriptSig(script_lock))) CTxIn(
COutPoint(tx_lock_id_int, locked_n),
scriptSig=self.getScriptScriptSig(script_lock),
)
)
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) tx.vout.append(
self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))
)
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
@@ -727,13 +964,18 @@ class NAVInterface(BTCInterface):
pay_fee = round(tx_fee_rate * vsize / 1000) pay_fee = round(tx_fee_rate * vsize / 1000)
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
fee_info['fee_paid'] = pay_fee fee_info["fee_paid"] = pay_fee
fee_info['rate_used'] = tx_fee_rate fee_info["rate_used"] = tx_fee_rate
fee_info['witness_bytes'] = witness_bytes fee_info["witness_bytes"] = witness_bytes
fee_info['vsize'] = vsize fee_info["vsize"] = vsize
tx.rehash() tx.rehash()
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', self._log.info(
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) "createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
)
return tx.serialize() return tx.serialize()

View File

@@ -14,26 +14,38 @@ class NMCInterface(BTCInterface):
def coin_type(): def coin_type():
return Coins.NMC return Coins.NMC
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): def getLockTxHeight(
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow self,
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible txid,
self._log.debug('[rm] scantxoutset end') dest_address,
bid_amount,
rescan_from,
find_index: bool = False,
vout: int = -1,
):
self._log.debug("[rm] scantxoutset start") # scantxoutset is slow
ro = self.rpc(
"scantxoutset", ["start", ["addr({})".format(dest_address)]]
) # TODO: Use combo(address) where possible
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"]:
if txid and o['txid'] != txid.hex(): if txid and o["txid"] != txid.hex():
continue continue
# Verify amount # Verify amount
if self.make_int(o['amount']) != int(bid_amount): if self.make_int(o["amount"]) != int(bid_amount):
self._log.warning('Found output to lock tx address of incorrect value: %s, %s', str(o['amount']), o['txid']) self._log.warning(
"Found output to lock tx address of incorrect value: %s, %s",
str(o["amount"]),
o["txid"],
)
continue continue
rv = { rv = {"depth": 0, "height": o["height"]}
'depth': 0, if o["height"] > 0:
'height': o['height']} rv["depth"] = ro["height"] - o["height"]
if o['height'] > 0:
rv['depth'] = ro['height'] - o['height']
if find_index: if find_index:
rv['index'] = o['vout'] rv["index"] = o["vout"]
if return_txid: if return_txid:
rv['txid'] = o['txid'] rv["txid"] = o["txid"]
return rv return rv

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,7 @@
# 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.contrib.test_framework.messages import ( from basicswap.contrib.test_framework.messages import CTxOut
CTxOut)
class PassthroughBTCInterface(BTCInterface): class PassthroughBTCInterface(BTCInterface):
@@ -15,5 +14,5 @@ class PassthroughBTCInterface(BTCInterface):
super().__init__(coin_settings, network) super().__init__(coin_settings, network)
self.txoType = CTxOut self.txoType = CTxOut
self._network = network self._network = network
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"])

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert # Copyright (c) 2022 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -11,11 +12,7 @@ from .btc import BTCInterface
from basicswap.rpc import make_rpc_func 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 CBlock, ToHex, FromHex, CTransaction
CBlock,
ToHex,
FromHex,
CTransaction)
from basicswap.contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import (
CScript, CScript,
OP_DUP, OP_DUP,
@@ -33,65 +30,79 @@ class PIVXInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super(PIVXInterface, self).__init__(coin_settings, network, swap_client) super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host
)
def checkWallets(self) -> int: def checkWallets(self) -> int:
return 1 return 1
def signTxWithWallet(self, tx): def signTxWithWallet(self, tx):
rv = self.rpc('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(
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) 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) 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 = {
'lockUnspents': lock_unspents, "lockUnspents": lock_unspents,
'feeRate': fee_rate, "feeRate": fee_rate,
} }
if sub_fee: if sub_fee:
options['subtractFeeFromOutputs'] = [0,] options["subtractFeeFromOutputs"] = [
return self.rpc('fundrawtransaction', [txn, options])['hex'] 0,
]
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('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('getblock', [block_hash, False]) block = self.rpc("getblock", [block_hash, False])
block_header = self.rpc('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('decoderawtransaction', [ToHex(tx)]) tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)])
tx_rv.append(tx_dec) tx_rv.append(tx_dec)
block_rv = { block_rv = {
'hash': block_hash, "hash": block_hash,
'previousblockhash': block_header['previousblockhash'], "previousblockhash": block_header["previousblockhash"],
'tx': tx_rv, "tx": tx_rv,
'confirmations': block_header['confirmations'], "confirmations": block_header["confirmations"],
'height': block_header['height'], "height": block_header["height"],
'time': block_header['time'], "time": block_header["time"],
'version': block_header['version'], "version": block_header["version"],
'merkleroot': block_header['merkleroot'], "merkleroot": block_header["merkleroot"],
} }
return block_rv return block_rv
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('sendtoaddress', params) return self.rpc("sendtoaddress", params)
def getSpendableBalance(self) -> int: def getSpendableBalance(self) -> int:
return self.make_int(self.rpc('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
@@ -107,22 +118,35 @@ class PIVXInterface(BTCInterface):
add_bytes = 107 add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000) pay_fee = round(fee_rate * size / 1000)
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') self._log.info(
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
)
return pay_fee return pay_fee
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('signrawtransaction', [tx.hex(), [], [key_wif, ]]) rv = self.rpc(
return bytes.fromhex(rv['hex']) "signrawtransaction",
[
tx.hex(),
[],
[
key_wif,
],
],
)
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('gettransaction', [txid_hex]) rv = self.rpc("gettransaction", [txid_hex])
except Exception as ex: except Exception as e: # noqa: F841
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
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

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -26,16 +27,9 @@ from coincurve.dleag import (
from basicswap.interface.base import ( from basicswap.interface.base import (
Curves, Curves,
) )
from basicswap.util import ( from basicswap.util import i2b, b2i, b2h, dumpj, ensure, TemporaryError
i2b, b2i, b2h, from basicswap.util.network import is_private_ip_address
dumpj, from basicswap.rpc_xmr import make_xmr_rpc_func, make_xmr_rpc2_func
ensure,
TemporaryError)
from basicswap.util.network import (
is_private_ip_address)
from basicswap.rpc_xmr import (
make_xmr_rpc_func,
make_xmr_rpc2_func)
from basicswap.chainparams import XMR_COIN, Coins from basicswap.chainparams import XMR_COIN, Coins
from basicswap.interface.base import CoinInterface from basicswap.interface.base import CoinInterface
@@ -75,7 +69,7 @@ class XMRInterface(CoinInterface):
@staticmethod @staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int: def xmr_swap_a_lock_spend_tx_vsize() -> int:
raise ValueError('Not possible') raise ValueError("Not possible")
@staticmethod @staticmethod
def xmr_swap_b_lock_spend_tx_vsize() -> int: def xmr_swap_b_lock_spend_tx_vsize() -> int:
@@ -84,62 +78,93 @@ class XMRInterface(CoinInterface):
def is_transient_error(self, ex) -> bool: def is_transient_error(self, ex) -> bool:
str_error: str = str(ex).lower() str_error: str = str(ex).lower()
if 'failed to get output distribution' in str_error: if "failed to get output distribution" in str_error:
return True return True
return super().is_transient_error(ex) return super().is_transient_error(ex)
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None):
super().__init__(network) super().__init__(network)
self._addr_prefix = self.chainparams_network()['address_prefix'] self._addr_prefix = self.chainparams_network()["address_prefix"]
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)
self.setFeePriority(coin_settings.get('fee_priority', 0)) self.setFeePriority(coin_settings.get("fee_priority", 0))
self._sc = swap_client self._sc = swap_client
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._wallet_password = None self._wallet_password = None
self._have_checked_seed = False self._have_checked_seed = False
daemon_login = None daemon_login = None
if coin_settings.get('rpcuser', '') != '': if coin_settings.get("rpcuser", "") != "":
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) daemon_login = (
coin_settings.get("rpcuser", ""),
coin_settings.get("rpcpassword", ""),
)
rpchost = coin_settings.get('rpchost', '127.0.0.1') rpchost = coin_settings.get("rpchost", "127.0.0.1")
proxy_host = None proxy_host = None
proxy_port = None proxy_port = None
# Connect to the daemon over a proxy if not running locally # Connect to the daemon over a proxy if not running locally
if swap_client: if swap_client:
chain_client_settings = swap_client.getChainClientSettings(self.coin_type()) chain_client_settings = swap_client.getChainClientSettings(self.coin_type())
manage_daemon: bool = chain_client_settings['manage_daemon'] manage_daemon: bool = chain_client_settings["manage_daemon"]
if swap_client.use_tor_proxy: if swap_client.use_tor_proxy:
if manage_daemon is False: if manage_daemon is False:
log_str: str = '' log_str: str = ""
have_cc_tor_opt = 'use_tor' in chain_client_settings have_cc_tor_opt = "use_tor" in chain_client_settings
if have_cc_tor_opt and chain_client_settings['use_tor'] is False: if have_cc_tor_opt and chain_client_settings["use_tor"] is False:
log_str = ' bypassing proxy (use_tor false for XMR)' log_str = " bypassing proxy (use_tor false for XMR)"
elif have_cc_tor_opt is False and is_private_ip_address(rpchost): elif have_cc_tor_opt is False and is_private_ip_address(rpchost):
log_str = ' bypassing proxy (private ip address)' log_str = " bypassing proxy (private ip address)"
else: else:
proxy_host = swap_client.tor_proxy_host proxy_host = swap_client.tor_proxy_host
proxy_port = swap_client.tor_proxy_port proxy_port = swap_client.tor_proxy_port
log_str = f' through proxy at {proxy_host}' log_str = f" through proxy at {proxy_host}"
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.') self._log.info(
f"Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}."
)
else: else:
self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.') self._log.info(
f"Not connecting to local {self.coin_name()} daemon through proxy."
)
elif manage_daemon is False: elif manage_daemon is False:
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.') self._log.info(
f"Connecting to remote {self.coin_name()} daemon at {rpchost}."
)
self._rpctimeout = coin_settings.get('rpctimeout', 60) self._rpctimeout = coin_settings.get("rpctimeout", 60)
self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120) self._walletrpctimeout = coin_settings.get("walletrpctimeout", 120)
self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600) 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.rpc = make_xmr_rpc_func(
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 coin_settings["rpcport"],
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 ') 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 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
def setWalletFilename(self, wallet_filename): def setWalletFilename(self, wallet_filename):
@@ -147,29 +172,31 @@ 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('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):
params = {'filename': filename} params = {"filename": filename}
if self._wallet_password is not None: if self._wallet_password is not None:
params['password'] = self._wallet_password params["password"] = self._wallet_password
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('close_wallet') self.rpc_wallet("close_wallet")
except Exception: except Exception:
pass pass
self.rpc_wallet('open_wallet', params) self.rpc_wallet("open_wallet", params)
def initialiseWallet(self, key_view: bytes, key_spend: bytes, restore_height=None) -> None: def initialiseWallet(
self, key_view: bytes, key_spend: bytes, restore_height=None
) -> None:
with self._mx_wallet: with self._mx_wallet:
try: try:
self.openWallet(self._wallet_filename) self.openWallet(self._wallet_filename)
# TODO: Check address # TODO: Check address
return # Wallet exists return # Wallet exists
except Exception as e: except Exception as e: # noqa: F841
pass pass
Kbv = self.getPubkey(key_view) Kbv = self.getPubkey(key_view)
@@ -177,11 +204,11 @@ class XMRInterface(CoinInterface):
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
params = { params = {
'filename': self._wallet_filename, "filename": self._wallet_filename,
'address': address_b58, "address": address_b58,
'viewkey': b2h(key_view[::-1]), "viewkey": b2h(key_view[::-1]),
'spendkey': b2h(key_spend[::-1]), "spendkey": b2h(key_spend[::-1]),
'restore_height': self._restore_height, "restore_height": self._restore_height,
} }
self.createWallet(params) self.createWallet(params)
self.openWallet(self._wallet_filename) self.openWallet(self._wallet_filename)
@@ -191,84 +218,95 @@ class XMRInterface(CoinInterface):
self.openWallet(self._wallet_filename) self.openWallet(self._wallet_filename)
def testDaemonRPC(self, with_wallet=True) -> None: def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_wallet('get_languages') self.rpc_wallet("get_languages")
def getDaemonVersion(self): def getDaemonVersion(self):
return self.rpc_wallet('get_version')['version'] return self.rpc_wallet("get_version")["version"]
def getBlockchainInfo(self): def getBlockchainInfo(self):
get_height = self.rpc2('get_height', timeout=self._rpctimeout) 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,
} }
try: try:
# get_block_count.block_count is how many blocks are in the longest chain known to the node. # get_block_count.block_count is how many blocks are in the longest chain known to the node.
# get_block_count returns "Internal error" if bootstrap-daemon is active # 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.rpc2('get_info', timeout=self._rpctimeout) 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"]
rv['known_block_count'] = get_info['height'] rv["known_block_count"] = get_info["height"]
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('get_block_count', timeout=self._rpctimeout)['count'] rv["known_block_count"] = self.rpc(
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] "get_block_count", timeout=self._rpctimeout
)["count"]
rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"]
except Exception as e: except Exception as e:
self._log.warning(f'{self.ticker_str()} get_block_count failed with: {e}') self._log.warning(f"{self.ticker_str()} get_block_count failed with: {e}")
rv['verificationprogress'] = 0.0 rv["verificationprogress"] = 0.0
return rv return rv
def getChainHeight(self): def getChainHeight(self):
return self.rpc2('get_height', timeout=self._rpctimeout)['height'] return self.rpc2("get_height", timeout=self._rpctimeout)["height"]
def getWalletInfo(self): def getWalletInfo(self):
with self._mx_wallet: with self._mx_wallet:
try: try:
self.openWallet(self._wallet_filename) self.openWallet(self._wallet_filename)
except Exception as e: except Exception as e:
if 'Failed to open wallet' in str(e): if "Failed to open wallet" in str(e):
rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0} rv = {
"encrypted": True,
"locked": True,
"balance": 0,
"unconfirmed_balance": 0,
}
return rv return rv
raise e raise e
rv = {} rv = {}
self.rpc_wallet('refresh') self.rpc_wallet("refresh")
balance_info = self.rpc_wallet('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(
rv['encrypted'] = False if self._wallet_password is None else True balance_info["balance"] - balance_info["unlocked_balance"]
rv['locked'] = False )
rv["encrypted"] = False if self._wallet_password is None else True
rv["locked"] = False
return rv return rv
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('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)
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address'] new_address = self.rpc_wallet("create_address", {"account_index": 0})[
self.rpc_wallet('store') "address"
]
self.rpc_wallet("store")
return new_address return new_address
def get_fee_rate(self, conf_target: int = 2): def get_fee_rate(self, conf_target: int = 2):
# fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest]. # fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest].
fee_est = self.rpc('get_fee_estimate') fee_est = self.rpc("get_fee_estimate")
if conf_target <= 1: if conf_target <= 1:
conf_target = 1 # normal conf_target = 1 # normal
else: else:
conf_target = 0 # slow conf_target = 0 # slow
fee_per_k_bytes = fee_est['fees'][conf_target] * 1000 fee_per_k_bytes = fee_est["fees"][conf_target] * 1000
return float(self.format_amount(fee_per_k_bytes)), 'get_fee_estimate' return float(self.format_amount(fee_per_k_bytes)), "get_fee_estimate"
def getNewSecretKey(self) -> bytes: def getNewSecretKey(self) -> bytes:
# Note: Returned bytes are in big endian order # Note: Returned bytes are in big endian order
@@ -299,7 +337,7 @@ class XMRInterface(CoinInterface):
def verifyKey(self, k: int) -> bool: def verifyKey(self, k: int) -> bool:
i = b2i(k) i = b2i(k)
return (i < edf.l and i > 8) return i < edf.l and i > 8
def verifyPubkey(self, pubkey_bytes): def verifyPubkey(self, pubkey_bytes):
# Calls ed25519_decode_check_point() in secp256k1 # Calls ed25519_decode_check_point() in secp256k1
@@ -325,45 +363,59 @@ class XMRInterface(CoinInterface):
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str: def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
return xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) return xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes: def publishBLockTx(
self,
kbv: bytes,
Kbs: bytes,
output_amount: int,
feerate: int,
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('refresh') self.rpc_wallet("refresh")
Kbv = self.getPubkey(kbv) Kbv = self.getPubkey(kbv)
shared_addr = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) shared_addr = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
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('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(
tx_hash = bytes.fromhex(rv['tx_hash']) "publishBLockTx %s to address_b58 %s", rv["tx_hash"], shared_addr
)
tx_hash = bytes.fromhex(rv["tx_hash"])
return tx_hash return tx_hash
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
):
with self._mx_wallet: with self._mx_wallet:
Kbv = self.getPubkey(kbv) Kbv = self.getPubkey(kbv)
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
kbv_le = kbv[::-1] kbv_le = kbv[::-1]
params = { params = {
'restore_height': restore_height, "restore_height": restore_height,
'filename': address_b58, "filename": address_b58,
'address': address_b58, "address": address_b58,
'viewkey': b2h(kbv_le), "viewkey": b2h(kbv_le),
} }
try: try:
self.openWallet(address_b58) self.openWallet(address_b58)
except Exception as e: except Exception as e: # noqa: F841
self.createWallet(params) self.createWallet(params)
self.openWallet(address_b58) self.openWallet(address_b58)
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong) self.rpc_wallet("refresh", timeout=self._walletrpctimeoutlong)
''' """
# Debug # Debug
try: try:
current_height = self.rpc_wallet('get_height')['height'] current_height = self.rpc_wallet('get_height')['height']
@@ -372,137 +424,222 @@ class XMRInterface(CoinInterface):
self._log.info('rpc 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('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('get_transfer_by_txid', {'txid': transfer['tx_hash']}) full_tx = self.rpc_wallet(
unlock_time = full_tx['transfer']['unlock_time'] "get_transfer_by_txid", {"txid": transfer["tx_hash"]}
)
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
)
)
rv = -1 rv = -1
continue continue
if transfer['amount'] == cb_swap_value: if transfer["amount"] == cb_swap_value:
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']} return {
"txid": transfer["tx_hash"],
"amount": transfer["amount"],
"height": (
0
if "block_height" not in transfer
else transfer["block_height"]
),
}
else: else:
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash'])) self._log.warning(
"Incorrect amount detected for coin b lock txn: {}".format(
transfer["tx_hash"]
)
)
rv = -1 rv = -1
return rv return rv
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('refresh', timeout=self._walletrpctimeoutlong) self.rpc_wallet("refresh", timeout=self._walletrpctimeoutlong)
try: try:
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height'] current_height = self.rpc2("get_height", timeout=self._rpctimeout)[
self._log.info(f'findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}') "height"
]
self._log.info(
f"findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}"
)
except Exception as e: except Exception as e:
self._log.info('rpc 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('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 and (
and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed): current_height is None
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']} or current_height - transfer["block_height"]
> self.blocks_confirmed
):
return {
"txid": transfer["tx_hash"],
"amount": transfer["amount"],
"height": transfer["block_height"],
}
return None return None
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes: def spendBLockTx(
''' self,
chain_b_lock_txid: bytes,
address_to: str,
kbv: bytes,
kbs: bytes,
cb_swap_value: int,
b_fee_rate: int,
restore_height: int,
spend_actual_balance: bool = False,
lock_tx_vout=None,
) -> bytes:
"""
Notes: Notes:
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee. "Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
''' """
with self._mx_wallet: with self._mx_wallet:
Kbv = self.getPubkey(kbv) Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs) Kbs = self.getPubkey(kbs)
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
wallet_filename = address_b58 + '_spend' wallet_filename = address_b58 + "_spend"
params = { params = {
'filename': wallet_filename, "filename": wallet_filename,
'address': address_b58, "address": address_b58,
'viewkey': b2h(kbv[::-1]), "viewkey": b2h(kbv[::-1]),
'spendkey': b2h(kbs[::-1]), "spendkey": b2h(kbs[::-1]),
'restore_height': restore_height, "restore_height": restore_height,
} }
try: try:
self.openWallet(wallet_filename) self.openWallet(wallet_filename)
except Exception as e: except Exception as e: # noqa: F841
self.createWallet(params) self.createWallet(params)
self.openWallet(wallet_filename) self.openWallet(wallet_filename)
self.rpc_wallet('refresh') self.rpc_wallet("refresh")
rv = self.rpc_wallet('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('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:
txid = txns[0]['txid'] txid = txns[0]["txid"]
self._log.warning(f'spendBLockTx detected spending tx: {txid}.') self._log.warning(f"spendBLockTx detected spending tx: {txid}.")
# Should check for address_to, but only the from address is found in the output # Should check for address_to, but only the from address is found in the output
if txns[0]['address'] == address_b58: if txns[0]["address"] == address_b58:
return bytes.fromhex(txid) return bytes.fromhex(txid)
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value)) self._log.error(
"wallet {} balance {}, expected {}".format(
wallet_filename, rv["balance"], cb_swap_value
)
)
if not spend_actual_balance: if not spend_actual_balance:
raise TemporaryError('Invalid balance') raise TemporaryError("Invalid balance")
if spend_actual_balance and rv['balance'] != cb_swap_value: if spend_actual_balance and rv["balance"] != cb_swap_value:
self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value)) self._log.warning(
cb_swap_value = rv['balance'] "Spending actual balance {}, not swap value {}.".format(
if rv['unlocked_balance'] < cb_swap_value: rv["balance"], cb_swap_value
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock'])) )
raise TemporaryError('Invalid unlocked_balance') )
cb_swap_value = rv["balance"]
if rv["unlocked_balance"] < cb_swap_value:
self._log.error(
"wallet {} balance {}, expected {}, blocks_to_unlock {}".format(
wallet_filename,
rv["unlocked_balance"],
cb_swap_value,
rv["blocks_to_unlock"],
)
)
raise TemporaryError("Invalid unlocked_balance")
params = {'address': address_to} params = {"address": address_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('sweep_all', params) rv = self.rpc_wallet("sweep_all", params)
return bytes.fromhex(rv['tx_hash_list'][0]) return bytes.fromhex(rv["tx_hash_list"][0])
def withdrawCoin(self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False) -> str: def withdrawCoin(
self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False
) -> str:
with self._mx_wallet: with self._mx_wallet:
self.openWallet(self._wallet_filename) self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh') self.rpc_wallet("refresh")
if sweepall: if sweepall:
balance = self.rpc_wallet('get_balance') balance = self.rpc_wallet("get_balance")
if balance['balance'] != balance['unlocked_balance']: if balance["balance"] != balance["unlocked_balance"]:
raise ValueError('Balance must be fully confirmed to use sweep all.') raise ValueError(
self._log.info('{} {} sweep_all.'.format(self.ticker_str(), 'estimate fee' if estimate_fee else 'withdraw')) "Balance must be fully confirmed to use sweep all."
self._log.debug('{} balance: {}'.format(self.ticker_str(), balance['balance'])) )
params = {'address': addr_to, 'do_not_relay': estimate_fee, 'subaddr_indices_all': True} self._log.info(
"{} {} sweep_all.".format(
self.ticker_str(),
"estimate fee" if estimate_fee else "withdraw",
)
)
self._log.debug(
"{} balance: {}".format(self.ticker_str(), balance["balance"])
)
params = {
"address": addr_to,
"do_not_relay": estimate_fee,
"subaddr_indices_all": True,
}
if self._fee_priority > 0: if self._fee_priority > 0:
params['priority'] = self._fee_priority params["priority"] = self._fee_priority
rv = self.rpc_wallet('sweep_all', params) rv = self.rpc_wallet("sweep_all", params)
if estimate_fee: if estimate_fee:
return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])} return {
return rv['tx_hash_list'][0] "num_txns": len(rv["fee_list"]),
"sum_amount": sum(rv["amount_list"]),
"sum_fee": sum(rv["fee_list"]),
"sum_weight": sum(rv["weight_list"]),
}
return rv["tx_hash_list"][0]
value_sats: int = self.make_int(value) value_sats: int = self.make_int(value)
params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee} params = {
"destinations": [{"amount": value_sats, "address": addr_to}],
"do_not_relay": estimate_fee,
}
if self._fee_priority > 0: if self._fee_priority > 0:
params['priority'] = self._fee_priority params["priority"] = self._fee_priority
rv = self.rpc_wallet('transfer', params) rv = self.rpc_wallet("transfer", params)
if estimate_fee: if estimate_fee:
return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']} return {
return rv['tx_hash'] "num_txns": 1,
"sum_amount": rv["amount"],
"sum_fee": rv["fee"],
"sum_weight": rv["weight"],
}
return rv["tx_hash"]
def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str: def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str:
return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True) return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True)
@@ -512,7 +649,7 @@ class XMRInterface(CoinInterface):
try: try:
Kbv = self.getPubkey(kbv) Kbv = self.getPubkey(kbv)
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
wallet_file = address_b58 + '_spend' wallet_file = address_b58 + "_spend"
try: try:
self.openWallet(wallet_file) self.openWallet(wallet_file)
except Exception: except Exception:
@@ -520,54 +657,62 @@ class XMRInterface(CoinInterface):
try: try:
self.openWallet(wallet_file) self.openWallet(wallet_file)
except Exception: except Exception:
self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.') self._log.info(
f"showLockTransfers trying to create wallet for address {address_b58}."
)
kbv_le = kbv[::-1] kbv_le = kbv[::-1]
params = { params = {
'restore_height': restore_height, "restore_height": restore_height,
'filename': address_b58, "filename": address_b58,
'address': address_b58, "address": address_b58,
'viewkey': b2h(kbv_le), "viewkey": b2h(kbv_le),
} }
self.createWallet(params) self.createWallet(params)
self.openWallet(address_b58) self.openWallet(address_b58)
self.rpc_wallet('refresh') self.rpc_wallet("refresh")
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True}) rv = self.rpc_wallet(
rv['filename'] = wallet_file "get_transfers",
{"in": True, "out": True, "pending": True, "failed": True},
)
rv["filename"] = wallet_file
return rv return rv
except Exception as e: except Exception as e:
return {'error': str(e)} return {"error": str(e)}
def getSpendableBalance(self) -> int: def getSpendableBalance(self) -> int:
with self._mx_wallet: with self._mx_wallet:
self.openWallet(self._wallet_filename) self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh') self.rpc_wallet("refresh")
balance_info = self.rpc_wallet('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):
self._log.info('changeWalletPassword - {}'.format(self.ticker())) self._log.info("changeWalletPassword - {}".format(self.ticker()))
orig_password = self._wallet_password orig_password = self._wallet_password
if old_password != '': if old_password != "":
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('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
def unlockWallet(self, password: str) -> None: def unlockWallet(self, password: str) -> None:
self._log.info('unlockWallet - {}'.format(self.ticker())) self._log.info("unlockWallet - {}".format(self.ticker()))
self._wallet_password = password self._wallet_password = password
if not self._have_checked_seed: if not self._have_checked_seed:
self._sc.checkWalletSeed(self.coin_type()) self._sc.checkWalletSeed(self.coin_type())
def lockWallet(self) -> None: def lockWallet(self) -> None:
self._log.info('lockWallet - {}'.format(self.ticker())) self._log.info("lockWallet - {}".format(self.ticker()))
self._wallet_password = None self._wallet_password = None
def isAddressMine(self, address): def isAddressMine(self, address):
@@ -576,7 +721,14 @@ class XMRInterface(CoinInterface):
def ensureFunds(self, amount: int) -> None: def ensureFunds(self, amount: int) -> None:
if self.getSpendableBalance() < amount: if self.getSpendableBalance() < amount:
raise ValueError('Balance too low') raise ValueError("Balance too low")
def getTransaction(self, txid: bytes): def getTransaction(self, txid: bytes):
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]}) return self.rpc2(
"get_transactions",
{
"txs_hashes": [
txid.hex(),
]
},
)

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
''' """
syntax = "proto3"; syntax = "proto3";
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum 0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
@@ -18,12 +18,12 @@ Don't encode fields of default values.
When decoding initialise all fields not set from data. When decoding initialise all fields not set from data.
protobuf ParseFromString would reset the whole object, from_bytes won't. protobuf ParseFromString would reset the whole object, from_bytes won't.
''' """
from basicswap.util.integer import encode_varint, decode_varint from basicswap.util.integer import encode_varint, decode_varint
class NonProtobufClass(): class NonProtobufClass:
def __init__(self, init_all: bool = True, **kwargs): def __init__(self, init_all: bool = True, **kwargs):
for key, value in kwargs.items(): for key, value in kwargs.items():
found_field: bool = False found_field: bool = False
@@ -34,7 +34,7 @@ class NonProtobufClass():
found_field = True found_field = True
break break
if found_field is False: if found_field is False:
raise ValueError(f'got an unexpected keyword argument \'{key}\'') raise ValueError(f"got an unexpected keyword argument '{key}'")
if init_all: if init_all:
self.init_fields() self.init_fields()
@@ -53,7 +53,7 @@ class NonProtobufClass():
else: else:
setattr(self, field_name, bytes()) setattr(self, field_name, bytes())
else: else:
raise ValueError(f'Unknown wire_type {wire_type}') raise ValueError(f"Unknown wire_type {wire_type}")
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
rv = bytes() rv = bytes()
@@ -74,11 +74,11 @@ class NonProtobufClass():
continue continue
rv += encode_varint(tag) rv += encode_varint(tag)
if isinstance(field_value, str): if isinstance(field_value, str):
field_value = field_value.encode('utf-8') field_value = field_value.encode("utf-8")
rv += encode_varint(len(field_value)) rv += encode_varint(len(field_value))
rv += field_value rv += field_value
else: else:
raise ValueError(f'Unknown wire_type {wire_type}') raise ValueError(f"Unknown wire_type {wire_type}")
return rv return rv
def from_bytes(self, b: bytes, init_all: bool = True) -> None: def from_bytes(self, b: bytes, init_all: bool = True) -> None:
@@ -92,7 +92,9 @@ class NonProtobufClass():
field_name, wire_type_expect, field_type = self._map[field_num] field_name, wire_type_expect, field_type = self._map[field_num]
if wire_type != wire_type_expect: if wire_type != wire_type_expect:
raise ValueError(f'Unexpected wire_type {wire_type} for field {field_num}') raise ValueError(
f"Unexpected wire_type {wire_type} for field {field_num}"
)
if wire_type == 0: if wire_type == 0:
field_value, lv = decode_varint(b, o) field_value, lv = decode_varint(b, o)
@@ -103,9 +105,9 @@ class NonProtobufClass():
field_value = b[o : o + field_len] field_value = b[o : o + field_len]
o += field_len o += field_len
if field_type == 1: if field_type == 1:
field_value = field_value.decode('utf-8') field_value = field_value.decode("utf-8")
else: else:
raise ValueError(f'Unknown wire_type {wire_type}') raise ValueError(f"Unknown wire_type {wire_type}")
setattr(self, field_name, field_value) setattr(self, field_name, field_value)
@@ -115,151 +117,150 @@ class NonProtobufClass():
class OfferMessage(NonProtobufClass): class OfferMessage(NonProtobufClass):
_map = { _map = {
1: ('protocol_version', 0, 0), 1: ("protocol_version", 0, 0),
2: ('coin_from', 0, 0), 2: ("coin_from", 0, 0),
3: ('coin_to', 0, 0), 3: ("coin_to", 0, 0),
4: ('amount_from', 0, 0), 4: ("amount_from", 0, 0),
5: ('amount_to', 0, 0), 5: ("amount_to", 0, 0),
6: ('min_bid_amount', 0, 0), 6: ("min_bid_amount", 0, 0),
7: ('time_valid', 0, 0), 7: ("time_valid", 0, 0),
8: ('lock_type', 0, 0), 8: ("lock_type", 0, 0),
9: ('lock_value', 0, 0), 9: ("lock_value", 0, 0),
10: ('swap_type', 0, 0), 10: ("swap_type", 0, 0),
11: ('proof_address', 2, 1), 11: ("proof_address", 2, 1),
12: ('proof_signature', 2, 1), 12: ("proof_signature", 2, 1),
13: ('pkhash_seller', 2, 0), 13: ("pkhash_seller", 2, 0),
14: ('secret_hash', 2, 0), 14: ("secret_hash", 2, 0),
15: ('fee_rate_from', 0, 0), 15: ("fee_rate_from", 0, 0),
16: ('fee_rate_to', 0, 0), 16: ("fee_rate_to", 0, 0),
17: ('amount_negotiable', 0, 2), 17: ("amount_negotiable", 0, 2),
18: ('rate_negotiable', 0, 2), 18: ("rate_negotiable", 0, 2),
19: ('proof_utxos', 2, 0), 19: ("proof_utxos", 2, 0),
} }
class BidMessage(NonProtobufClass): class BidMessage(NonProtobufClass):
_map = { _map = {
1: ('protocol_version', 0, 0), 1: ("protocol_version", 0, 0),
2: ('offer_msg_id', 2, 0), 2: ("offer_msg_id", 2, 0),
3: ('time_valid', 0, 0), 3: ("time_valid", 0, 0),
4: ('amount', 0, 0), 4: ("amount", 0, 0),
5: ('amount_to', 0, 0), 5: ("amount_to", 0, 0),
6: ('pkhash_buyer', 2, 0), 6: ("pkhash_buyer", 2, 0),
7: ('proof_address', 2, 1), 7: ("proof_address", 2, 1),
8: ('proof_signature', 2, 1), 8: ("proof_signature", 2, 1),
9: ('proof_utxos', 2, 0), 9: ("proof_utxos", 2, 0),
10: ('pkhash_buyer_to', 2, 0), 10: ("pkhash_buyer_to", 2, 0),
} }
class BidAcceptMessage(NonProtobufClass): class BidAcceptMessage(NonProtobufClass):
# Step 3, seller -> buyer # Step 3, seller -> buyer
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('initiate_txid', 2, 0), 2: ("initiate_txid", 2, 0),
3: ('contract_script', 2, 0), 3: ("contract_script", 2, 0),
4: ('pkhash_seller', 2, 0), 4: ("pkhash_seller", 2, 0),
} }
class OfferRevokeMessage(NonProtobufClass): class OfferRevokeMessage(NonProtobufClass):
_map = { _map = {
1: ('offer_msg_id', 2, 0), 1: ("offer_msg_id", 2, 0),
2: ('signature', 2, 0), 2: ("signature", 2, 0),
} }
class BidRejectMessage(NonProtobufClass): class BidRejectMessage(NonProtobufClass):
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('reject_code', 0, 0), 2: ("reject_code", 0, 0),
} }
class XmrBidMessage(NonProtobufClass): class XmrBidMessage(NonProtobufClass):
# MSG1L, F -> L # MSG1L, F -> L
_map = { _map = {
1: ('protocol_version', 0, 0), 1: ("protocol_version", 0, 0),
2: ('offer_msg_id', 2, 0), 2: ("offer_msg_id", 2, 0),
3: ('time_valid', 0, 0), 3: ("time_valid", 0, 0),
4: ('amount', 0, 0), 4: ("amount", 0, 0),
5: ('amount_to', 0, 0), 5: ("amount_to", 0, 0),
6: ('pkaf', 2, 0), 6: ("pkaf", 2, 0),
7: ('kbvf', 2, 0), 7: ("kbvf", 2, 0),
8: ('kbsf_dleag', 2, 0), 8: ("kbsf_dleag", 2, 0),
9: ('dest_af', 2, 0), 9: ("dest_af", 2, 0),
} }
class XmrSplitMessage(NonProtobufClass): class XmrSplitMessage(NonProtobufClass):
_map = { _map = {
1: ('msg_id', 2, 0), 1: ("msg_id", 2, 0),
2: ('msg_type', 0, 0), 2: ("msg_type", 0, 0),
3: ('sequence', 0, 0), 3: ("sequence", 0, 0),
4: ('dleag', 2, 0), 4: ("dleag", 2, 0),
} }
class XmrBidAcceptMessage(NonProtobufClass): class XmrBidAcceptMessage(NonProtobufClass):
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('pkal', 2, 0), 2: ("pkal", 2, 0),
3: ('kbvl', 2, 0), 3: ("kbvl", 2, 0),
4: ('kbsl_dleag', 2, 0), 4: ("kbsl_dleag", 2, 0),
# MSG2F # MSG2F
5: ('a_lock_tx', 2, 0), 5: ("a_lock_tx", 2, 0),
6: ('a_lock_tx_script', 2, 0), 6: ("a_lock_tx_script", 2, 0),
7: ('a_lock_refund_tx', 2, 0), 7: ("a_lock_refund_tx", 2, 0),
8: ('a_lock_refund_tx_script', 2, 0), 8: ("a_lock_refund_tx_script", 2, 0),
9: ('a_lock_refund_spend_tx', 2, 0), 9: ("a_lock_refund_spend_tx", 2, 0),
10: ('al_lock_refund_tx_sig', 2, 0), 10: ("al_lock_refund_tx_sig", 2, 0),
} }
class XmrBidLockTxSigsMessage(NonProtobufClass): class XmrBidLockTxSigsMessage(NonProtobufClass):
# MSG3L # MSG3L
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('af_lock_refund_spend_tx_esig', 2, 0), 2: ("af_lock_refund_spend_tx_esig", 2, 0),
3: ('af_lock_refund_tx_sig', 2, 0), 3: ("af_lock_refund_tx_sig", 2, 0),
} }
class XmrBidLockSpendTxMessage(NonProtobufClass): class XmrBidLockSpendTxMessage(NonProtobufClass):
# MSG4F # MSG4F
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('a_lock_spend_tx', 2, 0), 2: ("a_lock_spend_tx", 2, 0),
3: ('kal_sig', 2, 0), 3: ("kal_sig", 2, 0),
} }
class XmrBidLockReleaseMessage(NonProtobufClass): class XmrBidLockReleaseMessage(NonProtobufClass):
# MSG5F # MSG5F
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('al_lock_spend_tx_esig', 2, 0), 2: ("al_lock_spend_tx_esig", 2, 0),
} }
class ADSBidIntentMessage(NonProtobufClass): class ADSBidIntentMessage(NonProtobufClass):
# L -> F Sent from bidder, construct a reverse bid # L -> F Sent from bidder, construct a reverse bid
_map = { _map = {
1: ('protocol_version', 0, 0), 1: ("protocol_version", 0, 0),
2: ('offer_msg_id', 2, 0), 2: ("offer_msg_id", 2, 0),
3: ('time_valid', 0, 0), 3: ("time_valid", 0, 0),
4: ('amount_from', 0, 0), 4: ("amount_from", 0, 0),
5: ('amount_to', 0, 0), 5: ("amount_to", 0, 0),
} }
class ADSBidIntentAcceptMessage(NonProtobufClass): class ADSBidIntentAcceptMessage(NonProtobufClass):
# F -> L Sent from offerer, construct a reverse bid # F -> L Sent from offerer, construct a reverse bid
_map = { _map = {
1: ('bid_msg_id', 2, 0), 1: ("bid_msg_id", 2, 0),
2: ('pkaf', 2, 0), 2: ("pkaf", 2, 0),
3: ('kbvf', 2, 0), 3: ("kbvf", 2, 0),
4: ('kbsf_dleag', 2, 0), 4: ("kbsf_dleag", 2, 0),
5: ('dest_af', 2, 0), 5: ("dest_af", 2, 0),
} }

View File

@@ -5,7 +5,7 @@
# 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.
''' """
Message 2 bytes msg_class, 4 bytes length, [ 2 bytes msg_type, payload ] Message 2 bytes msg_class, 4 bytes length, [ 2 bytes msg_type, payload ]
Handshake procedure: Handshake procedure:
@@ -17,7 +17,7 @@
Both nodes are initialised Both nodes are initialised
XChaCha20_Poly1305 mac is 16bytes XChaCha20_Poly1305 mac is 16bytes
''' """
import time import time
import queue import queue
@@ -36,11 +36,12 @@ from Crypto.Cipher import ChaCha20_Poly1305 # TODO: Add to libsecp256k1/coincur
from coincurve.keys import PrivateKey, PublicKey from coincurve.keys import PrivateKey, PublicKey
from basicswap.contrib.rfc6979 import ( from basicswap.contrib.rfc6979 import (
rfc6979_hmac_sha256_initialize, rfc6979_hmac_sha256_initialize,
rfc6979_hmac_sha256_generate) rfc6979_hmac_sha256_generate,
)
START_TOKEN = 0xabcd START_TOKEN = 0xABCD
MSG_START_TOKEN = START_TOKEN.to_bytes(2, 'big') MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
MSG_MAX_SIZE = 0x200000 # 2MB MSG_MAX_SIZE = 0x200000 # 2MB
@@ -63,35 +64,37 @@ class NetMessageTypes(IntEnum):
return value in cls._value2member_map_ return value in cls._value2member_map_
''' """
class NetMessage: class NetMessage:
def __init__(self): def __init__(self):
self._msg_class = None # 2 bytes self._msg_class = None # 2 bytes
self._len = None # 4 bytes self._len = None # 4 bytes
self._msg_type = None # 2 bytes self._msg_type = None # 2 bytes
''' """
# Ensure handshake keys are not reused by including the time in the msg, mac and key hash # Ensure handshake keys are not reused by including the time in the msg, mac and key hash
# Verify timestamp is not too old # Verify timestamp is not too old
# Add keys to db to catch concurrent attempts, records can be cleared periodically, the timestamp should catch older replay attempts # Add keys to db to catch concurrent attempts, records can be cleared periodically, the timestamp should catch older replay attempts
class MsgHandshake: class MsgHandshake:
__slots__ = ('_timestamp', '_ephem_pk', '_ct', '_mac') __slots__ = ("_timestamp", "_ephem_pk", "_ct", "_mac")
def __init__(self): def __init__(self):
pass pass
def encode_aad(self): # Additional Authenticated Data def encode_aad(self): # Additional Authenticated Data
return int(NetMessageTypes.HANDSHAKE).to_bytes(2, 'big') + \ return (
self._timestamp.to_bytes(8, 'big') + \ int(NetMessageTypes.HANDSHAKE).to_bytes(2, "big")
self._ephem_pk + self._timestamp.to_bytes(8, "big")
+ self._ephem_pk
)
def encode(self): def encode(self):
return self.encode_aad() + self._ct + self._mac return self.encode_aad() + self._ct + self._mac
def decode(self, msg_mv): def decode(self, msg_mv):
o = 2 o = 2
self._timestamp = int.from_bytes(msg_mv[o: o + 8], 'big') 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
@@ -101,11 +104,31 @@ class MsgHandshake:
class Peer: class Peer:
__slots__ = ( __slots__ = (
'_mx', '_pubkey', '_address', '_socket', '_version', '_ready', '_incoming', "_mx",
'_connected_at', '_last_received_at', '_bytes_sent', '_bytes_received', "_pubkey",
'_receiving_length', '_receiving_buffer', '_recv_messages', '_misbehaving_score', "_address",
'_ke', '_km', '_dir', '_sent_nonce', '_recv_nonce', '_last_handshake_at', "_socket",
'_ping_nonce', '_last_ping_at', '_last_ping_rtt') "_version",
"_ready",
"_incoming",
"_connected_at",
"_last_received_at",
"_bytes_sent",
"_bytes_received",
"_receiving_length",
"_receiving_buffer",
"_recv_messages",
"_misbehaving_score",
"_ke",
"_km",
"_dir",
"_sent_nonce",
"_recv_nonce",
"_last_handshake_at",
"_ping_nonce",
"_last_ping_at",
"_last_ping_rtt",
)
def __init__(self, address, socket, pubkey): def __init__(self, address, socket, pubkey):
self._mx = threading.Lock() self._mx = threading.Lock()
@@ -141,14 +164,16 @@ def listen_thread(cls):
max_bytes = 0x10000 max_bytes = 0x10000
while cls._running: while cls._running:
# logging.info('[rm] network loop %d', cls._running) # logging.info('[rm] network loop %d', cls._running)
readable, writable, errored = select.select(cls._read_sockets, cls._write_sockets, cls._error_sockets, timeout) readable, writable, errored = select.select(
cls._read_sockets, cls._write_sockets, cls._error_sockets, timeout
)
cls._mx.acquire() cls._mx.acquire()
try: try:
disconnected_peers = [] disconnected_peers = []
for s in readable: for s in readable:
if s == cls._socket: if s == cls._socket:
peer_socket, address = cls._socket.accept() peer_socket, address = cls._socket.accept()
logging.info('Connection from %s', address) logging.info("Connection from %s", address)
new_peer = Peer(address, peer_socket, None) new_peer = Peer(address, peer_socket, None)
new_peer._incoming = True new_peer._incoming = True
cls._peers.append(new_peer) cls._peers.append(new_peer)
@@ -161,11 +186,11 @@ def listen_thread(cls):
bytes_recv = s.recv(max_bytes, socket.MSG_DONTWAIT) bytes_recv = s.recv(max_bytes, socket.MSG_DONTWAIT)
except socket.error as se: except socket.error as se:
if se.args[0] not in (socket.EWOULDBLOCK,): if se.args[0] not in (socket.EWOULDBLOCK,):
logging.error('Receive error %s', str(se)) logging.error("Receive error %s", str(se))
disconnected_peers.append(peer) disconnected_peers.append(peer)
continue continue
except Exception as e: except Exception as e:
logging.error('Receive error %s', str(e)) logging.error("Receive error %s", str(e))
disconnected_peers.append(peer) disconnected_peers.append(peer)
continue continue
@@ -175,7 +200,7 @@ def listen_thread(cls):
cls.receive_bytes(peer, bytes_recv) cls.receive_bytes(peer, bytes_recv)
for s in errored: for s in errored:
logging.warning('Socket error') logging.warning("Socket error")
for peer in disconnected_peers: for peer in disconnected_peers:
cls.disconnect(peer) cls.disconnect(peer)
@@ -193,7 +218,9 @@ def msg_thread(cls):
try: try:
now_us = time.time_ns() // 1000 now_us = time.time_ns() // 1000
if peer._ready is True: if peer._ready is True:
if now_us - peer._last_ping_at >= 5000000: # 5 seconds TODO: Make variable if (
now_us - peer._last_ping_at >= 5000000
): # 5 seconds TODO: Make variable
cls.send_ping(peer) cls.send_ping(peer)
msg = peer._recv_messages.get(False) msg = peer._recv_messages.get(False)
cls.process_message(peer, msg) cls.process_message(peer, msg)
@@ -201,7 +228,7 @@ def msg_thread(cls):
except queue.Empty: except queue.Empty:
pass pass
except Exception as e: except Exception as e:
logging.warning('process message error %s', str(e)) logging.warning("process message error %s", str(e))
if cls._sc.debug: if cls._sc.debug:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
@@ -211,9 +238,24 @@ def msg_thread(cls):
class Network: class Network:
__slots__ = ( __slots__ = (
'_p2p_host', '_p2p_port', '_network_key', '_network_pubkey', "_p2p_host",
'_sc', '_peers', '_max_connections', '_running', '_network_thread', '_msg_thread', "_p2p_port",
'_mx', '_socket', '_read_sockets', '_write_sockets', '_error_sockets', '_csprng', '_seen_ephem_keys') "_network_key",
"_network_pubkey",
"_sc",
"_peers",
"_max_connections",
"_running",
"_network_thread",
"_msg_thread",
"_mx",
"_socket",
"_read_sockets",
"_write_sockets",
"_error_sockets",
"_csprng",
"_seen_ephem_keys",
)
def __init__(self, p2p_host, p2p_port, network_key, swap_client): def __init__(self, p2p_host, p2p_port, network_key, swap_client):
self._p2p_host = p2p_host self._p2p_host = p2p_host
@@ -278,7 +320,13 @@ class Network:
self._mx.release() self._mx.release()
def add_connection(self, host, port, peer_pubkey): def add_connection(self, host, port, peer_pubkey):
self._sc.log.info('Connecting from %s to %s at %s %d', self._network_pubkey.hex(), peer_pubkey.hex(), host, port) self._sc.log.info(
"Connecting from %s to %s at %s %d",
self._network_pubkey.hex(),
peer_pubkey.hex(),
host,
port,
)
self._mx.acquire() self._mx.acquire()
try: try:
address = (host, port) address = (host, port)
@@ -294,7 +342,7 @@ class Network:
self.send_handshake(peer) self.send_handshake(peer)
def disconnect(self, peer): def disconnect(self, peer):
self._sc.log.info('Closing peer socket %s', peer._address) self._sc.log.info("Closing peer socket %s", peer._address)
self._read_sockets.pop(self._read_sockets.index(peer._socket)) self._read_sockets.pop(self._read_sockets.index(peer._socket))
self._error_sockets.pop(self._error_sockets.index(peer._socket)) self._error_sockets.pop(self._error_sockets.index(peer._socket))
peer.close() peer.close()
@@ -305,7 +353,11 @@ class Network:
used = self._seen_ephem_keys.get(ephem_pk) used = self._seen_ephem_keys.get(ephem_pk)
if used: if used:
raise ValueError('Handshake ephem_pk reused %s peer %s', 'for' if direction == 1 else 'by', used[0]) raise ValueError(
"Handshake ephem_pk reused %s peer %s",
"for" if direction == 1 else "by",
used[0],
)
self._seen_ephem_keys[ephem_pk] = (peer._address, timestamp) self._seen_ephem_keys[ephem_pk] = (peer._address, timestamp)
@@ -313,12 +365,14 @@ class Network:
self._seen_ephem_keys.popitem(last=False) self._seen_ephem_keys.popitem(last=False)
def send_handshake(self, peer): def send_handshake(self, peer):
self._sc.log.debug('send_handshake %s', peer._address) self._sc.log.debug("send_handshake %s", peer._address)
peer._mx.acquire() peer._mx.acquire()
try: try:
# TODO: Drain peer._recv_messages # TODO: Drain peer._recv_messages
if not peer._recv_messages.empty(): if not peer._recv_messages.empty():
self._sc.log.warning('send_handshake %s - Receive queue dumped.', peer._address) self._sc.log.warning(
"send_handshake %s - Receive queue dumped.", peer._address
)
while not peer._recv_messages.empty(): while not peer._recv_messages.empty():
peer._recv_messages.get(False) peer._recv_messages.get(False)
@@ -332,7 +386,7 @@ class Network:
ss = k.ecdh(peer._pubkey) ss = k.ecdh(peer._pubkey)
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).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:]
@@ -361,11 +415,13 @@ class Network:
peer._mx.release() peer._mx.release()
def process_handshake(self, peer, msg_mv): def process_handshake(self, peer, msg_mv):
self._sc.log.debug('process_handshake %s', peer._address) self._sc.log.debug("process_handshake %s", peer._address)
# TODO: Drain peer._recv_messages # TODO: Drain peer._recv_messages
if not peer._recv_messages.empty(): if not peer._recv_messages.empty():
self._sc.log.warning('process_handshake %s - Receive queue dumped.', peer._address) self._sc.log.warning(
"process_handshake %s - Receive queue dumped.", peer._address
)
while not peer._recv_messages.empty(): while not peer._recv_messages.empty():
peer._recv_messages.get(False) peer._recv_messages.get(False)
@@ -375,17 +431,19 @@ class Network:
try: try:
now = int(time.time()) now = int(time.time())
if now - peer._last_handshake_at < 30: if now - peer._last_handshake_at < 30:
raise ValueError('Too many handshakes from peer %s', peer._address) raise ValueError("Too many handshakes from peer %s", peer._address)
if abs(msg._timestamp - now) > TIMESTAMP_LEEWAY: if abs(msg._timestamp - now) > TIMESTAMP_LEEWAY:
raise ValueError('Bad handshake timestamp from peer %s', peer._address) raise ValueError("Bad handshake timestamp from peer %s", peer._address)
self.check_handshake_ephem_key(peer, msg._timestamp, msg._ephem_pk, direction=2) self.check_handshake_ephem_key(
peer, msg._timestamp, msg._ephem_pk, direction=2
)
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 + msg._timestamp.to_bytes(8, 'big')).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:]
@@ -395,7 +453,9 @@ class Network:
aad += nonce aad += nonce
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce) cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
cipher.update(aad) cipher.update(aad)
plaintext = cipher.decrypt_and_verify(msg._ct, msg._mac) # Will raise error if mac doesn't match plaintext = cipher.decrypt_and_verify(
msg._ct, msg._mac
) # Will raise error if mac doesn't match
peer._version = plaintext[:6] peer._version = plaintext[:6]
sig = plaintext[6:] sig = plaintext[6:]
@@ -414,7 +474,7 @@ class Network:
except Exception as e: except Exception as e:
# TODO: misbehaving # TODO: misbehaving
self._sc.log.debug('[rm] process_handshake %s', str(e)) self._sc.log.debug("[rm] process_handshake %s", str(e))
def process_ping(self, peer, msg_mv): def process_ping(self, peer, msg_mv):
nonce = peer._recv_nonce[:24] nonce = peer._recv_nonce[:24]
@@ -426,14 +486,18 @@ 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 = int.from_bytes(plaintext[:4], 'big') 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
version = plaintext[4:10] version = plaintext[4:10]
if peer._version is None: if peer._version is None:
peer._version = version peer._version = version
self._sc.log.debug('Set version from ping %s, %s', peer._pubkey.hex(), peer._version.hex()) self._sc.log.debug(
"Set version from ping %s, %s",
peer._pubkey.hex(),
peer._version.hex(),
)
peer._recv_nonce = hashlib.sha256(nonce + mac).digest() peer._recv_nonce = hashlib.sha256(nonce + mac).digest()
@@ -449,26 +513,26 @@ 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 = int.from_bytes(plaintext[:4], 'big') 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
else: else:
self._sc.log.debug('Pong received out of order %s', peer._address) self._sc.log.debug("Pong received out of order %s", peer._address)
peer._recv_nonce = hashlib.sha256(nonce + mac).digest() peer._recv_nonce = hashlib.sha256(nonce + mac).digest()
def send_ping(self, peer): def send_ping(self, peer):
ping_nonce = random.getrandbits(32) ping_nonce = random.getrandbits(32)
msg_bytes = int(NetMessageTypes.PING).to_bytes(2, 'big') 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 = ping_nonce.to_bytes(4, 'big') 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)
@@ -483,14 +547,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 = int(NetMessageTypes.PONG).to_bytes(2, 'big') 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 = ping_nonce.to_bytes(4, 'big') 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
@@ -502,19 +566,21 @@ 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) + len_encoded.to_bytes(4, 'big') + 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
def process_message(self, peer, msg_bytes): def process_message(self, peer, msg_bytes):
logging.info('[rm] process_message %s len %d', peer._address, len(msg_bytes)) logging.info("[rm] process_message %s len %d", peer._address, len(msg_bytes))
peer._mx.acquire() peer._mx.acquire()
try: try:
mv = memoryview(msg_bytes) mv = memoryview(msg_bytes)
o = 0 o = 0
msg_type = int.from_bytes(mv[o: o + 2], 'big') 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:
@@ -522,7 +588,7 @@ class Network:
elif msg_type == NetMessageTypes.PONG: elif msg_type == NetMessageTypes.PONG:
self.process_pong(peer, mv) self.process_pong(peer, mv)
else: else:
self._sc.log.debug('Unknown message type %d', msg_type) self._sc.log.debug("Unknown message type %d", msg_type)
finally: finally:
peer._mx.release() peer._mx.release()
@@ -533,7 +599,6 @@ class Network:
peer._last_received_at = time.time() peer._last_received_at = time.time()
peer._bytes_received += len_received peer._bytes_received += len_received
invalid_msg = False
mv = memoryview(bytes_recv) mv = memoryview(bytes_recv)
o = 0 o = 0
@@ -541,32 +606,32 @@ class Network:
while o < len_received: while o < len_received:
if peer._receiving_length == 0: if peer._receiving_length == 0:
if len(bytes_recv) < MSG_HEADER_LEN: if len(bytes_recv) < MSG_HEADER_LEN:
raise ValueError('Msg too short') raise ValueError("Msg too short")
if mv[o : o + 2] != MSG_START_TOKEN: if mv[o : o + 2] != MSG_START_TOKEN:
raise ValueError('Invalid start token') raise ValueError("Invalid start token")
o += 2 o += 2
msg_len = int.from_bytes(mv[o: o + 4], 'big') 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 = int.from_bytes(mv[o: o + 2], 'big') 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")
peer._receiving_length = msg_len peer._receiving_length = msg_len
len_pkt = (len_received - o) len_pkt = len_received - o
nc = msg_len if len_pkt > msg_len else len_pkt nc = msg_len if len_pkt > msg_len else len_pkt
peer._receiving_buffer = mv[o : o + nc] peer._receiving_buffer = mv[o : o + nc]
o += nc o += nc
else: else:
len_to_go = peer._receiving_length - len(peer._receiving_buffer) len_to_go = peer._receiving_length - len(peer._receiving_buffer)
len_pkt = (len_received - o) len_pkt = len_received - o
nc = len_to_go if len_pkt > len_to_go else len_pkt nc = len_to_go if len_pkt > len_to_go else len_pkt
peer._receiving_buffer = mv[o : o + nc] peer._receiving_buffer = mv[o : o + nc]
o += nc o += nc
@@ -576,11 +641,13 @@ class Network:
except Exception as e: except Exception as e:
if self._sc.debug: if self._sc.debug:
self._sc.log.error('Invalid message received from %s %s', peer._address, str(e)) self._sc.log.error(
"Invalid message received from %s %s", peer._address, str(e)
)
# TODO: misbehaving # TODO: misbehaving
def test_onion(self, path): def test_onion(self, path):
self._sc.log.debug('test_onion packet') self._sc.log.debug("test_onion packet")
def get_info(self): def get_info(self):
rv = {} rv = {}
@@ -589,14 +656,14 @@ class Network:
with self._mx: with self._mx:
for peer in self._peers: for peer in self._peers:
peer_info = { peer_info = {
'pubkey': 'Unknown' if not peer._pubkey else peer._pubkey.hex(), "pubkey": "Unknown" if not peer._pubkey else peer._pubkey.hex(),
'address': '{}:{}'.format(peer._address[0], peer._address[1]), "address": "{}:{}".format(peer._address[0], peer._address[1]),
'bytessent': peer._bytes_sent, "bytessent": peer._bytes_sent,
'bytesrecv': peer._bytes_received, "bytesrecv": peer._bytes_received,
'ready': peer._ready, "ready": peer._ready,
'incoming': peer._incoming, "incoming": peer._incoming,
} }
peers.append(peer_info) peers.append(peer_info)
rv['peers'] = peers rv["peers"] = peers
return rv return rv

View File

@@ -16,19 +16,26 @@ class ProtocolInterface:
swap_type = None swap_type = None
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
raise ValueError('base class') raise ValueError("base class")
def getMockScript(self) -> bytearray: def getMockScript(self) -> bytearray:
return bytearray([ return bytearray([OpCodes.OP_RETURN, OpCodes.OP_1])
OpCodes.OP_RETURN, OpCodes.OP_1])
def getMockScriptScriptPubkey(self, ci) -> bytearray: def getMockScriptScriptPubkey(self, ci) -> bytearray:
script = self.getMockScript() script = self.getMockScript()
return ci.getScriptDest(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.encodeScriptDest(ci.getScriptDest(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)

View File

@@ -26,52 +26,66 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
ABS_LOCK_TIME_LEEWAY = 10 * 60 ABS_LOCK_TIME_LEEWAY = 10 * 60
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256) -> bytearray: def buildContractScript(
script = bytearray([ lock_val: int,
secret_hash: bytes,
pkh_redeem: bytes,
pkh_refund: bytes,
op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY,
op_hash=OpCodes.OP_SHA256,
) -> bytearray:
script = (
bytearray(
[
OpCodes.OP_IF, OpCodes.OP_IF,
OpCodes.OP_SIZE, OpCodes.OP_SIZE,
0x01, 0x20, # 32 0x01,
0x20, # 32
OpCodes.OP_EQUALVERIFY, OpCodes.OP_EQUALVERIFY,
op_hash, op_hash,
0x20]) \ 0x20,
+ secret_hash \ ]
+ bytearray([ )
OpCodes.OP_EQUALVERIFY, + secret_hash
OpCodes.OP_DUP, + bytearray([OpCodes.OP_EQUALVERIFY, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14])
OpCodes.OP_HASH160, + pkh_redeem
0x14]) \ + bytearray(
+ pkh_redeem \ [
+ bytearray([OpCodes.OP_ELSE, ]) \ OpCodes.OP_ELSE,
+ SerialiseNum(lock_val) \ ]
+ bytearray([ )
op_lock, + SerialiseNum(lock_val)
OpCodes.OP_DROP, + bytearray(
OpCodes.OP_DUP, [op_lock, OpCodes.OP_DROP, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14]
OpCodes.OP_HASH160, )
0x14]) \ + pkh_refund
+ pkh_refund \ + bytearray([OpCodes.OP_ENDIF, OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG])
+ bytearray([ )
OpCodes.OP_ENDIF,
OpCodes.OP_EQUALVERIFY,
OpCodes.OP_CHECKSIG])
return script return script
def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256): def verifyContractScript(
if script[0] != OpCodes.OP_IF or \ script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256
script[1] != OpCodes.OP_SIZE or \ ):
script[2] != 0x01 or script[3] != 0x20 or \ if (
script[4] != OpCodes.OP_EQUALVERIFY or \ script[0] != OpCodes.OP_IF
script[5] != op_hash or \ or script[1] != OpCodes.OP_SIZE
script[6] != 0x20: or script[2] != 0x01
or script[3] != 0x20
or script[4] != OpCodes.OP_EQUALVERIFY
or script[5] != op_hash
or script[6] != 0x20
):
return False, None, None, None, None return False, None, None, None, None
o = 7 o = 7
script_hash = script[o : o + 32] script_hash = script[o : o + 32]
o += 32 o += 32
if script[o] != OpCodes.OP_EQUALVERIFY or \ if (
script[o + 1] != OpCodes.OP_DUP or \ script[o] != OpCodes.OP_EQUALVERIFY
script[o + 2] != OpCodes.OP_HASH160 or \ or script[o + 1] != OpCodes.OP_DUP
script[o + 3] != 0x14: or script[o + 2] != OpCodes.OP_HASH160
or script[o + 3] != 0x14
):
return False, script_hash, None, None, None return False, script_hash, None, None, None
o += 4 o += 4
pkh_redeem = script[o : o + 20] pkh_redeem = script[o : o + 20]
@@ -81,18 +95,22 @@ def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash
o += 1 o += 1
lock_val, nb = decodeScriptNum(script, o) lock_val, nb = decodeScriptNum(script, o)
o += nb o += nb
if script[o] != op_lock or \ if (
script[o + 1] != OpCodes.OP_DROP or \ script[o] != op_lock
script[o + 2] != OpCodes.OP_DUP or \ or script[o + 1] != OpCodes.OP_DROP
script[o + 3] != OpCodes.OP_HASH160 or \ or script[o + 2] != OpCodes.OP_DUP
script[o + 4] != 0x14: or script[o + 3] != OpCodes.OP_HASH160
or script[o + 4] != 0x14
):
return False, script_hash, pkh_redeem, lock_val, None return False, script_hash, pkh_redeem, lock_val, None
o += 5 o += 5
pkh_refund = script[o : o + 20] pkh_refund = script[o : o + 20]
o += 20 o += 20
if script[o] != OpCodes.OP_ENDIF or \ if (
script[o + 1] != OpCodes.OP_EQUALVERIFY or \ script[o] != OpCodes.OP_ENDIF
script[o + 2] != OpCodes.OP_CHECKSIG: or script[o + 1] != OpCodes.OP_EQUALVERIFY
or script[o + 2] != OpCodes.OP_CHECKSIG
):
return False, script_hash, pkh_redeem, lock_val, pkh_refund return False, script_hash, pkh_redeem, lock_val, pkh_refund
return True, script_hash, pkh_redeem, lock_val, pkh_refund return True, script_hash, pkh_redeem, lock_val, pkh_refund
@@ -105,12 +123,19 @@ def redeemITx(self, bid_id: bytes, session):
bid, offer = self.getBidAndOffer(bid_id, session) bid, offer = self.getBidAndOffer(bid_id, session)
ci_from = self.ci(offer.coin_from) ci_from = self.ci(offer.coin_from)
txn = self.createRedeemTxn(ci_from.coin_type(), bid, for_txn_type='initiate', session=session) txn = self.createRedeemTxn(
ci_from.coin_type(), bid, for_txn_type="initiate", session=session
)
txid = ci_from.publishTx(bytes.fromhex(txn)) txid = ci_from.publishTx(bytes.fromhex(txn))
bid.initiate_tx.spend_txid = bytes.fromhex(txid) bid.initiate_tx.spend_txid = bytes.fromhex(txid)
self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex()) self.log.debug(
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session) "Submitted initiate redeem txn %s to %s chain for bid %s",
txid,
ci_from.coin_name(),
bid_id.hex(),
)
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", session)
class AtomicSwapInterface(ProtocolInterface): class AtomicSwapInterface(ProtocolInterface):
@@ -118,13 +143,19 @@ class AtomicSwapInterface(ProtocolInterface):
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
addr_to = self.getMockAddrTo(ci) addr_to = self.getMockAddrTo(ci)
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False) funded_tx = ci.createRawFundedTransaction(
addr_to, amount, sub_fee, lock_unspents=False
)
return bytes.fromhex(funded_tx) return bytes.fromhex(funded_tx)
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.getScriptDest(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)
@@ -134,9 +165,9 @@ class AtomicSwapInterface(ProtocolInterface):
found += 1 found += 1
if found < 1: if found < 1:
raise ValueError('Mocked output not found') raise ValueError("Mocked output not found")
if found > 1: if found > 1:
raise ValueError('Too many mocked outputs found') raise ValueError("Too many mocked outputs found")
ctx.nLockTime = 0 ctx.nLockTime = 0
funded_tx = ctx.serialize() funded_tx = ctx.serialize()

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -19,18 +20,17 @@ from basicswap.basicswap_util import (
EventLogTypes, EventLogTypes,
) )
from . import ProtocolInterface from . import ProtocolInterface
from basicswap.contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import CScript, CScriptOp, OP_CHECKMULTISIG
CScript, CScriptOp,
OP_CHECKMULTISIG
)
def addLockRefundSigs(self, xmr_swap, ci): def addLockRefundSigs(self, xmr_swap, ci):
self.log.debug('Setting lock refund tx sigs') self.log.debug("Setting lock refund tx sigs")
witness_stack = [] witness_stack = []
if ci.coin_type() not in (Coins.DCR,): if ci.coin_type() not in (Coins.DCR,):
witness_stack += [b'', ] witness_stack += [
b"",
]
witness_stack += [ witness_stack += [
xmr_swap.al_lock_refund_tx_sig, xmr_swap.al_lock_refund_tx_sig,
xmr_swap.af_lock_refund_tx_sig, xmr_swap.af_lock_refund_tx_sig,
@@ -38,37 +38,40 @@ def addLockRefundSigs(self, xmr_swap, ci):
] ]
signed_tx = ci.setTxSignature(xmr_swap.a_lock_refund_tx, witness_stack) signed_tx = ci.setTxSignature(xmr_swap.a_lock_refund_tx, witness_stack)
ensure(signed_tx, 'setTxSignature failed') ensure(signed_tx, "setTxSignature failed")
xmr_swap.a_lock_refund_tx = signed_tx xmr_swap.a_lock_refund_tx = signed_tx
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None):
self.log.info(f'Manually recovering {bid_id.hex()}') self.log.info(f"Manually recovering {bid_id.hex()}")
# Manually recover txn if other key is known # Manually recover txn if other key is known
try: try:
use_session = self.openSession(session) use_session = self.openSession(session)
bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id)
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) ensure(bid, "Bid not found: {}.".format(bid_id.hex()))
ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex()))
offer, xmr_offer = self.getXmrOfferFromSession(use_session, bid.offer_id, sent=False) offer, xmr_offer = self.getXmrOfferFromSession(
ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) use_session, bid.offer_id, sent=False
ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) )
ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex()))
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()))
# The no-script coin is always the follower # The no-script coin is always the follower
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from)
ci_from = self.ci(Coins(offer.coin_from)) ci_from = self.ci(Coins(offer.coin_from))
ci_to = self.ci(Coins(offer.coin_to)) ci_to = self.ci(Coins(offer.coin_to))
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
try: try:
decoded_key_half = ci_follower.decodeKey(encoded_key) decoded_key_half = ci_follower.decodeKey(encoded_key)
except Exception as e: except Exception as e:
raise ValueError('Failed to decode provided key-half: ', str(e)) raise ValueError("Failed to decode provided key-half: ", str(e))
was_sent: bool = bid.was_received if reverse_bid else bid.was_sent was_sent: bool = bid.was_received if reverse_bid else bid.was_sent
localkeyhalf = ci_follower.decodeKey(getChainBSplitKey(self, bid, xmr_swap, offer)) localkeyhalf = ci_follower.decodeKey(
getChainBSplitKey(self, bid, xmr_swap, offer)
)
if was_sent: if was_sent:
kbsl = decoded_key_half kbsl = decoded_key_half
kbsf = localkeyhalf kbsf = localkeyhalf
@@ -76,32 +79,54 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None):
kbsl = localkeyhalf kbsl = localkeyhalf
kbsf = decoded_key_half kbsf = decoded_key_half
ensure(ci_follower.verifyKey(kbsl), 'Invalid kbsl') ensure(ci_follower.verifyKey(kbsl), "Invalid kbsl")
ensure(ci_follower.verifyKey(kbsf), 'Invalid kbsf') ensure(ci_follower.verifyKey(kbsf), "Invalid kbsf")
if kbsl == kbsf: if kbsl == kbsf:
raise ValueError('Provided key matches local key') raise ValueError("Provided key matches local key")
vkbs = ci_follower.sumKeys(kbsl, kbsf) vkbs = ci_follower.sumKeys(kbsl, kbsf)
ensure(ci_follower.verifyPubkey(xmr_swap.pkbs), 'Invalid pkbs') # Sanity check ensure(ci_follower.verifyPubkey(xmr_swap.pkbs), "Invalid pkbs") # Sanity check
# Ensure summed key matches the expected pubkey # Ensure summed key matches the expected pubkey
summed_pkbs = ci_follower.getPubkey(vkbs) summed_pkbs = ci_follower.getPubkey(vkbs)
if (summed_pkbs != xmr_swap.pkbs): if summed_pkbs != xmr_swap.pkbs:
err_msg: str = 'Summed key does not match expected wallet spend pubkey' err_msg: str = "Summed key does not match expected wallet spend pubkey"
have_pk = summed_pkbs.hex() have_pk = summed_pkbs.hex()
expect_pk = xmr_swap.pkbs.hex() expect_pk = xmr_swap.pkbs.hex()
self.log.error(f'{err_msg}. Got: {have_pk}, Expect: {expect_pk}') self.log.error(f"{err_msg}. Got: {have_pk}, Expect: {expect_pk}")
raise ValueError(err_msg) raise ValueError(err_msg)
if ci_follower.coin_type() in (Coins.XMR, Coins.WOW): if ci_follower.coin_type() in (Coins.XMR, Coins.WOW):
address_to = self.getCachedMainWalletAddress(ci_follower, use_session) address_to = self.getCachedMainWalletAddress(ci_follower, use_session)
else: else:
address_to = self.getCachedStealthAddressForCoin(ci_follower.coin_type(), use_session) address_to = self.getCachedStealthAddressForCoin(
ci_follower.coin_type(), use_session
)
amount = bid.amount_to amount = bid.amount_to
lock_tx_vout = bid.getLockTXBVout() lock_tx_vout = bid.getLockTXBVout()
txid = ci_follower.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout) txid = ci_follower.spendBLockTx(
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_follower.coin_name(), bid_id.hex()) xmr_swap.b_lock_tx_id,
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), use_session) address_to,
xmr_swap.vkbv,
vkbs,
amount,
xmr_offer.b_fee_rate,
bid.chain_b_height_start,
spend_actual_balance=True,
lock_tx_vout=lock_tx_vout,
)
self.log.debug(
"Submitted lock B spend txn %s to %s chain for bid %s",
txid.hex(),
ci_follower.coin_name(),
bid_id.hex(),
)
self.logBidEvent(
bid.bid_id,
EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED,
txid.hex(),
use_session,
)
use_session.commit() use_session.commit()
return txid return txid
@@ -122,7 +147,16 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
was_sent: bool = bid.was_received if reverse_bid else bid.was_sent was_sent: bool = bid.was_received if reverse_bid else bid.was_sent
key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL
return ci_follower.encodeKey(swap_client.getPathKey(ci_leader.coin_type(), ci_follower.coin_type(), bid.created_at, xmr_swap.contract_count, key_type, for_ed25519)) return ci_follower.encodeKey(
swap_client.getPathKey(
ci_leader.coin_type(),
ci_follower.coin_type(),
bid.created_at,
xmr_swap.contract_count,
key_type,
for_ed25519,
)
)
def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer): def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
@@ -132,13 +166,21 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
if bid.was_sent: if bid.was_sent:
if xmr_swap.a_lock_refund_spend_tx: if xmr_swap.a_lock_refund_spend_tx:
af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(xmr_swap.a_lock_refund_spend_tx) af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(
kbsl = ci_leader.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl) xmr_swap.a_lock_refund_spend_tx
)
kbsl = ci_leader.recoverEncKey(
xmr_swap.af_lock_refund_spend_tx_esig,
af_lock_refund_spend_tx_sig,
xmr_swap.pkasl,
)
return ci_follower.encodeKey(kbsl) return ci_follower.encodeKey(kbsl)
else: else:
if xmr_swap.a_lock_spend_tx: if xmr_swap.a_lock_spend_tx:
al_lock_spend_tx_sig = ci_leader.extractLeaderSig(xmr_swap.a_lock_spend_tx) al_lock_spend_tx_sig = ci_leader.extractLeaderSig(xmr_swap.a_lock_spend_tx)
kbsf = ci_leader.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf) kbsf = ci_leader.recoverEncKey(
xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf
)
return ci_follower.encodeKey(kbsf) return ci_follower.encodeKey(kbsf)
return None return None
@@ -149,15 +191,19 @@ def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33] xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33]
elif ci_to.curve_type() == Curves.secp256k1: elif ci_to.curve_type() == Curves.secp256k1:
for i in range(10): for i in range(10):
xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap') xmr_swap.kbsf_dleag = ci_to.signRecoverable(
pk_recovered: bytes = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap') kbsf, "proof kbsf owned for swap"
)
pk_recovered: bytes = ci_to.verifySigAndRecover(
xmr_swap.kbsf_dleag, "proof kbsf owned for swap"
)
if pk_recovered == xmr_swap.pkbsf: if pk_recovered == xmr_swap.pkbsf:
break break
# self.log.debug('kbsl recovered pubkey mismatch, retrying.') # self.log.debug('kbsl recovered pubkey mismatch, retrying.')
assert (pk_recovered == xmr_swap.pkbsf) assert pk_recovered == xmr_swap.pkbsf
xmr_swap.pkasf = xmr_swap.pkbsf xmr_swap.pkasf = xmr_swap.pkbsf
else: else:
raise ValueError('Unknown curve') raise ValueError("Unknown curve")
class XmrSwapInterface(ProtocolInterface): class XmrSwapInterface(ProtocolInterface):
@@ -165,7 +211,7 @@ class XmrSwapInterface(ProtocolInterface):
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript: def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
# fallthrough to ci if genScriptLockTxScript is implemented there # fallthrough to ci if genScriptLockTxScript is implemented there
if hasattr(ci, 'genScriptLockTxScript') and callable(ci.genScriptLockTxScript): if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript):
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs) return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal) Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
@@ -175,7 +221,9 @@ class XmrSwapInterface(ProtocolInterface):
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
addr_to = self.getMockAddrTo(ci) addr_to = self.getMockAddrTo(ci)
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False) funded_tx = ci.createRawFundedTransaction(
addr_to, amount, sub_fee, lock_unspents=False
)
return bytes.fromhex(funded_tx) return bytes.fromhex(funded_tx)
@@ -191,9 +239,9 @@ class XmrSwapInterface(ProtocolInterface):
found += 1 found += 1
if found < 1: if found < 1:
raise ValueError('Mocked output not found') raise ValueError("Mocked output not found")
if found > 1: if found > 1:
raise ValueError('Too many mocked outputs found') raise ValueError("Too many mocked outputs found")
ctx.nLockTime = 0 ctx.nLockTime = 0
return ctx.serialize() return ctx.serialize()

View File

@@ -18,31 +18,42 @@ from xmlrpc.client import (
from .util import jsonDecimal from .util import jsonDecimal
class Jsonrpc(): class Jsonrpc:
# __getattr__ complicates extending ServerProxy # __getattr__ complicates extending ServerProxy
def __init__(self, uri, transport=None, encoding=None, verbose=False, def __init__(
allow_none=False, use_datetime=False, use_builtin_types=False, self,
*, context=None): uri,
transport=None,
encoding=None,
verbose=False,
allow_none=False,
use_datetime=False,
use_builtin_types=False,
*,
context=None,
):
# establish a "logical" server connection # establish a "logical" server connection
# get the url # get the url
parsed = urllib.parse.urlparse(uri) parsed = urllib.parse.urlparse(uri)
if parsed.scheme not in ('http', 'https'): if parsed.scheme not in ("http", "https"):
raise OSError('unsupported XML-RPC protocol') raise OSError("unsupported XML-RPC protocol")
self.__host = parsed.netloc self.__host = parsed.netloc
self.__handler = parsed.path self.__handler = parsed.path
if not self.__handler: if not self.__handler:
self.__handler = '/RPC2' self.__handler = "/RPC2"
if transport is None: if transport is None:
handler = SafeTransport if parsed.scheme == 'https' else Transport handler = SafeTransport if parsed.scheme == "https" else Transport
extra_kwargs = {} extra_kwargs = {}
transport = handler(use_datetime=use_datetime, transport = handler(
use_datetime=use_datetime,
use_builtin_types=use_builtin_types, use_builtin_types=use_builtin_types,
**extra_kwargs) **extra_kwargs,
)
self.__transport = transport self.__transport = transport
self.__encoding = encoding or 'utf-8' self.__encoding = encoding or "utf-8"
self.__verbose = verbose self.__verbose = verbose
self.__allow_none = allow_none self.__allow_none = allow_none
@@ -57,17 +68,16 @@ class Jsonrpc():
connection = self.__transport.make_connection(self.__host) connection = self.__transport.make_connection(self.__host)
headers = self.__transport._extra_headers[:] headers = self.__transport._extra_headers[:]
request_body = { request_body = {"method": method, "params": params, "id": self.__request_id}
'method': method,
'params': params,
'id': self.__request_id
}
connection.putrequest('POST', self.__handler) connection.putrequest("POST", self.__handler)
headers.append(('Content-Type', 'application/json')) headers.append(("Content-Type", "application/json"))
headers.append(('User-Agent', 'jsonrpc')) headers.append(("User-Agent", "jsonrpc"))
self.__transport.send_headers(connection, headers) self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8')) self.__transport.send_content(
connection,
json.dumps(request_body, default=jsonDecimal).encode("utf-8"),
)
self.__request_id += 1 self.__request_id += 1
resp = connection.getresponse() resp = connection.getresponse()
@@ -82,55 +92,59 @@ class Jsonrpc():
raise raise
def callrpc(rpc_port, auth, method, params=[], wallet=None, host='127.0.0.1'): def callrpc(rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1"):
try: try:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) url = "http://{}@{}:{}/".format(auth, host, rpc_port)
if wallet is not None: if wallet is not None:
url += 'wallet/' + urllib.parse.quote(wallet) url += "wallet/" + urllib.parse.quote(wallet)
x = Jsonrpc(url) x = Jsonrpc(url)
v = x.json_request(method, params) v = x.json_request(method, params)
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:
traceback.print_exc() traceback.print_exc()
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method) 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"]))
return r['result'] return r["result"]
def openrpc(rpc_port, auth, wallet=None, host='127.0.0.1'): def openrpc(rpc_port, auth, wallet=None, host="127.0.0.1"):
try: try:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) url = "http://{}@{}:{}/".format(auth, host, rpc_port)
if wallet is not None: if wallet is not None:
url += 'wallet/' + urllib.parse.quote(wallet) url += "wallet/" + urllib.parse.quote(wallet)
return Jsonrpc(url) return Jsonrpc(url)
except Exception as ex: except Exception as ex:
traceback.print_exc() traceback.print_exc()
raise ValueError('RPC error ' + str(ex)) raise ValueError("RPC error " + str(ex))
def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli', wallet=None): def callrpc_cli(bindir, datadir, chain, cmd, cli_bin="particl-cli", wallet=None):
cli_bin = os.path.join(bindir, cli_bin) cli_bin = os.path.join(bindir, cli_bin)
args = [cli_bin, ] args = [
if chain != 'mainnet': cli_bin,
args.append('-' + chain) ]
args.append('-datadir=' + datadir) if chain != "mainnet":
args.append("-" + chain)
args.append("-datadir=" + datadir)
if wallet is not None: if wallet is not None:
args.append('-rpcwallet=' + wallet) args.append("-rpcwallet=" + wallet)
args += shlex.split(cmd) args += shlex.split(cmd)
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
out = p.communicate() out = p.communicate()
if len(out[1]) > 0: if len(out[1]) > 0:
raise ValueError('RPC error ' + str(out[1])) raise ValueError("RPC error " + str(out[1]))
r = out[0].decode('utf-8').strip() r = out[0].decode("utf-8").strip()
try: try:
r = json.loads(r) r = json.loads(r)
except Exception: except Exception:
@@ -138,7 +152,7 @@ def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli', wallet=None)
return r return r
def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'): def make_rpc_func(port, auth, wallet=None, host="127.0.0.1"):
port = port port = port
auth = auth auth = auth
wallet = wallet wallet = wallet
@@ -146,11 +160,19 @@ def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'):
def rpc_func(method, params=None, wallet_override=None): def rpc_func(method, params=None, wallet_override=None):
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: def escape_rpcauth(auth_str: str) -> str:
username, password = auth_str.split(':', 1) username, password = auth_str.split(":", 1)
password = urllib.parse.quote(password, safe='') password = urllib.parse.quote(password, safe="")
return f'{username}:{password}' return f"{username}:{password}"

View File

@@ -33,31 +33,50 @@ class SocksTransport(Transport):
return self._connection[1] return self._connection[1]
# create a HTTP connection object from a host descriptor # create a HTTP connection object from a host descriptor
chost, self._extra_headers, x509 = self.get_host_info(host) 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) 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] 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__(
allow_none=False, use_datetime=False, use_builtin_types=False, self,
*, context=None): uri,
transport=None,
encoding=None,
verbose=False,
allow_none=False,
use_datetime=False,
use_builtin_types=False,
*,
context=None,
):
parsed = urllib.parse.urlparse(uri) parsed = urllib.parse.urlparse(uri)
if parsed.scheme not in ('http', 'https'): if parsed.scheme not in ("http", "https"):
raise OSError('unsupported XML-RPC protocol') raise OSError("unsupported XML-RPC protocol")
self.__host = parsed.netloc self.__host = parsed.netloc
self.__handler = parsed.path self.__handler = parsed.path
if transport is None: if transport is None:
handler = SafeTransport if parsed.scheme == 'https' else Transport handler = SafeTransport if parsed.scheme == "https" else Transport
extra_kwargs = {} extra_kwargs = {}
transport = handler(use_datetime=use_datetime, transport = handler(
use_datetime=use_datetime,
use_builtin_types=use_builtin_types, use_builtin_types=use_builtin_types,
**extra_kwargs) **extra_kwargs,
)
self.__transport = transport self.__transport = transport
self.__encoding = encoding or 'utf-8' self.__encoding = encoding or "utf-8"
self.__verbose = verbose self.__verbose = verbose
self.__allow_none = allow_none self.__allow_none = allow_none
@@ -77,11 +96,18 @@ class JsonrpcDigest():
connection.timeout = timeout connection.timeout = timeout
headers = self.__transport._extra_headers[:] headers = self.__transport._extra_headers[:]
connection.putrequest('POST', self.__handler) connection.putrequest("POST", self.__handler)
headers.append(('Content-Type', 'application/json')) headers.append(("Content-Type", "application/json"))
headers.append(('User-Agent', 'jsonrpc')) headers.append(("User-Agent", "jsonrpc"))
self.__transport.send_headers(connection, headers) self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8')) self.__transport.send_content(
connection,
(
""
if params is None
else json.dumps(params, default=jsonDecimal).encode("utf-8")
),
)
self.__request_id += 1 self.__request_id += 1
resp = connection.getresponse() resp = connection.getresponse()
@@ -93,7 +119,7 @@ class JsonrpcDigest():
self.__transport.close() self.__transport.close()
raise raise
def json_request(self, request_body, username='', password='', timeout=None): def json_request(self, request_body, username="", password="", timeout=None):
try: try:
connection = self.__transport.make_connection(self.__host) connection = self.__transport.make_connection(self.__host)
if timeout: if timeout:
@@ -101,65 +127,82 @@ class JsonrpcDigest():
headers = self.__transport._extra_headers[:] headers = self.__transport._extra_headers[:]
connection.putrequest('POST', self.__handler) connection.putrequest("POST", self.__handler)
headers.append(('Content-Type', 'application/json')) headers.append(("Content-Type", "application/json"))
headers.append(('Connection', 'keep-alive')) headers.append(("Connection", "keep-alive"))
self.__transport.send_headers(connection, headers) self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '') self.__transport.send_content(
connection,
(
json.dumps(request_body, default=jsonDecimal).encode("utf-8")
if request_body
else ""
),
)
resp = connection.getresponse() resp = connection.getresponse()
if resp.status == 401: if resp.status == 401:
resp_headers = resp.getheaders() resp_headers = resp.getheaders()
v = resp.read() _ = resp.read()
algorithm = '' realm = ""
realm = '' nonce = ""
nonce = ''
for h in resp_headers: for h in resp_headers:
if h[0] != 'WWW-authenticate': if h[0] != "WWW-authenticate":
continue continue
fields = h[1].split(',') fields = h[1].split(",")
for f in fields: for f in fields:
key, value = f.split('=', 1) key, value = f.split("=", 1)
if key == 'algorithm' and value != 'MD5': if key == "algorithm" and value != "MD5":
break break
if key == 'realm': if key == "realm":
realm = value.strip('"') realm = value.strip('"')
if key == 'nonce': if key == "nonce":
nonce = value.strip('"') nonce = value.strip('"')
if realm != '' and nonce != '': if realm != "" and nonce != "":
break break
if realm == '' or nonce == '': if realm == "" or nonce == "":
raise ValueError('Authenticate header not found.') raise ValueError("Authenticate header not found.")
path = self.__handler path = self.__handler
HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest() HA1 = hashlib.md5(
f"{username}:{realm}:{password}".encode("utf-8")
).hexdigest()
http_method = 'POST' http_method = "POST"
HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest() HA2 = hashlib.md5(f"{http_method}:{path}".encode("utf-8")).hexdigest()
ncvalue = '{:08x}'.format(1) ncvalue = "{:08x}".format(1)
s = ncvalue.encode('utf-8') s = ncvalue.encode("utf-8")
s += nonce.encode('utf-8') s += nonce.encode("utf-8")
s += time.ctime().encode('utf-8') s += time.ctime().encode("utf-8")
s += os.urandom(8) s += os.urandom(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16]) cnonce = hashlib.sha1(s).hexdigest()[:16]
# MD5-SESS # MD5-SESS
HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest() HA1 = hashlib.md5(f"{HA1}:{nonce}:{cnonce}".encode("utf-8")).hexdigest()
respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest() respdig = hashlib.md5(
f"{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}".encode("utf-8")
).hexdigest()
header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"' header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
headers = self.__transport._extra_headers[:] headers = self.__transport._extra_headers[:]
headers.append(('Authorization', header_value)) headers.append(("Authorization", header_value))
connection.putrequest('POST', self.__handler) connection.putrequest("POST", self.__handler)
headers.append(('Content-Type', 'application/json')) headers.append(("Content-Type", "application/json"))
headers.append(('Connection', 'keep-alive')) headers.append(("Connection", "keep-alive"))
self.__transport.send_headers(connection, headers) self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '') self.__transport.send_content(
connection,
(
json.dumps(request_body, default=jsonDecimal).encode("utf-8")
if request_body
else ""
),
)
resp = connection.getresponse() resp = connection.getresponse()
self.__request_id += 1 self.__request_id += 1
@@ -172,57 +215,88 @@ class JsonrpcDigest():
raise raise
def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''): 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:
url = '{}:{}/{}'.format(rpc_host, rpc_port, path) url = "{}:{}/{}".format(rpc_host, rpc_port, path)
else: else:
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path) url = "http://{}:{}/{}".format(rpc_host, rpc_port, path)
x = JsonrpcDigest(url, transport=transport) x = JsonrpcDigest(url, transport=transport)
request_body = { request_body = {
'method': method, "method": method,
'params': params, "params": params,
'jsonrpc': '2.0', "jsonrpc": "2.0",
'id': x.request_id() "id": x.request_id(),
} }
if auth: if auth:
v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout) v = x.json_request(
request_body, username=auth[0], password=auth[1], timeout=timeout
)
else: else:
v = x.json_request(request_body, timeout=timeout) v = x.json_request(request_body, timeout=timeout)
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(tag, 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(tag + '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, transport=None, tag=''): 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, transport=transport) 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:
v = x.json_request(params, timeout=timeout) v = x.json_request(params, timeout=timeout)
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(tag, 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', proxy_host=None, proxy_port=None, default_timeout=120, tag=''): 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
@@ -236,11 +310,29 @@ def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port
def rpc_func(method, params=None, wallet=None, timeout=default_timeout): def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
nonlocal port, auth, host, transport, tag nonlocal port, auth, host, transport, tag
return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=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', proxy_host=None, proxy_port=None, default_timeout=120, tag=''): 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
@@ -254,5 +346,15 @@ def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=
def rpc_func(method, params=None, wallet=None, timeout=default_timeout): def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
nonlocal port, auth, host, transport, tag nonlocal port, auth, host, transport, tag
return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag) return callrpc_xmr(
port,
method,
params,
rpc_host=host,
auth=auth,
timeout=timeout,
transport=transport,
tag=tag,
)
return rpc_func return rpc_func

View File

@@ -8,23 +8,23 @@ from enum import IntEnum
class OpCodes(IntEnum): class OpCodes(IntEnum):
OP_0 = 0x00, OP_0 = (0x00,)
OP_PUSHDATA1 = 0x4c, OP_PUSHDATA1 = (0x4C,)
OP_1 = 0x51, OP_1 = (0x51,)
OP_16 = 0x60, OP_16 = (0x60,)
OP_IF = 0x63, OP_IF = (0x63,)
OP_ELSE = 0x67, OP_ELSE = (0x67,)
OP_ENDIF = 0x68, OP_ENDIF = (0x68,)
OP_RETURN = 0x6a, OP_RETURN = (0x6A,)
OP_DROP = 0x75, OP_DROP = (0x75,)
OP_DUP = 0x76, OP_DUP = (0x76,)
OP_SIZE = 0x82, OP_SIZE = (0x82,)
OP_EQUAL = 0x87, OP_EQUAL = (0x87,)
OP_EQUALVERIFY = 0x88, OP_EQUALVERIFY = (0x88,)
OP_SHA256 = 0xa8, OP_SHA256 = (0xA8,)
OP_HASH160 = 0xa9, OP_HASH160 = (0xA9,)
OP_CHECKSIG = 0xac, OP_CHECKSIG = (0xAC,)
OP_CHECKLOCKTIMEVERIFY = 0xb1, OP_CHECKLOCKTIMEVERIFY = (0xB1,)
OP_CHECKSEQUENCEVERIFY = 0xb2, OP_CHECKSEQUENCEVERIFY = (0xB2,)
OP_SHA256_DECRED = 0xc0, OP_SHA256_DECRED = (0xC0,)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert # Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -28,33 +29,33 @@ def page_automation_strategies(self, url_split, post_string):
summary = swap_client.getSummary() summary = swap_client.getSummary()
filters = { filters = {
'page_no': 1, "page_no": 1,
'limit': PAGE_LIMIT, "limit": PAGE_LIMIT,
'sort_by': 'created_at', "sort_by": "created_at",
'sort_dir': 'desc', "sort_dir": "desc",
} }
messages = [] messages = []
form_data = self.checkForm(post_string, 'automationstrategies', messages) form_data = self.checkForm(post_string, "automationstrategies", messages)
if form_data: if form_data:
if have_data_entry(form_data, 'clearfilters'): if have_data_entry(form_data, "clearfilters"):
swap_client.clearFilters('page_automation_strategies') swap_client.clearFilters("page_automation_strategies")
else: else:
if have_data_entry(form_data, 'sort_by'): if have_data_entry(form_data, "sort_by"):
sort_by = get_data_entry(form_data, 'sort_by') sort_by = get_data_entry(form_data, "sort_by")
ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') ensure(sort_by in ["created_at", "rate"], "Invalid sort by")
filters['sort_by'] = sort_by filters["sort_by"] = sort_by
if have_data_entry(form_data, 'sort_dir'): if have_data_entry(form_data, "sort_dir"):
sort_dir = get_data_entry(form_data, 'sort_dir') sort_dir = get_data_entry(form_data, "sort_dir")
ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') ensure(sort_dir in ["asc", "desc"], "Invalid sort dir")
filters['sort_dir'] = sort_dir filters["sort_dir"] = sort_dir
set_pagination_filters(form_data, filters) set_pagination_filters(form_data, filters)
if have_data_entry(form_data, 'applyfilters'): if have_data_entry(form_data, "applyfilters"):
swap_client.setFilters('page_automation_strategies', filters) swap_client.setFilters("page_automation_strategies", filters)
else: else:
saved_filters = swap_client.getFilters('page_automation_strategies') saved_filters = swap_client.getFilters("page_automation_strategies")
if saved_filters: if saved_filters:
filters.update(saved_filters) filters.update(saved_filters)
@@ -62,13 +63,16 @@ def page_automation_strategies(self, url_split, post_string):
for s in swap_client.listAutomationStrategies(filters): for s in swap_client.listAutomationStrategies(filters):
formatted_strategies.append((s[0], s[1], strConcepts(s[2]))) formatted_strategies.append((s[0], s[1], strConcepts(s[2])))
template = server.env.get_template('automation_strategies.html') template = server.env.get_template("automation_strategies.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'filters': filters, {
'strategies': formatted_strategies, "messages": messages,
'summary': summary, "filters": filters,
}) "strategies": formatted_strategies,
"summary": summary,
},
)
def page_automation_strategy_new(self, url_split, post_string): def page_automation_strategy_new(self, url_split, post_string):
@@ -78,21 +82,24 @@ def page_automation_strategy_new(self, url_split, post_string):
summary = swap_client.getSummary() summary = swap_client.getSummary()
messages = [] messages = []
form_data = self.checkForm(post_string, 'automationstrategynew', messages) _ = self.checkForm(post_string, "automationstrategynew", messages)
template = server.env.get_template('automation_strategy_new.html') template = server.env.get_template("automation_strategy_new.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'summary': summary, {
}) "messages": messages,
"summary": summary,
},
)
def page_automation_strategy(self, url_split, post_string): def page_automation_strategy(self, url_split, post_string):
ensure(len(url_split) > 2, 'Strategy ID not specified') ensure(len(url_split) > 2, "Strategy ID not specified")
try: try:
strategy_id = int(url_split[2]) strategy_id = int(url_split[2])
except Exception: except Exception:
raise ValueError('Bad strategy ID') raise ValueError("Bad strategy ID")
server = self.server server = self.server
swap_client = server.swap_client swap_client = server.swap_client
@@ -101,17 +108,17 @@ def page_automation_strategy(self, url_split, post_string):
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'automation_strategy', err_messages) form_data = self.checkForm(post_string, "automation_strategy", err_messages)
show_edit_form = False show_edit_form = False
if form_data: if form_data:
if have_data_entry(form_data, 'edit'): if have_data_entry(form_data, "edit"):
show_edit_form = True show_edit_form = True
if have_data_entry(form_data, 'apply'): if have_data_entry(form_data, "apply"):
try: try:
data = json.loads(get_data_entry_or(form_data, 'data', '')) data = json.loads(get_data_entry_or(form_data, "data", ""))
note = get_data_entry_or(form_data, 'note', '') note = get_data_entry_or(form_data, "note", "")
swap_client.updateAutomationStrategy(strategy_id, data, note) swap_client.updateAutomationStrategy(strategy_id, data, note)
messages.append('Updated') messages.append("Updated")
except Exception as e: except Exception as e:
err_messages.append(str(e)) err_messages.append(str(e))
show_edit_form = True show_edit_form = True
@@ -119,19 +126,24 @@ def page_automation_strategy(self, url_split, post_string):
strategy = swap_client.getAutomationStrategy(strategy_id) strategy = swap_client.getAutomationStrategy(strategy_id)
formatted_strategy = { formatted_strategy = {
'label': strategy.label, "label": strategy.label,
'type': strConcepts(strategy.type_ind), "type": strConcepts(strategy.type_ind),
'only_known_identities': 'True' if strategy.only_known_identities is True else 'False', "only_known_identities": (
'data': strategy.data.decode('utf-8'), "True" if strategy.only_known_identities is True else "False"
'note': '' if not strategy.note else strategy.note, ),
'created_at': strategy.created_at, "data": strategy.data.decode("utf-8"),
"note": "" if not strategy.note else strategy.note,
"created_at": strategy.created_at,
} }
template = server.env.get_template('automation_strategy.html') template = server.env.get_template("automation_strategy.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'strategy': formatted_strategy, "messages": messages,
'show_edit_form': show_edit_form, "err_messages": err_messages,
'summary': summary, "strategy": formatted_strategy,
}) "show_edit_form": show_edit_form,
"summary": summary,
},
)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert # Copyright (c) 2022-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -30,12 +31,12 @@ from basicswap.basicswap_util import (
def page_bid(self, url_split, post_string): def page_bid(self, url_split, post_string):
ensure(len(url_split) > 2, 'Bid ID not specified') ensure(len(url_split) > 2, "Bid ID not specified")
try: try:
bid_id = bytes.fromhex(url_split[2]) bid_id = bytes.fromhex(url_split[2])
assert len(bid_id) == 28 assert len(bid_id) == 28
except Exception: except Exception:
raise ValueError('Bad bid ID') raise ValueError("Bad bid ID")
server = self.server server = self.server
swap_client = server.swap_client swap_client = server.swap_client
swap_client.checkSystemStatus() swap_client.checkSystemStatus()
@@ -49,129 +50,163 @@ def page_bid(self, url_split, post_string):
show_lock_transfers = False show_lock_transfers = False
edit_bid = False edit_bid = False
view_tx_ind = None view_tx_ind = None
form_data = self.checkForm(post_string, 'bid', err_messages) form_data = self.checkForm(post_string, "bid", err_messages)
if form_data: if form_data:
if b'abandon_bid' in form_data: if b"abandon_bid" in form_data:
try: try:
swap_client.abandonBid(bid_id) swap_client.abandonBid(bid_id)
messages.append('Bid abandoned') messages.append("Bid abandoned")
except Exception as ex: except Exception as ex:
err_messages.append('Abandon failed ' + str(ex)) err_messages.append("Abandon failed " + str(ex))
elif b'accept_bid' in form_data: elif b"accept_bid" in form_data:
try: try:
swap_client.acceptBid(bid_id) swap_client.acceptBid(bid_id)
messages.append('Bid accepted') messages.append("Bid accepted")
except Exception as ex: except Exception as ex:
err_messages.append('Accept failed ' + str(ex)) err_messages.append("Accept failed " + str(ex))
elif b'show_txns' in form_data: elif b"show_txns" in form_data:
show_txns = True show_txns = True
elif b'show_offerer_seq_diagram' in form_data: elif b"show_offerer_seq_diagram" in form_data:
show_offerer_seq_diagram = True show_offerer_seq_diagram = True
elif b'show_bidder_seq_diagram' in form_data: elif b"show_bidder_seq_diagram" in form_data:
show_bidder_seq_diagram = True show_bidder_seq_diagram = True
elif b'edit_bid' in form_data: elif b"edit_bid" in form_data:
edit_bid = True edit_bid = True
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)), "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),
} }
try: try:
swap_client.manualBidUpdate(bid_id, data) swap_client.manualBidUpdate(bid_id, data)
messages.append('Bid edited') messages.append("Bid edited")
except Exception as ex: except Exception as ex:
err_messages.append('Edit failed ' + str(ex)) err_messages.append("Edit failed " + str(ex))
elif b'view_tx_submit' in form_data: elif b"view_tx_submit" in form_data:
show_txns = True show_txns = True
view_tx_ind = form_data[b'view_tx'][0].decode('utf-8') view_tx_ind = form_data[b"view_tx"][0].decode("utf-8")
if len(view_tx_ind) != 64: if len(view_tx_ind) != 64:
err_messages.append('Invalid transaction selected.') err_messages.append("Invalid transaction selected.")
view_tx_ind = None view_tx_ind = None
elif b'view_lock_transfers' in form_data: elif b"view_lock_transfers" in form_data:
show_txns = True show_txns = True
show_lock_transfers = True show_lock_transfers = True
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id) bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
ensure(bid, 'Unknown bid ID') ensure(bid, "Unknown bid ID")
data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, view_tx_ind, show_lock_transfers=show_lock_transfers) data = describeBid(
swap_client,
bid,
xmr_swap,
offer,
xmr_offer,
events,
edit_bid,
show_txns,
view_tx_ind,
show_lock_transfers=show_lock_transfers,
)
if bid.debug_ind is not None and bid.debug_ind > 0: if bid.debug_ind is not None and bid.debug_ind > 0:
messages.append('Debug flag set: {}, {}'.format(bid.debug_ind, DebugTypes(bid.debug_ind).name)) messages.append(
"Debug flag set: {}, {}".format(
bid.debug_ind, DebugTypes(bid.debug_ind).name
)
)
data['show_bidder_seq_diagram'] = show_bidder_seq_diagram data["show_bidder_seq_diagram"] = show_bidder_seq_diagram
data['show_offerer_seq_diagram'] = show_offerer_seq_diagram data["show_offerer_seq_diagram"] = show_offerer_seq_diagram
old_states = listOldBidStates(bid) old_states = listOldBidStates(bid)
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"] + ")"
data['can_accept_bid'] = True if bid.state == BidStates.BID_RECEIVED else False data["can_accept_bid"] = True if bid.state == BidStates.BID_RECEIVED else False
if swap_client.debug_ui: if swap_client.debug_ui:
data['bid_actions'] = [(-1, 'None'), ] + listBidActions() 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(
return self.render_template(template, { "bid_xmr.html" if offer.swap_type == SwapTypes.XMR_SWAP else "bid.html"
'bid_id': bid_id.hex(), )
'messages': messages, return self.render_template(
'err_messages': err_messages, template,
'data': data, {
'edit_bid': edit_bid, "bid_id": bid_id.hex(),
'old_states': old_states, "messages": messages,
'summary': summary, "err_messages": err_messages,
}) "data": data,
"edit_bid": edit_bid,
"old_states": old_states,
"summary": summary,
},
)
def page_bids(self, url_split, post_string, sent=False, available=False, received=False): def page_bids(
self, url_split, post_string, sent=False, available=False, received=False
):
server = self.server server = self.server
swap_client = server.swap_client swap_client = server.swap_client
swap_client.checkSystemStatus() swap_client.checkSystemStatus()
summary = swap_client.getSummary() summary = swap_client.getSummary()
filters = { filters = {
'page_no': 1, "page_no": 1,
'bid_state_ind': -1, "bid_state_ind": -1,
'with_expired': True, "with_expired": True,
'limit': PAGE_LIMIT, "limit": PAGE_LIMIT,
'sort_by': 'created_at', "sort_by": "created_at",
'sort_dir': 'desc', "sort_dir": "desc",
} }
if available: if available:
filters['bid_state_ind'] = BidStates.BID_RECEIVED filters["bid_state_ind"] = BidStates.BID_RECEIVED
filters['with_expired'] = False filters["with_expired"] = False
filter_prefix = 'page_bids_sent' if sent else 'page_bids_available' if available else 'page_bids_received' filter_prefix = (
"page_bids_sent"
if sent
else "page_bids_available" if available else "page_bids_received"
)
messages = [] messages = []
form_data = self.checkForm(post_string, 'bids', messages) form_data = self.checkForm(post_string, "bids", messages)
if form_data: if form_data:
if have_data_entry(form_data, 'clearfilters'): if have_data_entry(form_data, "clearfilters"):
swap_client.clearFilters(filter_prefix) swap_client.clearFilters(filter_prefix)
else: else:
if have_data_entry(form_data, 'sort_by'): if have_data_entry(form_data, "sort_by"):
sort_by = get_data_entry(form_data, 'sort_by') sort_by = get_data_entry(form_data, "sort_by")
ensure(sort_by in ['created_at', ], 'Invalid sort by') ensure(
filters['sort_by'] = sort_by sort_by
if have_data_entry(form_data, 'sort_dir'): in [
sort_dir = get_data_entry(form_data, 'sort_dir') "created_at",
ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') ],
filters['sort_dir'] = sort_dir "Invalid sort by",
if have_data_entry(form_data, 'state'): )
state_ind = int(get_data_entry(form_data, 'state')) filters["sort_by"] = sort_by
if have_data_entry(form_data, "sort_dir"):
sort_dir = get_data_entry(form_data, "sort_dir")
ensure(sort_dir in ["asc", "desc"], "Invalid sort dir")
filters["sort_dir"] = sort_dir
if have_data_entry(form_data, "state"):
state_ind = int(get_data_entry(form_data, "state"))
if state_ind != -1: if state_ind != -1:
try: try:
state = BidStates(state_ind) _ = BidStates(state_ind)
except Exception: except Exception as e: # noqa: F841
raise ValueError('Invalid state') raise ValueError("Invalid state")
filters['bid_state_ind'] = state_ind filters["bid_state_ind"] = state_ind
if have_data_entry(form_data, 'with_expired'): if have_data_entry(form_data, "with_expired"):
with_expired = toBool(get_data_entry(form_data, 'with_expired')) with_expired = toBool(get_data_entry(form_data, "with_expired"))
filters['with_expired'] = with_expired filters["with_expired"] = with_expired
set_pagination_filters(form_data, filters) set_pagination_filters(form_data, filters)
if have_data_entry(form_data, 'applyfilters'): if have_data_entry(form_data, "applyfilters"):
swap_client.setFilters(filter_prefix, filters) swap_client.setFilters(filter_prefix, filters)
else: else:
saved_filters = swap_client.getFilters(filter_prefix) saved_filters = swap_client.getFilters(filter_prefix)
@@ -181,21 +216,40 @@ 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(
'page_type_sent': 'Bids Sent' if sent else '', template,
'page_type_available': 'Bids Available' if available else '', {
'page_type_received': 'Received Bids' if received else '', "page_type_sent": "Bids Sent" if sent else "",
'page_type_sent_description': 'All the bids you have placed on offers.' if sent else '', "page_type_available": "Bids Available" if available else "",
'page_type_available_description': 'Bids available for you to accept.' if available else '', "page_type_received": "Received Bids" if received else "",
'page_type_received_description': 'All the bids placed on your offers.' if received else '', "page_type_sent_description": (
'messages': messages, "All the bids you have placed on offers." if sent else ""
'filters': filters, ),
'data': page_data, "page_type_available_description": (
'summary': summary, "Bids available for you to accept." if available else ""
'bids': [(format_timestamp(b[0]), ),
b[2].hex(), b[3].hex(), strBidState(b[5]), strTxState(b[7]), strTxState(b[8]), b[11]) for b in bids], "page_type_received_description": (
'bids_count': len(bids), "All the bids placed on your offers." if received else ""
}) ),
"messages": messages,
"filters": filters,
"data": page_data,
"summary": summary,
"bids": [
(
format_timestamp(b[0]),
b[2].hex(),
b[3].hex(),
strBidState(b[5]),
strTxState(b[7]),
strTxState(b[8]),
b[11],
)
for b in bids
],
"bids_count": len(bids),
},
)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2023 The BSX Developers # Copyright (c) 2023-2024 The Basicswap developers
# 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.
@@ -25,31 +25,34 @@ def page_debug(self, url_split, post_string):
result = None result = None
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'wallets', err_messages) form_data = self.checkForm(post_string, "wallets", err_messages)
if form_data: if form_data:
if have_data_entry(form_data, 'reinit_xmr'): if have_data_entry(form_data, "reinit_xmr"):
try: try:
swap_client.initialiseWallet(Coins.XMR) swap_client.initialiseWallet(Coins.XMR)
messages.append('Done.') messages.append("Done.")
except Exception as e: except Exception as e:
err_messages.append('Failed.') err_messages.append(f"Failed: {e}.")
if have_data_entry(form_data, 'remove_expired'): if have_data_entry(form_data, "remove_expired"):
try: try:
swap_client.log.warning('Removing expired data.') swap_client.log.warning("Removing expired data.")
remove_expired_data(swap_client) remove_expired_data(swap_client)
messages.append('Done.') messages.append("Done.")
except Exception as e: except Exception as e:
if swap_client.debug is True: if swap_client.debug is True:
swap_client.log.error(traceback.format_exc()) swap_client.log.error(traceback.format_exc())
else: else:
swap_client.log.error(f'remove_expired_data: {e}') swap_client.log.error(f"remove_expired_data: {e}")
err_messages.append('Failed.') err_messages.append("Failed.")
template = server.env.get_template('debug.html') template = server.env.get_template("debug.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'result': result, "messages": messages,
'summary': summary, "err_messages": err_messages,
}) "result": result,
"summary": summary,
},
)

View File

@@ -17,47 +17,52 @@ def page_changepassword(self, url_split, post_string):
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'changepassword', err_messages) form_data = self.checkForm(post_string, "changepassword", err_messages)
if form_data: if form_data:
old_password = get_data_entry_or(form_data, 'oldpassword', '') old_password = get_data_entry_or(form_data, "oldpassword", "")
new_password = get_data_entry_or(form_data, 'newpassword', '') new_password = get_data_entry_or(form_data, "newpassword", "")
confirm_password = get_data_entry_or(form_data, 'confirmpassword', '') confirm_password = get_data_entry_or(form_data, "confirmpassword", "")
try: try:
if new_password == '': if new_password == "":
raise ValueError('New password must be entered.') raise ValueError("New password must be entered.")
if new_password != confirm_password: if new_password != confirm_password:
raise ValueError('New password and confirm password must match.') raise ValueError("New password and confirm password must match.")
swap_client.changeWalletPasswords(old_password, new_password) swap_client.changeWalletPasswords(old_password, new_password)
messages.append('Password changed') messages.append("Password changed")
except Exception as e: except Exception as e:
err_messages.append(str(e)) err_messages.append(str(e))
template = server.env.get_template('changepassword.html') template = server.env.get_template("changepassword.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'summary': swap_client.getSummary(), "messages": messages,
}) "err_messages": err_messages,
"summary": swap_client.getSummary(),
},
)
def page_unlock(self, url_split, post_string): def page_unlock(self, url_split, post_string):
server = self.server server = self.server
swap_client = server.swap_client swap_client = server.swap_client
messages = ['Warning: This will unlock the system for all users!', ] messages = [
"Warning: This will unlock the system for all users!",
]
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'unlock', err_messages) form_data = self.checkForm(post_string, "unlock", err_messages)
if form_data: if form_data:
password = get_data_entry_or(form_data, 'password', '') password = get_data_entry_or(form_data, "password", "")
try: try:
if password == '': if password == "":
raise ValueError('Password must be entered.') raise ValueError("Password must be entered.")
swap_client.unlockWallets(password) swap_client.unlockWallets(password)
self.send_response(302) self.send_response(302)
self.send_header('Location', '/') self.send_header("Location", "/")
self.end_headers() self.end_headers()
return bytes() return bytes()
except Exception as e: except Exception as e:
@@ -65,11 +70,14 @@ def page_unlock(self, url_split, post_string):
swap_client.log.error(str(e)) 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")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
}) "messages": messages,
"err_messages": err_messages,
},
)
def page_lock(self, url_split, post_string): def page_lock(self, url_split, post_string):
@@ -79,6 +87,6 @@ def page_lock(self, url_split, post_string):
swap_client.lockWallets() swap_client.lockWallets()
self.send_response(302) self.send_response(302)
self.send_header('Location', '/') self.send_header("Location", "/")
self.end_headers() self.end_headers()
return bytes() return bytes()

View File

@@ -25,54 +25,71 @@ def page_identity(self, url_split, post_string):
swap_client.checkSystemStatus() swap_client.checkSystemStatus()
summary = swap_client.getSummary() summary = swap_client.getSummary()
ensure(len(url_split) > 2, 'Address not specified') ensure(len(url_split) > 2, "Address not specified")
identity_address = url_split[2] identity_address = url_split[2]
page_data = {'identity_address': identity_address} page_data = {"identity_address": identity_address}
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'identity', err_messages) form_data = self.checkForm(post_string, "identity", err_messages)
if form_data: if form_data:
if have_data_entry(form_data, 'edit'): if have_data_entry(form_data, "edit"):
page_data['show_edit_form'] = True page_data["show_edit_form"] = True
if have_data_entry(form_data, 'apply'): if have_data_entry(form_data, "apply"):
try: try:
data = { data = {
'label': get_data_entry_or(form_data, 'label', ''), "label": get_data_entry_or(form_data, "label", ""),
'note': get_data_entry_or(form_data, 'note', ''), "note": get_data_entry_or(form_data, "note", ""),
'automation_override': get_data_entry(form_data, 'automation_override'), "automation_override": get_data_entry(
form_data, "automation_override"
),
} }
swap_client.setIdentityData({'address': identity_address}, data) swap_client.setIdentityData({"address": identity_address}, data)
messages.append('Updated') messages.append("Updated")
except Exception as e: except Exception as e:
err_messages.append(str(e)) err_messages.append(str(e))
try: try:
identity = swap_client.getIdentity(identity_address) identity = swap_client.getIdentity(identity_address)
if identity is None: if identity is None:
raise ValueError('Unknown address') raise ValueError("Unknown address")
automation_override = zeroIfNone(identity.automation_override) automation_override = zeroIfNone(identity.automation_override)
page_data.update({ page_data.update(
'label': '' if identity.label is None else identity.label, {
'num_sent_bids_successful': zeroIfNone(identity.num_sent_bids_successful), "label": "" if identity.label is None else identity.label,
'num_recv_bids_successful': zeroIfNone(identity.num_recv_bids_successful), "num_sent_bids_successful": zeroIfNone(
'num_sent_bids_rejected': zeroIfNone(identity.num_sent_bids_rejected), identity.num_sent_bids_successful
'num_recv_bids_rejected': zeroIfNone(identity.num_recv_bids_rejected), ),
'num_sent_bids_failed': zeroIfNone(identity.num_sent_bids_failed), "num_recv_bids_successful": zeroIfNone(
'num_recv_bids_failed': zeroIfNone(identity.num_recv_bids_failed), identity.num_recv_bids_successful
'automation_override': automation_override, ),
'str_automation_override': strAutomationOverrideOption(automation_override), "num_sent_bids_rejected": zeroIfNone(identity.num_sent_bids_rejected),
'note': '' if identity.note is None else identity.note, "num_recv_bids_rejected": zeroIfNone(identity.num_recv_bids_rejected),
}) "num_sent_bids_failed": zeroIfNone(identity.num_sent_bids_failed),
"num_recv_bids_failed": zeroIfNone(identity.num_recv_bids_failed),
"automation_override": automation_override,
"str_automation_override": strAutomationOverrideOption(
automation_override
),
"note": "" if identity.note is None else identity.note,
}
)
except Exception as e: except Exception as e:
err_messages.append(e) err_messages.append(e)
template = server.env.get_template('identity.html') template = server.env.get_template("identity.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'data': page_data, "messages": messages,
'automation_override_options': [(int(opt), strAutomationOverrideOption(opt)) for opt in AutomationOverrideOptions if opt > 0], "err_messages": err_messages,
'summary': summary, "data": page_data,
}) "automation_override_options": [
(int(opt), strAutomationOverrideOption(opt))
for opt in AutomationOverrideOptions
if opt > 0
],
"summary": summary,
},
)

File diff suppressed because it is too large Load Diff

View File

@@ -28,140 +28,195 @@ def page_settings(self, url_split, post_string):
messages = [] messages = []
err_messages = [] err_messages = []
active_tab = 'default' active_tab = "default"
form_data = self.checkForm(post_string, 'settings', err_messages) form_data = self.checkForm(post_string, "settings", err_messages)
if form_data: if form_data:
try: try:
if have_data_entry(form_data, 'apply_general'): if have_data_entry(form_data, "apply_general"):
active_tab = 'general' active_tab = "general"
data = { data = {
'debug': toBool(get_data_entry(form_data, 'debugmode')), "debug": toBool(get_data_entry(form_data, "debugmode")),
'debug_ui': toBool(get_data_entry(form_data, 'debugui')), "debug_ui": toBool(get_data_entry(form_data, "debugui")),
'expire_db_records': toBool(get_data_entry(form_data, 'expire_db_records')), "expire_db_records": toBool(
get_data_entry(form_data, "expire_db_records")
),
} }
swap_client.editGeneralSettings(data) swap_client.editGeneralSettings(data)
elif have_data_entry(form_data, 'apply_chart'): elif have_data_entry(form_data, "apply_chart"):
active_tab = 'general' active_tab = "general"
data = { data = {
'show_chart': toBool(get_data_entry(form_data, 'showchart')), "show_chart": toBool(get_data_entry(form_data, "showchart")),
'chart_api_key': html.unescape(get_data_entry_or(form_data, 'chartapikey', '')), "chart_api_key": html.unescape(
'coingecko_api_key': html.unescape(get_data_entry_or(form_data, 'coingeckoapikey', '')), get_data_entry_or(form_data, "chartapikey", "")
'enabled_chart_coins': get_data_entry_or(form_data, 'enabledchartcoins', ''), ),
"coingecko_api_key": html.unescape(
get_data_entry_or(form_data, "coingeckoapikey", "")
),
"enabled_chart_coins": get_data_entry_or(
form_data, "enabledchartcoins", ""
),
} }
swap_client.editGeneralSettings(data) swap_client.editGeneralSettings(data)
elif have_data_entry(form_data, 'apply_tor'): elif have_data_entry(form_data, "apply_tor"):
active_tab = 'tor' active_tab = "tor"
# TODO: Detect if running in docker # TODO: Detect if running in docker
raise ValueError('TODO: If running in docker see doc/tor.md to enable/disable tor.') raise ValueError(
"TODO: If running in docker see doc/tor.md to enable/disable tor."
)
for name, c in swap_client.settings['chainclients'].items(): for name, c in swap_client.settings["chainclients"].items():
if have_data_entry(form_data, 'apply_' + name): if have_data_entry(form_data, "apply_" + name):
data = {'lookups': get_data_entry(form_data, 'lookups_' + name)} data = {"lookups": get_data_entry(form_data, "lookups_" + name)}
if name in ('monero', 'wownero'): if name in ("monero", "wownero"):
data['fee_priority'] = int(get_data_entry(form_data, 'fee_priority_' + name)) data["fee_priority"] = int(
data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False get_data_entry(form_data, "fee_priority_" + name)
data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name) )
data['rpcport'] = int(get_data_entry(form_data, 'rpcport_' + name)) data["manage_daemon"] = (
data['remotedaemonurls'] = get_data_entry_or(form_data, 'remotedaemonurls_' + name, '') True
data['automatically_select_daemon'] = True if get_data_entry(form_data, 'autosetdaemon_' + name) == 'true' else False if get_data_entry(form_data, "managedaemon_" + name)
== "true"
else False
)
data["rpchost"] = get_data_entry(form_data, "rpchost_" + name)
data["rpcport"] = int(
get_data_entry(form_data, "rpcport_" + 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
)
else: else:
data['conf_target'] = int(get_data_entry(form_data, 'conf_target_' + name)) data["conf_target"] = int(
if name == 'particl': get_data_entry(form_data, "conf_target_" + name)
data['anon_tx_ring_size'] = int(get_data_entry(form_data, 'rct_ring_size_' + name)) )
if name == "particl":
data["anon_tx_ring_size"] = int(
get_data_entry(form_data, "rct_ring_size_" + name)
)
settings_changed, suggest_reboot = swap_client.editSettings(name, data) settings_changed, suggest_reboot = swap_client.editSettings(
name, data
)
if settings_changed is True: if settings_changed is True:
messages.append('Settings applied.') messages.append("Settings applied.")
if suggest_reboot is True: if suggest_reboot is True:
messages.append('Please restart BasicSwap.') messages.append("Please restart BasicSwap.")
elif have_data_entry(form_data, 'enable_' + name): elif have_data_entry(form_data, "enable_" + name):
swap_client.enableCoin(name) swap_client.enableCoin(name)
display_name = getCoinName(swap_client.getCoinIdFromName(name)) display_name = getCoinName(swap_client.getCoinIdFromName(name))
messages.append(display_name + ' enabled, shutting down.') messages.append(display_name + " enabled, shutting down.")
swap_client.stopRunning() swap_client.stopRunning()
elif have_data_entry(form_data, 'disable_' + name): elif have_data_entry(form_data, "disable_" + name):
swap_client.disableCoin(name) swap_client.disableCoin(name)
display_name = getCoinName(swap_client.getCoinIdFromName(name)) display_name = getCoinName(swap_client.getCoinIdFromName(name))
messages.append(display_name + ' disabled, shutting down.') messages.append(display_name + " disabled, shutting down.")
swap_client.stopRunning() swap_client.stopRunning()
except InactiveCoin as ex: except InactiveCoin as ex:
err_messages.append('InactiveCoin {}'.format(Coins(ex.coinid).name)) err_messages.append("InactiveCoin {}".format(Coins(ex.coinid).name))
except Exception as e: except Exception as e:
err_messages.append(str(e)) err_messages.append(str(e))
chains_formatted = [] chains_formatted = []
sorted_names = sorted(swap_client.settings['chainclients'].keys()) sorted_names = sorted(swap_client.settings["chainclients"].keys())
for name in sorted_names: for name in sorted_names:
c = swap_client.settings['chainclients'][name] c = swap_client.settings["chainclients"][name]
try: try:
display_name = getCoinName(swap_client.getCoinIdFromName(name)) display_name = getCoinName(swap_client.getCoinIdFromName(name))
except Exception: except Exception:
display_name = name display_name = name
chains_formatted.append({ chains_formatted.append(
'name': name, {
'display_name': display_name, "name": name,
'lookups': c.get('chain_lookups', 'local'), "display_name": display_name,
'manage_daemon': c.get('manage_daemon', 'Unknown'), "lookups": c.get("chain_lookups", "local"),
'connection_type': c.get('connection_type', 'Unknown'), "manage_daemon": c.get("manage_daemon", "Unknown"),
}) "connection_type": c.get("connection_type", "Unknown"),
if name in ('monero', 'wownero'): }
chains_formatted[-1]['fee_priority'] = c.get('fee_priority', 0) )
chains_formatted[-1]['manage_wallet_daemon'] = c.get('manage_wallet_daemon', 'Unknown') if name in ("monero", "wownero"):
chains_formatted[-1]['rpchost'] = c.get('rpchost', 'localhost') chains_formatted[-1]["fee_priority"] = c.get("fee_priority", 0)
chains_formatted[-1]['rpcport'] = int(c.get('rpcport', 18081)) chains_formatted[-1]["manage_wallet_daemon"] = c.get(
chains_formatted[-1]['remotedaemonurls'] = '\n'.join(c.get('remote_daemon_urls', [])) "manage_wallet_daemon", "Unknown"
chains_formatted[-1]['autosetdaemon'] = c.get('automatically_select_daemon', False) )
chains_formatted[-1]["rpchost"] = c.get("rpchost", "localhost")
chains_formatted[-1]["rpcport"] = int(c.get("rpcport", 18081))
chains_formatted[-1]["remotedaemonurls"] = "\n".join(
c.get("remote_daemon_urls", [])
)
chains_formatted[-1]["autosetdaemon"] = c.get(
"automatically_select_daemon", False
)
else: else:
chains_formatted[-1]['conf_target'] = c.get('conf_target', 2) chains_formatted[-1]["conf_target"] = c.get("conf_target", 2)
if name == 'particl': if name == "particl":
chains_formatted[-1]['anon_tx_ring_size'] = c.get('anon_tx_ring_size', 12) chains_formatted[-1]["anon_tx_ring_size"] = c.get("anon_tx_ring_size", 12)
else: else:
if c.get('connection_type', 'Unknown') == 'none': if c.get("connection_type", "Unknown") == "none":
if 'connection_type_prev' in c: if "connection_type_prev" in c:
chains_formatted[-1]['can_reenable'] = True chains_formatted[-1]["can_reenable"] = True
else: else:
chains_formatted[-1]['can_disable'] = True chains_formatted[-1]["can_disable"] = True
general_settings = { general_settings = {
'debug': swap_client.debug, "debug": swap_client.debug,
'debug_ui': swap_client.debug_ui, "debug_ui": swap_client.debug_ui,
'expire_db_records': swap_client._expire_db_records, "expire_db_records": swap_client._expire_db_records,
} }
if 'chart_api_key_enc' in swap_client.settings: if "chart_api_key_enc" in swap_client.settings:
chart_api_key = html.escape(bytes.fromhex(swap_client.settings.get('chart_api_key_enc', '')).decode('utf-8')) chart_api_key = html.escape(
bytes.fromhex(swap_client.settings.get("chart_api_key_enc", "")).decode(
"utf-8"
)
)
else: else:
chart_api_key = swap_client.settings.get('chart_api_key', '') chart_api_key = swap_client.settings.get("chart_api_key", "")
if 'coingecko_api_key_enc' in swap_client.settings: if "coingecko_api_key_enc" in swap_client.settings:
coingecko_api_key = html.escape(bytes.fromhex(swap_client.settings.get('coingecko_api_key_enc', '')).decode('utf-8')) coingecko_api_key = html.escape(
bytes.fromhex(swap_client.settings.get("coingecko_api_key_enc", "")).decode(
"utf-8"
)
)
else: else:
coingecko_api_key = swap_client.settings.get('coingecko_api_key', '') coingecko_api_key = swap_client.settings.get("coingecko_api_key", "")
chart_settings = { chart_settings = {
'show_chart': swap_client.settings.get('show_chart', True), "show_chart": swap_client.settings.get("show_chart", True),
'chart_api_key': chart_api_key, "chart_api_key": chart_api_key,
'coingecko_api_key': coingecko_api_key, "coingecko_api_key": coingecko_api_key,
'enabled_chart_coins': swap_client.settings.get('enabled_chart_coins', ''), "enabled_chart_coins": swap_client.settings.get("enabled_chart_coins", ""),
} }
tor_control_password = '' if swap_client.tor_control_password is None else swap_client.tor_control_password tor_control_password = (
""
if swap_client.tor_control_password is None
else swap_client.tor_control_password
)
tor_settings = { tor_settings = {
'use_tor': swap_client.use_tor_proxy, "use_tor": swap_client.use_tor_proxy,
'proxy_host': swap_client.tor_proxy_host, "proxy_host": swap_client.tor_proxy_host,
'proxy_port': swap_client.tor_proxy_port, "proxy_port": swap_client.tor_proxy_port,
'control_password': html.escape(tor_control_password), "control_password": html.escape(tor_control_password),
'control_port': swap_client.tor_control_port, "control_port": swap_client.tor_control_port,
} }
template = server.env.get_template('settings.html') template = server.env.get_template("settings.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'summary': swap_client.getSummary(), "messages": messages,
'chains': chains_formatted, "err_messages": err_messages,
'general_settings': general_settings, "summary": swap_client.getSummary(),
'chart_settings': chart_settings, "chains": chains_formatted,
'tor_settings': tor_settings, "general_settings": general_settings,
'active_tab': active_tab, "chart_settings": chart_settings,
}) "tor_settings": tor_settings,
"active_tab": active_tab,
},
)

View File

@@ -28,11 +28,11 @@ def page_smsgaddresses(self, url_split, post_string):
summary = swap_client.getSummary() summary = swap_client.getSummary()
filters = { filters = {
'page_no': 1, "page_no": 1,
'limit': PAGE_LIMIT, "limit": PAGE_LIMIT,
'sort_by': 'created_at', "sort_by": "created_at",
'sort_dir': 'desc', "sort_dir": "desc",
'addr_type': -1, "addr_type": -1,
} }
page_data = {} page_data = {}
@@ -41,96 +41,114 @@ def page_smsgaddresses(self, url_split, post_string):
smsgaddresses = [] smsgaddresses = []
listaddresses = True listaddresses = True
form_data = self.checkForm(post_string, 'smsgaddresses', err_messages) form_data = self.checkForm(post_string, "smsgaddresses", err_messages)
if form_data: if form_data:
edit_address_id = None edit_address_id = None
for key in form_data: for key in form_data:
if key.startswith(b'editaddr_'): if key.startswith(b"editaddr_"):
edit_address_id = int(key.split(b'_')[1]) edit_address_id = int(key.split(b"_")[1])
break break
if edit_address_id is not None: if edit_address_id is not None:
listaddresses = False listaddresses = False
page_data['edit_address'] = edit_address_id page_data["edit_address"] = edit_address_id
page_data['addr_data'] = swap_client.listAllSMSGAddresses({'addr_id': edit_address_id})[0] page_data["addr_data"] = swap_client.listAllSMSGAddresses(
elif have_data_entry(form_data, 'saveaddr'): {"addr_id": edit_address_id}
edit_address_id = int(get_data_entry(form_data, 'edit_address_id')) )[0]
edit_addr = get_data_entry(form_data, 'edit_address') elif have_data_entry(form_data, "saveaddr"):
active_ind = int(get_data_entry(form_data, 'active_ind')) edit_address_id = int(get_data_entry(form_data, "edit_address_id"))
ensure(active_ind in (0, 1), 'Invalid sort by') edit_addr = get_data_entry(form_data, "edit_address")
addressnote = get_data_entry_or(form_data, 'addressnote', '') active_ind = int(get_data_entry(form_data, "active_ind"))
if not validateTextInput(addressnote, 'Address note', err_messages, max_length=30): ensure(active_ind in (0, 1), "Invalid sort by")
addressnote = get_data_entry_or(form_data, "addressnote", "")
if not validateTextInput(
addressnote, "Address note", err_messages, max_length=30
):
listaddresses = False listaddresses = False
page_data['edit_address'] = edit_address_id page_data["edit_address"] = edit_address_id
else: else:
swap_client.editSMSGAddress(edit_addr, active_ind=active_ind, addressnote=addressnote) swap_client.editSMSGAddress(
messages.append(f'Edited address {edit_addr}') edit_addr, active_ind=active_ind, addressnote=addressnote
elif have_data_entry(form_data, 'shownewaddr'): )
messages.append(f"Edited address {edit_addr}")
elif have_data_entry(form_data, "shownewaddr"):
listaddresses = False listaddresses = False
page_data['new_address'] = True page_data["new_address"] = True
elif have_data_entry(form_data, 'showaddaddr'): elif have_data_entry(form_data, "showaddaddr"):
listaddresses = False listaddresses = False
page_data['new_send_address'] = True page_data["new_send_address"] = True
elif have_data_entry(form_data, 'createnewaddr'): elif have_data_entry(form_data, "createnewaddr"):
addressnote = get_data_entry_or(form_data, 'addressnote', '') addressnote = get_data_entry_or(form_data, "addressnote", "")
if not validateTextInput(addressnote, 'Address note', err_messages, max_length=30): if not validateTextInput(
addressnote, "Address note", err_messages, max_length=30
):
listaddresses = False listaddresses = False
page_data['new_address'] = True page_data["new_address"] = True
else: else:
new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote) new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote)
messages.append(f'Created address {new_addr}, pubkey {pubkey}') messages.append(f"Created address {new_addr}, pubkey {pubkey}")
elif have_data_entry(form_data, 'createnewsendaddr'): elif have_data_entry(form_data, "createnewsendaddr"):
pubkey_hex = get_data_entry(form_data, 'addresspubkey') pubkey_hex = get_data_entry(form_data, "addresspubkey")
addressnote = get_data_entry_or(form_data, 'addressnote', '') addressnote = get_data_entry_or(form_data, "addressnote", "")
if not validateTextInput(addressnote, 'Address note', messages, max_length=30) or \ if not validateTextInput(
not validateTextInput(pubkey_hex, 'Pubkey', messages, max_length=66): addressnote, "Address note", messages, max_length=30
) or not validateTextInput(pubkey_hex, "Pubkey", messages, max_length=66):
listaddresses = False listaddresses = False
page_data['new_send_address'] = True page_data["new_send_address"] = True
else: else:
new_addr = swap_client.addSMSGAddress(pubkey_hex, addressnote=addressnote) new_addr = swap_client.addSMSGAddress(
messages.append(f'Added address {new_addr}') pubkey_hex, addressnote=addressnote
)
messages.append(f"Added address {new_addr}")
if have_data_entry(form_data, 'clearfilters'): if have_data_entry(form_data, "clearfilters"):
swap_client.clearFilters('page_smsgaddresses') swap_client.clearFilters("page_smsgaddresses")
else: else:
if have_data_entry(form_data, 'sort_by'): if have_data_entry(form_data, "sort_by"):
sort_by = get_data_entry(form_data, 'sort_by') sort_by = get_data_entry(form_data, "sort_by")
ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') ensure(sort_by in ["created_at", "rate"], "Invalid sort by")
filters['sort_by'] = sort_by filters["sort_by"] = sort_by
if have_data_entry(form_data, 'sort_dir'): if have_data_entry(form_data, "sort_dir"):
sort_dir = get_data_entry(form_data, 'sort_dir') sort_dir = get_data_entry(form_data, "sort_dir")
ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') ensure(sort_dir in ["asc", "desc"], "Invalid sort dir")
filters['sort_dir'] = sort_dir filters["sort_dir"] = sort_dir
if have_data_entry(form_data, 'filter_addressnote'): if have_data_entry(form_data, "filter_addressnote"):
addressnote = get_data_entry(form_data, 'filter_addressnote') addressnote = get_data_entry(form_data, "filter_addressnote")
if validateTextInput(addressnote, 'Address note', err_messages, max_length=30): if validateTextInput(
filters['addressnote'] = addressnote addressnote, "Address note", err_messages, max_length=30
if have_data_entry(form_data, 'filter_addr_type'): ):
filters['addr_type'] = int(get_data_entry(form_data, 'filter_addr_type')) filters["addressnote"] = addressnote
if have_data_entry(form_data, "filter_addr_type"):
filters["addr_type"] = int(
get_data_entry(form_data, "filter_addr_type")
)
set_pagination_filters(form_data, filters) set_pagination_filters(form_data, filters)
if have_data_entry(form_data, 'applyfilters'): if have_data_entry(form_data, "applyfilters"):
swap_client.setFilters('page_smsgaddresses', filters) swap_client.setFilters("page_smsgaddresses", filters)
else: else:
saved_filters = swap_client.getFilters('page_smsgaddresses') saved_filters = swap_client.getFilters("page_smsgaddresses")
if saved_filters: if saved_filters:
filters.update(saved_filters) filters.update(saved_filters)
if listaddresses is True: if listaddresses is True:
smsgaddresses = swap_client.listAllSMSGAddresses(filters) smsgaddresses = swap_client.listAllSMSGAddresses(filters)
page_data['addr_types'] = [(int(t), strAddressType(t)) for t in AddressTypes] page_data["addr_types"] = [(int(t), strAddressType(t)) for t in AddressTypes]
page_data['network_addr'] = swap_client.network_addr page_data["network_addr"] = swap_client.network_addr
for addr in smsgaddresses: for addr in smsgaddresses:
addr['type'] = strAddressType(addr['type']) addr["type"] = strAddressType(addr["type"])
template = self.server.env.get_template('smsgaddresses.html') template = self.server.env.get_template("smsgaddresses.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'filters': filters, "messages": messages,
'data': page_data, "err_messages": err_messages,
'smsgaddresses': smsgaddresses, "filters": filters,
'page_data': page_data, "data": page_data,
'summary': summary, "smsgaddresses": smsgaddresses,
}) "page_data": page_data,
"summary": summary,
},
)

View File

@@ -7,19 +7,19 @@
def extract_data(bytes_in): def extract_data(bytes_in):
if bytes_in is None: if bytes_in is None:
return None return None
str_in = bytes_in.decode('utf-8') str_in = bytes_in.decode("utf-8")
start = str_in.find('=') start = str_in.find("=")
if start < 0: if start < 0:
return None return None
start += 1 start += 1
end = str_in.find('\r', start) end = str_in.find("\r", start)
if end < 0: if end < 0:
return None return None
return str_in[start:end] return str_in[start:end]
def get_tor_established_state(swap_client): def get_tor_established_state(swap_client):
rv = swap_client.torControl('GETINFO status/circuit-established') rv = swap_client.torControl("GETINFO status/circuit-established")
return extract_data(rv) return extract_data(rv)
@@ -28,23 +28,26 @@ def page_tor(self, url_split, post_string):
summary = swap_client.getSummary() summary = swap_client.getSummary()
page_data = {} page_data = {}
try: try:
page_data['circuit_established'] = get_tor_established_state(swap_client) page_data["circuit_established"] = get_tor_established_state(swap_client)
except Exception: except Exception:
page_data['circuit_established'] = 'error' page_data["circuit_established"] = "error"
try: try:
rv = swap_client.torControl('GETINFO traffic/read') rv = swap_client.torControl("GETINFO traffic/read")
page_data['bytes_written'] = extract_data(rv) page_data["bytes_written"] = extract_data(rv)
except Exception: except Exception:
page_data['bytes_written'] = 'error' page_data["bytes_written"] = "error"
try: try:
rv = swap_client.torControl('GETINFO traffic/written') rv = swap_client.torControl("GETINFO traffic/written")
page_data['bytes_read'] = extract_data(rv) page_data["bytes_read"] = extract_data(rv)
except Exception: except Exception:
page_data['bytes_read'] = 'error' page_data["bytes_read"] = "error"
messages = [] messages = []
template = self.server.env.get_template('tor.html') template = self.server.env.get_template("tor.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'data': page_data, {
'summary': summary, "messages": messages,
}) "data": page_data,
"summary": summary,
},
)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert # Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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,52 +24,52 @@ from basicswap.chainparams import (
def format_wallet_data(swap_client, ci, w): def format_wallet_data(swap_client, ci, w):
wf = { wf = {
'name': ci.coin_name(), "name": ci.coin_name(),
'version': w.get('version', '?'), "version": w.get("version", "?"),
'ticker': ci.ticker_mainnet(), "ticker": ci.ticker_mainnet(),
'cid': str(int(ci.coin_type())), "cid": str(int(ci.coin_type())),
'balance': w.get('balance', '?'), "balance": w.get("balance", "?"),
'blocks': w.get('blocks', '?'), "blocks": w.get("blocks", "?"),
'synced': w.get('synced', '?'), "synced": w.get("synced", "?"),
'expected_seed': w.get('expected_seed', '?'), "expected_seed": w.get("expected_seed", "?"),
'encrypted': w.get('encrypted', '?'), "encrypted": w.get("encrypted", "?"),
'locked': w.get('locked', '?'), "locked": w.get("locked", "?"),
'updating': w.get('updating', '?'), "updating": w.get("updating", "?"),
'havedata': True, "havedata": True,
} }
if w.get('bootstrapping', False) is True: if w.get("bootstrapping", False) is True:
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: if "locked_utxos" in w:
wf['locked_utxos'] = w['locked_utxos'] 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 pending: int = 0
if 'unconfirmed' in w and float(w['unconfirmed']) > 0.0: if "unconfirmed" in w and float(w["unconfirmed"]) > 0.0:
pending += ci.make_int(w['unconfirmed']) pending += ci.make_int(w["unconfirmed"])
if 'immature' in w and float(w['immature']) > 0.0: if "immature" in w and float(w["immature"]) > 0.0:
pending += ci.make_int(w['immature']) pending += ci.make_int(w["immature"])
if pending > 0.0: if pending > 0.0:
wf['pending'] = ci.format_amount(pending) 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'] = w.get('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: elif ci.coin_type() == Coins.LTC:
wf['mweb_address'] = w.get('mweb_address', '?') wf["mweb_address"] = w.get("mweb_address", "?")
wf['mweb_balance'] = w.get('mweb_balance', '?') wf["mweb_balance"] = w.get("mweb_balance", "?")
wf['mweb_pending'] = w.get('mweb_pending', '?') wf["mweb_pending"] = w.get("mweb_pending", "?")
checkAddressesOwned(swap_client, ci, wf) checkAddressesOwned(swap_client, ci, wf)
return wf return wf
@@ -91,19 +92,18 @@ def page_wallets(self, url_split, post_string):
for k in sk: for k in sk:
w = wallets[k] w = wallets[k]
if 'error' in w: if "error" in w:
wallets_formatted.append({ wallets_formatted.append({"cid": str(int(k)), "error": w["error"]})
'cid': str(int(k)),
'error': w['error']
})
continue continue
if 'no_data' in w: if "no_data" in w:
wallets_formatted.append({ wallets_formatted.append(
'name': w['name'], {
'havedata': False, "name": w["name"],
'updating': w['updating'], "havedata": False,
}) "updating": w["updating"],
}
)
continue continue
ci = swap_client.ci(k) ci = swap_client.ci(k)
@@ -111,17 +111,20 @@ def page_wallets(self, url_split, post_string):
wallets_formatted.append(wf) wallets_formatted.append(wf)
template = server.env.get_template('wallets.html') template = server.env.get_template("wallets.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'wallets': wallets_formatted, "messages": messages,
'summary': summary, "err_messages": err_messages,
}) "wallets": wallets_formatted,
"summary": summary,
},
)
def page_wallet(self, url_split, post_string): def page_wallet(self, url_split, post_string):
ensure(len(url_split) > 2, 'Wallet not specified') ensure(len(url_split) > 2, "Wallet not specified")
wallet_ticker = url_split[2] wallet_ticker = url_split[2]
server = self.server server = self.server
swap_client = server.swap_client swap_client = server.swap_client
@@ -136,94 +139,130 @@ def page_wallet(self, url_split, post_string):
show_utxo_groups: bool = False show_utxo_groups: bool = False
withdrawal_successful: bool = False withdrawal_successful: bool = False
force_refresh: bool = False force_refresh: bool = False
form_data = self.checkForm(post_string, 'wallet', err_messages) form_data = self.checkForm(post_string, "wallet", err_messages)
if form_data: if form_data:
cid = str(int(coin_id)) cid = str(int(coin_id))
estimate_fee: bool = have_data_entry(form_data, 'estfee_' + cid) estimate_fee: bool = have_data_entry(form_data, "estfee_" + cid)
withdraw: bool = have_data_entry(form_data, 'withdraw_' + cid) withdraw: bool = have_data_entry(form_data, "withdraw_" + cid)
if have_data_entry(form_data, 'newaddr_' + cid): if have_data_entry(form_data, "newaddr_" + cid):
swap_client.cacheNewAddressForCoin(coin_id) swap_client.cacheNewAddressForCoin(coin_id)
elif have_data_entry(form_data, 'forcerefresh'): elif have_data_entry(form_data, "forcerefresh"):
force_refresh = True force_refresh = True
elif have_data_entry(form_data, 'newmwebaddr_' + cid): elif have_data_entry(form_data, "newmwebaddr_" + cid):
swap_client.cacheNewStealthAddressForCoin(coin_id) swap_client.cacheNewStealthAddressForCoin(coin_id)
elif have_data_entry(form_data, 'reseed_' + cid): elif have_data_entry(form_data, "reseed_" + cid):
try: try:
swap_client.reseedWallet(coin_id) swap_client.reseedWallet(coin_id)
messages.append('Reseed complete ' + str(coin_id)) messages.append("Reseed complete " + str(coin_id))
except Exception as ex: except Exception as ex:
err_messages.append('Reseed failed ' + str(ex)) err_messages.append("Reseed failed " + str(ex))
swap_client.updateWalletsInfo(True, coin_id) swap_client.updateWalletsInfo(True, coin_id)
elif withdraw or estimate_fee: elif withdraw or estimate_fee:
subfee = True if have_data_entry(form_data, 'subfee_' + cid) else False subfee = True if have_data_entry(form_data, "subfee_" + cid) else False
page_data['wd_subfee_' + cid] = subfee page_data["wd_subfee_" + cid] = subfee
sweepall = True if have_data_entry(form_data, 'sweepall_' + cid) else False sweepall = True if have_data_entry(form_data, "sweepall_" + cid) else False
page_data['wd_sweepall_' + cid] = sweepall page_data["wd_sweepall_" + cid] = sweepall
value = None value = None
if not sweepall: if not sweepall:
try: try:
value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') value = form_data[bytes("amt_" + cid, "utf-8")][0].decode("utf-8")
page_data['wd_value_' + cid] = value page_data["wd_value_" + cid] = value
except Exception as e: except Exception as e: # noqa: F841
err_messages.append('Missing value') err_messages.append("Missing value")
try: try:
address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8') address = form_data[bytes("to_" + cid, "utf-8")][0].decode("utf-8")
page_data['wd_address_' + cid] = address page_data["wd_address_" + cid] = address
except Exception as e: except Exception as e: # noqa: F841
err_messages.append('Missing address') err_messages.append("Missing address")
if estimate_fee and withdraw: if estimate_fee and withdraw:
err_messages.append('Estimate fee and withdraw can\'t be used together.') err_messages.append("Estimate fee and withdraw can't be used together.")
if estimate_fee and coin_id not in (Coins.XMR, Coins.WOW): if estimate_fee and coin_id not in (Coins.XMR, Coins.WOW):
ci = swap_client.ci(coin_id) ci = swap_client.ci(coin_id)
ticker: str = ci.ticker() ticker: str = ci.ticker()
err_messages.append(f'Estimate fee unavailable for {ticker}.') err_messages.append(f"Estimate fee unavailable for {ticker}.")
if coin_id == Coins.PART: if coin_id == Coins.PART:
try: try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8') 0
page_data['wd_type_from_' + cid] = type_from ].decode("utf-8")
page_data['wd_type_to_' + cid] = type_to type_to = form_data[bytes("withdraw_type_to_" + cid, "utf-8")][
except Exception as e: 0
err_messages.append('Missing type') ].decode("utf-8")
page_data["wd_type_from_" + cid] = type_from
page_data["wd_type_to_" + cid] = type_to
except Exception as e: # noqa: F841
err_messages.append("Missing type")
elif coin_id == Coins.LTC: elif coin_id == Coins.LTC:
try: try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
page_data['wd_type_from_' + cid] = type_from 0
except Exception as e: ].decode("utf-8")
err_messages.append('Missing type') page_data["wd_type_from_" + cid] = type_from
except Exception as e: # noqa: F841
err_messages.append("Missing type")
if len(err_messages) == 0: if len(err_messages) == 0:
ci = swap_client.ci(coin_id) ci = swap_client.ci(coin_id)
ticker: str = ci.ticker() ticker: str = ci.ticker()
try: try:
if coin_id == Coins.PART: if coin_id == Coins.PART:
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee) txid = swap_client.withdrawParticl(
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid)) 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
)
)
elif coin_id == Coins.LTC: elif coin_id == Coins.LTC:
txid = swap_client.withdrawLTC(type_from, value, address, subfee) txid = swap_client.withdrawLTC(
messages.append('Withdrew {} {} (from {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, address, txid)) type_from, value, address, subfee
)
messages.append(
"Withdrew {} {} (from {}) to address {}<br/>In txid: {}".format(
value, ticker, type_from, address, txid
)
)
elif coin_id in (Coins.XMR, Coins.WOW): elif coin_id in (Coins.XMR, Coins.WOW):
if estimate_fee: if estimate_fee:
fee_estimate = ci.estimateFee(value, address, sweepall) fee_estimate = ci.estimateFee(value, address, sweepall)
suffix = 's' if fee_estimate['num_txns'] > 1 else '' suffix = "s" if fee_estimate["num_txns"] > 1 else ""
sum_fees = ci.format_amount(fee_estimate['sum_fee']) sum_fees = ci.format_amount(fee_estimate["sum_fee"])
value_str = ci.format_amount(fee_estimate['sum_amount']) value_str = ci.format_amount(fee_estimate["sum_amount"])
messages.append(f'Estimated fee for {value_str} {ticker} to address {address}: {sum_fees} in {fee_estimate["num_txns"]} transaction{suffix}.') messages.append(
page_data['fee_estimate'] = fee_estimate f'Estimated fee for {value_str} {ticker} to address {address}: {sum_fees} in {fee_estimate["num_txns"]} transaction{suffix}.'
)
page_data["fee_estimate"] = fee_estimate
else: else:
txid = swap_client.withdrawCoin(coin_id, value, address, sweepall) txid = swap_client.withdrawCoin(
coin_id, value, address, sweepall
)
if sweepall: if sweepall:
messages.append('Swept all {} to address {}<br/>In txid: {}'.format(ticker, address, txid)) messages.append(
"Swept all {} to address {}<br/>In txid: {}".format(
ticker, address, txid
)
)
else: else:
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid)) messages.append(
messages.append('Note: The wallet balance can take a while to update.') "Withdrew {} {} to address {}<br/>In txid: {}".format(
value, ticker, address, txid
)
)
messages.append(
"Note: The wallet balance can take a while to update."
)
else: 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
)
)
if not estimate_fee: if not estimate_fee:
withdrawal_successful = True withdrawal_successful = True
except Exception as e: except Exception as e:
@@ -232,41 +271,44 @@ def page_wallet(self, url_split, post_string):
err_messages.append(str(e)) err_messages.append(str(e))
if not estimate_fee: if not estimate_fee:
swap_client.updateWalletsInfo(True, only_coin=coin_id) swap_client.updateWalletsInfo(True, only_coin=coin_id)
elif have_data_entry(form_data, 'showutxogroups'): elif have_data_entry(form_data, "showutxogroups"):
show_utxo_groups = True show_utxo_groups = True
elif have_data_entry(form_data, 'create_utxo'): elif have_data_entry(form_data, "create_utxo"):
show_utxo_groups = True show_utxo_groups = True
try: try:
value = get_data_entry(form_data, 'utxo_value') value = get_data_entry(form_data, "utxo_value")
page_data['utxo_value'] = value page_data["utxo_value"] = value
ci = swap_client.ci(coin_id) ci = swap_client.ci(coin_id)
value_sats = ci.make_int(value) value_sats = ci.make_int(value)
txid, address = ci.createUTXO(value_sats) txid, address = ci.createUTXO(value_sats)
messages.append('Created new utxo of value {} and address {}<br/>In txid: {}'.format(value, address, txid)) messages.append(
"Created new utxo of value {} and address {}<br/>In txid: {}".format(
value, address, txid
)
)
except Exception as e: except Exception as e:
err_messages.append(str(e)) err_messages.append(str(e))
if swap_client.debug is True: if swap_client.debug is True:
swap_client.log.error(traceback.format_exc()) swap_client.log.error(traceback.format_exc())
swap_client.updateWalletsInfo(force_refresh, only_coin=coin_id, wait_for_complete=True) swap_client.updateWalletsInfo(
wallets = swap_client.getCachedWalletsInfo({'coin_id': coin_id}) force_refresh, only_coin=coin_id, wait_for_complete=True
)
wallets = swap_client.getCachedWalletsInfo({"coin_id": coin_id})
wallet_data = {} wallet_data = {}
for k in wallets.keys(): for k in wallets.keys():
w = wallets[k] w = wallets[k]
if 'error' in w: if "error" in w:
wallet_data = { wallet_data = {"cid": str(int(k)), "error": w["error"]}
'cid': str(int(k)),
'error': w['error']
}
continue continue
if 'no_data' in w: if "no_data" in w:
wallet_data = { wallet_data = {
'name': w['name'], "name": w["name"],
'havedata': False, "havedata": False,
'updating': w['updating'], "updating": w["updating"],
} }
continue continue
@@ -277,55 +319,68 @@ def page_wallet(self, url_split, post_string):
fee_rate, fee_src = swap_client.getFeeRateForCoin(k) fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
est_fee = swap_client.estimateWithdrawFee(k, fee_rate) est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
wallet_data['fee_rate'] = ci.format_amount(int(fee_rate * ci.COIN())) wallet_data["fee_rate"] = ci.format_amount(int(fee_rate * ci.COIN()))
wallet_data['fee_rate_src'] = fee_src wallet_data["fee_rate_src"] = fee_src
wallet_data['est_fee'] = 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())) wallet_data["est_fee"] = (
wallet_data['deposit_address'] = w.get('deposit_address', 'Refresh necessary') "Unknown" if est_fee is None else ci.format_amount(int(est_fee * ci.COIN()))
)
wallet_data["deposit_address"] = w.get("deposit_address", "Refresh necessary")
if k in (Coins.XMR, Coins.WOW): if k in (Coins.XMR, Coins.WOW):
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary') wallet_data["main_address"] = w.get("main_address", "Refresh necessary")
elif k == Coins.LTC: elif k == Coins.LTC:
wallet_data['mweb_address'] = w.get('mweb_address', 'Refresh necessary') 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]
if 'wd_type_to_' + cid in page_data: if "wd_type_to_" + cid in page_data:
wallet_data['wd_type_to'] = page_data['wd_type_to_' + cid] wallet_data["wd_type_to"] = page_data["wd_type_to_" + cid]
if 'utxo_value' in page_data: if "utxo_value" in page_data:
wallet_data['utxo_value'] = page_data['utxo_value'] wallet_data["utxo_value"] = page_data["utxo_value"]
if not withdrawal_successful: if not withdrawal_successful:
if 'wd_value_' + cid in page_data: if "wd_value_" + cid in page_data:
wallet_data['wd_value'] = page_data['wd_value_' + cid] wallet_data["wd_value"] = page_data["wd_value_" + cid]
if 'wd_address_' + cid in page_data: if "wd_address_" + cid in page_data:
wallet_data['wd_address'] = page_data['wd_address_' + cid] wallet_data["wd_address"] = page_data["wd_address_" + cid]
if 'wd_subfee_' + cid in page_data: if "wd_subfee_" + cid in page_data:
wallet_data['wd_subfee'] = page_data['wd_subfee_' + cid] wallet_data["wd_subfee"] = page_data["wd_subfee_" + cid]
if 'wd_sweepall_' + cid in page_data: if "wd_sweepall_" + cid in page_data:
wallet_data['wd_sweepall'] = page_data['wd_sweepall_' + cid] wallet_data["wd_sweepall"] = page_data["wd_sweepall_" + cid]
if 'fee_estimate' in page_data: if "fee_estimate" in page_data:
wallet_data['est_fee'] = ci.format_amount(page_data['fee_estimate']['sum_fee']) wallet_data["est_fee"] = ci.format_amount(
wallet_data['fee_rate'] = ci.format_amount(page_data['fee_estimate']['sum_fee'] * 1000 // page_data['fee_estimate']['sum_weight']) page_data["fee_estimate"]["sum_fee"]
)
wallet_data["fee_rate"] = ci.format_amount(
page_data["fee_estimate"]["sum_fee"]
* 1000
// page_data["fee_estimate"]["sum_weight"]
)
if show_utxo_groups: if show_utxo_groups:
utxo_groups = '' utxo_groups = ""
unspent_by_addr = ci.getUnspentsByAddr() unspent_by_addr = ci.getUnspentsByAddr()
sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True) sorted_unspent_by_addr = sorted(
unspent_by_addr.items(), key=lambda x: x[1], reverse=True
)
for kv in sorted_unspent_by_addr: for kv in sorted_unspent_by_addr:
utxo_groups += kv[0] + ' ' + ci.format_amount(kv[1]) + '\n' utxo_groups += kv[0] + " " + ci.format_amount(kv[1]) + "\n"
wallet_data['show_utxo_groups'] = True wallet_data["show_utxo_groups"] = True
wallet_data['utxo_groups'] = utxo_groups wallet_data["utxo_groups"] = utxo_groups
checkAddressesOwned(swap_client, ci, wallet_data) checkAddressesOwned(swap_client, ci, wallet_data)
template = server.env.get_template('wallet.html') template = server.env.get_template("wallet.html")
return self.render_template(template, { return self.render_template(
'messages': messages, template,
'err_messages': err_messages, {
'w': wallet_data, "messages": messages,
'summary': summary, "err_messages": err_messages,
'block_unknown_seeds': swap_client._restrict_unknown_seed_wallets, "w": wallet_data,
}) "summary": summary,
"block_unknown_seeds": swap_client._restrict_unknown_seed_wallets,
},
)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# 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.
@@ -33,7 +34,21 @@ from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSpl
PAGE_LIMIT = 25 PAGE_LIMIT = 25
invalid_coins_from = [] invalid_coins_from = []
known_chart_coins = ['BTC', 'PART', 'XMR', 'LTC', 'FIRO', 'DASH', 'PIVX', 'DOGE', 'ETH', 'DCR', 'ZANO', 'WOW', 'BCH'] known_chart_coins = [
"BTC",
"PART",
"XMR",
"LTC",
"FIRO",
"DASH",
"PIVX",
"DOGE",
"ETH",
"DCR",
"ZANO",
"WOW",
"BCH",
]
def tickerToCoinId(ticker): def tickerToCoinId(ticker):
@@ -41,7 +56,7 @@ def tickerToCoinId(ticker):
for c in Coins: for c in Coins:
if c.name == search_str: if c.name == search_str:
return c.value return c.value
raise ValueError('Unknown coin') raise ValueError("Unknown coin")
def getCoinType(coin_type_ind): def getCoinType(coin_type_ind):
@@ -55,9 +70,9 @@ def getCoinType(coin_type_ind):
def validateAmountString(amount, ci): def validateAmountString(amount, ci):
if not isinstance(amount, str): if not isinstance(amount, str):
return return
ar = amount.split('.') ar = amount.split(".")
if len(ar) > 1 and len(ar[1]) > ci.exp(): if len(ar) > 1 and len(ar[1]) > ci.exp():
raise ValueError('Too many decimal places in amount {}'.format(amount)) raise ValueError("Too many decimal places in amount {}".format(amount))
def inputAmount(amount_str, ci): def inputAmount(amount_str, ci):
@@ -66,24 +81,24 @@ def inputAmount(amount_str, ci):
def get_data_entry_or(post_data, name, default): def get_data_entry_or(post_data, name, default):
if 'is_json' in post_data: if "is_json" in post_data:
return post_data.get(name, default) return post_data.get(name, default)
key_bytes = name.encode('utf-8') key_bytes = name.encode("utf-8")
if key_bytes in post_data: if key_bytes in post_data:
return post_data[key_bytes][0].decode('utf-8') return post_data[key_bytes][0].decode("utf-8")
return default return default
def get_data_entry(post_data, name): def get_data_entry(post_data, name):
if 'is_json' in post_data: if "is_json" in post_data:
return post_data[name] return post_data[name]
return post_data[name.encode('utf-8')][0].decode('utf-8') return post_data[name.encode("utf-8")][0].decode("utf-8")
def have_data_entry(post_data, name): def have_data_entry(post_data, name):
if 'is_json' in post_data: if "is_json" in post_data:
return name in post_data return name in post_data
return name.encode('utf-8') in post_data return name.encode("utf-8") in post_data
def setCoinFilter(form_data, field_name): def setCoinFilter(form_data, field_name):
@@ -96,18 +111,18 @@ def setCoinFilter(form_data, field_name):
try: try:
return Coins(coin_type) return Coins(coin_type)
except Exception: except Exception:
raise ValueError('Unknown Coin Type {}'.format(str(field_name))) raise ValueError("Unknown Coin Type {}".format(str(field_name)))
def set_pagination_filters(form_data, filters): def set_pagination_filters(form_data, filters):
if form_data and have_data_entry(form_data, 'pageback'): if form_data and have_data_entry(form_data, "pageback"):
filters['page_no'] = int(form_data[b'pageno'][0]) - 1 filters["page_no"] = int(form_data[b"pageno"][0]) - 1
if filters['page_no'] < 1: if filters["page_no"] < 1:
filters['page_no'] = 1 filters["page_no"] = 1
elif form_data and have_data_entry(form_data, 'pageforwards'): elif form_data and have_data_entry(form_data, "pageforwards"):
filters['page_no'] = int(form_data[b'pageno'][0]) + 1 filters["page_no"] = int(form_data[b"pageno"][0]) + 1
if filters['page_no'] > 1: if filters["page_no"] > 1:
filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT filters["offset"] = (filters["page_no"] - 1) * PAGE_LIMIT
def getTxIdHex(bid, tx_type, suffix): def getTxIdHex(bid, tx_type, suffix):
@@ -116,12 +131,12 @@ def getTxIdHex(bid, tx_type, suffix):
elif tx_type == TxTypes.PTX: elif tx_type == TxTypes.PTX:
obj = bid.participate_tx obj = bid.participate_tx
else: else:
return 'Unknown Type' return "Unknown Type"
if not obj: if not obj:
return 'None' return "None"
if not obj.txid: if not obj.txid:
return 'None' return "None"
return obj.txid.hex() + suffix return obj.txid.hex() + suffix
@@ -131,13 +146,13 @@ def getTxSpendHex(bid, tx_type):
elif tx_type == TxTypes.PTX: elif tx_type == TxTypes.PTX:
obj = bid.participate_tx obj = bid.participate_tx
else: else:
return 'Unknown Type' return "Unknown Type"
if not obj: if not obj:
return 'None' return "None"
if not obj.spend_txid: if not obj.spend_txid:
return 'None' return "None"
return obj.spend_txid.hex() + ' {}'.format(obj.spend_n) return obj.spend_txid.hex() + " {}".format(obj.spend_n)
def listBidStates(): def listBidStates():
@@ -154,7 +169,19 @@ def listBidActions():
return rv 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))
@@ -166,240 +193,404 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
bid_amount_to: int = bid.amount_to bid_amount_to: int = bid.amount_to
bid_rate: int = offer.rate if bid.rate is None else bid.rate bid_rate: int = offer.rate if bid.rate is None else bid.rate
initiator_role: str = 'offerer' # Leader initiator_role: str = "offerer" # Leader
participant_role: str = 'bidder' # Follower participant_role: str = "bidder" # Follower
if reverse_bid: if reverse_bid:
bid_amount = bid.amount_to bid_amount = bid.amount_to
bid_amount_to = bid.amount bid_amount_to = bid.amount
bid_rate = ci_from.make_int(bid.amount / bid.amount_to, r=1) bid_rate = ci_from.make_int(bid.amount / bid.amount_to, r=1)
initiator_role = 'bidder' initiator_role = "bidder"
participant_role = 'offerer' participant_role = "offerer"
state_description = '' state_description = ""
if offer.swap_type == SwapTypes.SELLER_FIRST: if offer.swap_type == SwapTypes.SELLER_FIRST:
if bid.state == BidStates.BID_SENT: if bid.state == BidStates.BID_SENT:
state_description = 'Waiting for seller to accept.' state_description = "Waiting for seller to accept."
elif bid.state == BidStates.BID_RECEIVED: elif bid.state == BidStates.BID_RECEIVED:
state_description = 'Waiting for seller to accept.' state_description = "Waiting for seller to accept."
elif bid.state == BidStates.BID_ACCEPTED: elif bid.state == BidStates.BID_ACCEPTED:
if not bid.initiate_tx: if not bid.initiate_tx:
state_description = 'Waiting for seller to send initiate tx.' state_description = "Waiting for seller to send initiate tx."
else: else:
state_description = 'Waiting for initiate tx to confirm.' state_description = "Waiting for initiate tx to confirm."
elif bid.state == BidStates.SWAP_INITIATED: elif bid.state == BidStates.SWAP_INITIATED:
state_description = 'Waiting for participate txn to be confirmed in {} chain'.format(ci_follower.ticker()) state_description = (
"Waiting for participate txn to be confirmed in {} chain".format(
ci_follower.ticker()
)
)
elif bid.state == BidStates.SWAP_PARTICIPATING: elif bid.state == BidStates.SWAP_PARTICIPATING:
if bid.was_sent: if bid.was_sent:
state_description = 'Waiting for participate txn to be spent in {} chain'.format(ci_follower.ticker()) state_description = (
"Waiting for participate txn to be spent in {} chain".format(
ci_follower.ticker()
)
)
else: else:
state_description = 'Waiting for initiate txn to be spent in {} chain'.format(ci_leader.ticker()) state_description = (
"Waiting for initiate txn to be spent in {} chain".format(
ci_leader.ticker()
)
)
elif bid.state == BidStates.SWAP_COMPLETED: elif bid.state == BidStates.SWAP_COMPLETED:
state_description = 'Swap completed' state_description = "Swap completed"
if bid.getITxState() == TxStates.TX_REDEEMED and bid.getPTxState() == TxStates.TX_REDEEMED: if (
state_description += ' successfully' bid.getITxState() == TxStates.TX_REDEEMED
and bid.getPTxState() == TxStates.TX_REDEEMED
):
state_description += " successfully"
else: else:
state_description += ', ITX ' + strTxState(bid.getITxState()) + ', PTX ' + strTxState(bid.getPTxState()) state_description += (
", ITX "
+ strTxState(bid.getITxState())
+ ", PTX "
+ strTxState(bid.getPTxState())
)
elif bid.state == BidStates.SWAP_TIMEDOUT: elif bid.state == BidStates.SWAP_TIMEDOUT:
state_description = 'Timed out waiting for initiate txn' state_description = "Timed out waiting for initiate txn"
elif bid.state == BidStates.BID_ABANDONED: elif bid.state == BidStates.BID_ABANDONED:
state_description = 'Bid abandoned' state_description = "Bid abandoned"
elif bid.state == BidStates.BID_ERROR: elif bid.state == BidStates.BID_ERROR:
state_description = bid.state_note state_description = bid.state_note
elif offer.swap_type == SwapTypes.XMR_SWAP: elif offer.swap_type == SwapTypes.XMR_SWAP:
if bid.state == BidStates.BID_SENT: if bid.state == BidStates.BID_SENT:
state_description = 'Waiting for offerer to accept' state_description = "Waiting for offerer to accept"
if bid.state == BidStates.BID_RECEIVING: if bid.state == BidStates.BID_RECEIVING:
# Offerer receiving bid from bidder # Offerer receiving bid from bidder
state_description = 'Waiting for bid to be fully received' state_description = "Waiting for bid to be fully received"
elif bid.state == BidStates.BID_RECEIVED: elif bid.state == BidStates.BID_RECEIVED:
# Offerer received bid from bidder # Offerer received bid from bidder
# TODO: Manual vs automatic # TODO: Manual vs automatic
state_description = 'Bid must be accepted' state_description = "Bid must be accepted"
elif bid.state == BidStates.BID_RECEIVING_ACC: elif bid.state == BidStates.BID_RECEIVING_ACC:
state_description = 'Receiving accepted bid message' state_description = "Receiving accepted bid message"
elif bid.state == BidStates.BID_ACCEPTED: elif bid.state == BidStates.BID_ACCEPTED:
state_description = 'Offerer has accepted bid, waiting for bidder to respond' state_description = (
"Offerer has accepted bid, waiting for bidder to respond"
)
elif bid.state == BidStates.SWAP_DELAYING: elif bid.state == BidStates.SWAP_DELAYING:
last_state = getLastBidState(bid.states) last_state = getLastBidState(bid.states)
if last_state == BidStates.BID_RECEIVED: if last_state == BidStates.BID_RECEIVED:
state_description = 'Delaying before accepting bid' state_description = "Delaying before accepting bid"
elif last_state == BidStates.BID_RECEIVING_ACC: elif last_state == BidStates.BID_RECEIVING_ACC:
state_description = 'Delaying before responding to accepted bid' state_description = "Delaying before responding to accepted bid"
elif last_state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: elif last_state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
state_description = f'Delaying before spending from {ci_follower.ticker()} lock tx' state_description = (
f"Delaying before spending from {ci_follower.ticker()} lock tx"
)
elif last_state == BidStates.BID_ACCEPTED: elif last_state == BidStates.BID_ACCEPTED:
state_description = f'Delaying before sending {ci_leader.ticker()} lock tx' state_description = (
f"Delaying before sending {ci_leader.ticker()} lock tx"
)
else: else:
state_description = 'Delaying before automated action' state_description = "Delaying before automated action"
elif bid.state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: elif bid.state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
state_description = f'Waiting for {ci_leader.ticker()} lock tx to confirm in chain ({ci_leader.blocks_confirmed} blocks)' state_description = f"Waiting for {ci_leader.ticker()} lock tx to confirm in chain ({ci_leader.blocks_confirmed} blocks)"
elif bid.state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: elif bid.state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
if xmr_swap.b_lock_tx_id is None: if xmr_swap.b_lock_tx_id is None:
state_description = f'Waiting for {ci_follower.ticker()} lock tx' state_description = f"Waiting for {ci_follower.ticker()} lock tx"
else: else:
state_description = f'Waiting for {ci_follower.ticker()} lock tx to confirm in chain ({ci_follower.blocks_confirmed} blocks)' state_description = f"Waiting for {ci_follower.ticker()} lock tx to confirm in chain ({ci_follower.blocks_confirmed} blocks)"
elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED: elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
state_description = f'Waiting for {initiator_role} to unlock {ci_leader.ticker()} lock tx' state_description = (
f"Waiting for {initiator_role} to unlock {ci_leader.ticker()} lock tx"
)
elif bid.state == BidStates.XMR_SWAP_LOCK_RELEASED: elif bid.state == BidStates.XMR_SWAP_LOCK_RELEASED:
state_description = f'Waiting for {participant_role} to spend from {ci_leader.ticker()} lock tx' state_description = f"Waiting for {participant_role} to spend from {ci_leader.ticker()} lock tx"
elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
state_description = f'Waiting for {initiator_role} to spend from {ci_follower.ticker()} lock tx' state_description = f"Waiting for {initiator_role} to spend from {ci_follower.ticker()} lock tx"
elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED: elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
state_description = f'Waiting for {ci_follower.ticker()} lock tx spend tx to confirm in chain' state_description = f"Waiting for {ci_follower.ticker()} lock tx spend tx to confirm in chain"
elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND: elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND:
if bid.was_sent: if bid.was_sent:
state_description = f'Waiting for {initiator_role} to redeem or locktime to expire' state_description = (
f"Waiting for {initiator_role} to redeem or locktime to expire"
)
else: else:
state_description = 'Redeeming output' state_description = "Redeeming output"
addr_label = swap_client.getAddressLabel([bid.bid_addr, ])[0] addr_label = swap_client.getAddressLabel(
[
bid.bid_addr,
]
)[0]
can_abandon: bool = False can_abandon: bool = False
if swap_client.debug and bid.state not in (BidStates.BID_ABANDONED, BidStates.SWAP_COMPLETED): if swap_client.debug and bid.state not in (
BidStates.BID_ABANDONED,
BidStates.SWAP_COMPLETED,
):
can_abandon = True can_abandon = True
data = { data = {
'coin_from': ci_from.coin_name(), "coin_from": ci_from.coin_name(),
'coin_to': ci_to.coin_name(), "coin_to": ci_to.coin_name(),
'amt_from': ci_from.format_amount(bid_amount), "amt_from": ci_from.format_amount(bid_amount),
'amt_to': ci_to.format_amount(bid_amount_to), "amt_to": ci_to.format_amount(bid_amount_to),
'bid_rate': ci_to.format_amount(bid_rate), "bid_rate": ci_to.format_amount(bid_rate),
'ticker_from': ci_from.ticker(), "ticker_from": ci_from.ticker(),
'ticker_to': ci_to.ticker(), "ticker_to": ci_to.ticker(),
'bid_state': strBidState(bid.state), "bid_state": strBidState(bid.state),
'state_description': state_description, "state_description": state_description,
'itx_state': strTxState(bid.getITxState()), "itx_state": strTxState(bid.getITxState()),
'ptx_state': strTxState(bid.getPTxState()), "ptx_state": strTxState(bid.getPTxState()),
'offer_id': bid.offer_id.hex(), "offer_id": bid.offer_id.hex(),
'addr_from': bid.bid_addr, "addr_from": bid.bid_addr,
'addr_from_label': addr_label, "addr_from_label": addr_label,
'addr_fund_proof': bid.proof_address, "addr_fund_proof": bid.proof_address,
'created_at': bid.created_at if for_api else format_timestamp(bid.created_at, with_seconds=True), "created_at": (
'expired_at': bid.expire_at if for_api else format_timestamp(bid.expire_at, with_seconds=True), bid.created_at
'was_sent': 'True' if bid.was_sent else 'False', if for_api
'was_received': 'True' if bid.was_received else 'False', else format_timestamp(bid.created_at, with_seconds=True)
'initiate_tx': getTxIdHex(bid, TxTypes.ITX, ' ' + ci_leader.ticker()), ),
'initiate_conf': 'None' if (not bid.initiate_tx or not bid.initiate_tx.conf) else bid.initiate_tx.conf, "expired_at": (
'participate_tx': getTxIdHex(bid, TxTypes.PTX, ' ' + ci_follower.ticker()), bid.expire_at
'participate_conf': 'None' if (not bid.participate_tx or not bid.participate_tx.conf) else bid.participate_tx.conf, if for_api
'show_txns': show_txns, else format_timestamp(bid.expire_at, with_seconds=True)
'can_abandon': can_abandon, ),
'events': bid_events, "was_sent": "True" if bid.was_sent else "False",
'debug_ui': swap_client.debug_ui, "was_received": "True" if bid.was_received else "False",
'reverse_bid': reverse_bid, "initiate_tx": getTxIdHex(bid, TxTypes.ITX, " " + ci_leader.ticker()),
"initiate_conf": (
"None"
if (not bid.initiate_tx or not bid.initiate_tx.conf)
else bid.initiate_tx.conf
),
"participate_tx": getTxIdHex(bid, TxTypes.PTX, " " + ci_follower.ticker()),
"participate_conf": (
"None"
if (not bid.participate_tx or not bid.participate_tx.conf)
else bid.participate_tx.conf
),
"show_txns": show_txns,
"can_abandon": can_abandon,
"events": bid_events,
"debug_ui": swap_client.debug_ui,
"reverse_bid": reverse_bid,
} }
if edit_bid: if edit_bid:
data['bid_state_ind'] = int(bid.state) data["bid_state_ind"] = int(bid.state)
data['bid_states'] = listBidStates() data["bid_states"] = listBidStates()
if swap_client.debug_ui: if swap_client.debug_ui:
data['debug_ind'] = bid.debug_ind data["debug_ind"] = bid.debug_ind
data['debug_options'] = [(int(t), t.name) for t in DebugTypes] data["debug_options"] = [(int(t), t.name) for t in DebugTypes]
if show_txns: if show_txns:
if offer.swap_type == SwapTypes.XMR_SWAP: if offer.swap_type == SwapTypes.XMR_SWAP:
txns = [] txns = []
if bid.xmr_a_lock_tx: if bid.xmr_a_lock_tx:
confirms = None confirms = None
if swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height: if (
confirms = (swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] - bid.xmr_a_lock_tx.chain_height) + 1 swap_client.coin_clients[ci_leader.coin_type()]["chain_height"]
txns.append({'type': 'Chain A Lock', 'txid': hex_or_none(bid.xmr_a_lock_tx.txid), 'confirms': confirms}) and bid.xmr_a_lock_tx.chain_height
):
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": hex_or_none(bid.xmr_a_lock_tx.txid),
"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_follower.coin_type()]['chain_height'] and bid.xmr_b_lock_tx.chain_height: if (
confirms = (swap_client.coin_clients[ci_follower.coin_type()]['chain_height'] - bid.xmr_b_lock_tx.chain_height) + 1 swap_client.coin_clients[ci_follower.coin_type()]["chain_height"]
txns.append({'type': 'Chain B Lock', 'txid': bid.xmr_b_lock_tx.txid.hex(), 'confirms': confirms}) and bid.xmr_b_lock_tx.chain_height
):
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,
}
)
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(),
}
)
if xmr_swap.a_lock_refund_tx: if xmr_swap.a_lock_refund_tx:
txns.append({'type': strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND), 'txid': xmr_swap.a_lock_refund_tx_id.hex()}) txns.append(
{
"type": strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND),
"txid": xmr_swap.a_lock_refund_tx_id.hex(),
}
)
if xmr_swap.a_lock_refund_spend_tx: if xmr_swap.a_lock_refund_spend_tx:
txns.append({'type': strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND), 'txid': xmr_swap.a_lock_refund_spend_tx_id.hex()}) txns.append(
{
"type": strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND),
"txid": xmr_swap.a_lock_refund_spend_tx_id.hex(),
}
)
for tx_type, tx in bid.txns.items(): for tx_type, tx in bid.txns.items():
if tx_type in (TxTypes.XMR_SWAP_A_LOCK_REFUND, TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND): if tx_type in (
TxTypes.XMR_SWAP_A_LOCK_REFUND,
TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND,
):
continue continue
txns.append({'type': strTxType(tx_type), 'txid': tx.txid.hex()}) txns.append({"type": strTxType(tx_type), "txid": tx.txid.hex()})
data['txns'] = txns data["txns"] = txns
data['xmr_b_shared_address'] = ci_to.encodeSharedAddress(xmr_swap.pkbv, xmr_swap.pkbs) if xmr_swap.pkbs else None data["xmr_b_shared_address"] = (
data['xmr_b_shared_viewkey'] = ci_to.encodeKey(xmr_swap.vkbv) if xmr_swap.vkbv else None ci_to.encodeSharedAddress(xmr_swap.pkbv, xmr_swap.pkbs)
if xmr_swap.pkbs
else None
)
data["xmr_b_shared_viewkey"] = (
ci_to.encodeKey(xmr_swap.vkbv) if xmr_swap.vkbv else None
)
if swap_client.debug_ui: if swap_client.debug_ui:
try: try:
data['xmr_b_half_privatekey'] = getChainBSplitKey(swap_client, bid, xmr_swap, offer) data["xmr_b_half_privatekey"] = getChainBSplitKey(
except Exception as e: swap_client, bid, xmr_swap, offer
swap_client.log.debug('Unable to get xmr_b_half_privatekey for bid: {}'.format(bid.bid_id.hex())) )
except Exception as e: # noqa: F841
swap_client.log.debug(
"Unable to get xmr_b_half_privatekey for bid: {}".format(
bid.bid_id.hex()
)
)
try: try:
remote_split_key = getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer) remote_split_key = getChainBRemoteSplitKey(
swap_client, bid, xmr_swap, offer
)
if remote_split_key: if remote_split_key:
data['xmr_b_half_privatekey_remote'] = remote_split_key data["xmr_b_half_privatekey_remote"] = remote_split_key
except Exception as e: except Exception as e: # noqa: F841
swap_client.log.debug('Unable to get xmr_b_half_privatekey_remote for bid: {}'.format(bid.bid_id.hex())) swap_client.log.debug(
"Unable to get xmr_b_half_privatekey_remote for bid: {}".format(
bid.bid_id.hex()
)
)
if show_lock_transfers: if show_lock_transfers:
if xmr_swap.pkbs: if xmr_swap.pkbs:
data['lock_transfers'] = json.dumps(ci_to.showLockTransfers(xmr_swap.vkbv, xmr_swap.pkbs, bid.chain_b_height_start), indent=4) data["lock_transfers"] = json.dumps(
ci_to.showLockTransfers(
xmr_swap.vkbv, xmr_swap.pkbs, bid.chain_b_height_start
),
indent=4,
)
else: else:
data['lock_transfers'] = 'Shared address not yet known.' data["lock_transfers"] = "Shared address not yet known."
else: else:
data['initiate_tx_refund'] = 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex() data["initiate_tx_refund"] = (
data['participate_tx_refund'] = 'None' if not bid.participate_txn_refund else bid.participate_txn_refund.hex() "None" if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex()
data['initiate_tx_spend'] = getTxSpendHex(bid, TxTypes.ITX) )
data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX) data["participate_tx_refund"] = (
"None"
if not bid.participate_txn_refund
else bid.participate_txn_refund.hex()
)
data["initiate_tx_spend"] = getTxSpendHex(bid, TxTypes.ITX)
data["participate_tx_spend"] = getTxSpendHex(bid, TxTypes.PTX)
if bid.initiate_tx and bid.initiate_tx.tx_data is not None: if bid.initiate_tx and bid.initiate_tx.tx_data is not None:
data['initiate_tx_inputs'] = ci_from.listInputs(bid.initiate_tx.tx_data) data["initiate_tx_inputs"] = ci_from.listInputs(bid.initiate_tx.tx_data)
if bid.participate_tx and bid.participate_tx.tx_data is not None: if bid.participate_tx and bid.participate_tx.tx_data is not None:
data['initiate_tx_inputs'] = ci_from.listInputs(bid.participate_tx.tx_data) data["initiate_tx_inputs"] = ci_from.listInputs(
bid.participate_tx.tx_data
)
if offer.swap_type == SwapTypes.XMR_SWAP: if offer.swap_type == SwapTypes.XMR_SWAP:
data['coin_a_lock_refund_tx_est_final'] = 'None' data["coin_a_lock_refund_tx_est_final"] = "None"
data['coin_a_lock_refund_swipe_tx_est_final'] = 'None' data["coin_a_lock_refund_swipe_tx_est_final"] = "None"
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.block_time: if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.block_time:
raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value) raw_sequence = ci_leader.getExpectedSequence(
offer.lock_type, offer.lock_value
)
seconds_locked = ci_leader.decodeSequence(raw_sequence) seconds_locked = ci_leader.decodeSequence(raw_sequence)
data['coin_a_lock_refund_tx_est_final'] = bid.xmr_a_lock_tx.block_time + seconds_locked data["coin_a_lock_refund_tx_est_final"] = (
data['coin_a_last_median_time'] = swap_client.coin_clients[offer.coin_from]['chain_median_time'] bid.xmr_a_lock_tx.block_time + seconds_locked
)
data["coin_a_last_median_time"] = swap_client.coin_clients[
offer.coin_from
]["chain_median_time"]
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if refund_tx.block_time is not None: if refund_tx.block_time is not None:
raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value) raw_sequence = ci_leader.getExpectedSequence(
offer.lock_type, offer.lock_value
)
seconds_locked = ci_leader.decodeSequence(raw_sequence) seconds_locked = ci_leader.decodeSequence(raw_sequence)
data['coin_a_lock_refund_swipe_tx_est_final'] = refund_tx.block_time + seconds_locked data["coin_a_lock_refund_swipe_tx_est_final"] = (
refund_tx.block_time + seconds_locked
)
if view_tx_ind: if view_tx_ind:
data['view_tx_ind'] = view_tx_ind data["view_tx_ind"] = view_tx_ind
view_tx_id = bytes.fromhex(view_tx_ind) view_tx_id = bytes.fromhex(view_tx_ind)
if xmr_swap: if xmr_swap:
if view_tx_id == xmr_swap.a_lock_tx_id and xmr_swap.a_lock_tx: if view_tx_id == xmr_swap.a_lock_tx_id and xmr_swap.a_lock_tx:
data['view_tx_hex'] = xmr_swap.a_lock_tx.hex() data["view_tx_hex"] = xmr_swap.a_lock_tx.hex()
data['chain_a_lock_tx_inputs'] = ci_leader.listInputs(xmr_swap.a_lock_tx) data["chain_a_lock_tx_inputs"] = ci_leader.listInputs(
if view_tx_id == xmr_swap.a_lock_refund_tx_id and xmr_swap.a_lock_refund_tx: xmr_swap.a_lock_tx
data['view_tx_hex'] = xmr_swap.a_lock_refund_tx.hex() )
if view_tx_id == xmr_swap.a_lock_refund_spend_tx_id and xmr_swap.a_lock_refund_spend_tx: if (
data['view_tx_hex'] = xmr_swap.a_lock_refund_spend_tx.hex() view_tx_id == xmr_swap.a_lock_refund_tx_id
if view_tx_id == xmr_swap.a_lock_spend_tx_id and xmr_swap.a_lock_spend_tx: and xmr_swap.a_lock_refund_tx
data['view_tx_hex'] = xmr_swap.a_lock_spend_tx.hex() ):
data["view_tx_hex"] = xmr_swap.a_lock_refund_tx.hex()
if (
view_tx_id == xmr_swap.a_lock_refund_spend_tx_id
and xmr_swap.a_lock_refund_spend_tx
):
data["view_tx_hex"] = xmr_swap.a_lock_refund_spend_tx.hex()
if (
view_tx_id == xmr_swap.a_lock_spend_tx_id
and xmr_swap.a_lock_spend_tx
):
data["view_tx_hex"] = xmr_swap.a_lock_spend_tx.hex()
if 'view_tx_hex' in data: if "view_tx_hex" in data:
data['view_tx_desc'] = json.dumps(ci_leader.describeTx(data['view_tx_hex']), indent=4) data["view_tx_desc"] = json.dumps(
ci_leader.describeTx(data["view_tx_hex"]), indent=4
)
else: else:
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if bid.initiate_tx and bid.initiate_tx.block_time is not None: if bid.initiate_tx and bid.initiate_tx.block_time is not None:
raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value) raw_sequence = ci_leader.getExpectedSequence(
offer.lock_type, offer.lock_value
)
seconds_locked = ci_leader.decodeSequence(raw_sequence) seconds_locked = ci_leader.decodeSequence(raw_sequence)
data['itx_refund_tx_est_final'] = bid.initiate_tx.block_time + seconds_locked data["itx_refund_tx_est_final"] = (
bid.initiate_tx.block_time + seconds_locked
)
if bid.participate_tx and bid.participate_tx.block_time is not None: if bid.participate_tx and bid.participate_tx.block_time is not None:
raw_sequence = ci_follower.getExpectedSequence(offer.lock_type, offer.lock_value // 2) raw_sequence = ci_follower.getExpectedSequence(
offer.lock_type, offer.lock_value // 2
)
seconds_locked = ci_follower.decodeSequence(raw_sequence) seconds_locked = ci_follower.decodeSequence(raw_sequence)
data['ptx_refund_tx_est_final'] = bid.participate_tx.block_time + seconds_locked data["ptx_refund_tx_est_final"] = (
bid.participate_tx.block_time + seconds_locked
)
return data return data
@@ -408,20 +599,24 @@ def listOldBidStates(bid):
old_states = [] old_states = []
num_states = len(bid.states) // 12 num_states = len(bid.states) // 12
for i in range(num_states): for i in range(num_states):
up = struct.unpack_from('<iq', bid.states[i * 12:(i + 1) * 12]) up = struct.unpack_from("<iq", bid.states[i * 12 : (i + 1) * 12])
old_states.append((up[1], 'Bid ' + strBidState(up[0]))) old_states.append((up[1], "Bid " + strBidState(up[0])))
if bid.initiate_tx and bid.initiate_tx.states is not None: if bid.initiate_tx and bid.initiate_tx.states is not None:
num_states = len(bid.initiate_tx.states) // 12 num_states = len(bid.initiate_tx.states) // 12
for i in range(num_states): for i in range(num_states):
up = struct.unpack_from('<iq', bid.initiate_tx.states[i * 12:(i + 1) * 12]) up = struct.unpack_from(
"<iq", bid.initiate_tx.states[i * 12 : (i + 1) * 12]
)
if up[0] != TxStates.TX_NONE: if up[0] != TxStates.TX_NONE:
old_states.append((up[1], 'ITX ' + strTxState(up[0]))) old_states.append((up[1], "ITX " + strTxState(up[0])))
if bid.participate_tx and bid.participate_tx.states is not None: if bid.participate_tx and bid.participate_tx.states is not None:
num_states = len(bid.participate_tx.states) // 12 num_states = len(bid.participate_tx.states) // 12
for i in range(num_states): for i in range(num_states):
up = struct.unpack_from('<iq', bid.participate_tx.states[i * 12:(i + 1) * 12]) up = struct.unpack_from(
"<iq", bid.participate_tx.states[i * 12 : (i + 1) * 12]
)
if up[0] != TxStates.TX_NONE: if up[0] != TxStates.TX_NONE:
old_states.append((up[1], 'PTX ' + strTxState(up[0]))) old_states.append((up[1], "PTX " + strTxState(up[0])))
if len(old_states) > 0: if len(old_states) > 0:
old_states.sort(key=lambda x: x[0]) old_states.sort(key=lambda x: x[0])
return old_states return old_states
@@ -429,16 +624,16 @@ def listOldBidStates(bid):
def getCoinName(c): def getCoinName(c):
if c == Coins.PART_ANON: if c == Coins.PART_ANON:
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: if c == Coins.LTC_MWEB:
return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB' return chainparams[Coins.LTC]["name"].capitalize() + " MWEB"
coin_chainparams = chainparams[c] coin_chainparams = chainparams[c]
if 'display_name' in coin_chainparams: if "display_name" in coin_chainparams:
return coin_chainparams['display_name'] return coin_chainparams["display_name"]
return coin_chainparams['name'].capitalize() return coin_chainparams["name"].capitalize()
def listAvailableCoins(swap_client, with_variants=True, split_from=False): def listAvailableCoins(swap_client, with_variants=True, split_from=False):
@@ -447,7 +642,7 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
for k, v in swap_client.coin_clients.items(): for k, v in swap_client.coin_clients.items():
if k not in chainparams: if k not in chainparams:
continue continue
if v['connection_type'] == 'rpc': if v["connection_type"] == "rpc":
coins.append((int(k), getCoinName(k))) coins.append((int(k), getCoinName(k)))
if split_from and k not in invalid_coins_from: if split_from and k not in invalid_coins_from:
coins_from.append(coins[-1]) coins_from.append(coins[-1])
@@ -465,29 +660,37 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
def checkAddressesOwned(swap_client, ci, wallet_info): def checkAddressesOwned(swap_client, ci, wallet_info):
if 'stealth_address' in wallet_info: if "stealth_address" in wallet_info:
if wallet_info['stealth_address'] != '?': if wallet_info["stealth_address"] != "?":
if not ci.isAddressMine(wallet_info['stealth_address']): if not ci.isAddressMine(wallet_info["stealth_address"]):
ci._log.error('Unowned stealth address: {}'.format(wallet_info['stealth_address'])) ci._log.error(
wallet_info['stealth_address'] = 'Error: unowned address' "Unowned stealth address: {}".format(wallet_info["stealth_address"])
elif swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed(): )
wallet_info['stealth_address'] = 'WARNING: Unknown wallet seed' wallet_info["stealth_address"] = "Error: unowned address"
elif (
swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed()
):
wallet_info["stealth_address"] = "WARNING: Unknown wallet seed"
if 'deposit_address' in wallet_info: if "deposit_address" in wallet_info:
if wallet_info['deposit_address'] != 'Refresh necessary': if wallet_info["deposit_address"] != "Refresh necessary":
if not ci.isAddressMine(wallet_info['deposit_address']): if not ci.isAddressMine(wallet_info["deposit_address"]):
ci._log.error('Unowned deposit address: {}'.format(wallet_info['deposit_address'])) ci._log.error(
wallet_info['deposit_address'] = 'Error: unowned address' "Unowned deposit address: {}".format(wallet_info["deposit_address"])
elif swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed(): )
wallet_info['deposit_address'] = 'WARNING: Unknown wallet seed' wallet_info["deposit_address"] = "Error: unowned address"
elif (
swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed()
):
wallet_info["deposit_address"] = "WARNING: Unknown wallet seed"
def validateTextInput(text, name, messages, max_length=None): def validateTextInput(text, name, messages, max_length=None):
if max_length is not None and len(text) > max_length: if max_length is not None and len(text) > max_length:
messages.append(f'Error: {name} is too long') messages.append(f"Error: {name} is too long")
return False return False
if len(text) > 0 and all(c.isalnum() or c.isspace() for c in text) is False: if len(text) > 0 and all(c.isalnum() or c.isspace() for c in text) is False:
messages.append(f'Error: {name} must consist of only letters and digits') messages.append(f"Error: {name} must consist of only letters and digits")
return False return False
return True return True

View File

@@ -39,7 +39,7 @@ class LockedCoinError(Exception):
self.coinid = coinid self.coinid = coinid
def __str__(self): def __str__(self):
return 'Coin must be unlocked: ' + str(self.coinid) return "Coin must be unlocked: " + str(self.coinid)
def ensure(v, err_string): def ensure(v, err_string):
@@ -50,7 +50,7 @@ def ensure(v, err_string):
def toBool(s) -> bool: def toBool(s) -> bool:
if isinstance(s, bool): if isinstance(s, bool):
return s return s
return s.lower() in ['1', 'true'] return s.lower() in ["1", "true"]
def jsonDecimal(obj): def jsonDecimal(obj):
@@ -76,8 +76,8 @@ def SerialiseNum(n: int) -> bytes:
rv = bytearray() rv = bytearray()
neg = n < 0 neg = n < 0
absvalue = -n if neg else n absvalue = -n if neg else n
while (absvalue): while absvalue:
rv.append(absvalue & 0xff) rv.append(absvalue & 0xFF)
absvalue >>= 8 absvalue >>= 8
if rv[-1] & 0x80: if rv[-1] & 0x80:
rv.append(0x80 if neg else 0) rv.append(0x80 if neg else 0)
@@ -106,34 +106,36 @@ def DeserialiseNum(b: bytes, o: int = 0) -> int:
def float_to_str(f: float) -> str: def float_to_str(f: float) -> str:
# stackoverflow.com/questions/38847690 # stackoverflow.com/questions/38847690
d1 = decimal_ctx.create_decimal(repr(f)) d1 = decimal_ctx.create_decimal(repr(f))
return format(d1, 'f') return format(d1, "f")
def make_int(v, scale: int = 8, r: int = 0) -> int: # r = 0, no rounding (fail), r > 0 round off, r < 0 floor def make_int(
v, scale: int = 8, r: int = 0
) -> int: # r = 0, no rounding (fail), r > 0 round off, r < 0 floor
if isinstance(v, float): if isinstance(v, float):
v = float_to_str(v) v = float_to_str(v)
elif isinstance(v, int): elif isinstance(v, int):
return v * 10**scale return v * 10**scale
sign = 1 sign = 1
if v[0] == '-': if v[0] == "-":
v = v[1:] v = v[1:]
sign = -1 sign = -1
ep = 10**scale ep = 10**scale
have_dp = False have_dp = False
rv = 0 rv = 0
for c in v: for c in v:
if c == '.': if c == ".":
rv *= ep rv *= ep
have_dp = True have_dp = True
continue continue
if not c.isdigit(): if not c.isdigit():
raise ValueError('Invalid char: ' + c) raise ValueError("Invalid char: " + c)
if have_dp: if have_dp:
ep //= 10 ep //= 10
if ep <= 0: if ep <= 0:
if r == 0: if r == 0:
raise ValueError('Mantissa too long') raise ValueError("Mantissa too long")
if r > 0: if r > 0:
# Round off # Round off
if int(c) > 4: if int(c) > 4:
@@ -151,21 +153,23 @@ def validate_amount(amount, scale: int = 8) -> bool:
str_amount = float_to_str(amount) if isinstance(amount, float) else str(amount) str_amount = float_to_str(amount) if isinstance(amount, float) else str(amount)
has_decimal = False has_decimal = False
for c in str_amount: for c in str_amount:
if c == '.' and not has_decimal: if c == "." and not has_decimal:
has_decimal = True has_decimal = True
continue continue
if not c.isdigit(): if not c.isdigit():
raise ValueError('Invalid amount') raise ValueError("Invalid amount")
ar = str_amount.split('.') ar = str_amount.split(".")
if len(ar) > 1 and len(ar[1]) > scale: if len(ar) > 1 and len(ar[1]) > scale:
raise ValueError('Too many decimal places in amount {}'.format(str_amount)) raise ValueError("Too many decimal places in amount {}".format(str_amount))
return True return True
def format_amount(i: int, display_scale: int, scale: int = None) -> str: def format_amount(i: int, display_scale: int, scale: int = None) -> str:
if not isinstance(i, int): if not isinstance(i, int):
raise ValueError('Amount must be an integer.') # Raise error instead of converting as amounts should always be integers raise ValueError(
"Amount must be an integer."
) # Raise error instead of converting as amounts should always be integers
if scale is None: if scale is None:
scale = display_scale scale = display_scale
ep = 10**scale ep = 10**scale
@@ -173,29 +177,29 @@ def format_amount(i: int, display_scale: int, scale: int = None) -> str:
quotient = n // ep quotient = n // ep
remainder = n % ep remainder = n % ep
if display_scale != scale: if display_scale != scale:
remainder %= (10 ** display_scale) remainder %= 10**display_scale
rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale) rv = "{}.{:0>{scale}}".format(quotient, remainder, scale=display_scale)
if i < 0: if i < 0:
rv = '-' + rv rv = "-" + rv
return rv return rv
def format_timestamp(value: int, with_seconds: bool = False) -> str: 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))
def b2i(b: bytes) -> int: def b2i(b: bytes) -> int:
# bytes32ToInt # bytes32ToInt
return int.from_bytes(b, byteorder='big') return int.from_bytes(b, byteorder="big")
def i2b(i: int) -> bytes: def i2b(i: int) -> bytes:
# intToBytes32 # intToBytes32
return i.to_bytes(32, byteorder='big') return i.to_bytes(32, byteorder="big")
def b2h(b: bytes) -> str: def b2h(b: bytes) -> str:
@@ -203,7 +207,7 @@ def b2h(b: bytes) -> str:
def h2b(h: str) -> bytes: def h2b(h: str) -> bytes:
if h.startswith('0x'): if h.startswith("0x"):
h = h[2:] h = h[2:]
return bytes.fromhex(h) return bytes.fromhex(h)
@@ -220,5 +224,5 @@ def zeroIfNone(value) -> int:
def hex_or_none(value: bytes) -> str: def hex_or_none(value: bytes) -> str:
if value is None: if value is None:
return 'None' return "None"
return value.hex() return value.hex()

View File

@@ -7,12 +7,12 @@
from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
from basicswap.util.crypto import ripemd160, sha256 from basicswap.util.crypto import ripemd160, sha256
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def b58decode(v, length=None): def b58decode(v, length=None):
long_value = 0 long_value = 0
for (i, c) in enumerate(v[::-1]): for i, c in enumerate(v[::-1]):
ofs = __b58chars.find(c) ofs = __b58chars.find(c)
if ofs < 0: if ofs < 0:
return None return None
@@ -38,10 +38,10 @@ def b58decode(v, length=None):
def b58encode(v): def b58encode(v):
long_value = 0 long_value = 0
for (i, c) in enumerate(v[::-1]): for i, c in enumerate(v[::-1]):
long_value += (256**i) * c long_value += (256**i) * c
result = '' result = ""
while long_value >= 58: while long_value >= 58:
div, mod = divmod(long_value, 58) div, mod = divmod(long_value, 58)
result = __b58chars[mod] + result result = __b58chars[mod] + result
@@ -58,7 +58,9 @@ def b58encode(v):
return (__b58chars[0] * nPad) + result return (__b58chars[0] * nPad) + result
def encodeStealthAddress(prefix_byte: int, scan_pubkey: bytes, spend_pubkey: bytes) -> str: def encodeStealthAddress(
prefix_byte: int, scan_pubkey: bytes, spend_pubkey: bytes
) -> str:
data = bytes((0x00,)) data = bytes((0x00,))
data += scan_pubkey data += scan_pubkey
data += bytes((0x01,)) data += bytes((0x01,))
@@ -114,7 +116,7 @@ def decodeAddress(address: str) -> bytes:
prefixed_data = addr_data[:-4] prefixed_data = addr_data[:-4]
checksum = addr_data[-4:] checksum = addr_data[-4:]
if sha256(sha256(prefixed_data))[:4] != checksum: if sha256(sha256(prefixed_data))[:4] != checksum:
raise ValueError('Checksum mismatch') raise ValueError("Checksum mismatch")
return prefixed_data return prefixed_data

View File

@@ -9,7 +9,7 @@ from basicswap.contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_sym
from . import i2b from . import i2b
class ECCParameters(): class ECCParameters:
def __init__(self, p, a, b, Gx, Gy, o): def __init__(self, p, a, b, Gx, Gy, o):
self.p = p self.p = p
self.a = a self.a = a
@@ -20,12 +20,13 @@ class ECCParameters():
ep = ECCParameters( ep = ECCParameters(
p=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, p=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
a=0x0, a=0x0,
b=0x7, b=0x7,
Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, Gx=0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, Gy=0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
o=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141) o=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
)
curve_secp256k1 = CurveFp(ep.p, ep.a, ep.b) curve_secp256k1 = CurveFp(ep.p, ep.a, ep.b)
@@ -34,7 +35,11 @@ SECP256K1_ORDER_HALF = ep.o // 2
def ToDER(P) -> bytes: def ToDER(P) -> bytes:
return bytes((4, )) + int(P.x()).to_bytes(32, byteorder='big') + int(P.y()).to_bytes(32, byteorder='big') return (
bytes((4,))
+ int(P.x()).to_bytes(32, byteorder="big")
+ int(P.y()).to_bytes(32, byteorder="big")
)
def getSecretBytes() -> bytes: def getSecretBytes() -> bytes:
@@ -50,7 +55,7 @@ def getInsecureBytes() -> bytes:
while True: while True:
s = os.urandom(32) s = os.urandom(32)
s_test = int.from_bytes(s, byteorder='big') s_test = int.from_bytes(s, byteorder="big")
if s_test > 1 and s_test < ep.o: if s_test > 1 and s_test < ep.o:
return s return s
@@ -59,7 +64,7 @@ def getInsecureInt() -> int:
while True: while True:
s = os.urandom(32) s = os.urandom(32)
s_test = int.from_bytes(s, byteorder='big') s_test = int.from_bytes(s, byteorder="big")
if s_test > 1 and s_test < ep.o: if s_test > 1 and s_test < ep.o:
return s_test return s_test
@@ -77,7 +82,7 @@ def powMod(x, y, z) -> int:
def ExpandPoint(xb, sign): def ExpandPoint(xb, sign):
x = int.from_bytes(xb, byteorder='big') x = int.from_bytes(xb, byteorder="big")
a = (powMod(x, 3, ep.p) + 7) % ep.p a = (powMod(x, 3, ep.p) + 7) % ep.p
y = powMod(a, (ep.p + 1) // 4, ep.p) y = powMod(a, (ep.p + 1) // 4, ep.p)
@@ -89,7 +94,7 @@ def ExpandPoint(xb, sign):
def CPKToPoint(cpk): def CPKToPoint(cpk):
y_parity = cpk[0] - 2 y_parity = cpk[0] - 2
x = int.from_bytes(cpk[1:], byteorder='big') x = int.from_bytes(cpk[1:], byteorder="big")
a = (powMod(x, 3, ep.p) + 7) % ep.p a = (powMod(x, 3, ep.p) + 7) % ep.p
y = powMod(a, (ep.p + 1) // 4, ep.p) y = powMod(a, (ep.p + 1) // 4, ep.p)
@@ -102,28 +107,29 @@ def CPKToPoint(cpk):
def pointToCPK2(point, ind=0x09): def pointToCPK2(point, ind=0x09):
# The function is_square(x), where x is an integer, returns whether or not x is a quadratic residue modulo p. Since p is prime, it is equivalent to the Legendre symbol (x / p) = x(p-1)/2 mod p being equal to 1[8]. # The function is_square(x), where x is an integer, returns whether or not x is a quadratic residue modulo p. Since p is prime, it is equivalent to the Legendre symbol (x / p) = x(p-1)/2 mod p being equal to 1[8].
ind = bytes((ind ^ (1 if jacobi_symbol(point.y(), ep.p) == 1 else 0),)) ind = bytes((ind ^ (1 if jacobi_symbol(point.y(), ep.p) == 1 else 0),))
return ind + point.x().to_bytes(32, byteorder='big') return ind + point.x().to_bytes(32, byteorder="big")
def pointToCPK(point): def pointToCPK(point):
y = point.y().to_bytes(32, byteorder='big') y = point.y().to_bytes(32, byteorder="big")
ind = bytes((0x03,)) if y[31] % 2 else bytes((0x02,)) ind = bytes((0x03,)) if y[31] % 2 else bytes((0x02,))
cpk = ind + point.x().to_bytes(32, byteorder='big') cpk = ind + point.x().to_bytes(32, byteorder="big")
return cpk return cpk
def secretToCPK(secret): def secretToCPK(secret):
secretInt = secret if isinstance(secret, int) \ secretInt = (
else int.from_bytes(secret, byteorder='big') secret if isinstance(secret, int) else int.from_bytes(secret, byteorder="big")
)
R = G * secretInt R = G * secretInt
Y = R.y().to_bytes(32, byteorder='big') Y = R.y().to_bytes(32, byteorder="big")
ind = bytes((0x03,)) if Y[31] % 2 else bytes((0x02,)) ind = bytes((0x03,)) if Y[31] % 2 else bytes((0x02,))
pubkey = ind + R.x().to_bytes(32, byteorder='big') pubkey = ind + R.x().to_bytes(32, byteorder="big")
return pubkey return pubkey
@@ -136,7 +142,7 @@ def getKeypair():
def hashToCurve(pubkey): def hashToCurve(pubkey):
xBytes = hashlib.sha256(pubkey).digest() xBytes = hashlib.sha256(pubkey).digest()
x = int.from_bytes(xBytes, byteorder='big') x = int.from_bytes(xBytes, byteorder="big")
for k in range(0, 100): for k in range(0, 100):
# get matching y element for point # get matching y element for point
@@ -155,12 +161,14 @@ def hashToCurve(pubkey):
x = (x + 1) % ep.p # % P? x = (x + 1) % ep.p # % P?
continue continue
if R == INFINITY or R * ep.o != INFINITY: # is R * O != INFINITY check necessary? Validation of Elliptic Curve Public Keys says no if cofactor = 1 if (
R == INFINITY or R * ep.o != INFINITY
): # is R * O != INFINITY check necessary? Validation of Elliptic Curve Public Keys says no if cofactor = 1
x = (x + 1) % ep.p # % P? x = (x + 1) % ep.p # % P?
continue continue
return R return R
raise ValueError('hashToCurve failed for 100 tries') raise ValueError("hashToCurve failed for 100 tries")
def hash256(inb): def hash256(inb):
@@ -168,23 +176,35 @@ def hash256(inb):
def testEccUtils(): def testEccUtils():
print('testEccUtils()') print("testEccUtils()")
G_enc = ToDER(G) G_enc = ToDER(G)
assert (G_enc.hex() == '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8') assert (
G_enc.hex()
== "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
)
G_enc = pointToCPK(G) G_enc = pointToCPK(G)
assert (G_enc.hex() == '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') assert (
G_enc.hex()
== "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
)
G_dec = CPKToPoint(G_enc) G_dec = CPKToPoint(G_enc)
assert (G_dec == G) assert G_dec == G
G_enc = pointToCPK2(G) G_enc = pointToCPK2(G)
assert (G_enc.hex() == '0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') assert (
G_enc.hex()
== "0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
)
H = hashToCurve(ToDER(G)) H = hashToCurve(ToDER(G))
assert (pointToCPK(H).hex() == '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0') assert (
pointToCPK(H).hex()
== "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
)
print('Passed.') print("Passed.")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -7,21 +7,30 @@
from .crypto import blake256, hash160, hmac_sha512, ripemd160 from .crypto import blake256, hash160, hmac_sha512, ripemd160
from coincurve.keys import ( from coincurve.keys import PrivateKey, PublicKey
PrivateKey,
PublicKey)
def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes): def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes):
return hmac_sha512(chaincode, key_data_type.to_bytes(1, 'big') + keydata + child_no.to_bytes(4, 'big')) return hmac_sha512(
chaincode,
key_data_type.to_bytes(1, "big") + keydata + child_no.to_bytes(4, "big"),
)
def hash160_dcr(data: bytes) -> bytes: def hash160_dcr(data: bytes) -> bytes:
return ripemd160(blake256(data)) return ripemd160(blake256(data))
class ExtKeyPair(): class ExtKeyPair:
__slots__ = ('_depth', '_fingerprint', '_child_no', '_chaincode', '_key', '_pubkey', 'hash_func') __slots__ = (
"_depth",
"_fingerprint",
"_child_no",
"_chaincode",
"_key",
"_pubkey",
"hash_func",
)
def __init__(self, coin_type=1): def __init__(self, coin_type=1):
if coin_type == 4: if coin_type == 4:
@@ -30,20 +39,20 @@ class ExtKeyPair():
self.hash_func = hash160 self.hash_func = hash160
def set_seed(self, seed: bytes) -> None: def set_seed(self, seed: bytes) -> None:
hashout: bytes = hmac_sha512(b'Bitcoin seed', seed) hashout: bytes = hmac_sha512(b"Bitcoin seed", seed)
self._key = hashout[:32] self._key = hashout[:32]
self._pubkey = None self._pubkey = None
self._chaincode = hashout[32:] self._chaincode = hashout[32:]
self._depth = 0 self._depth = 0
self._child_no = 0 self._child_no = 0
self._fingerprint = b'\0' * 4 self._fingerprint = b"\0" * 4
def has_key(self) -> bool: def has_key(self) -> bool:
return False if self._key is None else True return False if self._key is None else True
def neuter(self) -> None: def neuter(self) -> None:
if self._key is None: if self._key is None:
raise ValueError('Already neutered') raise ValueError("Already neutered")
self._pubkey = PublicKey.from_secret(self._key).format() self._pubkey = PublicKey.from_secret(self._key).format()
self._key = None self._key = None
@@ -74,7 +83,11 @@ class ExtKeyPair():
out._pubkey = K.format() out._pubkey = K.format()
else: else:
k = PrivateKey(self._key) k = PrivateKey(self._key)
out._fingerprint = self.hash_func(self._pubkey if self._pubkey else PublicKey.from_secret(self._key).format())[:4] out._fingerprint = self.hash_func(
self._pubkey
if self._pubkey
else PublicKey.from_secret(self._key).format()
)[:4]
new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key) new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key)
out._chaincode = new_hash[32:] out._chaincode = new_hash[32:]
k.add(new_hash[:32], update=True) k.add(new_hash[:32], update=True)
@@ -85,27 +98,35 @@ class ExtKeyPair():
return out return out
def encode_v(self) -> bytes: def encode_v(self) -> bytes:
return self._depth.to_bytes(1, 'big') + \ return (
self._fingerprint + \ self._depth.to_bytes(1, "big")
self._child_no.to_bytes(4, 'big') + \ + self._fingerprint
self._chaincode + \ + self._child_no.to_bytes(4, "big")
b'\x00' + \ + self._chaincode
self._key + b"\x00"
+ self._key
)
def encode_p(self) -> bytes: def encode_p(self) -> bytes:
pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey pubkey = (
return self._depth.to_bytes(1, 'big') + \ PublicKey.from_secret(self._key).format()
self._fingerprint + \ if self._pubkey is None
self._child_no.to_bytes(4, 'big') + \ else self._pubkey
self._chaincode + \ )
pubkey return (
self._depth.to_bytes(1, "big")
+ self._fingerprint
+ self._child_no.to_bytes(4, "big")
+ self._chaincode
+ pubkey
)
def decode(self, data: bytes) -> None: def decode(self, data: bytes) -> None:
if len(data) != 74: if len(data) != 74:
raise ValueError('Unexpected extkey length') raise ValueError("Unexpected extkey length")
self._depth = data[0] self._depth = data[0]
self._fingerprint = data[1:5] self._fingerprint = data[1:5]
self._child_no = int.from_bytes(data[5:9], 'big') self._child_no = int.from_bytes(data[5:9], "big")
self._chaincode = data[9:41] self._chaincode = data[9:41]
if data[41] == 0: if data[41] == 0:

View File

@@ -7,25 +7,25 @@
def decode_compactsize(b: bytes, offset: int = 0) -> (int, int): def decode_compactsize(b: bytes, offset: int = 0) -> (int, int):
i = b[offset] i = b[offset]
if i < 0xfd: if i < 0xFD:
return i, 1 return i, 1
offset += 1 offset += 1
if i == 0xfd: if i == 0xFD:
return int.from_bytes(b[offset: offset + 2], 'little'), 3 return int.from_bytes(b[offset : offset + 2], "little"), 3
if i == 0xfe: if i == 0xFE:
return int.from_bytes(b[offset: offset + 4], 'little'), 5 return int.from_bytes(b[offset : offset + 4], "little"), 5
# 0xff # 0xff
return int.from_bytes(b[offset: offset + 8], 'little'), 9 return int.from_bytes(b[offset : offset + 8], "little"), 9
def encode_compactsize(i: int) -> bytes: def encode_compactsize(i: int) -> bytes:
if i < 0xfd: if i < 0xFD:
return bytes((i,)) return bytes((i,))
if i <= 0xffff: if i <= 0xFFFF:
return bytes((0xfd,)) + i.to_bytes(2, 'little') return bytes((0xFD,)) + i.to_bytes(2, "little")
if i <= 0xffffffff: if i <= 0xFFFFFFFF:
return bytes((0xfe,)) + i.to_bytes(4, 'little') return bytes((0xFE,)) + i.to_bytes(4, "little")
return bytes((0xff,)) + i.to_bytes(8, 'little') return bytes((0xFF,)) + i.to_bytes(8, "little")
def decode_varint(b: bytes, offset: int = 0) -> (int, int): def decode_varint(b: bytes, offset: int = 0) -> (int, int):
@@ -38,7 +38,7 @@ def decode_varint(b: bytes, offset: int = 0) -> (int, int):
if not c & 0x80: if not c & 0x80:
break break
if num_bytes > 8: if num_bytes > 8:
raise ValueError('Too many bytes') raise ValueError("Too many bytes")
return i, num_bytes return i, num_bytes
@@ -46,6 +46,6 @@ def encode_varint(i: int) -> bytes:
b = bytearray() b = bytearray()
while i > 0x7F: while i > 0x7F:
b += bytes(((i & 0x7F) | 0x80,)) b += bytes(((i & 0x7F) | 0x80,))
i = (i >> 7) i = i >> 7
b += bytes((i,)) b += bytes((i,))
return b return b

View File

@@ -15,9 +15,9 @@ from urllib.request import Request, urlopen
def is_private_ip_address(addr: str): def is_private_ip_address(addr: str):
if addr == 'localhost': if addr == "localhost":
return True return True
if addr.endswith('.local'): if addr.endswith(".local"):
return True return True
try: try:
return ipaddress.ip_address(addr).is_private return ipaddress.ip_address(addr).is_private
@@ -27,7 +27,7 @@ def is_private_ip_address(addr: str):
def make_reporthook(read_start: int, logger): def make_reporthook(read_start: int, logger):
read = read_start # Number of bytes read so far read = read_start # Number of bytes read so far
last_percent_str = '' last_percent_str = ""
time_last = time.time() time_last = time.time()
read_last = read_start read_last = read_start
display_last = time_last display_last = time_last
@@ -35,7 +35,7 @@ def make_reporthook(read_start: int, logger):
average_buffer = [-1] * 8 average_buffer = [-1] * 8
if read_start > 0: if read_start > 0:
logger.info(f'Attempting to resume from byte {read_start}') logger.info(f"Attempting to resume from byte {read_start}")
def reporthook(blocknum, blocksize, totalsize): def reporthook(blocknum, blocksize, totalsize):
nonlocal read, last_percent_str, time_last, read_last, display_last, read_start nonlocal read, last_percent_str, time_last, read_last, display_last, read_start
@@ -73,31 +73,31 @@ def make_reporthook(read_start: int, logger):
speed_str: str speed_str: str
if average_bits_per_second > 1000**3: if average_bits_per_second > 1000**3:
speed_str = '{:.2f} Gbps'.format(average_bits_per_second / (1000 ** 3)) speed_str = "{:.2f} Gbps".format(average_bits_per_second / (1000**3))
elif average_bits_per_second > 1000**2: elif average_bits_per_second > 1000**2:
speed_str = '{:.2f} Mbps'.format(average_bits_per_second / (1000 ** 2)) speed_str = "{:.2f} Mbps".format(average_bits_per_second / (1000**2))
else: else:
speed_str = '{:.2f} kbps'.format(average_bits_per_second / 1000) speed_str = "{:.2f} kbps".format(average_bits_per_second / 1000)
if totalsize > 0: if totalsize > 0:
percent_str = '%5.0f%%' % (read * 1e2 / use_size) percent_str = "%5.0f%%" % (read * 1e2 / use_size)
if percent_str != last_percent_str or time_now - display_last > 10: if percent_str != last_percent_str or time_now - display_last > 10:
logger.info(percent_str + ' ' + speed_str) logger.info(percent_str + " " + speed_str)
last_percent_str = percent_str last_percent_str = percent_str
display_last = time_now display_last = time_now
else: else:
logger.info(f'Read {read}, {speed_str}') logger.info(f"Read {read}, {speed_str}")
return reporthook return reporthook
def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0): def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
'''urlretrieve with resume """urlretrieve with resume"""
'''
url_type, path = _splittype(url) url_type, path = _splittype(url)
req = Request(url) req = Request(url)
if resume_from > 0: if resume_from > 0:
req.add_header('Range', f'bytes={resume_from}-') req.add_header("Range", f"bytes={resume_from}-")
with contextlib.closing(urlopen(req)) as fp: with contextlib.closing(urlopen(req)) as fp:
headers = fp.info() headers = fp.info()
@@ -106,7 +106,7 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
if url_type == "file" and not filename: if url_type == "file" and not filename:
return os.path.normpath(path), headers return os.path.normpath(path), headers
with open(filename, 'ab' if resume_from > 0 else 'wb') as tfp: with open(filename, "ab" if resume_from > 0 else "wb") as tfp:
result = filename, headers result = filename, headers
bs = 1024 * 8 bs = 1024 * 8
size = -1 size = -1
@@ -117,10 +117,10 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
size = int(headers["Content-Length"]) size = int(headers["Content-Length"])
if "Content-Range" in headers: if "Content-Range" in headers:
range_str = headers["Content-Range"] range_str = headers["Content-Range"]
offset = range_str.find('-') offset = range_str.find("-")
range_from = int(range_str[6:offset]) range_from = int(range_str[6:offset])
if resume_from != range_from: if resume_from != range_from:
raise ValueError('Download is not resuming from the expected byte') raise ValueError("Download is not resuming from the expected byte")
if reporthook: if reporthook:
reporthook(blocknum, bs, size) reporthook(blocknum, bs, size)
@@ -137,7 +137,7 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
if size >= 0 and read < size: if size >= 0 and read < size:
raise ContentTooShortError( raise ContentTooShortError(
"retrieval incomplete: got only %i out of %i bytes" "retrieval incomplete: got only %i out of %i bytes" % (read, size), result
% (read, size), result) )
return result return result

View File

@@ -16,7 +16,7 @@ def rfc2440_hash_password(password, salt=None):
salt = secrets.token_bytes(8) salt = secrets.token_bytes(8)
assert len(salt) == 8 assert len(salt) == 8
hashbytes = salt + password.encode('utf-8') hashbytes = salt + password.encode("utf-8")
len_hashbytes = len(hashbytes) len_hashbytes = len(hashbytes)
h = hashlib.sha1() h = hashlib.sha1()
@@ -27,5 +27,5 @@ def rfc2440_hash_password(password, salt=None):
continue continue
h.update(hashbytes[:count]) h.update(hashbytes[:count])
break break
rv = '16:' + salt.hex() + '60' + h.hexdigest() rv = "16:" + salt.hex() + "60" + h.hexdigest()
return rv.upper() return rv.upper()

View File

@@ -6,7 +6,13 @@
import struct import struct
import hashlib import hashlib
from basicswap.contrib.test_framework.script import OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4, CScriptInvalidError, CScriptTruncatedPushDataError from basicswap.contrib.test_framework.script import (
OP_PUSHDATA1,
OP_PUSHDATA2,
OP_PUSHDATA4,
CScriptInvalidError,
CScriptTruncatedPushDataError,
)
from basicswap.script import OpCodes from basicswap.script import OpCodes
@@ -17,15 +23,15 @@ def decodeScriptNum(script_bytes, o):
return ((num_len - OpCodes.OP_1) + 1, 1) return ((num_len - OpCodes.OP_1) + 1, 1)
if num_len > 4: if num_len > 4:
raise ValueError('Bad scriptnum length') # Max 4 bytes raise ValueError("Bad scriptnum length") # Max 4 bytes
if num_len + o >= len(script_bytes): if num_len + o >= len(script_bytes):
raise ValueError('Bad script length') raise ValueError("Bad script length")
o += 1 o += 1
for i in range(num_len): for i in range(num_len):
b = script_bytes[o + i] b = script_bytes[o + i]
# Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended # Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended
if i == num_len - 1 and b & 0x80: if i == num_len - 1 and b & 0x80:
b &= (~(0x80) & 0xFF) b &= ~(0x80) & 0xFF
v += int(b) << 8 * i v += int(b) << 8 * i
v *= -1 v *= -1
else: else:
@@ -41,28 +47,33 @@ def decodePushData(script_bytes, o):
i += 1 i += 1
if opcode < OP_PUSHDATA1: if opcode < OP_PUSHDATA1:
pushdata_type = 'PUSHDATA(%d)' % opcode pushdata_type = "PUSHDATA(%d)" % opcode
datasize = opcode datasize = opcode
elif opcode == OP_PUSHDATA1: elif opcode == OP_PUSHDATA1:
pushdata_type = 'PUSHDATA1' pushdata_type = "PUSHDATA1"
if i >= len(script_bytes): if i >= len(script_bytes):
raise CScriptInvalidError('PUSHDATA1: missing data length') raise CScriptInvalidError("PUSHDATA1: missing data length")
datasize = script_bytes[i] datasize = script_bytes[i]
i += 1 i += 1
elif opcode == OP_PUSHDATA2: elif opcode == OP_PUSHDATA2:
pushdata_type = 'PUSHDATA2' pushdata_type = "PUSHDATA2"
if i + 1 >= len(script_bytes): if i + 1 >= len(script_bytes):
raise CScriptInvalidError('PUSHDATA2: missing data length') raise CScriptInvalidError("PUSHDATA2: missing data length")
datasize = script_bytes[i] + (script_bytes[i + 1] << 8) datasize = script_bytes[i] + (script_bytes[i + 1] << 8)
i += 2 i += 2
elif opcode == OP_PUSHDATA4: elif opcode == OP_PUSHDATA4:
pushdata_type = 'PUSHDATA4' pushdata_type = "PUSHDATA4"
if i + 3 >= len(script_bytes): if i + 3 >= len(script_bytes):
raise CScriptInvalidError('PUSHDATA4: missing data length') raise CScriptInvalidError("PUSHDATA4: missing data length")
datasize = script_bytes[i] + (script_bytes[i + 1] << 8) + (script_bytes[i + 2] << 16) + (script_bytes[i + 3] << 24) datasize = (
script_bytes[i]
+ (script_bytes[i + 1] << 8)
+ (script_bytes[i + 2] << 16)
+ (script_bytes[i + 3] << 24)
)
i += 4 i += 4
else: else:
@@ -72,16 +83,14 @@ def decodePushData(script_bytes, o):
# Check for truncation # Check for truncation
if len(data) < datasize: if len(data) < datasize:
raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data) raise CScriptTruncatedPushDataError("%s: truncated data" % pushdata_type, data)
# return data and the number of bytes to skip forward # return data and the number of bytes to skip forward
return (data, i + datasize - o) return (data, i + datasize - o)
def getP2SHScriptForHash(p2sh): def getP2SHScriptForHash(p2sh):
return bytes((OpCodes.OP_HASH160, 0x14)) \ return bytes((OpCodes.OP_HASH160, 0x14)) + p2sh + bytes((OpCodes.OP_EQUAL,))
+ p2sh \
+ bytes((OpCodes.OP_EQUAL,))
def getP2WSH(script): def getP2WSH(script):
@@ -91,26 +100,26 @@ def getP2WSH(script):
def SerialiseNumCompact(v): def SerialiseNumCompact(v):
if v < 253: if v < 253:
return bytes((v,)) return bytes((v,))
if v <= 0xffff: # USHRT_MAX if v <= 0xFFFF: # USHRT_MAX
return struct.pack("<BH", 253, v) return struct.pack("<BH", 253, v)
if v <= 0xffffffff: # UINT_MAX if v <= 0xFFFFFFFF: # UINT_MAX
return struct.pack("<BI", 254, v) return struct.pack("<BI", 254, v)
if v <= 0xffffffffffffffff: # UINT_MAX if v <= 0xFFFFFFFFFFFFFFFF: # UINT_MAX
return struct.pack("<BQ", 255, v) return struct.pack("<BQ", 255, v)
raise ValueError('Value too large') raise ValueError("Value too large")
def getCompactSizeLen(v): def getCompactSizeLen(v):
# Compact Size # Compact Size
if v < 253: if v < 253:
return 1 return 1
if v <= 0xffff: # USHRT_MAX if v <= 0xFFFF: # USHRT_MAX
return 3 return 3
if v <= 0xffffffff: # UINT_MAX if v <= 0xFFFFFFFF: # UINT_MAX
return 5 return 5
if v <= 0xffffffffffffffff: # UINT_MAX if v <= 0xFFFFFFFFFFFFFFFF: # UINT_MAX
return 9 return 9
raise ValueError('Value too large') raise ValueError("Value too large")
def getWitnessElementLen(v): def getWitnessElementLen(v):

View File

@@ -7,7 +7,9 @@ from .contrib.MoneroPy.base58 import encode as xmr_b58encode
def cn_fast_hash(s): def cn_fast_hash(s):
k = Keccak.Keccak() k = Keccak.Keccak()
return k.Keccak((len(s) * 8, s.hex()), 1088, 512, 32 * 8, False).lower() # r = bitrate = 1088, c = capacity, n = output length in bits return k.Keccak(
(len(s) * 8, s.hex()), 1088, 512, 32 * 8, False
).lower() # r = bitrate = 1088, c = capacity, n = output length in bits
def encode_address(view_point: bytes, spend_point: bytes, version=18) -> str: def encode_address(view_point: bytes, spend_point: bytes, version=18) -> str:

View File

@@ -11,17 +11,27 @@ import stat
import subprocess import subprocess
import sys import sys
STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR STAT_0o775 = (
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP stat.S_IRUSR
| stat.S_IROTH | stat.S_IXOTH ) | stat.S_IWUSR
| stat.S_IXUSR
| stat.S_IRGRP
| stat.S_IWGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH
)
def main(): def main():
openssl_dir, openssl_cafile = os.path.split( openssl_dir, openssl_cafile = os.path.split(
ssl.get_default_verify_paths().openssl_cafile) ssl.get_default_verify_paths().openssl_cafile
)
print(" -- pip install --upgrade certifi") print(" -- pip install --upgrade certifi")
subprocess.check_call([sys.executable, subprocess.check_call(
"-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"]) [sys.executable, "-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"]
)
import certifi import certifi
@@ -39,5 +49,6 @@ def main():
os.chmod(openssl_cafile, STAT_0o775) os.chmod(openssl_cafile, STAT_0o775)
print(" -- update complete") print(" -- update complete")
if __name__ == '__main__':
if __name__ == "__main__":
main() main()

View File

@@ -9,91 +9,119 @@
Join docker compose fragments Join docker compose fragments
""" """
__version__ = '0.1' __version__ = "0.1"
import os import os
import argparse import argparse
def get_bkp_offset(filename, ext='yml'): def get_bkp_offset(filename, ext="yml"):
for i in range(1000): for i in range(1000):
if not os.path.exists(f'{filename}_bkp_{i}.{ext}'): if not os.path.exists(f"{filename}_bkp_{i}.{ext}"):
return i return i
raise ValueError(f'Unable to get backup filename for: {filename}.{ext}') raise ValueError(f"Unable to get backup filename for: {filename}.{ext}")
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-v', '--version', action='version', parser.add_argument(
version='%(prog)s {version}'.format(version=__version__)) "-v",
parser.add_argument('-c', '--coins', nargs='+', help='<Required> Select coins', required=True) "--version",
parser.add_argument('--withscript', dest='withscript', help='Add container to run createoffers.py (default=false)', required=False, action='store_true') action="version",
version="%(prog)s {version}".format(version=__version__),
)
parser.add_argument(
"-c", "--coins", nargs="+", help="<Required> Select coins", required=True
)
parser.add_argument(
"--withscript",
dest="withscript",
help="Add container to run createoffers.py (default=false)",
required=False,
action="store_true",
)
args = parser.parse_args() args = parser.parse_args()
with_coins = ['particl', ] with_coins = [
"particl",
]
for coin_name in args.coins: for coin_name in args.coins:
parsed_name = coin_name.lower() parsed_name = coin_name.lower()
if parsed_name not in with_coins: if parsed_name not in with_coins:
with_coins.append(parsed_name) with_coins.append(parsed_name)
print('Preparing docker compose files with coins:', ','.join(with_coins)) print("Preparing docker compose files with coins:", ",".join(with_coins))
num_docker_compose = get_bkp_offset('docker-compose') num_docker_compose = get_bkp_offset("docker-compose")
num_docker_compose_prepare = get_bkp_offset('docker-compose-prepare') num_docker_compose_prepare = get_bkp_offset("docker-compose-prepare")
if os.path.exists('docker-compose.yml'): if os.path.exists("docker-compose.yml"):
os.rename('docker-compose.yml', f'docker-compose_bkp_{num_docker_compose}.yml') os.rename("docker-compose.yml", f"docker-compose_bkp_{num_docker_compose}.yml")
if os.path.exists('docker-compose-prepare.yml'): if os.path.exists("docker-compose-prepare.yml"):
os.rename('docker-compose-prepare.yml', f'docker-compose-prepare_bkp_{num_docker_compose_prepare}.yml') os.rename(
"docker-compose-prepare.yml",
f"docker-compose-prepare_bkp_{num_docker_compose_prepare}.yml",
)
fragments_dir = 'compose-fragments' fragments_dir = "compose-fragments"
with open('docker-compose.yml', 'wb') as fp, open('docker-compose-prepare.yml', 'wb') as fpp: with (
with open(os.path.join(fragments_dir, '0_start.yml'), 'rb') as fp_in: open("docker-compose.yml", "wb") as fp,
open("docker-compose-prepare.yml", "wb") as fpp,
):
with open(os.path.join(fragments_dir, "0_start.yml"), "rb") as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
fpp.write(line) fpp.write(line)
for coin_name in with_coins: for coin_name in with_coins:
if coin_name == 'particl': if coin_name == "particl":
# Nothing to do # Nothing to do
continue continue
if coin_name in ('monero', 'wownero'): if coin_name in ("monero", "wownero"):
with open(os.path.join(fragments_dir, '1_{coin_name}-wallet.yml'), 'rb') as fp_in: with open(
os.path.join(fragments_dir, "1_{coin_name}-wallet.yml"), "rb"
) as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
fpp.write(line) fpp.write(line)
with open(os.path.join(fragments_dir, '8_{coin_name}-daemon.yml'), 'rb') as fp_in: with open(
os.path.join(fragments_dir, "8_{coin_name}-daemon.yml"), "rb"
) as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
continue continue
if coin_name == 'decred': if coin_name == "decred":
with open(os.path.join(fragments_dir, '1_decred-wallet.yml'), 'rb') as fp_in: with open(
os.path.join(fragments_dir, "1_decred-wallet.yml"), "rb"
) as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
fpp.write(line) fpp.write(line)
with open(os.path.join(fragments_dir, '8_decred-daemon.yml'), 'rb') as fp_in: with open(
os.path.join(fragments_dir, "8_decred-daemon.yml"), "rb"
) as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
continue continue
with open(os.path.join(fragments_dir, f'1_{coin_name}.yml'), 'rb') as fp_in: with open(os.path.join(fragments_dir, f"1_{coin_name}.yml"), "rb") as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
fpp.write(line) fpp.write(line)
with open(os.path.join(fragments_dir, '8_swapclient.yml'), 'rb') as fp_in: with open(os.path.join(fragments_dir, "8_swapclient.yml"), "rb") as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
if args.withscript: if args.withscript:
with open(os.path.join(fragments_dir, '8_script.yml'), 'rb') as fp_in: with open(os.path.join(fragments_dir, "8_script.yml"), "rb") as fp_in:
for line in fp_in: for line in fp_in:
fp.write(line) fp.write(line)
with open(os.path.join(fragments_dir, '9_swapprepare.yml'), 'rb') as fp_in: with open(os.path.join(fragments_dir, "9_swapprepare.yml"), "rb") as fp_in:
for line in fp_in: for line in fp_in:
fpp.write(line) fpp.write(line)
print('Done.') print("Done.")
if __name__ == '__main__': if __name__ == "__main__":
main() main()

File diff suppressed because it is too large Load Diff

View File

@@ -125,7 +125,7 @@ def checkForks(ro):
assert ro["softforks"]["csv"]["active"] assert ro["softforks"]["csv"]["active"]
assert ro["softforks"]["segwit"]["active"] assert ro["softforks"]["segwit"]["active"]
except Exception as e: except Exception as e:
logging.warning("Could not parse deployment info") logging.warning(f"Could not parse deployment info: {e}")
def stopDaemons(daemons): def stopDaemons(daemons):