Reformat with black.

This commit is contained in:
tecnovert 2024-11-15 18:52:19 +02:00
parent 6be9a14335
commit 732c87b013
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
66 changed files with 16755 additions and 9343 deletions

View file

@ -6,7 +6,7 @@ lint_task:
- pip install flake8 codespell
script:
- 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
test_task:

View file

@ -20,7 +20,7 @@ jobs:
pip install flake8 codespell
- name: Running flake8
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
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

View file

@ -35,7 +35,7 @@ def getaddrinfo_tor(*args):
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.fp = fp
self.fail_code = 0
@ -47,19 +47,19 @@ class BaseApp:
self.coin_clients = {}
self.coin_interfaces = {}
self.mxDB = threading.Lock()
self.debug = self.settings.get('debug', False)
self.debug = self.settings.get("debug", False)
self.delay_event = threading.Event()
self.chainstate_delay_event = threading.Event()
self._network = None
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.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_control_password = self.settings.get('tor_control_password', None)
self.tor_control_port = self.settings.get('tor_control_port', 9051)
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_port = self.settings.get("tor_proxy_port", 9050)
self.tor_control_password = self.settings.get("tor_control_password", None)
self.tor_control_port = self.settings.get("tor_control_port", 9051)
self.default_socket = socket.socket
self.default_socket_timeout = socket.getdefaulttimeout()
self.default_socket_getaddrinfo = socket.getaddrinfo
@ -77,10 +77,17 @@ class BaseApp:
# Remove any existing 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()
if self.log_name != 'BasicSwap':
stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S'))
if self.log_name != "BasicSwap":
stream_stdout.setFormatter(
logging.Formatter(
"%(asctime)s %(name)s %(levelname)s : %(message)s",
"%Y-%m-%d %H:%M:%S",
)
)
else:
stream_stdout.setFormatter(formatter)
stream_fp = logging.StreamHandler(self.fp)
@ -92,68 +99,91 @@ class BaseApp:
def getChainClientSettings(self, coin):
try:
return self.settings['chainclients'][chainparams[coin]['name']]
return self.settings["chainclients"][chainparams[coin]["name"]]
except Exception:
return {}
def setDaemonPID(self, name, pid) -> None:
if isinstance(name, Coins):
self.coin_clients[name]['pid'] = pid
self.coin_clients[name]["pid"] = pid
return
for c, v in self.coin_clients.items():
if v['name'] == name:
v['pid'] = pid
if v["name"] == name:
v["pid"] = pid
def getChainDatadirPath(self, coin) -> str:
datadir = self.coin_clients[coin]['datadir']
testnet_name = '' if self.chain == 'mainnet' else chainparams[coin][self.chain].get('name', self.chain)
datadir = self.coin_clients[coin]["datadir"]
testnet_name = (
""
if self.chain == "mainnet"
else chainparams[coin][self.chain].get("name", self.chain)
)
return os.path.join(datadir, testnet_name)
def getCoinIdFromName(self, coin_name: str):
for c, params in chainparams.items():
if coin_name.lower() == params['name'].lower():
if coin_name.lower() == params["name"].lower():
return c
raise ValueError('Unknown coin: {}'.format(coin_name))
raise ValueError("Unknown coin: {}".format(coin_name))
def callrpc(self, method, params=[], wallet=None):
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):
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):
bindir = self.coin_clients[coin_type]['bindir']
datadir = self.coin_clients[coin_type]['datadir']
cli_bin: str = chainparams[coin_type].get('cli_binname', chainparams[coin_type]['name'] + '-cli')
command_cli = os.path.join(bindir, cli_bin + ('.exe' if os.name == 'nt' else ''))
args = [command_cli, ]
if self.chain != 'mainnet':
args.append('-' + self.chain)
args.append('-datadir=' + datadir)
bindir = self.coin_clients[coin_type]["bindir"]
datadir = self.coin_clients[coin_type]["datadir"]
cli_bin: str = chainparams[coin_type].get(
"cli_binname", chainparams[coin_type]["name"] + "-cli"
)
command_cli = os.path.join(
bindir, cli_bin + (".exe" if os.name == "nt" else "")
)
args = [
command_cli,
]
if self.chain != "mainnet":
args.append("-" + self.chain)
args.append("-datadir=" + datadir)
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)
if len(out[1]) > 0:
raise ValueError('CLI error ' + str(out[1]))
return out[0].decode('utf-8').strip()
raise ValueError("CLI error " + str(out[1]))
return out[0].decode("utf-8").strip()
def is_transient_error(self, ex) -> bool:
if isinstance(ex, TemporaryError):
return True
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):
opener = urllib.request.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
opener.addheaders = [("User-agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
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.getaddrinfo = getaddrinfo_tor # Without this accessing .onion links would fail
socket.getaddrinfo = (
getaddrinfo_tor # Without this accessing .onion links would fail
)
socket.setdefaulttimeout(timeout)
@ -166,10 +196,16 @@ class BaseApp:
def readURL(self, url: str, timeout: int = 120, headers={}) -> bytes:
open_handler = None
if self.use_tor_proxy:
open_handler = SocksiPyHandler(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()
open_handler = SocksiPyHandler(
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:
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
opener.addheaders = [("User-agent", "Mozilla/5.0")]
request = urllib.request.Request(url, headers=headers)
return opener.open(request, timeout=timeout).read()
@ -180,7 +216,9 @@ class BaseApp:
def torControl(self, query):
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.send(command)
response = bytearray()
@ -192,23 +230,23 @@ class BaseApp:
c.close()
return response
except Exception as e:
self.log.error(f'torControl {e}')
self.log.error(f"torControl {e}")
return
def getTime(self) -> int:
return int(time.time()) + self.mock_time_offset
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
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
value: int = self.settings.get(name, default_v)
if value < min_v:
self.log.warning(f'Setting {name} to {min_v}')
self.log.warning(f"Setting {name} to {min_v}")
value = min_v
if value > max_v:
self.log.warning(f'Setting {name} to {max_v}')
self.log.warning(f"Setting {name} to {max_v}")
value = max_v
return value

File diff suppressed because it is too large Load diff

View file

@ -76,13 +76,13 @@ class OfferStates(IntEnum):
class BidStates(IntEnum):
BID_SENT = 1
BID_RECEIVING = 2 # Partially received
BID_RECEIVING = 2 # Partially received
BID_RECEIVED = 3
BID_RECEIVING_ACC = 4 # Partially received accept message
BID_ACCEPTED = 5 # BidAcceptMessage received/sent
SWAP_INITIATED = 6 # Initiate txn validated
SWAP_PARTICIPATING = 7 # Participate txn validated
SWAP_COMPLETED = 8 # All swap txns spent
BID_RECEIVING_ACC = 4 # Partially received accept message
BID_ACCEPTED = 5 # BidAcceptMessage received/sent
SWAP_INITIATED = 6 # Initiate txn validated
SWAP_PARTICIPATING = 7 # Participate txn validated
SWAP_COMPLETED = 8 # All swap txns spent
XMR_SWAP_SCRIPT_COIN_LOCKED = 9
XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = 10
XMR_SWAP_NOSCRIPT_COIN_LOCKED = 11
@ -96,13 +96,13 @@ class BidStates(IntEnum):
XMR_SWAP_FAILED = 19
SWAP_DELAYING = 20
SWAP_TIMEDOUT = 21
BID_ABANDONED = 22 # Bid will no longer be processed
BID_ERROR = 23 # An error occurred
BID_ABANDONED = 22 # Bid will no longer be processed
BID_ERROR = 23 # An error occurred
BID_STALLED_FOR_TEST = 24
BID_REJECTED = 25
BID_STATE_UNKNOWN = 26
XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS = 27 # XmrBidLockTxSigsMessage
XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX = 28 # XmrBidLockSpendTxMessage
XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS = 27 # XmrBidLockTxSigsMessage
XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX = 28 # XmrBidLockSpendTxMessage
BID_REQUEST_SENT = 29
BID_REQUEST_ACCEPTED = 30
BID_EXPIRED = 31
@ -231,12 +231,12 @@ class AutomationOverrideOptions(IntEnum):
def strAutomationOverrideOption(option):
if option == AutomationOverrideOptions.DEFAULT:
return 'Default'
return "Default"
if option == AutomationOverrideOptions.ALWAYS_ACCEPT:
return 'Always Accept'
return "Always Accept"
if option == AutomationOverrideOptions.NEVER_ACCEPT:
return 'Never Accept'
return 'Unknown'
return "Never Accept"
return "Unknown"
class VisibilityOverrideOptions(IntEnum):
@ -247,250 +247,253 @@ class VisibilityOverrideOptions(IntEnum):
def strVisibilityOverrideOption(option):
if option == VisibilityOverrideOptions.DEFAULT:
return 'Default'
return "Default"
if option == VisibilityOverrideOptions.HIDE:
return 'Hide'
return "Hide"
if option == VisibilityOverrideOptions.BLOCK:
return 'Block'
return 'Unknown'
return "Block"
return "Unknown"
def strOfferState(state):
if state == OfferStates.OFFER_SENT:
return 'Sent'
return "Sent"
if state == OfferStates.OFFER_RECEIVED:
return 'Received'
return "Received"
if state == OfferStates.OFFER_ABANDONED:
return 'Abandoned'
return "Abandoned"
if state == OfferStates.OFFER_EXPIRED:
return 'Expired'
return 'Unknown'
return "Expired"
return "Unknown"
def strBidState(state):
if state == BidStates.BID_SENT:
return 'Sent'
return "Sent"
if state == BidStates.BID_RECEIVING:
return 'Receiving'
return "Receiving"
if state == BidStates.BID_RECEIVING_ACC:
return 'Receiving accept'
return "Receiving accept"
if state == BidStates.BID_RECEIVED:
return 'Received'
return "Received"
if state == BidStates.BID_ACCEPTED:
return 'Accepted'
return "Accepted"
if state == BidStates.SWAP_INITIATED:
return 'Initiated'
return "Initiated"
if state == BidStates.SWAP_PARTICIPATING:
return 'Participating'
return "Participating"
if state == BidStates.SWAP_COMPLETED:
return 'Completed'
return "Completed"
if state == BidStates.SWAP_TIMEDOUT:
return 'Timed-out'
return "Timed-out"
if state == BidStates.BID_ABANDONED:
return 'Abandoned'
return "Abandoned"
if state == BidStates.BID_STALLED_FOR_TEST:
return 'Stalled (debug)'
return "Stalled (debug)"
if state == BidStates.BID_ERROR:
return 'Error'
return "Error"
if state == BidStates.BID_REJECTED:
return 'Rejected'
return "Rejected"
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:
return 'Script coin spend tx valid'
return "Script coin spend tx valid"
if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
return 'Scriptless coin locked'
return "Scriptless coin locked"
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:
return 'Script tx redeemed'
return "Script tx redeemed"
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:
return 'Scriptless tx redeemed'
return "Scriptless tx redeemed"
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
return 'Scriptless tx recovered'
return "Scriptless tx recovered"
if state == BidStates.XMR_SWAP_FAILED_REFUNDED:
return 'Failed, refunded'
return "Failed, refunded"
if state == BidStates.XMR_SWAP_FAILED_SWIPED:
return 'Failed, swiped'
return "Failed, swiped"
if state == BidStates.XMR_SWAP_FAILED:
return 'Failed'
return "Failed"
if state == BidStates.SWAP_DELAYING:
return 'Delaying'
return "Delaying"
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:
return 'Exchanged script lock spend tx msg'
return "Exchanged script lock spend tx msg"
if state == BidStates.BID_REQUEST_SENT:
return 'Request sent'
return "Request sent"
if state == BidStates.BID_REQUEST_ACCEPTED:
return 'Request accepted'
return "Request accepted"
if state == BidStates.BID_STATE_UNKNOWN:
return 'Unknown bid state'
return "Unknown bid state"
if state == BidStates.BID_EXPIRED:
return 'Expired'
return 'Unknown' + ' ' + str(state)
return "Expired"
return "Unknown" + " " + str(state)
def strTxState(state):
if state == TxStates.TX_NONE:
return 'None'
return "None"
if state == TxStates.TX_SENT:
return 'Sent'
return "Sent"
if state == TxStates.TX_CONFIRMED:
return 'Confirmed'
return "Confirmed"
if state == TxStates.TX_REDEEMED:
return 'Redeemed'
return "Redeemed"
if state == TxStates.TX_REFUNDED:
return 'Refunded'
return "Refunded"
if state == TxStates.TX_IN_MEMPOOL:
return 'In Mempool'
return "In Mempool"
if state == TxStates.TX_IN_CHAIN:
return 'In Chain'
return 'Unknown'
return "In Chain"
return "Unknown"
def strTxType(tx_type):
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:
return 'Chain A Lock Spend Tx'
return "Chain A Lock Spend Tx"
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:
return 'Chain A Lock Refund Spend Tx'
return "Chain A Lock Refund Spend Tx"
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:
return 'Chain B Lock Tx'
return "Chain B Lock Tx"
if tx_type == TxTypes.ITX_PRE_FUNDED:
return 'Funded mock initiate Tx'
return "Funded mock initiate Tx"
if tx_type == TxTypes.BCH_MERCY:
return 'BCH Mercy Tx'
return 'Unknown'
return "BCH Mercy Tx"
return "Unknown"
def strAddressType(addr_type):
if addr_type == AddressTypes.OFFER:
return 'Offer'
return "Offer"
if addr_type == AddressTypes.BID:
return 'Bid'
return "Bid"
if addr_type == AddressTypes.RECV_OFFER:
return 'Offer recv'
return "Offer recv"
if addr_type == AddressTypes.SEND_OFFER:
return 'Offer send'
return 'Unknown'
return "Offer send"
return "Unknown"
def getLockName(lock_type):
if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
return 'Sequence lock, blocks'
return "Sequence lock, blocks"
if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
return 'Sequence lock, time'
return "Sequence lock, time"
if lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
return 'blocks'
return "blocks"
if lock_type == TxLockTypes.ABS_LOCK_TIME:
return 'time'
return "time"
def describeEventEntry(event_type, event_msg):
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:
return 'Lock tx A published'
return "Lock tx A 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:
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:
return 'Lock tx A seen in chain'
return "Lock tx A seen in chain"
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:
return 'Lock tx B seen in chain'
return "Lock tx B seen in chain"
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
return 'Lock tx B confirmed in chain'
return "Lock tx B confirmed in chain"
if event_type == EventLogTypes.LOCK_TX_B_IN_MEMPOOL:
return 'Lock tx B seen in mempool'
return "Lock tx B seen in mempool"
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:
return 'Failed to publish lock tx B refund'
return "Failed to publish lock tx B refund"
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:
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:
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:
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:
return 'Lock tx B refund tx published'
return "Lock tx B refund 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:
return 'Lock tx B spend tx published'
return "Lock tx B spend tx published"
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:
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:
return 'Warning: ' + event_msg
return "Warning: " + event_msg
if event_type == EventLogTypes.ERROR:
return 'Error: ' + event_msg
return "Error: " + event_msg
if event_type == EventLogTypes.AUTOMATION_CONSTRAINT:
return 'Failed auto accepting'
return "Failed auto accepting"
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
return 'Auto accepting'
return "Auto accepting"
if event_type == EventLogTypes.ITX_PUBLISHED:
return 'Initiate tx published'
return "Initiate tx 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:
return 'Initiate tx refund tx published'
return "Initiate tx refund tx published"
if event_type == EventLogTypes.PTX_PUBLISHED:
return 'Participate tx published'
return "Participate tx 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:
return 'Participate tx refund tx published'
return "Participate tx refund tx published"
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:
return 'Lock tx B mercy tx published'
return "Lock tx B mercy tx published"
def getVoutByAddress(txjs, p2sh):
for o in txjs['vout']:
for o in txjs["vout"]:
try:
if 'address' in o['scriptPubKey'] and o['scriptPubKey']['address'] == p2sh:
return o['n']
if p2sh in o['scriptPubKey']['addresses']:
return o['n']
if "address" in o["scriptPubKey"] and o["scriptPubKey"]["address"] == p2sh:
return o["n"]
if p2sh in o["scriptPubKey"]["addresses"]:
return o["n"]
except Exception:
pass
raise ValueError('Address output not found in txn')
raise ValueError("Address output not found in txn")
def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int:
for o in txjs['vout']:
for o in txjs["vout"]:
try:
if scriptPubKey_hex == o['scriptPubKey']['hex']:
return o['n']
if scriptPubKey_hex == o["scriptPubKey"]["hex"]:
return o["n"]
except Exception:
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'):
return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:])
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type="pubkey_address"):
return encodeAddress(
bytes((chainparams[coin_type][chain_name][addr_type],))
+ decodeAddress(addr)[1:]
)
def getOfferProofOfFundsHash(offer_msg, offer_addr):
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
h = hashlib.sha256()
h.update(offer_addr.encode('utf-8'))
h.update(offer_addr.encode("utf-8"))
offer_bytes = offer_msg.to_bytes()
h.update(offer_bytes)
return h.digest()
@ -500,33 +503,40 @@ def getLastBidState(packed_states):
num_states = len(packed_states) // 12
if num_states < 2:
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:
num_states = len(packed_states) // 12
if num_states < 2:
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:
return BidStates.BID_STATE_UNKNOWN
def strSwapType(swap_type):
if swap_type == SwapTypes.SELLER_FIRST:
return 'seller_first'
return "seller_first"
if swap_type == SwapTypes.XMR_SWAP:
return 'xmr_swap'
return "xmr_swap"
return None
def strSwapDesc(swap_type):
if swap_type == SwapTypes.SELLER_FIRST:
return 'Secret Hash'
return "Secret Hash"
if swap_type == SwapTypes.XMR_SWAP:
return 'Adaptor Sig'
return "Adaptor Sig"
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):

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -32,7 +33,7 @@ swap_client = None
class Daemon:
__slots__ = ('handle', 'files')
__slots__ = ("handle", "files")
def __init__(self, handle, files):
self.handle = handle
@ -41,14 +42,14 @@ class Daemon:
def is_known_coin(coin_name: str) -> bool:
for k, v in chainparams.items():
if coin_name == v['name']:
if coin_name == v["name"]:
return True
return False
def signal_handler(sig, frame):
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:
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
# 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):
config_to_add = ['blockfilterindex=0', 'peerblockfilters=0']
config_to_add = ["blockfilterindex=0", "peerblockfilters=0"]
with open(ltc_conf_path) as fp:
for line in fp:
line = line.strip()
@ -69,23 +70,30 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
config_to_add.remove(line)
if len(config_to_add) > 0:
logger.info('Rewriting litecoin.conf')
shutil.copyfile(ltc_conf_path, ltc_conf_path + '.last')
with open(ltc_conf_path, 'a') as fp:
logger.info("Rewriting litecoin.conf")
shutil.copyfile(ltc_conf_path, ltc_conf_path + ".last")
with open(ltc_conf_path, "a") as fp:
for line in config_to_add:
fp.write(line + '\n')
fp.write(line + "\n")
args = [daemon_bin, ]
add_datadir: bool = extra_config.get('add_datadir', True)
args = [
daemon_bin,
]
add_datadir: bool = extra_config.get("add_datadir", True)
if add_datadir:
args.append('-datadir=' + datadir_path)
args.append("-datadir=" + datadir_path)
args += opts
logger.info('Starting node {}'.format(daemon_bin))
logger.debug('Arguments {}'.format(' '.join(args)))
logger.info("Starting node {}".format(daemon_bin))
logger.debug("Arguments {}".format(" ".join(args)))
opened_files = []
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')
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",
)
opened_files.append(stdout_dest)
stderr_dest = stdout_dest
else:
@ -93,62 +101,113 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
stderr_dest = subprocess.PIPE
shell: bool = False
if extra_config.get('use_shell', False):
args = ' '.join(args)
if extra_config.get("use_shell", False):
args = " ".join(args)
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=[]):
daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
datadir_path = os.path.expanduser(node_dir)
config_filename = 'wownerod.conf' if daemon_bin.startswith('wow') else 'monerod.conf'
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)))
config_filename = (
"wownerod.conf" if daemon_bin.startswith("wow") else "monerod.conf"
)
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)
file_stdout = open(os.path.join(datadir_path, 'core_stdout.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])
file_stdout = open(os.path.join(datadir_path, "core_stdout.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],
)
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
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
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)
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)
if os.path.exists(config_path):
args += ['--config-file=' + config_path]
args += ["--config-file=" + config_path]
with open(config_path) as fp:
for line in fp:
if any(line.startswith(config_line) for config_line in config_to_remove):
logger.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
if any(
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
args += opts
if needs_rewrite:
logger.info('Rewriting wallet config')
shutil.copyfile(config_path, config_path + '.last')
with open(config_path + '.last') as fp_from, open(config_path, 'w') as fp_to:
logger.info("Rewriting wallet config")
shutil.copyfile(config_path, config_path + ".last")
with open(config_path + ".last") as fp_from, open(config_path, "w") as fp_to:
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)
logger.info('Starting wallet daemon {}'.format(wallet_bin))
logger.debug('Arguments {}'.format(' '.join(args)))
logger.info("Starting wallet daemon {}".format(wallet_bin))
logger.debug("Arguments {}".format(" ".join(args)))
# 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_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])
wallet_stdout = open(os.path.join(data_dir, "wallet_stdout.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],
)
def ws_new_client(client, server):
@ -165,25 +224,29 @@ def ws_client_left(client, server):
def ws_message_received(client, server, message):
if len(message) > 200:
message = message[:200] + '..'
message = message[:200] + ".."
if swap_client:
swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
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:
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):
extra_args = []
if 'config_filename' in coin_settings:
extra_args.append('--conf=' + coin_settings['config_filename'])
if 'port' in coin_settings:
extra_args.append('--port=' + str(int(coin_settings['port'])))
if "config_filename" in coin_settings:
extra_args.append("--conf=" + coin_settings["config_filename"])
if "port" in coin_settings:
extra_args.append("--port=" + str(int(coin_settings["port"])))
return extra_args
@ -193,17 +256,21 @@ def runClient(fp, data_dir, chain, start_only_coins):
pids = []
threads = []
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 'decred' in start_only_coins:
if os.getenv("WALLET_ENCRYPTION_PWD", "") != "":
if "decred" in start_only_coins:
# 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:
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):
raise ValueError('Settings file not found: ' + str(settings_path))
raise ValueError("Settings file not found: " + str(settings_path))
with open(settings_path) as fs:
settings = json.load(fs)
@ -215,7 +282,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
with open(pids_path) as fd:
for ln in fd:
# 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
swap_client.stopDaemons()
@ -224,155 +291,227 @@ def runClient(fp, data_dir, chain, start_only_coins):
settings = swap_client.settings
try:
# Try start daemons
for c, v in settings['chainclients'].items():
for c, v in settings["chainclients"].items():
if len(start_only_coins) > 0 and c not in start_only_coins:
continue
try:
coin_id = swap_client.getCoinIdFromName(c)
display_name = getCoinName(coin_id)
except Exception as e:
logger.warning('Not starting unknown coin: {}'.format(c))
except Exception as e: # noqa: F841
logger.warning("Not starting unknown coin: {}".format(c))
continue
if c in ('monero', 'wownero'):
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
filename: str = getCoreBinName(coin_id, v, c + 'd')
if c in ("monero", "wownero"):
if v["manage_daemon"] is True:
swap_client.log.info(f"Starting {display_name} daemon")
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
swap_client.log.info('Started {} {}'.format(filename, pid))
swap_client.log.info("Started {} {}".format(filename, pid))
if v['manage_wallet_daemon'] is True:
swap_client.log.info(f'Starting {display_name} wallet daemon')
daemon_addr = '{}:{}'.format(v['rpchost'], v['rpcport'])
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(coin_id, v['rpchost'])
opts = ['--daemon-address', daemon_addr, ]
if v["manage_wallet_daemon"] is True:
swap_client.log.info(f"Starting {display_name} wallet daemon")
daemon_addr = "{}:{}".format(v["rpchost"], v["rpcport"])
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
coin_id, v["rpchost"]
)
opts = [
"--daemon-address",
daemon_addr,
]
proxy_log_str = ''
proxy_host, proxy_port = swap_client.getXMRWalletProxy(coin_id, v['rpchost'])
proxy_log_str = ""
proxy_host, proxy_port = swap_client.getXMRWalletProxy(
coin_id, v["rpchost"]
)
if proxy_host:
proxy_log_str = ' through proxy'
opts += ['--proxy', f'{proxy_host}:{proxy_port}', '--daemon-ssl-allow-any-cert', ]
proxy_log_str = " through proxy"
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_rpcpass = v.get('rpcpassword', '')
if daemon_rpcuser != '':
opts.append('--daemon-login')
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
daemon_rpcuser = v.get("rpcuser", "")
daemon_rpcpass = v.get("rpcpassword", "")
if daemon_rpcuser != "":
opts.append("--daemon-login")
opts.append(daemon_rpcuser + ":" + daemon_rpcpass)
opts.append('--trusted-daemon' if trusted_daemon else '--untrusted-daemon')
filename: str = getWalletBinName(coin_id, v, c + '-wallet-rpc')
opts.append(
"--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
swap_client.log.info('Started {} {}'.format(filename, pid))
swap_client.log.info("Started {} {}".format(filename, pid))
continue # /monero
if c == 'decred':
appdata = v['datadir']
extra_opts = [f'--appdata="{appdata}"', ]
use_shell: bool = True if os.name == 'nt' else False
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
filename: str = getCoreBinName(coin_id, v, 'dcrd')
if c == "decred":
appdata = v["datadir"]
extra_opts = [
f'--appdata="{appdata}"',
]
use_shell: bool = True if os.name == "nt" else False
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}
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=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
swap_client.log.info('Started {} {}'.format(filename, pid))
swap_client.log.info("Started {} {}".format(filename, pid))
if v['manage_wallet_daemon'] is True:
swap_client.log.info(f'Starting {display_name} wallet daemon')
filename: str = getWalletBinName(coin_id, v, 'dcrwallet')
if v["manage_wallet_daemon"] is True:
swap_client.log.info(f"Starting {display_name} wallet daemon")
filename: str = getWalletBinName(coin_id, v, "dcrwallet")
wallet_pwd = v['wallet_pwd']
if wallet_pwd == '':
wallet_pwd = v["wallet_pwd"]
if wallet_pwd == "":
# Only set when in startonlycoin mode
wallet_pwd = os.getenv('WALLET_ENCRYPTION_PWD', '')
if wallet_pwd != '':
wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "")
if 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}
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=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
swap_client.log.info('Started {} {}'.format(filename, pid))
swap_client.log.info("Started {} {}".format(filename, pid))
continue # /decred
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
if v["manage_daemon"] is True:
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)
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
pids.append((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:
with open(pids_path, 'w') as fd:
with open(pids_path, "w") as fd:
for p in pids:
fd.write('{}:{}\n'.format(*p))
fd.write("{}:{}\n".format(*p))
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
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):
pass
else:
swap_client.start()
if 'htmlhost' in settings:
swap_client.log.info('Starting http server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport']))
allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS
thread_http = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
if "htmlhost" in settings:
swap_client.log.info(
"Starting http server at http://%s:%d."
% (settings["htmlhost"], settings["htmlport"])
)
allow_cors = (
settings["allowcors"]
if "allowcors" in settings
else cfg.DEFAULT_ALLOW_CORS
)
thread_http = HttpThread(
fp,
settings["htmlhost"],
settings["htmlport"],
allow_cors,
swap_client,
)
threads.append(thread_http)
thread_http.start()
if 'wshost' in settings:
ws_url = 'ws://{}:{}'.format(settings['wshost'], settings['wsport'])
swap_client.log.info(f'Starting ws server at {ws_url}.')
if "wshost" in settings:
ws_url = "ws://{}:{}".format(settings["wshost"], settings["wsport"])
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.client_port = settings.get('wsclientport', settings['wsport'])
swap_client.ws_server = WebsocketServer(
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_client_left(ws_client_left)
swap_client.ws_server.set_fn_message_received(ws_message_received)
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):
swap_client.update()
except Exception as ex:
except Exception as e: # noqa: F841
traceback.print_exc()
if swap_client.ws_server:
try:
swap_client.log.info('Stopping websocket server.')
swap_client.log.info("Stopping websocket server.")
swap_client.ws_server.shutdown_gracefully()
except Exception as ex:
except Exception as e: # noqa: F841
traceback.print_exc()
swap_client.finalise()
swap_client.log.info('Stopping HTTP threads.')
swap_client.log.info("Stopping HTTP threads.")
for t in threads:
try:
t.stop()
t.join()
except Exception as ex:
except Exception as e: # noqa: F841
traceback.print_exc()
closed_pids = []
for d in daemons:
swap_client.log.info('Interrupting {}'.format(d.handle.pid))
swap_client.log.info("Interrupting {}".format(d.handle.pid))
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:
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:
try:
d.handle.wait(timeout=120)
@ -381,96 +520,106 @@ def runClient(fp, data_dir, chain, start_only_coins):
fp.close()
closed_pids.append(d.handle.pid)
except Exception as ex:
swap_client.log.error('Error: {}'.format(ex))
swap_client.log.error("Error: {}".format(ex))
if os.path.exists(pids_path):
with open(pids_path) as fd:
lines = fd.read().split('\n')
still_running = ''
lines = fd.read().split("\n")
still_running = ""
for ln in lines:
try:
if not int(ln.split(':')[1]) in closed_pids:
still_running += ln + '\n'
if int(ln.split(":")[1]) not in closed_pids:
still_running += ln + "\n"
except Exception:
pass
with open(pids_path, 'w') as fd:
with open(pids_path, "w") as fd:
fd.write(still_running)
def printVersion():
logger.info('Basicswap version: %s', __version__)
logger.info("Basicswap version: %s", __version__)
def printHelp():
print('Usage: basicswap-run ')
print('\n--help, -h Print help.')
print('--version, -v Print version.')
print('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR))
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.')
print("Usage: basicswap-run ")
print("\n--help, -h Print help.")
print("--version, -v Print version.")
print(
"--datadir=PATH Path to basicswap data directory, default:{}.".format(
cfg.BASICSWAP_DATADIR
)
)
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():
data_dir = None
chain = 'mainnet'
chain = "mainnet"
start_only_coins = set()
for v in sys.argv[1:]:
if len(v) < 2 or v[0] != '-':
logger.warning('Unknown argument %s', v)
if len(v) < 2 or v[0] != "-":
logger.warning("Unknown argument %s", v)
continue
s = v.split('=')
s = v.split("=")
name = s[0].strip()
for i in range(2):
if name[0] == '-':
if name[0] == "-":
name = name[1:]
if name == 'v' or name == 'version':
if name == "v" or name == "version":
printVersion()
return 0
if name == 'h' or name == 'help':
if name == "h" or name == "help":
printHelp()
return 0
if name in ('mainnet', 'testnet', 'regtest'):
if name in ("mainnet", "testnet", "regtest"):
chain = name
continue
if len(s) == 2:
if name == 'datadir':
if name == "datadir":
data_dir = os.path.expanduser(s[1])
continue
if name == 'startonlycoin':
for coin in [s.lower() for s in s[1].split(',')]:
if name == "startonlycoin":
for coin in [s.lower() for s in s[1].split(",")]:
if is_known_coin(coin) is False:
raise ValueError(f'Unknown coin: {coin}')
raise ValueError(f"Unknown coin: {coin}")
start_only_coins.add(coin)
continue
logger.warning('Unknown argument %s', v)
logger.warning("Unknown argument %s", v)
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.')
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."
)
if data_dir is None:
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
logger.info('Using datadir: %s', data_dir)
logger.info('Chain: %s', chain)
logger.info("Using datadir: %s", data_dir)
logger.info("Chain: %s", chain)
if not os.path.exists(data_dir):
os.makedirs(data_dir)
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
logger.info(os.path.basename(sys.argv[0]) + ', version: ' + __version__ + '\n\n')
with open(os.path.join(data_dir, "basicswap.log"), "a") as fp:
logger.info(
os.path.basename(sys.argv[0]) + ", version: " + __version__ + "\n\n"
)
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
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

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

