mirror of
https://github.com/basicswap/basicswap.git
synced 2026-03-19 16:27:22 +01:00
Reformat with black.
This commit is contained in:
@@ -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
@@ -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 = ""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
from .dcr import DCRInterface
|
||||
|
||||
__all__ = ['DCRInterface',]
|
||||
__all__ = [
|
||||
"DCRInterface",
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user