Reformat with black.

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

View File

@@ -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(),
]
},
)