View file

@ -6,35 +6,47 @@
import os
CONFIG_FILENAME = 'basicswap.json'
BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', os.path.join('~', '.basicswap'))
CONFIG_FILENAME = "basicswap.json"
BASICSWAP_DATADIR = os.getenv("BASICSWAP_DATADIR", os.path.join("~", ".basicswap"))
DEFAULT_ALLOW_CORS = False
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')))
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"))
)
bin_suffix = ('.exe' if os.name == 'nt' else '')
PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'particl')))
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)
bin_suffix = ".exe" if os.name == "nt" else ""
PARTICL_BINDIR = os.path.expanduser(
os.getenv("PARTICL_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "particl"))
)
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')))
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)
BITCOIN_BINDIR = os.path.expanduser(
os.getenv("BITCOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "bitcoin"))
)
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')))
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)
LITECOIN_BINDIR = os.path.expanduser(
os.getenv("LITECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "litecoin"))
)
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')))
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)
NAMECOIN_BINDIR = os.path.expanduser(
os.getenv("NAMECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "namecoin"))
)
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')))
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
XMR_BINDIR = os.path.expanduser(
os.getenv("XMR_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "monero"))
)
XMRD = os.getenv("XMRD", "monerod" + bin_suffix)
XMR_WALLET_RPC = os.getenv("XMR_WALLET_RPC", "monero-wallet-rpc" + bin_suffix)
# 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):
if state == Concepts.OFFER:
return 'Offer'
return "Offer"
if state == Concepts.BID:
return 'Bid'
return "Bid"
if state == Concepts.NETWORK_MESSAGE:
return 'Network Message'
return 'Unknown'
return "Network Message"
return "Unknown"
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):
__tablename__ = 'kv_int'
__tablename__ = "kv_int"
key = sa.Column(sa.String, primary_key=True)
value = sa.Column(sa.Integer)
class DBKVString(Base):
__tablename__ = 'kv_string'
__tablename__ = "kv_string"
key = sa.Column(sa.String, primary_key=True)
value = sa.Column(sa.String)
class Offer(Base):
__tablename__ = 'offers'
__tablename__ = "offers"
offer_id = sa.Column(sa.LargeBinary, primary_key=True)
active_ind = sa.Column(sa.Integer)
@ -89,7 +89,9 @@ class Offer(Base):
# Local fields
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)
bid_reversed = sa.Column(sa.Boolean)
@ -106,10 +108,10 @@ class Offer(Base):
class Bid(Base):
__tablename__ = 'bids'
__tablename__ = "bids"
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)
protocol_version = sa.Column(sa.Integer)
@ -121,13 +123,17 @@ class Bid(Base):
bid_addr = sa.Column(sa.String)
proof_address = sa.Column(sa.String)
proof_utxos = sa.Column(sa.LargeBinary)
withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO
withdraw_to_addr = sa.Column(
sa.String
) # Address to spend lock tx to - address from wallet if empty TODO
recovered_secret = sa.Column(sa.LargeBinary)
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
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)
rate = sa.Column(sa.BigInteger)
@ -149,8 +155,12 @@ class Bid(Base):
debug_ind = sa.Column(sa.Integer)
security_token = sa.Column(sa.LargeBinary)
chain_a_height_start = sa.Column(sa.Integer) # Height of script chain before the swap
chain_b_height_start = sa.Column(sa.Integer) # Height of scriptless chain before the swap
chain_a_height_start = sa.Column(
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)
@ -199,12 +209,12 @@ class Bid(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
__table_args__ = (
sa.PrimaryKeyConstraint('bid_id', 'tx_type'),
sa.PrimaryKeyConstraint("bid_id", "tx_type"),
{},
)
@ -240,7 +250,7 @@ class SwapTx(Base):
class PrefundedTx(Base):
__tablename__ = 'prefunded_transactions'
__tablename__ = "prefunded_transactions"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -253,7 +263,7 @@ class PrefundedTx(Base):
class PooledAddress(Base):
__tablename__ = 'addresspool'
__tablename__ = "addresspool"
addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
coin_type = sa.Column(sa.Integer)
@ -263,13 +273,13 @@ class PooledAddress(Base):
class SentOffer(Base):
__tablename__ = 'sentoffers'
__tablename__ = "sentoffers"
offer_id = sa.Column(sa.LargeBinary, primary_key=True)
class SmsgAddress(Base):
__tablename__ = 'smsgaddresses'
__tablename__ = "smsgaddresses"
addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -281,7 +291,7 @@ class SmsgAddress(Base):
class Action(Base):
__tablename__ = 'actions'
__tablename__ = "actions"
action_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -293,7 +303,7 @@ class Action(Base):
class EventLog(Base):
__tablename__ = 'eventlog'
__tablename__ = "eventlog"
event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -303,32 +313,38 @@ class EventLog(Base):
event_type = sa.Column(sa.Integer)
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):
__tablename__ = 'xmr_offers'
__tablename__ = "xmr_offers"
# TODO: Merge to Offer
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
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_2 = sa.Column(sa.Integer) # Delay before the follower can spend from the chain a lock refund tx
lock_time_1 = sa.Column(
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):
__tablename__ = 'xmr_swaps'
__tablename__ = "xmr_swaps"
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)
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)
pkasl = sa.Column(sa.LargeBinary)
@ -349,9 +365,9 @@ class XmrSwap(Base):
kbsl_dleag = sa.Column(sa.LargeBinary)
kbsf_dleag = sa.Column(sa.LargeBinary)
vkbv = sa.Column(sa.LargeBinary) # chain b view private key
pkbv = sa.Column(sa.LargeBinary) # chain b view public key
pkbs = sa.Column(sa.LargeBinary) # chain b spend public key
vkbv = sa.Column(sa.LargeBinary) # chain b view private key
pkbv = sa.Column(sa.LargeBinary) # chain b view public key
pkbs = sa.Column(sa.LargeBinary) # chain b spend public key
a_lock_tx = sa.Column(sa.LargeBinary)
a_lock_tx_script = sa.Column(sa.LargeBinary)
@ -376,13 +392,15 @@ class XmrSwap(Base):
al_lock_spend_tx_esig = 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)
class XmrSplitData(Base):
__tablename__ = 'xmr_split_data'
__tablename__ = "xmr_split_data"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
addr_from = sa.Column(sa.String)
@ -393,11 +411,13 @@ class XmrSplitData(Base):
dleag = sa.Column(sa.LargeBinary)
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):
__tablename__ = 'revoked_messages'
__tablename__ = "revoked_messages"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -407,7 +427,7 @@ class RevokedMessage(Base):
class Wallets(Base):
__tablename__ = 'wallets'
__tablename__ = "wallets"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -419,7 +439,7 @@ class Wallets(Base):
class KnownIdentity(Base):
__tablename__ = 'knownidentities'
__tablename__ = "knownidentities"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -441,7 +461,7 @@ class KnownIdentity(Base):
class AutomationStrategy(Base):
__tablename__ = 'automationstrategies'
__tablename__ = "automationstrategies"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -457,7 +477,7 @@ class AutomationStrategy(Base):
class AutomationLink(Base):
__tablename__ = 'automationlinks'
__tablename__ = "automationlinks"
# Contains per order/bid options
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
@ -474,11 +494,11 @@ class AutomationLink(Base):
note = sa.Column(sa.String)
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):
__tablename__ = 'history'
__tablename__ = "history"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
concept_type = sa.Column(sa.Integer)
@ -489,7 +509,7 @@ class History(Base):
class BidState(Base):
__tablename__ = 'bidstates'
__tablename__ = "bidstates"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -505,7 +525,7 @@ class BidState(Base):
class Notification(Base):
__tablename__ = 'notifications'
__tablename__ = "notifications"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -515,7 +535,7 @@ class Notification(Base):
class MessageLink(Base):
__tablename__ = 'message_links'
__tablename__ = "message_links"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
@ -531,7 +551,7 @@ class MessageLink(Base):
class CheckedBlock(Base):
__tablename__ = 'checkedblocks'
__tablename__ = "checkedblocks"
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
created_at = sa.Column(sa.BigInteger)

View file

@ -14,7 +14,8 @@ from .db import (
Concepts,
AutomationStrategy,
CURRENT_DB_VERSION,
CURRENT_DB_DATA_VERSION)
CURRENT_DB_DATA_VERSION,
)
from .basicswap_util import (
BidStates,
@ -30,7 +31,11 @@ def upgradeDatabaseData(self, data_version):
if data_version >= CURRENT_DB_DATA_VERSION:
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:
try:
session = scoped_session(self.session_factory)
@ -38,65 +43,100 @@ def upgradeDatabaseData(self, data_version):
now = int(time.time())
if data_version < 1:
session.add(AutomationStrategy(
active_ind=1,
label='Accept All',
type_ind=Concepts.OFFER,
data=json.dumps({'exact_rate_only': True,
'max_concurrent_bids': 5}).encode('utf-8'),
only_known_identities=False,
created_at=now))
session.add(AutomationStrategy(
active_ind=1,
label='Accept Known',
type_ind=Concepts.OFFER,
data=json.dumps({'exact_rate_only': True,
'max_concurrent_bids': 5}).encode('utf-8'),
only_known_identities=True,
note='Accept bids from identities with previously successful swaps only',
created_at=now))
session.add(
AutomationStrategy(
active_ind=1,
label="Accept All",
type_ind=Concepts.OFFER,
data=json.dumps(
{"exact_rate_only": True, "max_concurrent_bids": 5}
).encode("utf-8"),
only_known_identities=False,
created_at=now,
)
)
session.add(
AutomationStrategy(
active_ind=1,
label="Accept Known",
type_ind=Concepts.OFFER,
data=json.dumps(
{"exact_rate_only": True, "max_concurrent_bids": 5}
).encode("utf-8"),
only_known_identities=True,
note="Accept bids from identities with previously successful swaps only",
created_at=now,
)
)
for state in BidStates:
session.add(BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
in_error=isErrorBidState(state),
swap_failed=isFailingBidState(state),
swap_ended=isFinalBidState(state),
label=strBidState(state),
created_at=now))
session.add(
BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
in_error=isErrorBidState(state),
swap_failed=isFailingBidState(state),
swap_ended=isFinalBidState(state),
label=strBidState(state),
created_at=now,
)
)
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):
session.add(BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
label=strBidState(state),
created_at=now))
for state in (
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS,
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
):
session.add(
BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
label=strBidState(state),
created_at=now,
)
)
if data_version > 0 and data_version < 3:
for state in BidStates:
in_error = isErrorBidState(state)
swap_failed = isFailingBidState(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:
for state in (BidStates.BID_REQUEST_SENT, BidStates.BID_REQUEST_ACCEPTED):
session.add(BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
in_error=isErrorBidState(state),
swap_failed=isFailingBidState(state),
swap_ended=isFinalBidState(state),
label=strBidState(state),
created_at=now))
for state in (
BidStates.BID_REQUEST_SENT,
BidStates.BID_REQUEST_ACCEPTED,
):
session.add(
BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
in_error=isErrorBidState(state),
swap_failed=isFailingBidState(state),
swap_ended=isFinalBidState(state),
label=strBidState(state),
created_at=now,
)
)
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()
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:
session.close()
session.remove()
@ -106,23 +146,31 @@ def upgradeDatabase(self, db_version):
if db_version >= CURRENT_DB_VERSION:
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:
session = scoped_session(self.session_factory)
current_version = db_version
if current_version == 6:
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 bids ADD COLUMN security_token BLOB"))
session.execute(text("ALTER TABLE offers ADD COLUMN security_token BLOB"))
db_version += 1
elif current_version == 7:
session.execute(text('ALTER TABLE transactions ADD COLUMN block_hash BLOB'))
session.execute(text('ALTER TABLE transactions ADD COLUMN block_height INTEGER'))
session.execute(text('ALTER TABLE transactions ADD COLUMN block_time INTEGER'))
session.execute(text("ALTER TABLE transactions ADD COLUMN block_hash BLOB"))
session.execute(
text("ALTER TABLE transactions ADD COLUMN block_height INTEGER")
)
session.execute(
text("ALTER TABLE transactions ADD COLUMN block_time INTEGER")
)
db_version += 1
elif current_version == 8:
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE wallets (
record_id INTEGER NOT NULL,
coin_id INTEGER,
@ -130,30 +178,48 @@ def upgradeDatabase(self, db_version):
wallet_data VARCHAR,
balance_type INTEGER,
created_at BIGINT,
PRIMARY KEY (record_id))'''))
PRIMARY KEY (record_id))"""
)
)
db_version += 1
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
elif current_version == 10:
session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER'))
session.execute(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 smsgaddresses ADD COLUMN active_ind INTEGER")
)
session.execute(
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}"'))
db_version += 1
elif current_version == 11:
session.execute(text('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER'))
session.execute(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'))
session.execute(
text("ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER")
)
session.execute(
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
elif current_version == 12:
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE knownidentities (
record_id INTEGER NOT NULL,
address VARCHAR,
@ -168,15 +234,23 @@ def upgradeDatabase(self, db_version):
note VARCHAR,
updated_at BIGINT,
created_at BIGINT,
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 offers ADD COLUMN rate_negotiable INTEGER'))
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 offers ADD COLUMN rate_negotiable INTEGER")
)
db_version += 1
elif current_version == 13:
db_version += 1
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE automationstrategies (
record_id INTEGER NOT NULL,
active_ind INTEGER,
@ -188,9 +262,13 @@ def upgradeDatabase(self, db_version):
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))'''))
PRIMARY KEY (record_id))"""
)
)
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE automationlinks (
record_id INTEGER NOT NULL,
active_ind INTEGER,
@ -205,9 +283,13 @@ def upgradeDatabase(self, db_version):
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))'''))
PRIMARY KEY (record_id))"""
)
)
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE history (
record_id INTEGER NOT NULL,
concept_type INTEGER,
@ -216,9 +298,13 @@ def upgradeDatabase(self, db_version):
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))'''))
PRIMARY KEY (record_id))"""
)
)
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE bidstates (
record_id INTEGER NOT NULL,
active_ind INTEGER,
@ -228,31 +314,53 @@ def upgradeDatabase(self, db_version):
note VARCHAR,
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 knownidentities ADD COLUMN active_ind INTEGER'))
session.execute(text('ALTER TABLE eventqueue RENAME TO actions'))
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'))
session.execute(text("ALTER TABLE wallets ADD COLUMN active_ind INTEGER"))
session.execute(
text("ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER")
)
session.execute(text("ALTER TABLE eventqueue RENAME TO actions"))
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:
db_version += 1
session.execute(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'))
session.execute(
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:
db_version += 1
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE notifications (
record_id INTEGER NOT NULL,
active_ind INTEGER,
event_type INTEGER,
event_data BLOB,
created_at BIGINT,
PRIMARY KEY (record_id))'''))
PRIMARY KEY (record_id))"""
)
)
elif current_version == 16:
db_version += 1
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE prefunded_transactions (
record_id INTEGER NOT NULL,
active_ind INTEGER,
@ -262,25 +370,43 @@ def upgradeDatabase(self, db_version):
tx_type INTEGER,
tx_data BLOB,
used_by BLOB,
PRIMARY KEY (record_id))'''))
PRIMARY KEY (record_id))"""
)
)
elif current_version == 17:
db_version += 1
session.execute(text('ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER'))
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'))
session.execute(
text(
"ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER"
)
)
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:
db_version += 1
session.execute(text('ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING'))
session.execute(text('ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING'))
session.execute(
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:
db_version += 1
session.execute(text('ALTER TABLE bidstates ADD COLUMN in_error INTEGER'))
session.execute(text('ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER'))
session.execute(text('ALTER TABLE bidstates ADD COLUMN swap_ended 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(text("ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER"))
elif current_version == 20:
db_version += 1
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE message_links (
record_id INTEGER NOT NULL,
active_ind INTEGER,
@ -292,18 +418,22 @@ def upgradeDatabase(self, db_version):
msg_type INTEGER,
msg_sequence INTEGER,
msg_id BLOB,
PRIMARY KEY (record_id))'''))
session.execute(text('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER'))
PRIMARY KEY (record_id))"""
)
)
session.execute(text("ALTER TABLE offers ADD COLUMN bid_reversed INTEGER"))
elif current_version == 21:
db_version += 1
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 offers ADD COLUMN proof_utxos BLOB"))
session.execute(text("ALTER TABLE bids ADD COLUMN proof_utxos BLOB"))
elif current_version == 22:
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:
db_version += 1
session.execute(text('''
session.execute(
text(
"""
CREATE TABLE checkedblocks (
record_id INTEGER NOT NULL,
created_at BIGINT,
@ -311,17 +441,19 @@ def upgradeDatabase(self, db_version):
block_height INTEGER,
block_hash BLOB,
block_time INTEGER,
PRIMARY KEY (record_id))'''))
session.execute(text('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB'))
PRIMARY KEY (record_id))"""
)
)
session.execute(text("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB"))
if current_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.close()
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
break
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:
session = self.openSession()
active_bids_insert = self.activeBidsQueryStr(now, '', 'b2')
query_str = f'''
active_bids_insert = self.activeBidsQueryStr(now, "", "b2")
query_str = f"""
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})
'''
"""
num_offers = 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:
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:
num_bids += 1
session.execute(text('DELETE FROM transactions WHERE transactions.bid_id = :bid_id'), {'bid_id': bid_row[0]})
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]})
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 transactions WHERE transactions.bid_id = :bid_id"
),
{"bid_id": bid_row[0]},
)
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]},
)
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(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]})
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]})
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(
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]},
)
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:
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:
self.closeSession(session)

View file

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

View file

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

View file

@ -14,7 +14,8 @@ from basicswap.chainparams import (
)
from basicswap.util import (
ensure,
i2b, b2i,
i2b,
b2i,
make_int,
format_amount,
TemporaryError,
@ -26,9 +27,7 @@ from basicswap.util.ecc import (
ep,
getSecretInt,
)
from coincurve.dleag import (
verify_secp256k1_point
)
from coincurve.dleag import verify_secp256k1_point
from coincurve.keys import (
PublicKey,
)
@ -67,33 +66,33 @@ class CoinInterface:
def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()]
if 'display_name' in coin_chainparams:
return coin_chainparams['display_name']
return coin_chainparams['name'].capitalize()
if "display_name" in coin_chainparams:
return coin_chainparams["display_name"]
return coin_chainparams["name"].capitalize()
def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
elif self._network == 'regtest':
ticker = 'rt' + ticker
ticker = chainparams[self.coin_type()]["ticker"]
if self._network == "testnet":
ticker = "t" + ticker
elif self._network == "regtest":
ticker = "rt" + ticker
return ticker
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:
return chainparams[self.coin_type()]['name']
return chainparams[self.coin_type()]["name"]
def ticker_mainnet(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
ticker = chainparams[self.coin_type()]["ticker"]
return ticker
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:
return chainparams[self.coin_type()][self._network]['max_amount']
return chainparams[self.coin_type()][self._network]["max_amount"]
def setWalletSeedWarning(self, value: bool) -> None:
self._unknown_wallet_seed = value
@ -111,7 +110,7 @@ class CoinInterface:
return chainparams[self.coin_type()][self._network]
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:
# p2sh-p2wsh
@ -121,24 +120,26 @@ class CoinInterface:
if isinstance(ex, TemporaryError):
return True
str_error: str = str(ex).lower()
if 'not enough unlocked money' in str_error:
if "not enough unlocked money" in str_error:
return True
if 'no unlocked balance' in str_error:
if "no unlocked balance" in str_error:
return True
if 'transaction was rejected by daemon' in str_error:
if "transaction was rejected by daemon" in str_error:
return True
if 'invalid unlocked_balance' in str_error:
if "invalid unlocked_balance" in str_error:
return True
if 'daemon is busy' in str_error:
if "daemon is busy" in str_error:
return True
if 'timed out' in str_error:
if "timed out" in str_error:
return True
if 'request-sent' in str_error:
if "request-sent" in str_error:
return True
return False
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
def walletRestoreHeight(self) -> int:
@ -171,30 +172,15 @@ class CoinInterface:
return self._altruistic
class AdaptorSigInterface():
class AdaptorSigInterface:
def getScriptLockTxDummyWitness(self, script: bytes):
return [
b'',
bytes(72),
bytes(72),
bytes(len(script))
]
return [b"", bytes(72), bytes(72), bytes(len(script))]
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
return [
b'',
bytes(72),
bytes(72),
bytes((1,)),
bytes(len(script))
]
return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))]
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
return [
bytes(72),
b'',
bytes(len(script))
]
return [bytes(72), b"", bytes(len(script))]
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
@ -213,7 +199,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def verifyKey(self, k: bytes) -> bool:
i = b2i(k)
return (i < ep.o and i > 0)
return i < ep.o and i > 0
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
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 -*-
# Copyright (c) 2022-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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.test_framework.script import (
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):
super().__init__(coin_settings, network, swap_client)
self._wallet_passphrase = ''
self._wallet_passphrase = ""
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:
return decodeAddress(address)[1:]
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()
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:
self._have_checked_seed = False
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)
mnemonic_passphrase = ''
self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
mnemonic_passphrase = ""
self.rpc_wallet(
"upgradetohd", [words, mnemonic_passphrase, self._wallet_passphrase]
)
self._have_checked_seed = False
if self._wallet_passphrase != '':
if self._wallet_passphrase != "":
self.unlockWallet(self._wallet_passphrase)
return
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:
self._expect_seedid_hex = expect_seedid
rv = self.rpc_wallet('dumphdinfo')
if rv['mnemonic'] != '':
entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' '))
rv = self.rpc_wallet("dumphdinfo")
if rv["mnemonic"] != "":
entropy = Mnemonic("english").to_entropy(rv["mnemonic"].split(" "))
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
have_expected_seed: bool = expect_seedid == entropy_hash
else:
@ -65,11 +77,11 @@ class DASHInterface(BTCInterface):
return have_expected_seed
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
return self.rpc_wallet('sendtoaddress', params)
params = [addr_to, value, "", "", subfee, False, False, self._conf_target]
return self.rpc_wallet("sendtoaddress", params)
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:
# Return P2PKH
@ -79,19 +91,23 @@ class DASHInterface(BTCInterface):
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
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
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc_wallet('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
rv = self.rpc_wallet("gettransaction", [txid_hex])
except Exception as e: # noqa: F841
self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
block_height = self.getBlockHeader(rv['blockhash'])['height']
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
block_height = self.getBlockHeader(rv["blockhash"])["height"]
return {"txid": txid_hex, "amount": 0, "height": block_height}
return None
@ -105,8 +121,8 @@ class DASHInterface(BTCInterface):
self._sc.checkWalletSeed(self.coin_type())
except Exception as ex:
# 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):
super().lockWallet()
self._wallet_passphrase = ''
self._wallet_passphrase = ""

View file

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

View file

@ -9,34 +9,34 @@ import traceback
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:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
x = Jsonrpc(url)
x.__handler = None
v = x.json_request(method, params)
x.close()
r = json.loads(v.decode('utf-8'))
r = json.loads(v.decode("utf-8"))
except Exception as ex:
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:
raise ValueError('RPC error ' + str(r['error']))
if "error" in r and r["error"] is not None:
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:
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
return Jsonrpc(url)
except Exception as ex:
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
auth = auth
host = host
@ -44,4 +44,5 @@ def make_rpc_func(port, auth, host='127.0.0.1'):
def rpc_func(method, params=None):
nonlocal port, auth, host
return callrpc(port, auth, method, params, host)
return rpc_func

View file

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

View file

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

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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):
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host
)
def getExchangeName(self, exchange_name: str) -> str:
return 'zcoin'
return "zcoin"
def initialiseWallet(self, key):
# load with -hdseed= parameter
@ -52,8 +55,8 @@ class FIROInterface(BTCInterface):
def checkWallets(self) -> int:
return 1
def getNewAddress(self, use_segwit, label='swap_receive'):
return self.rpc('getnewaddress', [label])
def getNewAddress(self, use_segwit, label="swap_receive"):
return self.rpc("getnewaddress", [label])
# addr_plain = self.rpc('getnewaddress', [label])
# return self.rpc('addwitnessaddress', [addr_plain])
@ -61,20 +64,20 @@ class FIROInterface(BTCInterface):
return decodeAddress(address)[1:]
def encodeSegwitAddress(self, script):
raise ValueError('TODO')
raise ValueError("TODO")
def decodeSegwitAddress(self, addr):
raise ValueError('TODO')
raise ValueError("TODO")
def isWatchOnlyAddress(self, address):
addr_info = self.rpc('validateaddress', [address])
return addr_info['iswatchonly']
addr_info = self.rpc("validateaddress", [address])
return addr_info["iswatchonly"]
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:
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
return addr_info["ismine"]
return addr_info["ismine"] or addr_info["iswatchonly"]
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
lock_tx_dest = self.getScriptDest(lock_script)
@ -82,58 +85,83 @@ class FIROInterface(BTCInterface):
if not self.isAddressMine(address, or_watch_only=True):
# Expects P2WSH nested in BIP16_P2SH
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
addr_info = self.rpc('validateaddress', [address])
self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True])
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
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, 'bid')
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info("Imported watch-only addr: {}".format(dest_address))
self._log.info(
"Rescanning {} chain from height: {}".format(
self.coin_name(), rescan_from
)
)
self.rescanBlockchainForAddress(rescan_from, dest_address)
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
txns = self.rpc(
"listunspent",
[
0,
9999999,
[
dest_address,
],
],
)
for tx in txns:
if self.make_int(tx['amount']) == bid_amount:
txid = bytes.fromhex(tx['txid'])
if self.make_int(tx["amount"]) == bid_amount:
txid = bytes.fromhex(tx["txid"])
break
if txid is None:
return None
try:
tx = self.rpc('gettransaction', [txid.hex()])
tx = self.rpc("gettransaction", [txid.hex()])
block_height = 0
if 'blockhash' in tx:
block_header = self.rpc('getblockheader', [tx['blockhash']])
block_height = block_header['height']
if "blockhash" in tx:
block_header = self.rpc("getblockheader", [tx["blockhash"]])
block_height = block_header["height"]
rv = {
'depth': 0 if 'confirmations' not in tx else tx['confirmations'],
'height': block_height}
"depth": 0 if "confirmations" not in tx else tx["confirmations"],
"height": block_height,
}
except Exception as e:
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e))
self._log.debug(
"getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e)
)
return None
if find_index:
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
tx_obj = self.rpc("decoderawtransaction", [tx["hex"]])
rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address)
if return_txid:
rv['txid'] = txid.hex()
rv["txid"] = txid.hex()
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.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
@ -144,24 +172,36 @@ class FIROInterface(BTCInterface):
return self.fundTx(tx_bytes, feerate)
def signTxWithWallet(self, tx):
rv = self.rpc('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
rv = self.rpc("signrawtransaction", [tx.hex()])
return bytes.fromhex(rv["hex"])
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
def createRawFundedTransaction(
self,
addr_to: str,
amount: int,
sub_fee: bool = False,
lock_unspents: bool = True,
) -> str:
txn = self.rpc(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
)
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
self._log.debug(
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
)
options = {
'lockUnspents': lock_unspents,
'feeRate': fee_rate,
"lockUnspents": lock_unspents,
"feeRate": fee_rate,
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc('fundrawtransaction', [txn, options])['hex']
options["subtractFeeFromOutputs"] = [
0,
]
return self.rpc("fundrawtransaction", [txn, options])["hex"]
def createRawSignedTransaction(self, addr_to, amount) -> str:
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:
# Return P2PKH
@ -188,60 +228,75 @@ class FIROInterface(BTCInterface):
return CScript([OP_HASH160, script_hash, OP_EQUAL])
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee]
return self.rpc('sendtoaddress', params)
params = [addr_to, value, "", "", subfee]
return self.rpc("sendtoaddress", params)
def getWalletSeedID(self):
return self.rpc('getwalletinfo')['hdmasterkeyid']
return self.rpc("getwalletinfo")["hdmasterkeyid"]
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:
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
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
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
key_wif = self.encodeKey(key)
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
return bytes.fromhex(rv['hex'])
rv = self.rpc(
"signrawtransaction",
[
tx.hex(),
[],
[
key_wif,
],
],
)
return bytes.fromhex(rv["hex"])
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
rv = self.rpc("gettransaction", [txid_hex])
except Exception as e: # noqa: F841
self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
block_height = self.getBlockHeader(rv['blockhash'])['height']
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
block_height = self.getBlockHeader(rv["blockhash"])["height"]
return {"txid": txid_hex, "amount": 0, "height": block_height}
return None
def getProofOfFunds(self, amount_for, extra_commit_bytes):
# TODO: Lock unspent and use same output/s to fund bid
unspents_by_addr = dict()
unspents = self.rpc('listunspent')
unspents = self.rpc("listunspent")
for u in unspents:
if u['spendable'] is not True:
if u["spendable"] is not True:
continue
if u['address'] not in unspents_by_addr:
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []}
utxo_amount: int = self.make_int(u['amount'], r=1)
unspents_by_addr[u['address']]['total'] += utxo_amount
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout']))
if u["address"] not in unspents_by_addr:
unspents_by_addr[u["address"]] = {"total": 0, "utxos": []}
utxo_amount: int = self.make_int(u["amount"], r=1)
unspents_by_addr[u["address"]]["total"] += utxo_amount
unspents_by_addr[u["address"]]["utxos"].append(
(utxo_amount, u["txid"], u["vout"])
)
max_utxos: int = 4
viable_addrs = []
for addr, data in unspents_by_addr.items():
if data['total'] >= amount_for:
if data["total"] >= amount_for:
# 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
utxos_req: int = 0
@ -256,13 +311,17 @@ class FIROInterface(BTCInterface):
viable_addrs.append(addr)
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)
self._log.debug('sign_for_addr %s', sign_for_addr)
self._log.debug("sign_for_addr %s", sign_for_addr)
prove_utxos = []
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0])
sorted_utxos = sorted(
unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0]
)
hasher = hashlib.sha256()
@ -272,18 +331,29 @@ class FIROInterface(BTCInterface):
outpoint = (bytes.fromhex(utxo[1]), utxo[2])
prove_utxos.append(outpoint)
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:
break
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
pkh = self.decodeAddress(sign_for_addr)
sign_for_addr = self.pkh_to_address(pkh)
self._log.debug('sign_for_addr converted %s', sign_for_addr)
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)
@ -292,19 +362,23 @@ class FIROInterface(BTCInterface):
sum_value: int = 0
for outpoint in utxos:
hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big'))
hasher.update(outpoint[1].to_bytes(2, "big"))
utxos_hash = hasher.digest()
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature)
ensure(passed is True, 'Proof of funds signature invalid')
passed = self.verifyMessage(
address,
address + "_swap_proof_" + utxos_hash.hex() + extra_commit_bytes.hex(),
signature,
)
ensure(passed is True, "Proof of funds signature invalid")
if self.using_segwit():
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
sum_value: int = 0
for outpoint in utxos:
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
sum_value += self.make_int(txout['value'])
txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1]])
sum_value += self.make_int(txout["value"])
return sum_value
@ -314,15 +388,15 @@ class FIROInterface(BTCInterface):
chain_blocks: int = self.getChainHeight()
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)
find_scriptPubKey = self.getDestForScriptHash(script_hash)
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 = FromHex(decoded_block, block)
for tx in decoded_block.vtx:
@ -330,38 +404,46 @@ class FIROInterface(BTCInterface):
if txo.scriptPubKey == find_scriptPubKey:
tx.rehash()
txid = i2b(tx.sha256)
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
self.rpc('invalidateblock', [block_hash])
self.rpc('reconsiderblock', [block_hash])
self._log.info(
"Found output to addr: {} in tx {} in block {}".format(
addr_find, txid.hex(), block_hash
)
)
self._log.info(
"rescanblockchain hack invalidateblock {}".format(
block_hash
)
)
self.rpc("invalidateblock", [block_hash])
self.rpc("reconsiderblock", [block_hash])
return
current_height -= 1
def getBlockWithTxns(self, block_hash: str):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc('getblock', [block_hash, False])
block_header = self.rpc('getblockheader', [block_hash])
block = self.rpc("getblock", [block_hash, False])
block_header = self.rpc("getblockheader", [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = []
for tx in decoded_block.vtx:
tx_hex = tx.serialize_with_witness().hex()
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
if 'hex' not in tx_dec:
tx_dec['hex'] = tx_hex
tx_dec = self.rpc("decoderawtransaction", [tx_hex])
if "hex" not in tx_dec:
tx_dec["hex"] = tx_hex
tx_rv.append(tx_dec)
block_rv = {
'hash': block_hash,
'previousblockhash': block_header['previousblockhash'],
'tx': tx_rv,
'confirmations': block_header['confirmations'],
'height': block_header['height'],
'time': block_header['time'],
'version': block_header['version'],
'merkleroot': block_header['merkleroot'],
"hash": block_hash,
"previousblockhash": block_header["previousblockhash"],
"tx": tx_rv,
"confirmations": block_header["confirmations"],
"height": block_header["height"],
"time": block_header["time"],
"version": block_header["version"],
"merkleroot": block_header["merkleroot"],
}
return block_rv

View file

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

View file

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

View file

@ -14,26 +14,38 @@ class NMCInterface(BTCInterface):
def coin_type():
return Coins.NMC
def getLockTxHeight(self, txid, 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')
def getLockTxHeight(
self,
txid,
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
for o in ro['unspents']:
if txid and o['txid'] != txid.hex():
for o in ro["unspents"]:
if txid and o["txid"] != txid.hex():
continue
# Verify 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'])
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"],
)
continue
rv = {
'depth': 0,
'height': o['height']}
if o['height'] > 0:
rv['depth'] = ro['height'] - o['height']
rv = {"depth": 0, "height": o["height"]}
if o["height"] > 0:
rv["depth"] = ro["height"] - o["height"]
if find_index:
rv['index'] = o['vout']
rv["index"] = o["vout"]
if return_txid:
rv['txid'] = o['txid']
rv["txid"] = o["txid"]
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.
from .btc import BTCInterface
from basicswap.contrib.test_framework.messages import (
CTxOut)
from basicswap.contrib.test_framework.messages import CTxOut
class PassthroughBTCInterface(BTCInterface):
@ -15,5 +14,5 @@ class PassthroughBTCInterface(BTCInterface):
super().__init__(coin_settings, network)
self.txoType = CTxOut
self._network = network
self.blocks_confirmed = coin_settings['blocks_confirmed']
self.setConfTarget(coin_settings['conf_target'])
self.blocks_confirmed = coin_settings["blocks_confirmed"]
self.setConfTarget(coin_settings["conf_target"])

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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.chainparams import Coins
from basicswap.util.address import decodeAddress
from .contrib.pivx_test_framework.messages import (
CBlock,
ToHex,
FromHex,
CTransaction)
from .contrib.pivx_test_framework.messages import CBlock, ToHex, FromHex, CTransaction
from basicswap.contrib.test_framework.script import (
CScript,
OP_DUP,
@ -33,65 +30,79 @@ class PIVXInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None):
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host
)
def checkWallets(self) -> int:
return 1
def signTxWithWallet(self, tx):
rv = self.rpc('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
rv = self.rpc("signrawtransaction", [tx.hex()])
return bytes.fromhex(rv["hex"])
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
def createRawFundedTransaction(
self,
addr_to: str,
amount: int,
sub_fee: bool = False,
lock_unspents: bool = True,
) -> str:
txn = self.rpc(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
)
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
self._log.debug(
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
)
options = {
'lockUnspents': lock_unspents,
'feeRate': fee_rate,
"lockUnspents": lock_unspents,
"feeRate": fee_rate,
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc('fundrawtransaction', [txn, options])['hex']
options["subtractFeeFromOutputs"] = [
0,
]
return self.rpc("fundrawtransaction", [txn, options])["hex"]
def createRawSignedTransaction(self, addr_to, amount) -> str:
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):
return decodeAddress(address)[1:]
def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc('getblock', [block_hash, False])
block_header = self.rpc('getblockheader', [block_hash])
block = self.rpc("getblock", [block_hash, False])
block_header = self.rpc("getblockheader", [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = []
for tx in decoded_block.vtx:
tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)])
tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)])
tx_rv.append(tx_dec)
block_rv = {
'hash': block_hash,
'previousblockhash': block_header['previousblockhash'],
'tx': tx_rv,
'confirmations': block_header['confirmations'],
'height': block_header['height'],
'time': block_header['time'],
'version': block_header['version'],
'merkleroot': block_header['merkleroot'],
"hash": block_hash,
"previousblockhash": block_header["previousblockhash"],
"tx": tx_rv,
"confirmations": block_header["confirmations"],
"height": block_header["height"],
"time": block_header["time"],
"version": block_header["version"],
"merkleroot": block_header["merkleroot"],
}
return block_rv
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee]
return self.rpc('sendtoaddress', params)
params = [addr_to, value, "", "", subfee]
return self.rpc("sendtoaddress", params)
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):
# Load tx from bytes to internal representation
@ -107,22 +118,35 @@ class PIVXInterface(BTCInterface):
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
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
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
key_wif = self.encodeKey(key)
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
return bytes.fromhex(rv['hex'])
rv = self.rpc(
"signrawtransaction",
[
tx.hex(),
[],
[
key_wif,
],
],
)
return bytes.fromhex(rv["hex"])
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
rv = self.rpc("gettransaction", [txid_hex])
except Exception as e: # noqa: F841
self._log.debug(
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
)
return None
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
block_height = self.getBlockHeader(rv['blockhash'])['height']
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
block_height = self.getBlockHeader(rv["blockhash"])["height"]
return {"txid": txid_hex, "amount": 0, "height": block_height}
return None

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -26,16 +27,9 @@ from coincurve.dleag import (
from basicswap.interface.base import (
Curves,
)
from basicswap.util import (
i2b, b2i, b2h,
dumpj,
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.util import i2b, b2i, b2h, dumpj, 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.interface.base import CoinInterface
@ -75,7 +69,7 @@ class XMRInterface(CoinInterface):
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
raise ValueError('Not possible')
raise ValueError("Not possible")
@staticmethod
def xmr_swap_b_lock_spend_tx_vsize() -> int:
@ -84,62 +78,93 @@ class XMRInterface(CoinInterface):
def is_transient_error(self, ex) -> bool:
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 super().is_transient_error(ex)
def __init__(self, coin_settings, network, swap_client=None):
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._restore_height = coin_settings.get('restore_height', 0)
self.setFeePriority(coin_settings.get('fee_priority', 0))
self.blocks_confirmed = coin_settings["blocks_confirmed"]
self._restore_height = coin_settings.get("restore_height", 0)
self.setFeePriority(coin_settings.get("fee_priority", 0))
self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging
self._wallet_password = None
self._have_checked_seed = False
daemon_login = None
if coin_settings.get('rpcuser', '') != '':
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
if coin_settings.get("rpcuser", "") != "":
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_port = None
# Connect to the daemon over a proxy if not running locally
if swap_client:
chain_client_settings = swap_client.getChainClientSettings(self.coin_type())
manage_daemon: bool = chain_client_settings['manage_daemon']
manage_daemon: bool = chain_client_settings["manage_daemon"]
if swap_client.use_tor_proxy:
if manage_daemon is False:
log_str: str = ''
have_cc_tor_opt = 'use_tor' in chain_client_settings
if have_cc_tor_opt and chain_client_settings['use_tor'] is False:
log_str = ' bypassing proxy (use_tor false for XMR)'
log_str: str = ""
have_cc_tor_opt = "use_tor" in chain_client_settings
if have_cc_tor_opt and chain_client_settings["use_tor"] is False:
log_str = " bypassing proxy (use_tor false for XMR)"
elif have_cc_tor_opt is False and is_private_ip_address(rpchost):
log_str = ' bypassing proxy (private ip address)'
log_str = " bypassing proxy (private ip address)"
else:
proxy_host = swap_client.tor_proxy_host
proxy_port = swap_client.tor_proxy_port
log_str = f' through proxy at {proxy_host}'
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.')
log_str = f" through proxy at {proxy_host}"
self._log.info(
f"Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}."
)
else:
self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.')
self._log.info(
f"Not connecting to local {self.coin_name()} daemon through proxy."
)
elif manage_daemon is False:
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.')
self._log.info(
f"Connecting to remote {self.coin_name()} daemon at {rpchost}."
)
self._rpctimeout = coin_settings.get('rpctimeout', 60)
self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120)
self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600)
self._rpctimeout = coin_settings.get("rpctimeout", 60)
self._walletrpctimeout = coin_settings.get("walletrpctimeout", 120)
self._walletrpctimeoutlong = coin_settings.get("walletrpctimeoutlong", 600)
self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node(j) ')
self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint
self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ')
self.rpc = make_xmr_rpc_func(
coin_settings["rpcport"],
daemon_login,
host=rpchost,
proxy_host=proxy_host,
proxy_port=proxy_port,
default_timeout=self._rpctimeout,
tag="Node(j) ",
)
self.rpc2 = make_xmr_rpc2_func(
coin_settings["rpcport"],
daemon_login,
host=rpchost,
proxy_host=proxy_host,
proxy_port=proxy_port,
default_timeout=self._rpctimeout,
tag="Node ",
) # non-json endpoint
self.rpc_wallet = make_xmr_rpc_func(
coin_settings["walletrpcport"],
coin_settings["walletrpcauth"],
host=coin_settings.get("walletrpchost", "127.0.0.1"),
default_timeout=self._walletrpctimeout,
tag="Wallet ",
)
def 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
def setWalletFilename(self, wallet_filename):
@ -147,29 +172,31 @@ class XMRInterface(CoinInterface):
def createWallet(self, params):
if self._wallet_password is not None:
params['password'] = self._wallet_password
rv = self.rpc_wallet('generate_from_keys', params)
self._log.info('generate_from_keys %s', dumpj(rv))
params["password"] = self._wallet_password
rv = self.rpc_wallet("generate_from_keys", params)
self._log.info("generate_from_keys %s", dumpj(rv))
def openWallet(self, filename):
params = {'filename': filename}
params = {"filename": filename}
if self._wallet_password is not None:
params['password'] = self._wallet_password
params["password"] = self._wallet_password
try:
# Can't reopen the same wallet in windows, !is_keys_file_locked()
self.rpc_wallet('close_wallet')
self.rpc_wallet("close_wallet")
except Exception:
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:
try:
self.openWallet(self._wallet_filename)
# TODO: Check address
return # Wallet exists
except Exception as e:
except Exception as e: # noqa: F841
pass
Kbv = self.getPubkey(key_view)
@ -177,11 +204,11 @@ class XMRInterface(CoinInterface):
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
params = {
'filename': self._wallet_filename,
'address': address_b58,
'viewkey': b2h(key_view[::-1]),
'spendkey': b2h(key_spend[::-1]),
'restore_height': self._restore_height,
"filename": self._wallet_filename,
"address": address_b58,
"viewkey": b2h(key_view[::-1]),
"spendkey": b2h(key_spend[::-1]),
"restore_height": self._restore_height,
}
self.createWallet(params)
self.openWallet(self._wallet_filename)
@ -191,84 +218,95 @@ class XMRInterface(CoinInterface):
self.openWallet(self._wallet_filename)
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_wallet('get_languages')
self.rpc_wallet("get_languages")
def getDaemonVersion(self):
return self.rpc_wallet('get_version')['version']
return self.rpc_wallet("get_version")["version"]
def getBlockchainInfo(self):
get_height = self.rpc2('get_height', timeout=self._rpctimeout)
get_height = self.rpc2("get_height", timeout=self._rpctimeout)
rv = {
'blocks': get_height['height'],
'verificationprogress': 0.0,
"blocks": get_height["height"],
"verificationprogress": 0.0,
}
try:
# get_block_count.block_count is how many blocks are in the longest chain known to the node.
# get_block_count returns "Internal error" if bootstrap-daemon is active
if get_height['untrusted'] is True:
rv['bootstrapping'] = True
get_info = self.rpc2('get_info', timeout=self._rpctimeout)
if 'height_without_bootstrap' in get_info:
rv['blocks'] = get_info['height_without_bootstrap']
if get_height["untrusted"] is True:
rv["bootstrapping"] = True
get_info = self.rpc2("get_info", timeout=self._rpctimeout)
if "height_without_bootstrap" in get_info:
rv["blocks"] = get_info["height_without_bootstrap"]
rv['known_block_count'] = get_info['height']
if rv['known_block_count'] > rv['blocks']:
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
rv["known_block_count"] = get_info["height"]
if rv["known_block_count"] > rv["blocks"]:
rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"]
else:
rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count']
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
rv["known_block_count"] = self.rpc(
"get_block_count", timeout=self._rpctimeout
)["count"]
rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"]
except Exception as e:
self._log.warning(f'{self.ticker_str()} get_block_count failed with: {e}')
rv['verificationprogress'] = 0.0
self._log.warning(f"{self.ticker_str()} get_block_count failed with: {e}")
rv["verificationprogress"] = 0.0
return rv
def getChainHeight(self):
return self.rpc2('get_height', timeout=self._rpctimeout)['height']
return self.rpc2("get_height", timeout=self._rpctimeout)["height"]
def getWalletInfo(self):
with self._mx_wallet:
try:
self.openWallet(self._wallet_filename)
except Exception as e:
if 'Failed to open wallet' in str(e):
rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0}
if "Failed to open wallet" in str(e):
rv = {
"encrypted": True,
"locked": True,
"balance": 0,
"unconfirmed_balance": 0,
}
return rv
raise e
rv = {}
self.rpc_wallet('refresh')
balance_info = self.rpc_wallet('get_balance')
self.rpc_wallet("refresh")
balance_info = self.rpc_wallet("get_balance")
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance'])
rv['encrypted'] = False if self._wallet_password is None else True
rv['locked'] = False
rv["balance"] = self.format_amount(balance_info["unlocked_balance"])
rv["unconfirmed_balance"] = self.format_amount(
balance_info["balance"] - balance_info["unlocked_balance"]
)
rv["encrypted"] = False if self._wallet_password is None else True
rv["locked"] = False
return rv
def getMainWalletAddress(self) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
return self.rpc_wallet('get_address')['address']
return self.rpc_wallet("get_address")["address"]
def getNewAddress(self, placeholder) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address']
self.rpc_wallet('store')
new_address = self.rpc_wallet("create_address", {"account_index": 0})[
"address"
]
self.rpc_wallet("store")
return new_address
def get_fee_rate(self, conf_target: int = 2):
# fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest].
fee_est = self.rpc('get_fee_estimate')
fee_est = self.rpc("get_fee_estimate")
if conf_target <= 1:
conf_target = 1 # normal
else:
conf_target = 0 # slow
fee_per_k_bytes = fee_est['fees'][conf_target] * 1000
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:
# Note: Returned bytes are in big endian order
@ -299,7 +337,7 @@ class XMRInterface(CoinInterface):
def verifyKey(self, k: int) -> bool:
i = b2i(k)
return (i < edf.l and i > 8)
return i < edf.l and i > 8
def verifyPubkey(self, pubkey_bytes):
# Calls ed25519_decode_check_point() in secp256k1
@ -325,45 +363,59 @@ class XMRInterface(CoinInterface):
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
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:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh')
self.rpc_wallet("refresh")
Kbv = self.getPubkey(kbv)
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:
params['priority'] = self._fee_priority
rv = self.rpc_wallet('transfer', params)
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
tx_hash = bytes.fromhex(rv['tx_hash'])
params["priority"] = self._fee_priority
rv = self.rpc_wallet("transfer", params)
self._log.info(
"publishBLockTx %s to address_b58 %s", rv["tx_hash"], shared_addr
)
tx_hash = bytes.fromhex(rv["tx_hash"])
return tx_hash
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
def findTxB(
self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender
):
with self._mx_wallet:
Kbv = self.getPubkey(kbv)
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
kbv_le = kbv[::-1]
params = {
'restore_height': restore_height,
'filename': address_b58,
'address': address_b58,
'viewkey': b2h(kbv_le),
"restore_height": restore_height,
"filename": address_b58,
"address": address_b58,
"viewkey": b2h(kbv_le),
}
try:
self.openWallet(address_b58)
except Exception as e:
except Exception as e: # noqa: F841
self.createWallet(params)
self.openWallet(address_b58)
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
self.rpc_wallet("refresh", timeout=self._walletrpctimeoutlong)
'''
"""
# Debug
try:
current_height = self.rpc_wallet('get_height')['height']
@ -372,137 +424,222 @@ class XMRInterface(CoinInterface):
self._log.info('rpc failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
'''
params = {'transfer_type': 'available'}
transfers = self.rpc_wallet('incoming_transfers', params)
"""
params = {"transfer_type": "available"}
transfers = self.rpc_wallet("incoming_transfers", params)
rv = None
if 'transfers' in transfers:
for transfer in transfers['transfers']:
if "transfers" in transfers:
for transfer in transfers["transfers"]:
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
if not transfer['unlocked']:
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
unlock_time = full_tx['transfer']['unlock_time']
if not transfer["unlocked"]:
full_tx = self.rpc_wallet(
"get_transfer_by_txid", {"txid": transfer["tx_hash"]}
)
unlock_time = full_tx["transfer"]["unlock_time"]
if unlock_time != 0:
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
self._log.warning(
"Coin b lock txn is locked: {}, unlock_time {}".format(
transfer["tx_hash"], unlock_time
)
)
rv = -1
continue
if transfer['amount'] == cb_swap_value:
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']}
if transfer["amount"] == cb_swap_value:
return {
"txid": transfer["tx_hash"],
"amount": transfer["amount"],
"height": (
0
if "block_height" not in transfer
else transfer["block_height"]
),
}
else:
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash']))
self._log.warning(
"Incorrect amount detected for coin b lock txn: {}".format(
transfer["tx_hash"]
)
)
rv = -1
return rv
def findTxnByHash(self, txid):
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
self.rpc_wallet("refresh", timeout=self._walletrpctimeoutlong)
try:
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height']
self._log.info(f'findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}')
current_height = self.rpc2("get_height", timeout=self._rpctimeout)[
"height"
]
self._log.info(
f"findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}"
)
except Exception as e:
self._log.info('rpc failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
self._log.info("rpc failed %s", str(e))
current_height = (
None # If the transfer is available it will be deep enough
)
params = {'transfer_type': 'available'}
rv = self.rpc_wallet('incoming_transfers', params)
if 'transfers' in rv:
for transfer in rv['transfers']:
if transfer['tx_hash'] == txid \
and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed):
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']}
params = {"transfer_type": "available"}
rv = self.rpc_wallet("incoming_transfers", params)
if "transfers" in rv:
for transfer in rv["transfers"]:
if transfer["tx_hash"] == txid and (
current_height is None
or current_height - transfer["block_height"]
> self.blocks_confirmed
):
return {
"txid": transfer["tx_hash"],
"amount": transfer["amount"],
"height": transfer["block_height"],
}
return None
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, 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:
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
'''
"""
with self._mx_wallet:
Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs)
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
wallet_filename = address_b58 + '_spend'
wallet_filename = address_b58 + "_spend"
params = {
'filename': wallet_filename,
'address': address_b58,
'viewkey': b2h(kbv[::-1]),
'spendkey': b2h(kbs[::-1]),
'restore_height': restore_height,
"filename": wallet_filename,
"address": address_b58,
"viewkey": b2h(kbv[::-1]),
"spendkey": b2h(kbs[::-1]),
"restore_height": restore_height,
}
try:
self.openWallet(wallet_filename)
except Exception as e:
except Exception as e: # noqa: F841
self.createWallet(params)
self.openWallet(wallet_filename)
self.rpc_wallet('refresh')
rv = self.rpc_wallet('get_balance')
if rv['balance'] < cb_swap_value:
self._log.warning('Balance is too low, checking for existing spend.')
txns = self.rpc_wallet('get_transfers', {'out': True})
if 'out' in txns:
txns = txns['out']
self.rpc_wallet("refresh")
rv = self.rpc_wallet("get_balance")
if rv["balance"] < cb_swap_value:
self._log.warning("Balance is too low, checking for existing spend.")
txns = self.rpc_wallet("get_transfers", {"out": True})
if "out" in txns:
txns = txns["out"]
if len(txns) > 0:
txid = txns[0]['txid']
self._log.warning(f'spendBLockTx detected spending tx: {txid}.')
txid = txns[0]["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
if txns[0]['address'] == address_b58:
if txns[0]["address"] == address_b58:
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:
raise TemporaryError('Invalid balance')
raise TemporaryError("Invalid balance")
if spend_actual_balance and rv['balance'] != cb_swap_value:
self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value))
cb_swap_value = rv['balance']
if rv['unlocked_balance'] < cb_swap_value:
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock']))
raise TemporaryError('Invalid unlocked_balance')
if spend_actual_balance and rv["balance"] != cb_swap_value:
self._log.warning(
"Spending actual balance {}, not swap value {}.".format(
rv["balance"], cb_swap_value
)
)
cb_swap_value = rv["balance"]
if rv["unlocked_balance"] < cb_swap_value:
self._log.error(
"wallet {} balance {}, expected {}, blocks_to_unlock {}".format(
wallet_filename,
rv["unlocked_balance"],
cb_swap_value,
rv["blocks_to_unlock"],
)
)
raise TemporaryError("Invalid unlocked_balance")
params = {'address': address_to}
params = {"address": address_to}
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:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh')
self.rpc_wallet("refresh")
if sweepall:
balance = self.rpc_wallet('get_balance')
if balance['balance'] != balance['unlocked_balance']:
raise ValueError('Balance must be fully confirmed to use sweep all.')
self._log.info('{} {} 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}
balance = self.rpc_wallet("get_balance")
if balance["balance"] != balance["unlocked_balance"]:
raise ValueError(
"Balance must be fully confirmed to use sweep all."
)
self._log.info(
"{} {} 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:
params['priority'] = self._fee_priority
rv = self.rpc_wallet('sweep_all', params)
params["priority"] = self._fee_priority
rv = self.rpc_wallet("sweep_all", params)
if estimate_fee:
return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])}
return rv['tx_hash_list'][0]
return {
"num_txns": len(rv["fee_list"]),
"sum_amount": sum(rv["amount_list"]),
"sum_fee": sum(rv["fee_list"]),
"sum_weight": sum(rv["weight_list"]),
}
return rv["tx_hash_list"][0]
value_sats: int = 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:
params['priority'] = self._fee_priority
rv = self.rpc_wallet('transfer', params)
params["priority"] = self._fee_priority
rv = self.rpc_wallet("transfer", params)
if estimate_fee:
return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']}
return rv['tx_hash']
return {
"num_txns": 1,
"sum_amount": rv["amount"],
"sum_fee": rv["fee"],
"sum_weight": rv["weight"],
}
return rv["tx_hash"]
def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str:
return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True)
@ -512,7 +649,7 @@ class XMRInterface(CoinInterface):
try:
Kbv = self.getPubkey(kbv)
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
wallet_file = address_b58 + '_spend'
wallet_file = address_b58 + "_spend"
try:
self.openWallet(wallet_file)
except Exception:
@ -520,54 +657,62 @@ class XMRInterface(CoinInterface):
try:
self.openWallet(wallet_file)
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]
params = {
'restore_height': restore_height,
'filename': address_b58,
'address': address_b58,
'viewkey': b2h(kbv_le),
"restore_height": restore_height,
"filename": address_b58,
"address": address_b58,
"viewkey": b2h(kbv_le),
}
self.createWallet(params)
self.openWallet(address_b58)
self.rpc_wallet('refresh')
self.rpc_wallet("refresh")
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
rv['filename'] = wallet_file
rv = self.rpc_wallet(
"get_transfers",
{"in": True, "out": True, "pending": True, "failed": True},
)
rv["filename"] = wallet_file
return rv
except Exception as e:
return {'error': str(e)}
return {"error": str(e)}
def getSpendableBalance(self) -> int:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh')
balance_info = self.rpc_wallet('get_balance')
return balance_info['unlocked_balance']
self.rpc_wallet("refresh")
balance_info = self.rpc_wallet("get_balance")
return balance_info["unlocked_balance"]
def changeWalletPassword(self, old_password, new_password):
self._log.info('changeWalletPassword - {}'.format(self.ticker()))
self._log.info("changeWalletPassword - {}".format(self.ticker()))
orig_password = self._wallet_password
if old_password != '':
if old_password != "":
self._wallet_password = old_password
try:
self.openWallet(self._wallet_filename)
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
self.rpc_wallet(
"change_wallet_password",
{"old_password": old_password, "new_password": new_password},
)
except Exception as e:
self._wallet_password = orig_password
raise e
def unlockWallet(self, password: str) -> None:
self._log.info('unlockWallet - {}'.format(self.ticker()))
self._log.info("unlockWallet - {}".format(self.ticker()))
self._wallet_password = password
if not self._have_checked_seed:
self._sc.checkWalletSeed(self.coin_type())
def lockWallet(self) -> None:
self._log.info('lockWallet - {}'.format(self.ticker()))
self._log.info("lockWallet - {}".format(self.ticker()))
self._wallet_password = None
def isAddressMine(self, address):
@ -576,7 +721,14 @@ class XMRInterface(CoinInterface):
def ensureFunds(self, amount: int) -> None:
if self.getSpendableBalance() < amount:
raise ValueError('Balance too low')
raise ValueError("Balance too low")
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.
'''
"""
syntax = "proto3";
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.
protobuf ParseFromString would reset the whole object, from_bytes won't.
'''
"""
from basicswap.util.integer import encode_varint, decode_varint
class NonProtobufClass():
class NonProtobufClass:
def __init__(self, init_all: bool = True, **kwargs):
for key, value in kwargs.items():
found_field: bool = False
@ -34,7 +34,7 @@ class NonProtobufClass():
found_field = True
break
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:
self.init_fields()
@ -53,7 +53,7 @@ class NonProtobufClass():
else:
setattr(self, field_name, bytes())
else:
raise ValueError(f'Unknown wire_type {wire_type}')
raise ValueError(f"Unknown wire_type {wire_type}")
def to_bytes(self) -> bytes:
rv = bytes()
@ -74,11 +74,11 @@ class NonProtobufClass():
continue
rv += encode_varint(tag)
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 += field_value
else:
raise ValueError(f'Unknown wire_type {wire_type}')
raise ValueError(f"Unknown wire_type {wire_type}")
return rv
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]
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:
field_value, lv = decode_varint(b, o)
@ -100,12 +102,12 @@ class NonProtobufClass():
elif wire_type == 2:
field_len, lv = decode_varint(b, o)
o += lv
field_value = b[o: o + field_len]
field_value = b[o : o + field_len]
o += field_len
if field_type == 1:
field_value = field_value.decode('utf-8')
field_value = field_value.decode("utf-8")
else:
raise ValueError(f'Unknown wire_type {wire_type}')
raise ValueError(f"Unknown wire_type {wire_type}")
setattr(self, field_name, field_value)
@ -115,151 +117,150 @@ class NonProtobufClass():
class OfferMessage(NonProtobufClass):
_map = {
1: ('protocol_version', 0, 0),
2: ('coin_from', 0, 0),
3: ('coin_to', 0, 0),
4: ('amount_from', 0, 0),
5: ('amount_to', 0, 0),
6: ('min_bid_amount', 0, 0),
7: ('time_valid', 0, 0),
8: ('lock_type', 0, 0),
9: ('lock_value', 0, 0),
10: ('swap_type', 0, 0),
11: ('proof_address', 2, 1),
12: ('proof_signature', 2, 1),
13: ('pkhash_seller', 2, 0),
14: ('secret_hash', 2, 0),
15: ('fee_rate_from', 0, 0),
16: ('fee_rate_to', 0, 0),
17: ('amount_negotiable', 0, 2),
18: ('rate_negotiable', 0, 2),
19: ('proof_utxos', 2, 0),
1: ("protocol_version", 0, 0),
2: ("coin_from", 0, 0),
3: ("coin_to", 0, 0),
4: ("amount_from", 0, 0),
5: ("amount_to", 0, 0),
6: ("min_bid_amount", 0, 0),
7: ("time_valid", 0, 0),
8: ("lock_type", 0, 0),
9: ("lock_value", 0, 0),
10: ("swap_type", 0, 0),
11: ("proof_address", 2, 1),
12: ("proof_signature", 2, 1),
13: ("pkhash_seller", 2, 0),
14: ("secret_hash", 2, 0),
15: ("fee_rate_from", 0, 0),
16: ("fee_rate_to", 0, 0),
17: ("amount_negotiable", 0, 2),
18: ("rate_negotiable", 0, 2),
19: ("proof_utxos", 2, 0),
}
class BidMessage(NonProtobufClass):
_map = {
1: ('protocol_version', 0, 0),
2: ('offer_msg_id', 2, 0),
3: ('time_valid', 0, 0),
4: ('amount', 0, 0),
5: ('amount_to', 0, 0),
6: ('pkhash_buyer', 2, 0),
7: ('proof_address', 2, 1),
8: ('proof_signature', 2, 1),
9: ('proof_utxos', 2, 0),
10: ('pkhash_buyer_to', 2, 0),
1: ("protocol_version", 0, 0),
2: ("offer_msg_id", 2, 0),
3: ("time_valid", 0, 0),
4: ("amount", 0, 0),
5: ("amount_to", 0, 0),
6: ("pkhash_buyer", 2, 0),
7: ("proof_address", 2, 1),
8: ("proof_signature", 2, 1),
9: ("proof_utxos", 2, 0),
10: ("pkhash_buyer_to", 2, 0),
}
class BidAcceptMessage(NonProtobufClass):
# Step 3, seller -> buyer
_map = {
1: ('bid_msg_id', 2, 0),
2: ('initiate_txid', 2, 0),
3: ('contract_script', 2, 0),
4: ('pkhash_seller', 2, 0),
1: ("bid_msg_id", 2, 0),
2: ("initiate_txid", 2, 0),
3: ("contract_script", 2, 0),
4: ("pkhash_seller", 2, 0),
}
class OfferRevokeMessage(NonProtobufClass):
_map = {
1: ('offer_msg_id', 2, 0),
2: ('signature', 2, 0),
1: ("offer_msg_id", 2, 0),
2: ("signature", 2, 0),
}
class BidRejectMessage(NonProtobufClass):
_map = {
1: ('bid_msg_id', 2, 0),
2: ('reject_code', 0, 0),
1: ("bid_msg_id", 2, 0),
2: ("reject_code", 0, 0),
}
class XmrBidMessage(NonProtobufClass):
# MSG1L, F -> L
_map = {
1: ('protocol_version', 0, 0),
2: ('offer_msg_id', 2, 0),
3: ('time_valid', 0, 0),
4: ('amount', 0, 0),
5: ('amount_to', 0, 0),
6: ('pkaf', 2, 0),
7: ('kbvf', 2, 0),
8: ('kbsf_dleag', 2, 0),
9: ('dest_af', 2, 0),
1: ("protocol_version", 0, 0),
2: ("offer_msg_id", 2, 0),
3: ("time_valid", 0, 0),
4: ("amount", 0, 0),
5: ("amount_to", 0, 0),
6: ("pkaf", 2, 0),
7: ("kbvf", 2, 0),
8: ("kbsf_dleag", 2, 0),
9: ("dest_af", 2, 0),
}
class XmrSplitMessage(NonProtobufClass):
_map = {
1: ('msg_id', 2, 0),
2: ('msg_type', 0, 0),
3: ('sequence', 0, 0),
4: ('dleag', 2, 0),
1: ("msg_id", 2, 0),
2: ("msg_type", 0, 0),
3: ("sequence", 0, 0),
4: ("dleag", 2, 0),
}
class XmrBidAcceptMessage(NonProtobufClass):
_map = {
1: ('bid_msg_id', 2, 0),
2: ('pkal', 2, 0),
3: ('kbvl', 2, 0),
4: ('kbsl_dleag', 2, 0),
1: ("bid_msg_id", 2, 0),
2: ("pkal", 2, 0),
3: ("kbvl", 2, 0),
4: ("kbsl_dleag", 2, 0),
# MSG2F
5: ('a_lock_tx', 2, 0),
6: ('a_lock_tx_script', 2, 0),
7: ('a_lock_refund_tx', 2, 0),
8: ('a_lock_refund_tx_script', 2, 0),
9: ('a_lock_refund_spend_tx', 2, 0),
10: ('al_lock_refund_tx_sig', 2, 0),
5: ("a_lock_tx", 2, 0),
6: ("a_lock_tx_script", 2, 0),
7: ("a_lock_refund_tx", 2, 0),
8: ("a_lock_refund_tx_script", 2, 0),
9: ("a_lock_refund_spend_tx", 2, 0),
10: ("al_lock_refund_tx_sig", 2, 0),
}
class XmrBidLockTxSigsMessage(NonProtobufClass):
# MSG3L
_map = {
1: ('bid_msg_id', 2, 0),
2: ('af_lock_refund_spend_tx_esig', 2, 0),
3: ('af_lock_refund_tx_sig', 2, 0),
1: ("bid_msg_id", 2, 0),
2: ("af_lock_refund_spend_tx_esig", 2, 0),
3: ("af_lock_refund_tx_sig", 2, 0),
}
class XmrBidLockSpendTxMessage(NonProtobufClass):
# MSG4F
_map = {
1: ('bid_msg_id', 2, 0),
2: ('a_lock_spend_tx', 2, 0),
3: ('kal_sig', 2, 0),
1: ("bid_msg_id", 2, 0),
2: ("a_lock_spend_tx", 2, 0),
3: ("kal_sig", 2, 0),
}
class XmrBidLockReleaseMessage(NonProtobufClass):
# MSG5F
_map = {
1: ('bid_msg_id', 2, 0),
2: ('al_lock_spend_tx_esig', 2, 0),
1: ("bid_msg_id", 2, 0),
2: ("al_lock_spend_tx_esig", 2, 0),
}
class ADSBidIntentMessage(NonProtobufClass):
# L -> F Sent from bidder, construct a reverse bid
_map = {
1: ('protocol_version', 0, 0),
2: ('offer_msg_id', 2, 0),
3: ('time_valid', 0, 0),
4: ('amount_from', 0, 0),
5: ('amount_to', 0, 0),
1: ("protocol_version", 0, 0),
2: ("offer_msg_id", 2, 0),
3: ("time_valid", 0, 0),
4: ("amount_from", 0, 0),
5: ("amount_to", 0, 0),
}
class ADSBidIntentAcceptMessage(NonProtobufClass):
# F -> L Sent from offerer, construct a reverse bid
_map = {
1: ('bid_msg_id', 2, 0),
2: ('pkaf', 2, 0),
3: ('kbvf', 2, 0),
4: ('kbsf_dleag', 2, 0),
5: ('dest_af', 2, 0),
1: ("bid_msg_id", 2, 0),
2: ("pkaf", 2, 0),
3: ("kbvf", 2, 0),
4: ("kbsf_dleag", 2, 0),
5: ("dest_af", 2, 0),
}

View file

@ -5,7 +5,7 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
'''
"""
Message 2 bytes msg_class, 4 bytes length, [ 2 bytes msg_type, payload ]
Handshake procedure:
@ -17,7 +17,7 @@
Both nodes are initialised
XChaCha20_Poly1305 mac is 16bytes
'''
"""
import time
import queue
@ -36,11 +36,12 @@ from Crypto.Cipher import ChaCha20_Poly1305 # TODO: Add to libsecp256k1/coincur
from coincurve.keys import PrivateKey, PublicKey
from basicswap.contrib.rfc6979 import (
rfc6979_hmac_sha256_initialize,
rfc6979_hmac_sha256_generate)
rfc6979_hmac_sha256_generate,
)
START_TOKEN = 0xabcd
MSG_START_TOKEN = START_TOKEN.to_bytes(2, 'big')
START_TOKEN = 0xABCD
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
MSG_MAX_SIZE = 0x200000 # 2MB
@ -63,49 +64,71 @@ class NetMessageTypes(IntEnum):
return value in cls._value2member_map_
'''
"""
class NetMessage:
def __init__(self):
self._msg_class = None # 2 bytes
self._len = None # 4 bytes
self._msg_type = None # 2 bytes
'''
"""
# Ensure handshake keys are not reused by including the time in the msg, mac and key hash
# 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
class MsgHandshake:
__slots__ = ('_timestamp', '_ephem_pk', '_ct', '_mac')
__slots__ = ("_timestamp", "_ephem_pk", "_ct", "_mac")
def __init__(self):
pass
def encode_aad(self): # Additional Authenticated Data
return int(NetMessageTypes.HANDSHAKE).to_bytes(2, 'big') + \
self._timestamp.to_bytes(8, 'big') + \
self._ephem_pk
return (
int(NetMessageTypes.HANDSHAKE).to_bytes(2, "big")
+ self._timestamp.to_bytes(8, "big")
+ self._ephem_pk
)
def encode(self):
return self.encode_aad() + self._ct + self._mac
def decode(self, msg_mv):
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
self._ephem_pk = bytes(msg_mv[o: o + 33])
self._ephem_pk = bytes(msg_mv[o : o + 33])
o += 33
self._ct = bytes(msg_mv[o: -16])
self._ct = bytes(msg_mv[o:-16])
self._mac = bytes(msg_mv[-16:])
class Peer:
__slots__ = (
'_mx', '_pubkey', '_address', '_socket', '_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')
"_mx",
"_pubkey",
"_address",
"_socket",
"_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):
self._mx = threading.Lock()
@ -141,14 +164,16 @@ def listen_thread(cls):
max_bytes = 0x10000
while 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()
try:
disconnected_peers = []
for s in readable:
if s == cls._socket:
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._incoming = True
cls._peers.append(new_peer)
@ -160,12 +185,12 @@ def listen_thread(cls):
try:
bytes_recv = s.recv(max_bytes, socket.MSG_DONTWAIT)
except socket.error as se:
if se.args[0] not in (socket.EWOULDBLOCK, ):
logging.error('Receive error %s', str(se))
if se.args[0] not in (socket.EWOULDBLOCK,):
logging.error("Receive error %s", str(se))
disconnected_peers.append(peer)
continue
except Exception as e:
logging.error('Receive error %s', str(e))
logging.error("Receive error %s", str(e))
disconnected_peers.append(peer)
continue
@ -175,7 +200,7 @@ def listen_thread(cls):
cls.receive_bytes(peer, bytes_recv)
for s in errored:
logging.warning('Socket error')
logging.warning("Socket error")
for peer in disconnected_peers:
cls.disconnect(peer)
@ -193,7 +218,9 @@ def msg_thread(cls):
try:
now_us = time.time_ns() // 1000
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)
msg = peer._recv_messages.get(False)
cls.process_message(peer, msg)
@ -201,7 +228,7 @@ def msg_thread(cls):
except queue.Empty:
pass
except Exception as e:
logging.warning('process message error %s', str(e))
logging.warning("process message error %s", str(e))
if cls._sc.debug:
logging.error(traceback.format_exc())
@ -211,9 +238,24 @@ def msg_thread(cls):
class Network:
__slots__ = (
'_p2p_host', '_p2p_port', '_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')
"_p2p_host",
"_p2p_port",
"_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):
self._p2p_host = p2p_host
@ -278,7 +320,13 @@ class Network:
self._mx.release()
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()
try:
address = (host, port)
@ -294,7 +342,7 @@ class Network:
self.send_handshake(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._error_sockets.pop(self._error_sockets.index(peer._socket))
peer.close()
@ -305,7 +353,11 @@ class Network:
used = self._seen_ephem_keys.get(ephem_pk)
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)
@ -313,12 +365,14 @@ class Network:
self._seen_ephem_keys.popitem(last=False)
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()
try:
# TODO: Drain peer._recv_messages
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():
peer._recv_messages.get(False)
@ -332,7 +386,7 @@ class Network:
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._km = hashed[32:]
@ -361,11 +415,13 @@ class Network:
peer._mx.release()
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
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():
peer._recv_messages.get(False)
@ -375,17 +431,19 @@ class Network:
try:
now = int(time.time())
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:
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)
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._km = hashed[32:]
@ -395,7 +453,9 @@ class Network:
aad += nonce
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
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]
sig = plaintext[6:]
@ -414,26 +474,30 @@ class Network:
except Exception as e:
# 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):
nonce = peer._recv_nonce[:24]
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
cipher.update(msg_mv[0: 2])
cipher.update(msg_mv[0:2])
cipher.update(nonce)
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
if len(plaintext) >= 10:
peer._ready = True
version = plaintext[4: 10]
version = plaintext[4:10]
if peer._version is None:
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()
@ -443,32 +507,32 @@ class Network:
nonce = peer._recv_nonce[:24]
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
cipher.update(msg_mv[0: 2])
cipher.update(msg_mv[0:2])
cipher.update(nonce)
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:
peer._last_ping_rtt = (time.time_ns() // 1000) - peer._last_ping_at
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()
def send_ping(self, peer):
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]
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
cipher.update(msg_bytes)
cipher.update(nonce)
payload = ping_nonce.to_bytes(4, 'big')
payload = ping_nonce.to_bytes(4, "big")
if peer._last_ping_at == 0:
payload += self._sc._version
ct, mac = cipher.encrypt_and_digest(payload)
@ -483,14 +547,14 @@ class Network:
self.send_msg(peer, msg_bytes)
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]
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
cipher.update(msg_bytes)
cipher.update(nonce)
payload = ping_nonce.to_bytes(4, 'big')
payload = ping_nonce.to_bytes(4, "big")
ct, mac = cipher.encrypt_and_digest(payload)
msg_bytes += ct + mac
@ -502,19 +566,21 @@ class Network:
msg_encoded = msg if isinstance(msg, bytes) else msg.encode()
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._bytes_sent += len_encoded
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()
try:
mv = memoryview(msg_bytes)
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:
self.process_handshake(peer, mv)
elif msg_type == NetMessageTypes.PING:
@ -522,7 +588,7 @@ class Network:
elif msg_type == NetMessageTypes.PONG:
self.process_pong(peer, mv)
else:
self._sc.log.debug('Unknown message type %d', msg_type)
self._sc.log.debug("Unknown message type %d", msg_type)
finally:
peer._mx.release()
@ -533,7 +599,6 @@ class Network:
peer._last_received_at = time.time()
peer._bytes_received += len_received
invalid_msg = False
mv = memoryview(bytes_recv)
o = 0
@ -541,34 +606,34 @@ class Network:
while o < len_received:
if peer._receiving_length == 0:
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:
raise ValueError('Invalid start token')
if mv[o : o + 2] != MSG_START_TOKEN:
raise ValueError("Invalid start token")
o += 2
msg_len = int.from_bytes(mv[o: o + 4], 'big')
msg_len = int.from_bytes(mv[o : o + 4], "big")
o += 4
if msg_len < 2 or msg_len > MSG_MAX_SIZE:
raise ValueError('Invalid data length')
raise ValueError("Invalid data length")
# 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
if not NetMessageTypes.has_value(msg_type):
raise ValueError('Invalid msg type')
raise ValueError("Invalid msg type")
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
peer._receiving_buffer = mv[o: o + nc]
peer._receiving_buffer = mv[o : o + nc]
o += nc
else:
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
peer._receiving_buffer = mv[o: o + nc]
peer._receiving_buffer = mv[o : o + nc]
o += nc
if len(peer._receiving_buffer) == peer._receiving_length:
peer._recv_messages.put(peer._receiving_buffer)
@ -576,11 +641,13 @@ class Network:
except Exception as e:
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
def test_onion(self, path):
self._sc.log.debug('test_onion packet')
self._sc.log.debug("test_onion packet")
def get_info(self):
rv = {}
@ -589,14 +656,14 @@ class Network:
with self._mx:
for peer in self._peers:
peer_info = {
'pubkey': 'Unknown' if not peer._pubkey else peer._pubkey.hex(),
'address': '{}:{}'.format(peer._address[0], peer._address[1]),
'bytessent': peer._bytes_sent,
'bytesrecv': peer._bytes_received,
'ready': peer._ready,
'incoming': peer._incoming,
"pubkey": "Unknown" if not peer._pubkey else peer._pubkey.hex(),
"address": "{}:{}".format(peer._address[0], peer._address[1]),
"bytessent": peer._bytes_sent,
"bytesrecv": peer._bytes_received,
"ready": peer._ready,
"incoming": peer._incoming,
}
peers.append(peer_info)
rv['peers'] = peers
rv["peers"] = peers
return rv

View file

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

View file

@ -26,73 +26,91 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
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:
script = bytearray([
OpCodes.OP_IF,
OpCodes.OP_SIZE,
0x01, 0x20, # 32
OpCodes.OP_EQUALVERIFY,
op_hash,
0x20]) \
+ secret_hash \
+ bytearray([
OpCodes.OP_EQUALVERIFY,
OpCodes.OP_DUP,
OpCodes.OP_HASH160,
0x14]) \
+ pkh_redeem \
+ bytearray([OpCodes.OP_ELSE, ]) \
+ SerialiseNum(lock_val) \
+ bytearray([
op_lock,
OpCodes.OP_DROP,
OpCodes.OP_DUP,
OpCodes.OP_HASH160,
0x14]) \
+ pkh_refund \
+ bytearray([
OpCodes.OP_ENDIF,
OpCodes.OP_EQUALVERIFY,
OpCodes.OP_CHECKSIG])
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:
script = (
bytearray(
[
OpCodes.OP_IF,
OpCodes.OP_SIZE,
0x01,
0x20, # 32
OpCodes.OP_EQUALVERIFY,
op_hash,
0x20,
]
)
+ secret_hash
+ bytearray([OpCodes.OP_EQUALVERIFY, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14])
+ pkh_redeem
+ bytearray(
[
OpCodes.OP_ELSE,
]
)
+ SerialiseNum(lock_val)
+ bytearray(
[op_lock, OpCodes.OP_DROP, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14]
)
+ pkh_refund
+ bytearray([OpCodes.OP_ENDIF, OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG])
)
return script
def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256):
if script[0] != OpCodes.OP_IF or \
script[1] != OpCodes.OP_SIZE or \
script[2] != 0x01 or script[3] != 0x20 or \
script[4] != OpCodes.OP_EQUALVERIFY or \
script[5] != op_hash or \
script[6] != 0x20:
def verifyContractScript(
script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256
):
if (
script[0] != OpCodes.OP_IF
or script[1] != OpCodes.OP_SIZE
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
o = 7
script_hash = script[o: o + 32]
script_hash = script[o : o + 32]
o += 32
if script[o] != OpCodes.OP_EQUALVERIFY or \
script[o + 1] != OpCodes.OP_DUP or \
script[o + 2] != OpCodes.OP_HASH160 or \
script[o + 3] != 0x14:
if (
script[o] != OpCodes.OP_EQUALVERIFY
or script[o + 1] != OpCodes.OP_DUP
or script[o + 2] != OpCodes.OP_HASH160
or script[o + 3] != 0x14
):
return False, script_hash, None, None, None
o += 4
pkh_redeem = script[o: o + 20]
pkh_redeem = script[o : o + 20]
o += 20
if script[o] != OpCodes.OP_ELSE:
return False, script_hash, pkh_redeem, None, None
o += 1
lock_val, nb = decodeScriptNum(script, o)
o += nb
if script[o] != op_lock or \
script[o + 1] != OpCodes.OP_DROP or \
script[o + 2] != OpCodes.OP_DUP or \
script[o + 3] != OpCodes.OP_HASH160 or \
script[o + 4] != 0x14:
if (
script[o] != op_lock
or script[o + 1] != OpCodes.OP_DROP
or script[o + 2] != OpCodes.OP_DUP
or script[o + 3] != OpCodes.OP_HASH160
or script[o + 4] != 0x14
):
return False, script_hash, pkh_redeem, lock_val, None
o += 5
pkh_refund = script[o: o + 20]
pkh_refund = script[o : o + 20]
o += 20
if script[o] != OpCodes.OP_ENDIF or \
script[o + 1] != OpCodes.OP_EQUALVERIFY or \
script[o + 2] != OpCodes.OP_CHECKSIG:
if (
script[o] != OpCodes.OP_ENDIF
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 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)
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))
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.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session)
self.log.debug(
"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):
@ -118,13 +143,19 @@ class AtomicSwapInterface(ProtocolInterface):
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
addr_to = self.getMockAddrTo(ci)
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
funded_tx = ci.createRawFundedTransaction(
addr_to, amount, sub_fee, lock_unspents=False
)
return bytes.fromhex(funded_tx)
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
mock_txo_script = self.getMockScriptScriptPubkey(ci)
real_txo_script = ci.getScriptDest(script) 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
ctx = ci.loadTx(mock_tx)
@ -134,9 +165,9 @@ class AtomicSwapInterface(ProtocolInterface):
found += 1
if found < 1:
raise ValueError('Mocked output not found')
raise ValueError("Mocked output not found")
if found > 1:
raise ValueError('Too many mocked outputs found')
raise ValueError("Too many mocked outputs found")
ctx.nLockTime = 0
funded_tx = ctx.serialize()

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -19,18 +20,17 @@ from basicswap.basicswap_util import (
EventLogTypes,
)
from . import ProtocolInterface
from basicswap.contrib.test_framework.script import (
CScript, CScriptOp,
OP_CHECKMULTISIG
)
from basicswap.contrib.test_framework.script import CScript, CScriptOp, OP_CHECKMULTISIG
def addLockRefundSigs(self, xmr_swap, ci):
self.log.debug('Setting lock refund tx sigs')
self.log.debug("Setting lock refund tx sigs")
witness_stack = []
if ci.coin_type() not in (Coins.DCR, ):
witness_stack += [b'', ]
if ci.coin_type() not in (Coins.DCR,):
witness_stack += [
b"",
]
witness_stack += [
xmr_swap.al_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)
ensure(signed_tx, 'setTxSignature failed')
ensure(signed_tx, "setTxSignature failed")
xmr_swap.a_lock_refund_tx = signed_tx
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
try:
use_session = self.openSession(session)
bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id)
ensure(bid, 'Bid 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)
ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex()))
ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex()))
ensure(bid, "Bid 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
)
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
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from)
ci_from = self.ci(Coins(offer.coin_from))
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
try:
decoded_key_half = ci_follower.decodeKey(encoded_key)
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
localkeyhalf = ci_follower.decodeKey(getChainBSplitKey(self, bid, xmr_swap, offer))
localkeyhalf = ci_follower.decodeKey(
getChainBSplitKey(self, bid, xmr_swap, offer)
)
if was_sent:
kbsl = decoded_key_half
kbsf = localkeyhalf
@ -76,32 +79,54 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None):
kbsl = localkeyhalf
kbsf = decoded_key_half
ensure(ci_follower.verifyKey(kbsl), 'Invalid kbsl')
ensure(ci_follower.verifyKey(kbsf), 'Invalid kbsf')
ensure(ci_follower.verifyKey(kbsl), "Invalid kbsl")
ensure(ci_follower.verifyKey(kbsf), "Invalid kbsf")
if kbsl == kbsf:
raise ValueError('Provided key matches local key')
raise ValueError("Provided key matches local key")
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
summed_pkbs = ci_follower.getPubkey(vkbs)
if (summed_pkbs != xmr_swap.pkbs):
err_msg: str = 'Summed key does not match expected wallet spend pubkey'
if summed_pkbs != xmr_swap.pkbs:
err_msg: str = "Summed key does not match expected wallet spend pubkey"
have_pk = summed_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)
if ci_follower.coin_type() in (Coins.XMR, Coins.WOW):
address_to = self.getCachedMainWalletAddress(ci_follower, use_session)
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
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)
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)
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,
)
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()
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
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):
@ -132,13 +166,21 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
if bid.was_sent:
if xmr_swap.a_lock_refund_spend_tx:
af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(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)
af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(
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)
else:
if 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 None
@ -146,18 +188,22 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
if ci_to.curve_type() == Curves.ed25519:
xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf)
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:
for i in range(10):
xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap')
pk_recovered: bytes = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap')
xmr_swap.kbsf_dleag = ci_to.signRecoverable(
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:
break
# 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
else:
raise ValueError('Unknown curve')
raise ValueError("Unknown curve")
class XmrSwapInterface(ProtocolInterface):
@ -165,7 +211,7 @@ class XmrSwapInterface(ProtocolInterface):
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
# 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)
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:
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)
@ -191,9 +239,9 @@ class XmrSwapInterface(ProtocolInterface):
found += 1
if found < 1:
raise ValueError('Mocked output not found')
raise ValueError("Mocked output not found")
if found > 1:
raise ValueError('Too many mocked outputs found')
raise ValueError("Too many mocked outputs found")
ctx.nLockTime = 0
return ctx.serialize()

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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):
ensure(len(url_split) > 2, 'Bid ID not specified')
ensure(len(url_split) > 2, "Bid ID not specified")
try:
bid_id = bytes.fromhex(url_split[2])
assert len(bid_id) == 28
except Exception:
raise ValueError('Bad bid ID')
raise ValueError("Bad bid ID")
server = self.server
swap_client = server.swap_client
swap_client.checkSystemStatus()
@ -49,129 +50,163 @@ def page_bid(self, url_split, post_string):
show_lock_transfers = False
edit_bid = False
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 b'abandon_bid' in form_data:
if b"abandon_bid" in form_data:
try:
swap_client.abandonBid(bid_id)
messages.append('Bid abandoned')
messages.append("Bid abandoned")
except Exception as ex:
err_messages.append('Abandon failed ' + str(ex))
elif b'accept_bid' in form_data:
err_messages.append("Abandon failed " + str(ex))
elif b"accept_bid" in form_data:
try:
swap_client.acceptBid(bid_id)
messages.append('Bid accepted')
messages.append("Bid accepted")
except Exception as ex:
err_messages.append('Accept failed ' + str(ex))
elif b'show_txns' in form_data:
err_messages.append("Accept failed " + str(ex))
elif b"show_txns" in form_data:
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
elif b'show_bidder_seq_diagram' in form_data:
elif b"show_bidder_seq_diagram" in form_data:
show_bidder_seq_diagram = True
elif b'edit_bid' in form_data:
elif b"edit_bid" in form_data:
edit_bid = True
elif b'edit_bid_submit' in form_data:
elif b"edit_bid_submit" in form_data:
data = {
'bid_state': int(form_data[b'new_state'][0]),
'bid_action': int(get_data_entry_or(form_data, 'new_action', -1)),
'debug_ind': int(get_data_entry_or(form_data, 'debugind', -1)),
'kbs_other': get_data_entry_or(form_data, 'kbs_other', None),
"bid_state": int(form_data[b"new_state"][0]),
"bid_action": int(get_data_entry_or(form_data, "new_action", -1)),
"debug_ind": int(get_data_entry_or(form_data, "debugind", -1)),
"kbs_other": get_data_entry_or(form_data, "kbs_other", None),
}
try:
swap_client.manualBidUpdate(bid_id, data)
messages.append('Bid edited')
messages.append("Bid edited")
except Exception as ex:
err_messages.append('Edit failed ' + str(ex))
elif b'view_tx_submit' in form_data:
err_messages.append("Edit failed " + str(ex))
elif b"view_tx_submit" in form_data:
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:
err_messages.append('Invalid transaction selected.')
err_messages.append("Invalid transaction selected.")
view_tx_ind = None
elif b'view_lock_transfers' in form_data:
elif b"view_lock_transfers" in form_data:
show_txns = True
show_lock_transfers = True
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:
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_offerer_seq_diagram'] = show_offerer_seq_diagram
data["show_bidder_seq_diagram"] = show_bidder_seq_diagram
data["show_offerer_seq_diagram"] = show_offerer_seq_diagram
old_states = listOldBidStates(bid)
if len(data['addr_from_label']) > 0:
data['addr_from_label'] = '(' + data['addr_from_label'] + ')'
data['can_accept_bid'] = True if bid.state == BidStates.BID_RECEIVED else False
if len(data["addr_from_label"]) > 0:
data["addr_from_label"] = "(" + data["addr_from_label"] + ")"
data["can_accept_bid"] = True if bid.state == BidStates.BID_RECEIVED else False
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')
return self.render_template(template, {
'bid_id': bid_id.hex(),
'messages': messages,
'err_messages': err_messages,
'data': data,
'edit_bid': edit_bid,
'old_states': old_states,
'summary': summary,
})
template = server.env.get_template(
"bid_xmr.html" if offer.swap_type == SwapTypes.XMR_SWAP else "bid.html"
)
return self.render_template(
template,
{
"bid_id": bid_id.hex(),
"messages": messages,
"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
swap_client = server.swap_client
swap_client.checkSystemStatus()
summary = swap_client.getSummary()
filters = {
'page_no': 1,
'bid_state_ind': -1,
'with_expired': True,
'limit': PAGE_LIMIT,
'sort_by': 'created_at',
'sort_dir': 'desc',
"page_no": 1,
"bid_state_ind": -1,
"with_expired": True,
"limit": PAGE_LIMIT,
"sort_by": "created_at",
"sort_dir": "desc",
}
if available:
filters['bid_state_ind'] = BidStates.BID_RECEIVED
filters['with_expired'] = False
filters["bid_state_ind"] = BidStates.BID_RECEIVED
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 = []
form_data = self.checkForm(post_string, 'bids', messages)
form_data = self.checkForm(post_string, "bids", messages)
if form_data:
if have_data_entry(form_data, 'clearfilters'):
if have_data_entry(form_data, "clearfilters"):
swap_client.clearFilters(filter_prefix)
else:
if have_data_entry(form_data, 'sort_by'):
sort_by = get_data_entry(form_data, 'sort_by')
ensure(sort_by in ['created_at', ], 'Invalid sort by')
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 have_data_entry(form_data, "sort_by"):
sort_by = get_data_entry(form_data, "sort_by")
ensure(
sort_by
in [
"created_at",
],
"Invalid sort by",
)
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:
try:
state = BidStates(state_ind)
except Exception:
raise ValueError('Invalid state')
filters['bid_state_ind'] = state_ind
if have_data_entry(form_data, 'with_expired'):
with_expired = toBool(get_data_entry(form_data, 'with_expired'))
filters['with_expired'] = with_expired
_ = BidStates(state_ind)
except Exception as e: # noqa: F841
raise ValueError("Invalid state")
filters["bid_state_ind"] = state_ind
if have_data_entry(form_data, "with_expired"):
with_expired = toBool(get_data_entry(form_data, "with_expired"))
filters["with_expired"] = with_expired
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)
else:
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)
page_data = {
'bid_states': listBidStates(),
"bid_states": listBidStates(),
}
template = server.env.get_template('bids.html')
return self.render_template(template, {
'page_type_sent': 'Bids Sent' if sent else '',
'page_type_available': 'Bids Available' if available else '',
'page_type_received': 'Received Bids' if received else '',
'page_type_sent_description': 'All the bids you have placed on offers.' if sent else '',
'page_type_available_description': 'Bids available for you to accept.' if available else '',
'page_type_received_description': '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),
})
template = server.env.get_template("bids.html")
return self.render_template(
template,
{
"page_type_sent": "Bids Sent" if sent else "",
"page_type_available": "Bids Available" if available else "",
"page_type_received": "Received Bids" if received else "",
"page_type_sent_description": (
"All the bids you have placed on offers." if sent else ""
),
"page_type_available_description": (
"Bids available for you to accept." if available else ""
),
"page_type_received_description": (
"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 -*-
# Copyright (c) 2023 The BSX Developers
# Copyright (c) 2023-2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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
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 have_data_entry(form_data, 'reinit_xmr'):
if have_data_entry(form_data, "reinit_xmr"):
try:
swap_client.initialiseWallet(Coins.XMR)
messages.append('Done.')
messages.append("Done.")
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:
swap_client.log.warning('Removing expired data.')
swap_client.log.warning("Removing expired data.")
remove_expired_data(swap_client)
messages.append('Done.')
messages.append("Done.")
except Exception as e:
if swap_client.debug is True:
swap_client.log.error(traceback.format_exc())
else:
swap_client.log.error(f'remove_expired_data: {e}')
err_messages.append('Failed.')
swap_client.log.error(f"remove_expired_data: {e}")
err_messages.append("Failed.")
template = server.env.get_template('debug.html')
return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'result': result,
'summary': summary,
})
template = server.env.get_template("debug.html")
return self.render_template(
template,
{
"messages": messages,
"err_messages": err_messages,
"result": result,
"summary": summary,
},
)

View file

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

View file

@ -25,54 +25,71 @@ def page_identity(self, url_split, post_string):
swap_client.checkSystemStatus()
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]
page_data = {'identity_address': identity_address}
page_data = {"identity_address": identity_address}
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 have_data_entry(form_data, 'edit'):
page_data['show_edit_form'] = True
if have_data_entry(form_data, 'apply'):
if have_data_entry(form_data, "edit"):
page_data["show_edit_form"] = True
if have_data_entry(form_data, "apply"):
try:
data = {
'label': get_data_entry_or(form_data, 'label', ''),
'note': get_data_entry_or(form_data, 'note', ''),
'automation_override': get_data_entry(form_data, 'automation_override'),
"label": get_data_entry_or(form_data, "label", ""),
"note": get_data_entry_or(form_data, "note", ""),
"automation_override": get_data_entry(
form_data, "automation_override"
),
}
swap_client.setIdentityData({'address': identity_address}, data)
messages.append('Updated')
swap_client.setIdentityData({"address": identity_address}, data)
messages.append("Updated")
except Exception as e:
err_messages.append(str(e))
try:
identity = swap_client.getIdentity(identity_address)
if identity is None:
raise ValueError('Unknown address')
raise ValueError("Unknown address")
automation_override = zeroIfNone(identity.automation_override)
page_data.update({
'label': '' if identity.label is None else identity.label,
'num_sent_bids_successful': zeroIfNone(identity.num_sent_bids_successful),
'num_recv_bids_successful': zeroIfNone(identity.num_recv_bids_successful),
'num_sent_bids_rejected': zeroIfNone(identity.num_sent_bids_rejected),
'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,
})
page_data.update(
{
"label": "" if identity.label is None else identity.label,
"num_sent_bids_successful": zeroIfNone(
identity.num_sent_bids_successful
),
"num_recv_bids_successful": zeroIfNone(
identity.num_recv_bids_successful
),
"num_sent_bids_rejected": zeroIfNone(identity.num_sent_bids_rejected),
"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:
err_messages.append(e)
template = server.env.get_template('identity.html')
return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'data': page_data,
'automation_override_options': [(int(opt), strAutomationOverrideOption(opt)) for opt in AutomationOverrideOptions if opt > 0],
'summary': summary,
})
template = server.env.get_template("identity.html")
return self.render_template(
template,
{
"messages": messages,
"err_messages": err_messages,
"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 = []
err_messages = []
active_tab = 'default'
form_data = self.checkForm(post_string, 'settings', err_messages)
active_tab = "default"
form_data = self.checkForm(post_string, "settings", err_messages)
if form_data:
try:
if have_data_entry(form_data, 'apply_general'):
active_tab = 'general'
if have_data_entry(form_data, "apply_general"):
active_tab = "general"
data = {
'debug': toBool(get_data_entry(form_data, 'debugmode')),
'debug_ui': toBool(get_data_entry(form_data, 'debugui')),
'expire_db_records': toBool(get_data_entry(form_data, 'expire_db_records')),
"debug": toBool(get_data_entry(form_data, "debugmode")),
"debug_ui": toBool(get_data_entry(form_data, "debugui")),
"expire_db_records": toBool(
get_data_entry(form_data, "expire_db_records")
),
}
swap_client.editGeneralSettings(data)
elif have_data_entry(form_data, 'apply_chart'):
active_tab = 'general'
elif have_data_entry(form_data, "apply_chart"):
active_tab = "general"
data = {
'show_chart': toBool(get_data_entry(form_data, 'showchart')),
'chart_api_key': html.unescape(get_data_entry_or(form_data, 'chartapikey', '')),
'coingecko_api_key': html.unescape(get_data_entry_or(form_data, 'coingeckoapikey', '')),
'enabled_chart_coins': get_data_entry_or(form_data, 'enabledchartcoins', ''),
"show_chart": toBool(get_data_entry(form_data, "showchart")),
"chart_api_key": html.unescape(
get_data_entry_or(form_data, "chartapikey", "")
),
"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)
elif have_data_entry(form_data, 'apply_tor'):
active_tab = 'tor'
elif have_data_entry(form_data, "apply_tor"):
active_tab = "tor"
# 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():
if have_data_entry(form_data, 'apply_' + name):
data = {'lookups': get_data_entry(form_data, 'lookups_' + name)}
if name in ('monero', 'wownero'):
data['fee_priority'] = int(get_data_entry(form_data, 'fee_priority_' + name))
data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False
data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name)
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
for name, c in swap_client.settings["chainclients"].items():
if have_data_entry(form_data, "apply_" + name):
data = {"lookups": get_data_entry(form_data, "lookups_" + name)}
if name in ("monero", "wownero"):
data["fee_priority"] = int(
get_data_entry(form_data, "fee_priority_" + name)
)
data["manage_daemon"] = (
True
if get_data_entry(form_data, "managedaemon_" + name)
== "true"
else False
)
data["rpchost"] = get_data_entry(form_data, "rpchost_" + name)
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:
data['conf_target'] = int(get_data_entry(form_data, 'conf_target_' + name))
if name == 'particl':
data['anon_tx_ring_size'] = int(get_data_entry(form_data, 'rct_ring_size_' + name))
data["conf_target"] = int(
get_data_entry(form_data, "conf_target_" + 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:
messages.append('Settings applied.')
messages.append("Settings applied.")
if suggest_reboot is True:
messages.append('Please restart BasicSwap.')
elif have_data_entry(form_data, 'enable_' + name):
messages.append("Please restart BasicSwap.")
elif have_data_entry(form_data, "enable_" + name):
swap_client.enableCoin(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()
elif have_data_entry(form_data, 'disable_' + name):
elif have_data_entry(form_data, "disable_" + name):
swap_client.disableCoin(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()
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:
err_messages.append(str(e))
chains_formatted = []
sorted_names = sorted(swap_client.settings['chainclients'].keys())
sorted_names = sorted(swap_client.settings["chainclients"].keys())
for name in sorted_names:
c = swap_client.settings['chainclients'][name]
c = swap_client.settings["chainclients"][name]
try:
display_name = getCoinName(swap_client.getCoinIdFromName(name))
except Exception:
display_name = name
chains_formatted.append({
'name': name,
'display_name': display_name,
'lookups': c.get('chain_lookups', 'local'),
'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')
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)
chains_formatted.append(
{
"name": name,
"display_name": display_name,
"lookups": c.get("chain_lookups", "local"),
"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"
)
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:
chains_formatted[-1]['conf_target'] = c.get('conf_target', 2)
chains_formatted[-1]["conf_target"] = c.get("conf_target", 2)
if name == 'particl':
chains_formatted[-1]['anon_tx_ring_size'] = c.get('anon_tx_ring_size', 12)
if name == "particl":
chains_formatted[-1]["anon_tx_ring_size"] = c.get("anon_tx_ring_size", 12)
else:
if c.get('connection_type', 'Unknown') == 'none':
if 'connection_type_prev' in c:
chains_formatted[-1]['can_reenable'] = True
if c.get("connection_type", "Unknown") == "none":
if "connection_type_prev" in c:
chains_formatted[-1]["can_reenable"] = True
else:
chains_formatted[-1]['can_disable'] = True
chains_formatted[-1]["can_disable"] = True
general_settings = {
'debug': swap_client.debug,
'debug_ui': swap_client.debug_ui,
'expire_db_records': swap_client._expire_db_records,
"debug": swap_client.debug,
"debug_ui": swap_client.debug_ui,
"expire_db_records": swap_client._expire_db_records,
}
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'))
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"
)
)
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:
coingecko_api_key = html.escape(bytes.fromhex(swap_client.settings.get('coingecko_api_key_enc', '')).decode('utf-8'))
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"
)
)
else:
coingecko_api_key = swap_client.settings.get('coingecko_api_key', '')
coingecko_api_key = swap_client.settings.get("coingecko_api_key", "")
chart_settings = {
'show_chart': swap_client.settings.get('show_chart', True),
'chart_api_key': chart_api_key,
'coingecko_api_key': coingecko_api_key,
'enabled_chart_coins': swap_client.settings.get('enabled_chart_coins', ''),
"show_chart": swap_client.settings.get("show_chart", True),
"chart_api_key": chart_api_key,
"coingecko_api_key": coingecko_api_key,
"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 = {
'use_tor': swap_client.use_tor_proxy,
'proxy_host': swap_client.tor_proxy_host,
'proxy_port': swap_client.tor_proxy_port,
'control_password': html.escape(tor_control_password),
'control_port': swap_client.tor_control_port,
"use_tor": swap_client.use_tor_proxy,
"proxy_host": swap_client.tor_proxy_host,
"proxy_port": swap_client.tor_proxy_port,
"control_password": html.escape(tor_control_password),
"control_port": swap_client.tor_control_port,
}
template = server.env.get_template('settings.html')
return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'summary': swap_client.getSummary(),
'chains': chains_formatted,
'general_settings': general_settings,
'chart_settings': chart_settings,
'tor_settings': tor_settings,
'active_tab': active_tab,
})
template = server.env.get_template("settings.html")
return self.render_template(
template,
{
"messages": messages,
"err_messages": err_messages,
"summary": swap_client.getSummary(),
"chains": chains_formatted,
"general_settings": general_settings,
"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()
filters = {
'page_no': 1,
'limit': PAGE_LIMIT,
'sort_by': 'created_at',
'sort_dir': 'desc',
'addr_type': -1,
"page_no": 1,
"limit": PAGE_LIMIT,
"sort_by": "created_at",
"sort_dir": "desc",
"addr_type": -1,
}
page_data = {}
@ -41,96 +41,114 @@ def page_smsgaddresses(self, url_split, post_string):
smsgaddresses = []
listaddresses = True
form_data = self.checkForm(post_string, 'smsgaddresses', err_messages)
form_data = self.checkForm(post_string, "smsgaddresses", err_messages)
if form_data:
edit_address_id = None
for key in form_data:
if key.startswith(b'editaddr_'):
edit_address_id = int(key.split(b'_')[1])
if key.startswith(b"editaddr_"):
edit_address_id = int(key.split(b"_")[1])
break
if edit_address_id is not None:
listaddresses = False
page_data['edit_address'] = edit_address_id
page_data['addr_data'] = swap_client.listAllSMSGAddresses({'addr_id': edit_address_id})[0]
elif have_data_entry(form_data, 'saveaddr'):
edit_address_id = int(get_data_entry(form_data, 'edit_address_id'))
edit_addr = get_data_entry(form_data, 'edit_address')
active_ind = int(get_data_entry(form_data, 'active_ind'))
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):
page_data["edit_address"] = edit_address_id
page_data["addr_data"] = swap_client.listAllSMSGAddresses(
{"addr_id": edit_address_id}
)[0]
elif have_data_entry(form_data, "saveaddr"):
edit_address_id = int(get_data_entry(form_data, "edit_address_id"))
edit_addr = get_data_entry(form_data, "edit_address")
active_ind = int(get_data_entry(form_data, "active_ind"))
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
page_data['edit_address'] = edit_address_id
page_data["edit_address"] = edit_address_id
else:
swap_client.editSMSGAddress(edit_addr, active_ind=active_ind, addressnote=addressnote)
messages.append(f'Edited address {edit_addr}')
elif have_data_entry(form_data, 'shownewaddr'):
swap_client.editSMSGAddress(
edit_addr, active_ind=active_ind, addressnote=addressnote
)
messages.append(f"Edited address {edit_addr}")
elif have_data_entry(form_data, "shownewaddr"):
listaddresses = False
page_data['new_address'] = True
elif have_data_entry(form_data, 'showaddaddr'):
page_data["new_address"] = True
elif have_data_entry(form_data, "showaddaddr"):
listaddresses = False
page_data['new_send_address'] = True
elif have_data_entry(form_data, 'createnewaddr'):
addressnote = get_data_entry_or(form_data, 'addressnote', '')
if not validateTextInput(addressnote, 'Address note', err_messages, max_length=30):
page_data["new_send_address"] = True
elif have_data_entry(form_data, "createnewaddr"):
addressnote = get_data_entry_or(form_data, "addressnote", "")
if not validateTextInput(
addressnote, "Address note", err_messages, max_length=30
):
listaddresses = False
page_data['new_address'] = True
page_data["new_address"] = True
else:
new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote)
messages.append(f'Created address {new_addr}, pubkey {pubkey}')
elif have_data_entry(form_data, 'createnewsendaddr'):
pubkey_hex = get_data_entry(form_data, 'addresspubkey')
addressnote = get_data_entry_or(form_data, 'addressnote', '')
if not validateTextInput(addressnote, 'Address note', messages, max_length=30) or \
not validateTextInput(pubkey_hex, 'Pubkey', messages, max_length=66):
messages.append(f"Created address {new_addr}, pubkey {pubkey}")
elif have_data_entry(form_data, "createnewsendaddr"):
pubkey_hex = get_data_entry(form_data, "addresspubkey")
addressnote = get_data_entry_or(form_data, "addressnote", "")
if not validateTextInput(
addressnote, "Address note", messages, max_length=30
) or not validateTextInput(pubkey_hex, "Pubkey", messages, max_length=66):
listaddresses = False
page_data['new_send_address'] = True
page_data["new_send_address"] = True
else:
new_addr = swap_client.addSMSGAddress(pubkey_hex, addressnote=addressnote)
messages.append(f'Added address {new_addr}')
new_addr = swap_client.addSMSGAddress(
pubkey_hex, addressnote=addressnote
)
messages.append(f"Added address {new_addr}")
if have_data_entry(form_data, 'clearfilters'):
swap_client.clearFilters('page_smsgaddresses')
if have_data_entry(form_data, "clearfilters"):
swap_client.clearFilters("page_smsgaddresses")
else:
if have_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')
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, 'filter_addressnote'):
addressnote = get_data_entry(form_data, 'filter_addressnote')
if validateTextInput(addressnote, 'Address note', err_messages, max_length=30):
filters['addressnote'] = addressnote
if have_data_entry(form_data, 'filter_addr_type'):
filters['addr_type'] = int(get_data_entry(form_data, 'filter_addr_type'))
if have_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")
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, "filter_addressnote"):
addressnote = get_data_entry(form_data, "filter_addressnote")
if validateTextInput(
addressnote, "Address note", err_messages, max_length=30
):
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)
if have_data_entry(form_data, 'applyfilters'):
swap_client.setFilters('page_smsgaddresses', filters)
if have_data_entry(form_data, "applyfilters"):
swap_client.setFilters("page_smsgaddresses", filters)
else:
saved_filters = swap_client.getFilters('page_smsgaddresses')
saved_filters = swap_client.getFilters("page_smsgaddresses")
if saved_filters:
filters.update(saved_filters)
if listaddresses is True:
smsgaddresses = swap_client.listAllSMSGAddresses(filters)
page_data['addr_types'] = [(int(t), strAddressType(t)) for t in AddressTypes]
page_data['network_addr'] = swap_client.network_addr
page_data["addr_types"] = [(int(t), strAddressType(t)) for t in AddressTypes]
page_data["network_addr"] = swap_client.network_addr
for addr in smsgaddresses:
addr['type'] = strAddressType(addr['type'])
addr["type"] = strAddressType(addr["type"])
template = self.server.env.get_template('smsgaddresses.html')
return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'filters': filters,
'data': page_data,
'smsgaddresses': smsgaddresses,
'page_data': page_data,
'summary': summary,
})
template = self.server.env.get_template("smsgaddresses.html")
return self.render_template(
template,
{
"messages": messages,
"err_messages": err_messages,
"filters": filters,
"data": page_data,
"smsgaddresses": smsgaddresses,
"page_data": page_data,
"summary": summary,
},
)

View file

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

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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):
wf = {
'name': ci.coin_name(),
'version': w.get('version', '?'),
'ticker': ci.ticker_mainnet(),
'cid': str(int(ci.coin_type())),
'balance': w.get('balance', '?'),
'blocks': w.get('blocks', '?'),
'synced': w.get('synced', '?'),
'expected_seed': w.get('expected_seed', '?'),
'encrypted': w.get('encrypted', '?'),
'locked': w.get('locked', '?'),
'updating': w.get('updating', '?'),
'havedata': True,
"name": ci.coin_name(),
"version": w.get("version", "?"),
"ticker": ci.ticker_mainnet(),
"cid": str(int(ci.coin_type())),
"balance": w.get("balance", "?"),
"blocks": w.get("blocks", "?"),
"synced": w.get("synced", "?"),
"expected_seed": w.get("expected_seed", "?"),
"encrypted": w.get("encrypted", "?"),
"locked": w.get("locked", "?"),
"updating": w.get("updating", "?"),
"havedata": True,
}
if w.get('bootstrapping', False) is True:
wf['bootstrapping'] = True
if 'known_block_count' in w:
wf['known_block_count'] = w['known_block_count']
if 'locked_utxos' in w:
wf['locked_utxos'] = w['locked_utxos']
if w.get("bootstrapping", False) is True:
wf["bootstrapping"] = True
if "known_block_count" in w:
wf["known_block_count"] = w["known_block_count"]
if "locked_utxos" in w:
wf["locked_utxos"] = w["locked_utxos"]
if 'balance' in w and 'unconfirmed' in w:
wf['balance_all'] = float(w['balance']) + float(w['unconfirmed'])
if 'lastupdated' in w:
wf['lastupdated'] = format_timestamp(w['lastupdated'])
if "balance" in w and "unconfirmed" in w:
wf["balance_all"] = float(w["balance"]) + float(w["unconfirmed"])
if "lastupdated" in w:
wf["lastupdated"] = format_timestamp(w["lastupdated"])
pending: int = 0
if 'unconfirmed' in w and float(w['unconfirmed']) > 0.0:
pending += ci.make_int(w['unconfirmed'])
if 'immature' in w and float(w['immature']) > 0.0:
pending += ci.make_int(w['immature'])
if "unconfirmed" in w and float(w["unconfirmed"]) > 0.0:
pending += ci.make_int(w["unconfirmed"])
if "immature" in w and float(w["immature"]) > 0.0:
pending += ci.make_int(w["immature"])
if pending > 0.0:
wf['pending'] = ci.format_amount(pending)
wf["pending"] = ci.format_amount(pending)
if ci.coin_type() == Coins.PART:
wf['stealth_address'] = w.get('stealth_address', '?')
wf['blind_balance'] = w.get('blind_balance', '?')
if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0:
wf['blind_unconfirmed'] = w['blind_unconfirmed']
wf['anon_balance'] = w.get('anon_balance', '?')
if 'anon_pending' in w and float(w['anon_pending']) > 0.0:
wf['anon_pending'] = w['anon_pending']
wf["stealth_address"] = w.get("stealth_address", "?")
wf["blind_balance"] = w.get("blind_balance", "?")
if "blind_unconfirmed" in w and float(w["blind_unconfirmed"]) > 0.0:
wf["blind_unconfirmed"] = w["blind_unconfirmed"]
wf["anon_balance"] = w.get("anon_balance", "?")
if "anon_pending" in w and float(w["anon_pending"]) > 0.0:
wf["anon_pending"] = w["anon_pending"]
elif ci.coin_type() == Coins.LTC:
wf['mweb_address'] = w.get('mweb_address', '?')
wf['mweb_balance'] = w.get('mweb_balance', '?')
wf['mweb_pending'] = w.get('mweb_pending', '?')
wf["mweb_address"] = w.get("mweb_address", "?")
wf["mweb_balance"] = w.get("mweb_balance", "?")
wf["mweb_pending"] = w.get("mweb_pending", "?")
checkAddressesOwned(swap_client, ci, wf)
return wf
@ -91,19 +92,18 @@ def page_wallets(self, url_split, post_string):
for k in sk:
w = wallets[k]
if 'error' in w:
wallets_formatted.append({
'cid': str(int(k)),
'error': w['error']
})
if "error" in w:
wallets_formatted.append({"cid": str(int(k)), "error": w["error"]})
continue
if 'no_data' in w:
wallets_formatted.append({
'name': w['name'],
'havedata': False,
'updating': w['updating'],
})
if "no_data" in w:
wallets_formatted.append(
{
"name": w["name"],
"havedata": False,
"updating": w["updating"],
}
)
continue
ci = swap_client.ci(k)
@ -111,17 +111,20 @@ def page_wallets(self, url_split, post_string):
wallets_formatted.append(wf)
template = server.env.get_template('wallets.html')
return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'wallets': wallets_formatted,
'summary': summary,
})
template = server.env.get_template("wallets.html")
return self.render_template(
template,
{
"messages": messages,
"err_messages": err_messages,
"wallets": wallets_formatted,
"summary": summary,
},
)
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]
server = self.server
swap_client = server.swap_client
@ -136,94 +139,130 @@ def page_wallet(self, url_split, post_string):
show_utxo_groups: bool = False
withdrawal_successful: 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:
cid = str(int(coin_id))
estimate_fee: bool = have_data_entry(form_data, 'estfee_' + cid)
withdraw: bool = have_data_entry(form_data, 'withdraw_' + cid)
if have_data_entry(form_data, 'newaddr_' + cid):
estimate_fee: bool = have_data_entry(form_data, "estfee_" + cid)
withdraw: bool = have_data_entry(form_data, "withdraw_" + cid)
if have_data_entry(form_data, "newaddr_" + cid):
swap_client.cacheNewAddressForCoin(coin_id)
elif have_data_entry(form_data, 'forcerefresh'):
elif have_data_entry(form_data, "forcerefresh"):
force_refresh = True
elif have_data_entry(form_data, 'newmwebaddr_' + cid):
elif have_data_entry(form_data, "newmwebaddr_" + cid):
swap_client.cacheNewStealthAddressForCoin(coin_id)
elif have_data_entry(form_data, 'reseed_' + cid):
elif have_data_entry(form_data, "reseed_" + cid):
try:
swap_client.reseedWallet(coin_id)
messages.append('Reseed complete ' + str(coin_id))
messages.append("Reseed complete " + str(coin_id))
except Exception as ex:
err_messages.append('Reseed failed ' + str(ex))
err_messages.append("Reseed failed " + str(ex))
swap_client.updateWalletsInfo(True, coin_id)
elif withdraw or estimate_fee:
subfee = True if have_data_entry(form_data, 'subfee_' + cid) else False
page_data['wd_subfee_' + cid] = subfee
subfee = True if have_data_entry(form_data, "subfee_" + cid) else False
page_data["wd_subfee_" + cid] = subfee
sweepall = True if have_data_entry(form_data, 'sweepall_' + cid) else False
page_data['wd_sweepall_' + cid] = sweepall
sweepall = True if have_data_entry(form_data, "sweepall_" + cid) else False
page_data["wd_sweepall_" + cid] = sweepall
value = None
if not sweepall:
try:
value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_value_' + cid] = value
except Exception as e:
err_messages.append('Missing value')
value = form_data[bytes("amt_" + cid, "utf-8")][0].decode("utf-8")
page_data["wd_value_" + cid] = value
except Exception as e: # noqa: F841
err_messages.append("Missing value")
try:
address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_address_' + cid] = address
except Exception as e:
err_messages.append('Missing address')
address = form_data[bytes("to_" + cid, "utf-8")][0].decode("utf-8")
page_data["wd_address_" + cid] = address
except Exception as e: # noqa: F841
err_messages.append("Missing address")
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):
ci = swap_client.ci(coin_id)
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:
try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8')
type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_type_from_' + cid] = type_from
page_data['wd_type_to_' + cid] = type_to
except Exception as e:
err_messages.append('Missing type')
type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
0
].decode("utf-8")
type_to = form_data[bytes("withdraw_type_to_" + cid, "utf-8")][
0
].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:
try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_type_from_' + cid] = type_from
except Exception as e:
err_messages.append('Missing type')
type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
0
].decode("utf-8")
page_data["wd_type_from_" + cid] = type_from
except Exception as e: # noqa: F841
err_messages.append("Missing type")
if len(err_messages) == 0:
ci = swap_client.ci(coin_id)
ticker: str = ci.ticker()
try:
if coin_id == Coins.PART:
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
txid = swap_client.withdrawParticl(
type_from, type_to, value, address, subfee
)
messages.append(
"Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}".format(
value, ticker, type_from, type_to, address, txid
)
)
elif coin_id == Coins.LTC:
txid = swap_client.withdrawLTC(type_from, value, address, subfee)
messages.append('Withdrew {} {} (from {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, address, txid))
txid = swap_client.withdrawLTC(
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):
if estimate_fee:
fee_estimate = ci.estimateFee(value, address, sweepall)
suffix = 's' if fee_estimate['num_txns'] > 1 else ''
sum_fees = ci.format_amount(fee_estimate['sum_fee'])
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}.')
page_data['fee_estimate'] = fee_estimate
suffix = "s" if fee_estimate["num_txns"] > 1 else ""
sum_fees = ci.format_amount(fee_estimate["sum_fee"])
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}.'
)
page_data["fee_estimate"] = fee_estimate
else:
txid = swap_client.withdrawCoin(coin_id, value, address, sweepall)
txid = swap_client.withdrawCoin(
coin_id, value, address, 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:
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
messages.append('Note: The wallet balance can take a while to update.')
messages.append(
"Withdrew {} {} to address {}<br/>In txid: {}".format(
value, ticker, address, txid
)
)
messages.append(
"Note: The wallet balance can take a while to update."
)
else:
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:
withdrawal_successful = True
except Exception as e:
@ -232,41 +271,44 @@ def page_wallet(self, url_split, post_string):
err_messages.append(str(e))
if not estimate_fee:
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
elif have_data_entry(form_data, 'create_utxo'):
elif have_data_entry(form_data, "create_utxo"):
show_utxo_groups = True
try:
value = get_data_entry(form_data, 'utxo_value')
page_data['utxo_value'] = value
value = get_data_entry(form_data, "utxo_value")
page_data["utxo_value"] = value
ci = swap_client.ci(coin_id)
value_sats = ci.make_int(value)
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:
err_messages.append(str(e))
if swap_client.debug is True:
swap_client.log.error(traceback.format_exc())
swap_client.updateWalletsInfo(force_refresh, only_coin=coin_id, wait_for_complete=True)
wallets = swap_client.getCachedWalletsInfo({'coin_id': coin_id})
swap_client.updateWalletsInfo(
force_refresh, only_coin=coin_id, wait_for_complete=True
)
wallets = swap_client.getCachedWalletsInfo({"coin_id": coin_id})
wallet_data = {}
for k in wallets.keys():
w = wallets[k]
if 'error' in w:
wallet_data = {
'cid': str(int(k)),
'error': w['error']
}
if "error" in w:
wallet_data = {"cid": str(int(k)), "error": w["error"]}
continue
if 'no_data' in w:
if "no_data" in w:
wallet_data = {
'name': w['name'],
'havedata': False,
'updating': w['updating'],
"name": w["name"],
"havedata": False,
"updating": w["updating"],
}
continue
@ -277,55 +319,68 @@ def page_wallet(self, url_split, post_string):
fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
wallet_data['fee_rate'] = ci.format_amount(int(fee_rate * ci.COIN()))
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['deposit_address'] = w.get('deposit_address', 'Refresh necessary')
wallet_data["fee_rate"] = ci.format_amount(int(fee_rate * ci.COIN()))
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["deposit_address"] = w.get("deposit_address", "Refresh necessary")
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:
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:
wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid]
if 'wd_type_to_' + cid in page_data:
wallet_data['wd_type_to'] = page_data['wd_type_to_' + cid]
if "wd_type_from_" + cid in page_data:
wallet_data["wd_type_from"] = page_data["wd_type_from_" + cid]
if "wd_type_to_" + cid in page_data:
wallet_data["wd_type_to"] = page_data["wd_type_to_" + cid]
if 'utxo_value' in page_data:
wallet_data['utxo_value'] = page_data['utxo_value']
if "utxo_value" in page_data:
wallet_data["utxo_value"] = page_data["utxo_value"]
if not withdrawal_successful:
if 'wd_value_' + cid in page_data:
wallet_data['wd_value'] = page_data['wd_value_' + cid]
if 'wd_address_' + cid in page_data:
wallet_data['wd_address'] = page_data['wd_address_' + cid]
if 'wd_subfee_' + cid in page_data:
wallet_data['wd_subfee'] = page_data['wd_subfee_' + cid]
if 'wd_sweepall_' + cid in page_data:
wallet_data['wd_sweepall'] = page_data['wd_sweepall_' + cid]
if 'fee_estimate' in page_data:
wallet_data['est_fee'] = ci.format_amount(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 "wd_value_" + cid in page_data:
wallet_data["wd_value"] = page_data["wd_value_" + cid]
if "wd_address_" + cid in page_data:
wallet_data["wd_address"] = page_data["wd_address_" + cid]
if "wd_subfee_" + cid in page_data:
wallet_data["wd_subfee"] = page_data["wd_subfee_" + cid]
if "wd_sweepall_" + cid in page_data:
wallet_data["wd_sweepall"] = page_data["wd_sweepall_" + cid]
if "fee_estimate" in page_data:
wallet_data["est_fee"] = ci.format_amount(
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:
utxo_groups = ''
utxo_groups = ""
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:
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['utxo_groups'] = utxo_groups
wallet_data["show_utxo_groups"] = True
wallet_data["utxo_groups"] = utxo_groups
checkAddressesOwned(swap_client, ci, wallet_data)
template = server.env.get_template('wallet.html')
return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'w': wallet_data,
'summary': summary,
'block_unknown_seeds': swap_client._restrict_unknown_seed_wallets,
})
template = server.env.get_template("wallet.html")
return self.render_template(
template,
{
"messages": messages,
"err_messages": err_messages,
"w": wallet_data,
"summary": summary,
"block_unknown_seeds": swap_client._restrict_unknown_seed_wallets,
},
)

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# 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
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):
@ -41,7 +56,7 @@ def tickerToCoinId(ticker):
for c in Coins:
if c.name == search_str:
return c.value
raise ValueError('Unknown coin')
raise ValueError("Unknown coin")
def getCoinType(coin_type_ind):
@ -55,9 +70,9 @@ def getCoinType(coin_type_ind):
def validateAmountString(amount, ci):
if not isinstance(amount, str):
return
ar = amount.split('.')
ar = amount.split(".")
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):
@ -66,24 +81,24 @@ def inputAmount(amount_str, ci):
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)
key_bytes = name.encode('utf-8')
key_bytes = name.encode("utf-8")
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
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.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):
if 'is_json' in post_data:
if "is_json" 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):
@ -96,18 +111,18 @@ def setCoinFilter(form_data, field_name):
try:
return Coins(coin_type)
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):
if form_data and have_data_entry(form_data, 'pageback'):
filters['page_no'] = int(form_data[b'pageno'][0]) - 1
if filters['page_no'] < 1:
filters['page_no'] = 1
elif form_data and have_data_entry(form_data, 'pageforwards'):
filters['page_no'] = int(form_data[b'pageno'][0]) + 1
if filters['page_no'] > 1:
filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT
if form_data and have_data_entry(form_data, "pageback"):
filters["page_no"] = int(form_data[b"pageno"][0]) - 1
if filters["page_no"] < 1:
filters["page_no"] = 1
elif form_data and have_data_entry(form_data, "pageforwards"):
filters["page_no"] = int(form_data[b"pageno"][0]) + 1
if filters["page_no"] > 1:
filters["offset"] = (filters["page_no"] - 1) * PAGE_LIMIT
def getTxIdHex(bid, tx_type, suffix):
@ -116,12 +131,12 @@ def getTxIdHex(bid, tx_type, suffix):
elif tx_type == TxTypes.PTX:
obj = bid.participate_tx
else:
return 'Unknown Type'
return "Unknown Type"
if not obj:
return 'None'
return "None"
if not obj.txid:
return 'None'
return "None"
return obj.txid.hex() + suffix
@ -131,13 +146,13 @@ def getTxSpendHex(bid, tx_type):
elif tx_type == TxTypes.PTX:
obj = bid.participate_tx
else:
return 'Unknown Type'
return "Unknown Type"
if not obj:
return 'None'
return "None"
if not obj.spend_txid:
return 'None'
return obj.spend_txid.hex() + ' {}'.format(obj.spend_n)
return "None"
return obj.spend_txid.hex() + " {}".format(obj.spend_n)
def listBidStates():
@ -154,7 +169,19 @@ def listBidActions():
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_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_rate: int = offer.rate if bid.rate is None else bid.rate
initiator_role: str = 'offerer' # Leader
participant_role: str = 'bidder' # Follower
initiator_role: str = "offerer" # Leader
participant_role: str = "bidder" # Follower
if reverse_bid:
bid_amount = bid.amount_to
bid_amount_to = bid.amount
bid_rate = ci_from.make_int(bid.amount / bid.amount_to, r=1)
initiator_role = 'bidder'
participant_role = 'offerer'
initiator_role = "bidder"
participant_role = "offerer"
state_description = ''
state_description = ""
if offer.swap_type == SwapTypes.SELLER_FIRST:
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:
state_description = 'Waiting for seller to accept.'
state_description = "Waiting for seller to accept."
elif bid.state == BidStates.BID_ACCEPTED:
if not bid.initiate_tx:
state_description = 'Waiting for seller to send initiate tx.'
state_description = "Waiting for seller to send initiate tx."
else:
state_description = 'Waiting for initiate tx to confirm.'
state_description = "Waiting for initiate tx to confirm."
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:
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:
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:
state_description = 'Swap completed'
if bid.getITxState() == TxStates.TX_REDEEMED and bid.getPTxState() == TxStates.TX_REDEEMED:
state_description += ' successfully'
state_description = "Swap completed"
if (
bid.getITxState() == TxStates.TX_REDEEMED
and bid.getPTxState() == TxStates.TX_REDEEMED
):
state_description += " successfully"
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:
state_description = 'Timed out waiting for initiate txn'
state_description = "Timed out waiting for initiate txn"
elif bid.state == BidStates.BID_ABANDONED:
state_description = 'Bid abandoned'
state_description = "Bid abandoned"
elif bid.state == BidStates.BID_ERROR:
state_description = bid.state_note
elif offer.swap_type == SwapTypes.XMR_SWAP:
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:
# 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:
# Offerer received bid from bidder
# TODO: Manual vs automatic
state_description = 'Bid must be accepted'
state_description = "Bid must be accepted"
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:
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:
last_state = getLastBidState(bid.states)
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:
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:
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:
state_description = f'Delaying before sending {ci_leader.ticker()} lock tx'
state_description = (
f"Delaying before sending {ci_leader.ticker()} lock tx"
)
else:
state_description = 'Delaying before automated action'
state_description = "Delaying before automated action"
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:
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:
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:
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:
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:
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:
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:
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:
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
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
data = {
'coin_from': ci_from.coin_name(),
'coin_to': ci_to.coin_name(),
'amt_from': ci_from.format_amount(bid_amount),
'amt_to': ci_to.format_amount(bid_amount_to),
'bid_rate': ci_to.format_amount(bid_rate),
'ticker_from': ci_from.ticker(),
'ticker_to': ci_to.ticker(),
'bid_state': strBidState(bid.state),
'state_description': state_description,
'itx_state': strTxState(bid.getITxState()),
'ptx_state': strTxState(bid.getPTxState()),
'offer_id': bid.offer_id.hex(),
'addr_from': bid.bid_addr,
'addr_from_label': addr_label,
'addr_fund_proof': bid.proof_address,
'created_at': bid.created_at if for_api else format_timestamp(bid.created_at, with_seconds=True),
'expired_at': bid.expire_at if for_api else format_timestamp(bid.expire_at, with_seconds=True),
'was_sent': 'True' if bid.was_sent else 'False',
'was_received': 'True' if bid.was_received else 'False',
'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,
"coin_from": ci_from.coin_name(),
"coin_to": ci_to.coin_name(),
"amt_from": ci_from.format_amount(bid_amount),
"amt_to": ci_to.format_amount(bid_amount_to),
"bid_rate": ci_to.format_amount(bid_rate),
"ticker_from": ci_from.ticker(),
"ticker_to": ci_to.ticker(),
"bid_state": strBidState(bid.state),
"state_description": state_description,
"itx_state": strTxState(bid.getITxState()),
"ptx_state": strTxState(bid.getPTxState()),
"offer_id": bid.offer_id.hex(),
"addr_from": bid.bid_addr,
"addr_from_label": addr_label,
"addr_fund_proof": bid.proof_address,
"created_at": (
bid.created_at
if for_api
else format_timestamp(bid.created_at, with_seconds=True)
),
"expired_at": (
bid.expire_at
if for_api
else format_timestamp(bid.expire_at, with_seconds=True)
),
"was_sent": "True" if bid.was_sent else "False",
"was_received": "True" if bid.was_received else "False",
"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:
data['bid_state_ind'] = int(bid.state)
data['bid_states'] = listBidStates()
data["bid_state_ind"] = int(bid.state)
data["bid_states"] = listBidStates()
if swap_client.debug_ui:
data['debug_ind'] = bid.debug_ind
data['debug_options'] = [(int(t), t.name) for t in DebugTypes]
data["debug_ind"] = bid.debug_ind
data["debug_options"] = [(int(t), t.name) for t in DebugTypes]
if show_txns:
if offer.swap_type == SwapTypes.XMR_SWAP:
txns = []
if bid.xmr_a_lock_tx:
confirms = None
if swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height:
confirms = (swap_client.coin_clients[ci_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 (
swap_client.coin_clients[ci_leader.coin_type()]["chain_height"]
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:
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:
confirms = None
if swap_client.coin_clients[ci_follower.coin_type()]['chain_height'] and bid.xmr_b_lock_tx.chain_height:
confirms = (swap_client.coin_clients[ci_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 (
swap_client.coin_clients[ci_follower.coin_type()]["chain_height"]
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:
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:
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:
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():
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
txns.append({'type': strTxType(tx_type), 'txid': tx.txid.hex()})
data['txns'] = txns
txns.append({"type": strTxType(tx_type), "txid": tx.txid.hex()})
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_viewkey'] = ci_to.encodeKey(xmr_swap.vkbv) if xmr_swap.vkbv else None
data["xmr_b_shared_address"] = (
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:
try:
data['xmr_b_half_privatekey'] = getChainBSplitKey(swap_client, bid, xmr_swap, offer)
except Exception as e:
swap_client.log.debug('Unable to get xmr_b_half_privatekey for bid: {}'.format(bid.bid_id.hex()))
data["xmr_b_half_privatekey"] = getChainBSplitKey(
swap_client, bid, xmr_swap, offer
)
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:
remote_split_key = getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer)
remote_split_key = getChainBRemoteSplitKey(
swap_client, bid, xmr_swap, offer
)
if remote_split_key:
data['xmr_b_half_privatekey_remote'] = remote_split_key
except Exception as e:
swap_client.log.debug('Unable to get xmr_b_half_privatekey_remote for bid: {}'.format(bid.bid_id.hex()))
data["xmr_b_half_privatekey_remote"] = remote_split_key
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()
)
)
if show_lock_transfers:
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:
data['lock_transfers'] = 'Shared address not yet known.'
data["lock_transfers"] = "Shared address not yet known."
else:
data['initiate_tx_refund'] = 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex()
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)
data["initiate_tx_refund"] = (
"None" if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex()
)
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:
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:
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:
data['coin_a_lock_refund_tx_est_final'] = 'None'
data['coin_a_lock_refund_swipe_tx_est_final'] = 'None'
data["coin_a_lock_refund_tx_est_final"] = "None"
data["coin_a_lock_refund_swipe_tx_est_final"] = "None"
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_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)
data['coin_a_lock_refund_tx_est_final'] = 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']
data["coin_a_lock_refund_tx_est_final"] = (
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:
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
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)
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:
data['view_tx_ind'] = view_tx_ind
data["view_tx_ind"] = view_tx_ind
view_tx_id = bytes.fromhex(view_tx_ind)
if xmr_swap:
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['chain_a_lock_tx_inputs'] = ci_leader.listInputs(xmr_swap.a_lock_tx)
if view_tx_id == xmr_swap.a_lock_refund_tx_id and xmr_swap.a_lock_refund_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:
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()
data["view_tx_hex"] = xmr_swap.a_lock_tx.hex()
data["chain_a_lock_tx_inputs"] = ci_leader.listInputs(
xmr_swap.a_lock_tx
)
if (
view_tx_id == xmr_swap.a_lock_refund_tx_id
and xmr_swap.a_lock_refund_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
):
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:
data['view_tx_desc'] = json.dumps(ci_leader.describeTx(data['view_tx_hex']), indent=4)
if "view_tx_hex" in data:
data["view_tx_desc"] = json.dumps(
ci_leader.describeTx(data["view_tx_hex"]), indent=4
)
else:
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
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)
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:
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)
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
@ -408,20 +599,24 @@ def listOldBidStates(bid):
old_states = []
num_states = len(bid.states) // 12
for i in range(num_states):
up = struct.unpack_from('<iq', bid.states[i * 12:(i + 1) * 12])
old_states.append((up[1], 'Bid ' + strBidState(up[0])))
up = struct.unpack_from("<iq", bid.states[i * 12 : (i + 1) * 12])
old_states.append((up[1], "Bid " + strBidState(up[0])))
if bid.initiate_tx and bid.initiate_tx.states is not None:
num_states = len(bid.initiate_tx.states) // 12
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:
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:
num_states = len(bid.participate_tx.states) // 12
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:
old_states.append((up[1], 'PTX ' + strTxState(up[0])))
old_states.append((up[1], "PTX " + strTxState(up[0])))
if len(old_states) > 0:
old_states.sort(key=lambda x: x[0])
return old_states
@ -429,16 +624,16 @@ def listOldBidStates(bid):
def getCoinName(c):
if c == Coins.PART_ANON:
return chainparams[Coins.PART]['name'].capitalize() + ' Anon'
return chainparams[Coins.PART]["name"].capitalize() + " Anon"
if c == Coins.PART_BLIND:
return chainparams[Coins.PART]['name'].capitalize() + ' Blind'
return chainparams[Coins.PART]["name"].capitalize() + " Blind"
if c == Coins.LTC_MWEB:
return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB'
return chainparams[Coins.LTC]["name"].capitalize() + " MWEB"
coin_chainparams = chainparams[c]
if 'display_name' in coin_chainparams:
return coin_chainparams['display_name']
return coin_chainparams['name'].capitalize()
if "display_name" in coin_chainparams:
return coin_chainparams["display_name"]
return coin_chainparams["name"].capitalize()
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():
if k not in chainparams:
continue
if v['connection_type'] == 'rpc':
if v["connection_type"] == "rpc":
coins.append((int(k), getCoinName(k)))
if split_from and k not in invalid_coins_from:
coins_from.append(coins[-1])
@ -457,7 +652,7 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
if split_from and v not in invalid_coins_from:
coins_from.append(coins[-1])
if with_variants and k == Coins.LTC:
for v in (Coins.LTC_MWEB, ):
for v in (Coins.LTC_MWEB,):
pass # Add when swappable
if split_from:
return coins_from, coins
@ -465,29 +660,37 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
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 not ci.isAddressMine(wallet_info['stealth_address']):
ci._log.error('Unowned stealth address: {}'.format(wallet_info['stealth_address']))
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 wallet_info["stealth_address"] != "?":
if not ci.isAddressMine(wallet_info["stealth_address"]):
ci._log.error(
"Unowned stealth address: {}".format(wallet_info["stealth_address"])
)
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 wallet_info['deposit_address'] != 'Refresh necessary':
if not ci.isAddressMine(wallet_info['deposit_address']):
ci._log.error('Unowned deposit address: {}'.format(wallet_info['deposit_address']))
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'
if "deposit_address" in wallet_info:
if wallet_info["deposit_address"] != "Refresh necessary":
if not ci.isAddressMine(wallet_info["deposit_address"]):
ci._log.error(
"Unowned deposit address: {}".format(wallet_info["deposit_address"])
)
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):
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
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 True

View file

@ -39,7 +39,7 @@ class LockedCoinError(Exception):
self.coinid = coinid
def __str__(self):
return 'Coin must be unlocked: ' + str(self.coinid)
return "Coin must be unlocked: " + str(self.coinid)
def ensure(v, err_string):
@ -50,7 +50,7 @@ def ensure(v, err_string):
def toBool(s) -> bool:
if isinstance(s, bool):
return s
return s.lower() in ['1', 'true']
return s.lower() in ["1", "true"]
def jsonDecimal(obj):
@ -76,8 +76,8 @@ def SerialiseNum(n: int) -> bytes:
rv = bytearray()
neg = n < 0
absvalue = -n if neg else n
while (absvalue):
rv.append(absvalue & 0xff)
while absvalue:
rv.append(absvalue & 0xFF)
absvalue >>= 8
if rv[-1] & 0x80:
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:
# stackoverflow.com/questions/38847690
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):
v = float_to_str(v)
elif isinstance(v, int):
return v * 10 ** scale
return v * 10**scale
sign = 1
if v[0] == '-':
if v[0] == "-":
v = v[1:]
sign = -1
ep = 10 ** scale
ep = 10**scale
have_dp = False
rv = 0
for c in v:
if c == '.':
if c == ".":
rv *= ep
have_dp = True
continue
if not c.isdigit():
raise ValueError('Invalid char: ' + c)
raise ValueError("Invalid char: " + c)
if have_dp:
ep //= 10
if ep <= 0:
if r == 0:
raise ValueError('Mantissa too long')
raise ValueError("Mantissa too long")
if r > 0:
# Round off
if int(c) > 4:
@ -151,51 +153,53 @@ def validate_amount(amount, scale: int = 8) -> bool:
str_amount = float_to_str(amount) if isinstance(amount, float) else str(amount)
has_decimal = False
for c in str_amount:
if c == '.' and not has_decimal:
if c == "." and not has_decimal:
has_decimal = True
continue
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:
raise ValueError('Too many decimal places in amount {}'.format(str_amount))
raise ValueError("Too many decimal places in amount {}".format(str_amount))
return True
def format_amount(i: int, display_scale: int, scale: int = None) -> str:
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:
scale = display_scale
ep = 10 ** scale
ep = 10**scale
n = abs(i)
quotient = n // ep
remainder = n % ep
if display_scale != scale:
remainder %= (10 ** display_scale)
rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale)
remainder %= 10**display_scale
rv = "{}.{:0>{scale}}".format(quotient, remainder, scale=display_scale)
if i < 0:
rv = '-' + rv
rv = "-" + rv
return rv
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:
str_format += ':%S'
str_format += ' %z'
str_format += ":%S"
str_format += " %z"
return time.strftime(str_format, time.localtime(value))
def b2i(b: bytes) -> int:
# bytes32ToInt
return int.from_bytes(b, byteorder='big')
return int.from_bytes(b, byteorder="big")
def i2b(i: int) -> bytes:
# intToBytes32
return i.to_bytes(32, byteorder='big')
return i.to_bytes(32, byteorder="big")
def b2h(b: bytes) -> str:
@ -203,7 +207,7 @@ def b2h(b: bytes) -> str:
def h2b(h: str) -> bytes:
if h.startswith('0x'):
if h.startswith("0x"):
h = h[2:]
return bytes.fromhex(h)
@ -220,5 +224,5 @@ def zeroIfNone(value) -> int:
def hex_or_none(value: bytes) -> str:
if value is None:
return 'None'
return "None"
return value.hex()

View file

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

View file

@ -9,7 +9,7 @@ from basicswap.contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_sym
from . import i2b
class ECCParameters():
class ECCParameters:
def __init__(self, p, a, b, Gx, Gy, o):
self.p = p
self.a = a
@ -20,12 +20,13 @@ class ECCParameters():
ep = ECCParameters(
p=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
p=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
a=0x0,
b=0x7,
Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
o=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141)
Gx=0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
Gy=0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
o=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
)
curve_secp256k1 = CurveFp(ep.p, ep.a, ep.b)
@ -34,7 +35,11 @@ SECP256K1_ORDER_HALF = ep.o // 2
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:
@ -50,7 +55,7 @@ def getInsecureBytes() -> bytes:
while True:
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:
return s
@ -59,7 +64,7 @@ def getInsecureInt() -> int:
while True:
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:
return s_test
@ -77,7 +82,7 @@ def powMod(x, y, z) -> int:
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
y = powMod(a, (ep.p + 1) // 4, ep.p)
@ -89,7 +94,7 @@ def ExpandPoint(xb, sign):
def CPKToPoint(cpk):
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
y = powMod(a, (ep.p + 1) // 4, ep.p)
@ -102,28 +107,29 @@ def CPKToPoint(cpk):
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].
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):
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,))
cpk = ind + point.x().to_bytes(32, byteorder='big')
cpk = ind + point.x().to_bytes(32, byteorder="big")
return cpk
def secretToCPK(secret):
secretInt = secret if isinstance(secret, int) \
else int.from_bytes(secret, byteorder='big')
secretInt = (
secret if isinstance(secret, int) else int.from_bytes(secret, byteorder="big")
)
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,))
pubkey = ind + R.x().to_bytes(32, byteorder='big')
pubkey = ind + R.x().to_bytes(32, byteorder="big")
return pubkey
@ -136,7 +142,7 @@ def getKeypair():
def hashToCurve(pubkey):
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):
# get matching y element for point
@ -155,12 +161,14 @@ def hashToCurve(pubkey):
x = (x + 1) % ep.p # % P?
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?
continue
return R
raise ValueError('hashToCurve failed for 100 tries')
raise ValueError("hashToCurve failed for 100 tries")
def hash256(inb):
@ -168,23 +176,35 @@ def hash256(inb):
def testEccUtils():
print('testEccUtils()')
print("testEccUtils()")
G_enc = ToDER(G)
assert (G_enc.hex() == '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8')
assert (
G_enc.hex()
== "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
)
G_enc = pointToCPK(G)
assert (G_enc.hex() == '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
assert (
G_enc.hex()
== "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
)
G_dec = CPKToPoint(G_enc)
assert (G_dec == G)
assert G_dec == G
G_enc = pointToCPK2(G)
assert (G_enc.hex() == '0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
assert (
G_enc.hex()
== "0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
)
H = hashToCurve(ToDER(G))
assert (pointToCPK(H).hex() == '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0')
assert (
pointToCPK(H).hex()
== "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
)
print('Passed.')
print("Passed.")
if __name__ == "__main__":

View file

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

View file

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

View file

@ -15,9 +15,9 @@ from urllib.request import Request, urlopen
def is_private_ip_address(addr: str):
if addr == 'localhost':
if addr == "localhost":
return True
if addr.endswith('.local'):
if addr.endswith(".local"):
return True
try:
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):
read = read_start # Number of bytes read so far
last_percent_str = ''
last_percent_str = ""
time_last = time.time()
read_last = read_start
display_last = time_last
@ -35,7 +35,7 @@ def make_reporthook(read_start: int, logger):
average_buffer = [-1] * 8
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):
nonlocal read, last_percent_str, time_last, read_last, display_last, read_start
@ -72,32 +72,32 @@ def make_reporthook(read_start: int, logger):
average_bits_per_second /= samples
speed_str: str
if average_bits_per_second > 1000 ** 3:
speed_str = '{:.2f} Gbps'.format(average_bits_per_second / (1000 ** 3))
elif average_bits_per_second > 1000 ** 2:
speed_str = '{:.2f} Mbps'.format(average_bits_per_second / (1000 ** 2))
if average_bits_per_second > 1000**3:
speed_str = "{:.2f} Gbps".format(average_bits_per_second / (1000**3))
elif average_bits_per_second > 1000**2:
speed_str = "{:.2f} Mbps".format(average_bits_per_second / (1000**2))
else:
speed_str = '{:.2f} kbps'.format(average_bits_per_second / 1000)
speed_str = "{:.2f} kbps".format(average_bits_per_second / 1000)
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:
logger.info(percent_str + ' ' + speed_str)
logger.info(percent_str + " " + speed_str)
last_percent_str = percent_str
display_last = time_now
else:
logger.info(f'Read {read}, {speed_str}')
logger.info(f"Read {read}, {speed_str}")
return reporthook
def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
'''urlretrieve with resume
'''
"""urlretrieve with resume"""
url_type, path = _splittype(url)
req = Request(url)
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:
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:
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
bs = 1024 * 8
size = -1
@ -117,10 +117,10 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
size = int(headers["Content-Length"])
if "Content-Range" in headers:
range_str = headers["Content-Range"]
offset = range_str.find('-')
offset = range_str.find("-")
range_from = int(range_str[6:offset])
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:
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:
raise ContentTooShortError(
"retrieval incomplete: got only %i out of %i bytes"
% (read, size), result)
"retrieval incomplete: got only %i out of %i bytes" % (read, size), result
)
return result

View file

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

View file

@ -6,7 +6,13 @@
import struct
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
@ -17,15 +23,15 @@ def decodeScriptNum(script_bytes, o):
return ((num_len - OpCodes.OP_1) + 1, 1)
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):
raise ValueError('Bad script length')
raise ValueError("Bad script length")
o += 1
for i in range(num_len):
b = script_bytes[o + i]
# 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:
b &= (~(0x80) & 0xFF)
b &= ~(0x80) & 0xFF
v += int(b) << 8 * i
v *= -1
else:
@ -41,47 +47,50 @@ def decodePushData(script_bytes, o):
i += 1
if opcode < OP_PUSHDATA1:
pushdata_type = 'PUSHDATA(%d)' % opcode
pushdata_type = "PUSHDATA(%d)" % opcode
datasize = opcode
elif opcode == OP_PUSHDATA1:
pushdata_type = 'PUSHDATA1'
pushdata_type = "PUSHDATA1"
if i >= len(script_bytes):
raise CScriptInvalidError('PUSHDATA1: missing data length')
raise CScriptInvalidError("PUSHDATA1: missing data length")
datasize = script_bytes[i]
i += 1
elif opcode == OP_PUSHDATA2:
pushdata_type = 'PUSHDATA2'
pushdata_type = "PUSHDATA2"
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)
i += 2
elif opcode == OP_PUSHDATA4:
pushdata_type = 'PUSHDATA4'
pushdata_type = "PUSHDATA4"
if i + 3 >= len(script_bytes):
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)
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)
)
i += 4
else:
assert False # shouldn't happen
data = bytes(script_bytes[i:i + datasize])
data = bytes(script_bytes[i : i + datasize])
# Check for truncation
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, i + datasize - o)
def getP2SHScriptForHash(p2sh):
return bytes((OpCodes.OP_HASH160, 0x14)) \
+ p2sh \
+ bytes((OpCodes.OP_EQUAL,))
return bytes((OpCodes.OP_HASH160, 0x14)) + p2sh + bytes((OpCodes.OP_EQUAL,))
def getP2WSH(script):
@ -91,26 +100,26 @@ def getP2WSH(script):
def SerialiseNumCompact(v):
if v < 253:
return bytes((v,))
if v <= 0xffff: # USHRT_MAX
if v <= 0xFFFF: # USHRT_MAX
return struct.pack("<BH", 253, v)
if v <= 0xffffffff: # UINT_MAX
if v <= 0xFFFFFFFF: # UINT_MAX
return struct.pack("<BI", 254, v)
if v <= 0xffffffffffffffff: # UINT_MAX
if v <= 0xFFFFFFFFFFFFFFFF: # UINT_MAX
return struct.pack("<BQ", 255, v)
raise ValueError('Value too large')
raise ValueError("Value too large")
def getCompactSizeLen(v):
# Compact Size
if v < 253:
return 1
if v <= 0xffff: # USHRT_MAX
if v <= 0xFFFF: # USHRT_MAX
return 3
if v <= 0xffffffff: # UINT_MAX
if v <= 0xFFFFFFFF: # UINT_MAX
return 5
if v <= 0xffffffffffffffff: # UINT_MAX
if v <= 0xFFFFFFFFFFFFFFFF: # UINT_MAX
return 9
raise ValueError('Value too large')
raise ValueError("Value too large")
def getWitnessElementLen(v):

View file

@ -7,13 +7,15 @@ from .contrib.MoneroPy.base58 import encode as xmr_b58encode
def cn_fast_hash(s):
k = Keccak.Keccak()
return k.Keccak((len(s) * 8, s.hex()), 1088, 512, 32 * 8, False).lower() # r = bitrate = 1088, c = capacity, n = output length in bits
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:
prefix_bytes = version if isinstance(version, bytes) else encode_varint(version)
buf = prefix_bytes + spend_point + view_point
h = cn_fast_hash(buf)
buf = buf + bytes.fromhex(h[0: 8])
buf = buf + bytes.fromhex(h[0:8])
return xmr_b58encode(buf.hex())

View file

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

View file

@ -9,91 +9,119 @@
Join docker compose fragments
"""
__version__ = '0.1'
__version__ = "0.1"
import os
import argparse
def get_bkp_offset(filename, ext='yml'):
def get_bkp_offset(filename, ext="yml"):
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
raise ValueError(f'Unable to get backup filename for: {filename}.{ext}')
raise ValueError(f"Unable to get backup filename for: {filename}.{ext}")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-v', '--version', 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')
parser.add_argument(
"-v",
"--version",
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()
with_coins = ['particl', ]
with_coins = [
"particl",
]
for coin_name in args.coins:
parsed_name = coin_name.lower()
if parsed_name not in with_coins:
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_prepare = get_bkp_offset('docker-compose-prepare')
num_docker_compose = get_bkp_offset("docker-compose")
num_docker_compose_prepare = get_bkp_offset("docker-compose-prepare")
if os.path.exists('docker-compose.yml'):
os.rename('docker-compose.yml', f'docker-compose_bkp_{num_docker_compose}.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')
if os.path.exists("docker-compose.yml"):
os.rename("docker-compose.yml", f"docker-compose_bkp_{num_docker_compose}.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",
)
fragments_dir = 'compose-fragments'
with 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:
fragments_dir = "compose-fragments"
with (
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:
fp.write(line)
fpp.write(line)
for coin_name in with_coins:
if coin_name == 'particl':
if coin_name == "particl":
# Nothing to do
continue
if coin_name in ('monero', 'wownero'):
with open(os.path.join(fragments_dir, '1_{coin_name}-wallet.yml'), 'rb') as fp_in:
if coin_name in ("monero", "wownero"):
with open(
os.path.join(fragments_dir, "1_{coin_name}-wallet.yml"), "rb"
) as fp_in:
for line in fp_in:
fp.write(line)
fpp.write(line)
with open(os.path.join(fragments_dir, '8_{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:
fp.write(line)
continue
if coin_name == 'decred':
with open(os.path.join(fragments_dir, '1_decred-wallet.yml'), 'rb') as fp_in:
if coin_name == "decred":
with open(
os.path.join(fragments_dir, "1_decred-wallet.yml"), "rb"
) as fp_in:
for line in fp_in:
fp.write(line)
fpp.write(line)
with open(os.path.join(fragments_dir, '8_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:
fp.write(line)
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:
fp.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:
fp.write(line)
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:
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:
fpp.write(line)
print('Done.')
print("Done.")
if __name__ == '__main__':
if __name__ == "__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"]["segwit"]["active"]
except Exception as e:
logging.warning("Could not parse deployment info")
logging.warning(f"Could not parse deployment info: {e}")
def stopDaemons(daemons):