mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-23 03:49:25 +00:00
Decred test_008_gettxout
This commit is contained in:
parent
c640836fbf
commit
026b222e90
13 changed files with 652 additions and 189 deletions
|
@ -30,7 +30,7 @@ from typing import Optional
|
||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
from sqlalchemy.orm.session import close_all_sessions
|
from sqlalchemy.orm.session import close_all_sessions
|
||||||
|
|
||||||
from .interface import Curves
|
from .interface.base import Curves
|
||||||
from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
|
from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
@ -2334,9 +2334,7 @@ class BasicSwap(BaseApp):
|
||||||
msg_buf.proof_signature = proof_sig
|
msg_buf.proof_signature = proof_sig
|
||||||
|
|
||||||
if len(proof_utxos) > 0:
|
if len(proof_utxos) > 0:
|
||||||
msg_buf.proof_utxos = bytes()
|
msg_buf.proof_utxos = ci_to.encodeProofUtxos(proof_utxos)
|
||||||
for utxo in proof_utxos:
|
|
||||||
msg_buf.proof_utxos += utxo[0] + utxo[1].to_bytes(2, 'big')
|
|
||||||
|
|
||||||
contract_count = self.getNewContractId()
|
contract_count = self.getNewContractId()
|
||||||
msg_buf.pkhash_buyer = getKeyID(self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count))
|
msg_buf.pkhash_buyer = getKeyID(self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count))
|
||||||
|
@ -3356,10 +3354,11 @@ class BasicSwap(BaseApp):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ci = self.ci(coin_type)
|
ci = self.ci(coin_type)
|
||||||
if coin_type in (Coins.NAV, ):
|
if coin_type in (Coins.NAV, Coins.DCR):
|
||||||
wif_prefix = chainparams[coin_type][self.chain]['key_prefix']
|
wif_prefix = chainparams[coin_type][self.chain]['key_prefix']
|
||||||
prevout = ci.find_prevout_info(txn, txn_script)
|
prevout = ci.find_prevout_info(txn, txn_script)
|
||||||
else:
|
else:
|
||||||
|
# TODO: Sign in bsx for all coins
|
||||||
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
|
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
|
||||||
txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [txn])
|
txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [txn])
|
||||||
if ci.using_segwit():
|
if ci.using_segwit():
|
||||||
|
@ -3414,7 +3413,7 @@ class BasicSwap(BaseApp):
|
||||||
options = {}
|
options = {}
|
||||||
if self.coin_clients[coin_type]['use_segwit']:
|
if self.coin_clients[coin_type]['use_segwit']:
|
||||||
options['force_segwit'] = True
|
options['force_segwit'] = True
|
||||||
if coin_type in (Coins.NAV, ):
|
if coin_type in (Coins.NAV, Coins.DCR):
|
||||||
refund_sig = ci.getTxSignature(refund_txn, prevout, privkey)
|
refund_sig = ci.getTxSignature(refund_txn, prevout, privkey)
|
||||||
else:
|
else:
|
||||||
refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey, 'ALL', options])
|
refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey, 'ALL', options])
|
||||||
|
@ -3432,7 +3431,7 @@ class BasicSwap(BaseApp):
|
||||||
script += format(OpCodes.OP_PUSHDATA1, '02x') + format(len(txn_script), '02x') + txn_script.hex()
|
script += format(OpCodes.OP_PUSHDATA1, '02x') + format(len(txn_script), '02x') + txn_script.hex()
|
||||||
refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, bytes.fromhex(script)).hex()
|
refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, bytes.fromhex(script)).hex()
|
||||||
|
|
||||||
if coin_type in (Coins.NAV, ):
|
if coin_type in (Coins.NAV, Coins.DCR):
|
||||||
# Only checks signature
|
# Only checks signature
|
||||||
ro = ci.verifyRawTransaction(refund_txn, [prevout])
|
ro = ci.verifyRawTransaction(refund_txn, [prevout])
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4,14 +4,9 @@
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from .util import (
|
from .util import (
|
||||||
COIN,
|
COIN,
|
||||||
make_int,
|
|
||||||
format_amount,
|
|
||||||
TemporaryError,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
XMR_COIN = 10 ** 12
|
XMR_COIN = 10 ** 12
|
||||||
|
@ -180,11 +175,11 @@ chainparams = {
|
||||||
'max_amount': 100000 * COIN,
|
'max_amount': 100000 * COIN,
|
||||||
'name': 'testnet3',
|
'name': 'testnet3',
|
||||||
},
|
},
|
||||||
'regtest': {
|
'regtest': { # simnet
|
||||||
'rpcport': 18656,
|
'rpcport': 18656,
|
||||||
'pubkey_address': 0x0e00,
|
'pubkey_address': 0x0e91,
|
||||||
'script_address': 0x0ddb,
|
'script_address': 0x0e6c,
|
||||||
'key_prefix': 0x22fe,
|
'key_prefix': 0x2307,
|
||||||
'bip44': 1,
|
'bip44': 1,
|
||||||
'min_amount': 1000,
|
'min_amount': 1000,
|
||||||
'max_amount': 100000 * COIN,
|
'max_amount': 100000 * COIN,
|
||||||
|
@ -422,89 +417,3 @@ def getCoinIdFromTicker(ticker):
|
||||||
return ticker_map[ticker.lower()]
|
return ticker_map[ticker.lower()]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Unknown coin')
|
raise ValueError('Unknown coin')
|
||||||
|
|
||||||
|
|
||||||
class CoinInterface:
|
|
||||||
def __init__(self, network):
|
|
||||||
self.setDefaults()
|
|
||||||
self._network = network
|
|
||||||
self._mx_wallet = threading.Lock()
|
|
||||||
|
|
||||||
def setDefaults(self):
|
|
||||||
self._unknown_wallet_seed = True
|
|
||||||
self._restore_height = None
|
|
||||||
|
|
||||||
def make_int(self, amount_in: int, r: int = 0) -> int:
|
|
||||||
return make_int(amount_in, self.exp(), r=r)
|
|
||||||
|
|
||||||
def format_amount(self, amount_in, conv_int=False, r=0):
|
|
||||||
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
|
|
||||||
return format_amount(amount_int, self.exp())
|
|
||||||
|
|
||||||
def coin_name(self) -> str:
|
|
||||||
coin_chainparams = chainparams[self.coin_type()]
|
|
||||||
if coin_chainparams.get('use_ticker_as_name', False):
|
|
||||||
return coin_chainparams['ticker']
|
|
||||||
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
|
|
||||||
return ticker
|
|
||||||
|
|
||||||
def getExchangeTicker(self, exchange_name: str) -> str:
|
|
||||||
return chainparams[self.coin_type()]['ticker']
|
|
||||||
|
|
||||||
def getExchangeName(self, exchange_name: str) -> str:
|
|
||||||
return chainparams[self.coin_type()]['name']
|
|
||||||
|
|
||||||
def ticker_mainnet(self) -> str:
|
|
||||||
ticker = chainparams[self.coin_type()]['ticker']
|
|
||||||
return ticker
|
|
||||||
|
|
||||||
def min_amount(self) -> int:
|
|
||||||
return chainparams[self.coin_type()][self._network]['min_amount']
|
|
||||||
|
|
||||||
def max_amount(self) -> int:
|
|
||||||
return chainparams[self.coin_type()][self._network]['max_amount']
|
|
||||||
|
|
||||||
def setWalletSeedWarning(self, value: bool) -> None:
|
|
||||||
self._unknown_wallet_seed = value
|
|
||||||
|
|
||||||
def setWalletRestoreHeight(self, value: int) -> None:
|
|
||||||
self._restore_height = value
|
|
||||||
|
|
||||||
def knownWalletSeed(self) -> bool:
|
|
||||||
return not self._unknown_wallet_seed
|
|
||||||
|
|
||||||
def chainparams(self):
|
|
||||||
return chainparams[self.coin_type()]
|
|
||||||
|
|
||||||
def chainparams_network(self):
|
|
||||||
return chainparams[self.coin_type()][self._network]
|
|
||||||
|
|
||||||
def has_segwit(self) -> bool:
|
|
||||||
return chainparams[self.coin_type()].get('has_segwit', True)
|
|
||||||
|
|
||||||
def is_transient_error(self, ex) -> bool:
|
|
||||||
if isinstance(ex, TemporaryError):
|
|
||||||
return True
|
|
||||||
str_error: str = str(ex).lower()
|
|
||||||
if 'not enough unlocked money' in str_error:
|
|
||||||
return True
|
|
||||||
if 'no unlocked balance' in str_error:
|
|
||||||
return True
|
|
||||||
if 'transaction was rejected by daemon' in str_error:
|
|
||||||
return True
|
|
||||||
if 'invalid unlocked_balance' in str_error:
|
|
||||||
return True
|
|
||||||
if 'daemon is busy' in str_error:
|
|
||||||
return True
|
|
||||||
if 'timed out' in str_error:
|
|
||||||
return True
|
|
||||||
if 'request-sent' in str_error:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (c) 2023 tecnovert
|
|
||||||
# Distributed under the MIT software license, see the accompanying
|
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
|
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
|
|
||||||
class Curves(IntEnum):
|
|
||||||
secp256k1 = 1
|
|
||||||
ed25519 = 2
|
|
148
basicswap/interface/base.py
Normal file
148
basicswap/interface/base.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
from basicswap.chainparams import (
|
||||||
|
chainparams,
|
||||||
|
)
|
||||||
|
from basicswap.util import (
|
||||||
|
ensure,
|
||||||
|
i2b, b2i,
|
||||||
|
make_int,
|
||||||
|
format_amount,
|
||||||
|
TemporaryError,
|
||||||
|
)
|
||||||
|
from basicswap.util.ecc import (
|
||||||
|
ep,
|
||||||
|
getSecretInt,
|
||||||
|
)
|
||||||
|
from coincurve.dleag import (
|
||||||
|
verify_secp256k1_point
|
||||||
|
)
|
||||||
|
from coincurve.keys import (
|
||||||
|
PublicKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Curves(IntEnum):
|
||||||
|
secp256k1 = 1
|
||||||
|
ed25519 = 2
|
||||||
|
|
||||||
|
|
||||||
|
class CoinInterface:
|
||||||
|
def __init__(self, network):
|
||||||
|
self.setDefaults()
|
||||||
|
self._network = network
|
||||||
|
self._mx_wallet = threading.Lock()
|
||||||
|
|
||||||
|
def setDefaults(self):
|
||||||
|
self._unknown_wallet_seed = True
|
||||||
|
self._restore_height = None
|
||||||
|
|
||||||
|
def make_int(self, amount_in: int, r: int = 0) -> int:
|
||||||
|
return make_int(amount_in, self.exp(), r=r)
|
||||||
|
|
||||||
|
def format_amount(self, amount_in, conv_int=False, r=0):
|
||||||
|
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
|
||||||
|
return format_amount(amount_int, self.exp())
|
||||||
|
|
||||||
|
def coin_name(self) -> str:
|
||||||
|
coin_chainparams = chainparams[self.coin_type()]
|
||||||
|
if coin_chainparams.get('use_ticker_as_name', False):
|
||||||
|
return coin_chainparams['ticker']
|
||||||
|
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
|
||||||
|
return ticker
|
||||||
|
|
||||||
|
def getExchangeTicker(self, exchange_name: str) -> str:
|
||||||
|
return chainparams[self.coin_type()]['ticker']
|
||||||
|
|
||||||
|
def getExchangeName(self, exchange_name: str) -> str:
|
||||||
|
return chainparams[self.coin_type()]['name']
|
||||||
|
|
||||||
|
def ticker_mainnet(self) -> str:
|
||||||
|
ticker = chainparams[self.coin_type()]['ticker']
|
||||||
|
return ticker
|
||||||
|
|
||||||
|
def min_amount(self) -> int:
|
||||||
|
return chainparams[self.coin_type()][self._network]['min_amount']
|
||||||
|
|
||||||
|
def max_amount(self) -> int:
|
||||||
|
return chainparams[self.coin_type()][self._network]['max_amount']
|
||||||
|
|
||||||
|
def setWalletSeedWarning(self, value: bool) -> None:
|
||||||
|
self._unknown_wallet_seed = value
|
||||||
|
|
||||||
|
def setWalletRestoreHeight(self, value: int) -> None:
|
||||||
|
self._restore_height = value
|
||||||
|
|
||||||
|
def knownWalletSeed(self) -> bool:
|
||||||
|
return not self._unknown_wallet_seed
|
||||||
|
|
||||||
|
def chainparams(self):
|
||||||
|
return chainparams[self.coin_type()]
|
||||||
|
|
||||||
|
def chainparams_network(self):
|
||||||
|
return chainparams[self.coin_type()][self._network]
|
||||||
|
|
||||||
|
def has_segwit(self) -> bool:
|
||||||
|
return chainparams[self.coin_type()].get('has_segwit', True)
|
||||||
|
|
||||||
|
def is_transient_error(self, ex) -> bool:
|
||||||
|
if isinstance(ex, TemporaryError):
|
||||||
|
return True
|
||||||
|
str_error: str = str(ex).lower()
|
||||||
|
if 'not enough unlocked money' in str_error:
|
||||||
|
return True
|
||||||
|
if 'no unlocked balance' in str_error:
|
||||||
|
return True
|
||||||
|
if 'transaction was rejected by daemon' in str_error:
|
||||||
|
return True
|
||||||
|
if 'invalid unlocked_balance' in str_error:
|
||||||
|
return True
|
||||||
|
if 'daemon is busy' in str_error:
|
||||||
|
return True
|
||||||
|
if 'timed out' in str_error:
|
||||||
|
return True
|
||||||
|
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')
|
||||||
|
self._conf_target = new_conf_target
|
||||||
|
|
||||||
|
def walletRestoreHeight(self) -> int:
|
||||||
|
return self._restore_height
|
||||||
|
|
||||||
|
|
||||||
|
class Secp256k1Interface(CoinInterface):
|
||||||
|
@staticmethod
|
||||||
|
def curve_type():
|
||||||
|
return Curves.secp256k1
|
||||||
|
|
||||||
|
def getNewSecretKey(self) -> bytes:
|
||||||
|
return i2b(getSecretInt())
|
||||||
|
|
||||||
|
def getPubkey(self, privkey):
|
||||||
|
return PublicKey.from_secret(privkey).format()
|
||||||
|
|
||||||
|
def verifyKey(self, k: bytes) -> bool:
|
||||||
|
i = b2i(k)
|
||||||
|
return (i < ep.o and i > 0)
|
||||||
|
|
||||||
|
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
||||||
|
return verify_secp256k1_point(pubkey_bytes)
|
|
@ -5,24 +5,32 @@
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import json
|
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from basicswap.contrib.test_framework import segwit_addr
|
from basicswap.basicswap_util import (
|
||||||
|
getVoutByAddress,
|
||||||
from basicswap.interface import (
|
getVoutByScriptPubKey,
|
||||||
Curves)
|
)
|
||||||
|
from basicswap.contrib.test_framework import (
|
||||||
|
segwit_addr,
|
||||||
|
)
|
||||||
|
from basicswap.interface.base import (
|
||||||
|
Secp256k1Interface,
|
||||||
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
ensure,
|
ensure,
|
||||||
b2h, i2b, b2i, i2h)
|
b2h, i2b, b2i, i2h,
|
||||||
|
)
|
||||||
from basicswap.util.ecc import (
|
from basicswap.util.ecc import (
|
||||||
ep,
|
ep,
|
||||||
pointToCPK, CPKToPoint,
|
pointToCPK, CPKToPoint,
|
||||||
getSecretInt)
|
)
|
||||||
from basicswap.util.script import (
|
from basicswap.util.script import (
|
||||||
decodeScriptNum,
|
decodeScriptNum,
|
||||||
getCompactSizeLen,
|
getCompactSizeLen,
|
||||||
|
@ -42,14 +50,14 @@ from basicswap.util.crypto import (
|
||||||
)
|
)
|
||||||
from coincurve.keys import (
|
from coincurve.keys import (
|
||||||
PrivateKey,
|
PrivateKey,
|
||||||
PublicKey)
|
PublicKey,
|
||||||
from coincurve.dleag import (
|
)
|
||||||
verify_secp256k1_point)
|
|
||||||
from coincurve.ecdsaotves import (
|
from coincurve.ecdsaotves import (
|
||||||
ecdsaotves_enc_sign,
|
ecdsaotves_enc_sign,
|
||||||
ecdsaotves_enc_verify,
|
ecdsaotves_enc_verify,
|
||||||
ecdsaotves_dec_sig,
|
ecdsaotves_dec_sig,
|
||||||
ecdsaotves_rec_enc_key)
|
ecdsaotves_rec_enc_key
|
||||||
|
)
|
||||||
|
|
||||||
from basicswap.contrib.test_framework.messages import (
|
from basicswap.contrib.test_framework.messages import (
|
||||||
COIN,
|
COIN,
|
||||||
|
@ -70,12 +78,13 @@ from basicswap.contrib.test_framework.script import (
|
||||||
OP_DROP,
|
OP_DROP,
|
||||||
OP_HASH160, OP_EQUAL,
|
OP_HASH160, OP_EQUAL,
|
||||||
SIGHASH_ALL,
|
SIGHASH_ALL,
|
||||||
SegwitV0SignatureHash)
|
SegwitV0SignatureHash,
|
||||||
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
TxLockTypes)
|
TxLockTypes
|
||||||
|
)
|
||||||
|
|
||||||
from basicswap.chainparams import CoinInterface, Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.rpc import make_rpc_func, openrpc
|
from basicswap.rpc import make_rpc_func, openrpc
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,25 +120,6 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int:
|
||||||
raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
|
raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
|
||||||
|
|
||||||
|
|
||||||
class Secp256k1Interface(CoinInterface):
|
|
||||||
@staticmethod
|
|
||||||
def curve_type():
|
|
||||||
return Curves.secp256k1
|
|
||||||
|
|
||||||
def getNewSecretKey(self) -> bytes:
|
|
||||||
return i2b(getSecretInt())
|
|
||||||
|
|
||||||
def getPubkey(self, privkey):
|
|
||||||
return PublicKey.from_secret(privkey).format()
|
|
||||||
|
|
||||||
def verifyKey(self, k: bytes) -> bool:
|
|
||||||
i = b2i(k)
|
|
||||||
return (i < ep.o and i > 0)
|
|
||||||
|
|
||||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
|
||||||
return verify_secp256k1_point(pubkey_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
class BTCInterface(Secp256k1Interface):
|
class BTCInterface(Secp256k1Interface):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -185,7 +175,7 @@ class BTCInterface(Secp256k1Interface):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getExpectedSequence(lockType: int, lockVal: int) -> int:
|
def getExpectedSequence(lockType: int, lockVal: int) -> int:
|
||||||
assert (lockVal >= 1), 'Bad lockVal'
|
ensure(lockVal >= 1, 'Bad lockVal')
|
||||||
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
||||||
return lockVal
|
return lockVal
|
||||||
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
|
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
|
||||||
|
@ -271,10 +261,6 @@ class BTCInterface(Secp256k1Interface):
|
||||||
def close_rpc(self, rpc_conn):
|
def close_rpc(self, rpc_conn):
|
||||||
rpc_conn.close()
|
rpc_conn.close()
|
||||||
|
|
||||||
def setConfTarget(self, new_conf_target: int) -> None:
|
|
||||||
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
|
|
||||||
self._conf_target = new_conf_target
|
|
||||||
|
|
||||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||||
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||||
|
|
||||||
|
@ -321,9 +307,6 @@ class BTCInterface(Secp256k1Interface):
|
||||||
rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent'))
|
rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent'))
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def walletRestoreHeight(self) -> int:
|
|
||||||
return self._restore_height
|
|
||||||
|
|
||||||
def getWalletRestoreHeight(self) -> int:
|
def getWalletRestoreHeight(self) -> int:
|
||||||
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
||||||
|
|
||||||
|
@ -1249,7 +1232,7 @@ class BTCInterface(Secp256k1Interface):
|
||||||
'vout': utxo['vout']})
|
'vout': utxo['vout']})
|
||||||
return rv, chain_height
|
return rv, chain_height
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value: float, addr_to: str, subfee: bool):
|
||||||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||||
return self.rpc_wallet('sendtoaddress', params)
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
|
@ -1435,6 +1418,12 @@ class BTCInterface(Secp256k1Interface):
|
||||||
prove_utxos = [] # TODO: Send specific utxos
|
prove_utxos = [] # TODO: Send specific utxos
|
||||||
return (sign_for_addr, signature, prove_utxos)
|
return (sign_for_addr, signature, prove_utxos)
|
||||||
|
|
||||||
|
def encodeProofUtxos(self, proof_utxos):
|
||||||
|
packed_utxos = bytes()
|
||||||
|
for utxo in proof_utxos:
|
||||||
|
packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big')
|
||||||
|
return packed_utxos
|
||||||
|
|
||||||
def decodeProofUtxos(self, msg_utxos):
|
def decodeProofUtxos(self, msg_utxos):
|
||||||
proof_utxos = []
|
proof_utxos = []
|
||||||
if len(msg_utxos) > 0:
|
if len(msg_utxos) > 0:
|
||||||
|
@ -1555,6 +1544,24 @@ class BTCInterface(Secp256k1Interface):
|
||||||
tx_vsize += 323 if redeem else 287
|
tx_vsize += 323 if redeem else 287
|
||||||
return tx_vsize
|
return tx_vsize
|
||||||
|
|
||||||
|
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
|
||||||
|
txjs = self.rpc('decoderawtransaction', [txn_hex])
|
||||||
|
|
||||||
|
if self.using_segwit():
|
||||||
|
p2wsh = self.getScriptDest(txn_script)
|
||||||
|
n = getVoutByScriptPubKey(txjs, p2wsh.hex())
|
||||||
|
else:
|
||||||
|
addr_to = self.encode_p2sh(txn_script)
|
||||||
|
n = getVoutByAddress(txjs, addr_to)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'txid': txjs['txid'],
|
||||||
|
'vout': n,
|
||||||
|
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
|
||||||
|
'redeemScript': txn_script.hex(),
|
||||||
|
'amount': txjs['vout'][n]['value']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def testBTCInterface():
|
def testBTCInterface():
|
||||||
print('TODO: testBTCInterface')
|
print('TODO: testBTCInterface')
|
||||||
|
|
|
@ -5,10 +5,20 @@
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
|
|
||||||
|
from basicswap.basicswap_util import (
|
||||||
|
getVoutByScriptPubKey,
|
||||||
|
TxLockTypes
|
||||||
|
)
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.interface.btc import Secp256k1Interface
|
from basicswap.interface.btc import Secp256k1Interface
|
||||||
|
from basicswap.util import (
|
||||||
|
ensure,
|
||||||
|
)
|
||||||
from basicswap.util.address import (
|
from basicswap.util.address import (
|
||||||
b58decode,
|
b58decode,
|
||||||
b58encode,
|
b58encode,
|
||||||
|
@ -18,6 +28,9 @@ from basicswap.util.crypto import (
|
||||||
hash160,
|
hash160,
|
||||||
ripemd160,
|
ripemd160,
|
||||||
)
|
)
|
||||||
|
from basicswap.util.script import (
|
||||||
|
SerialiseNumCompact,
|
||||||
|
)
|
||||||
from basicswap.util.extkey import ExtKeyPair
|
from basicswap.util.extkey import ExtKeyPair
|
||||||
from basicswap.util.integer import encode_varint
|
from basicswap.util.integer import encode_varint
|
||||||
from basicswap.interface.dcr.rpc import make_rpc_func
|
from basicswap.interface.dcr.rpc import make_rpc_func
|
||||||
|
@ -25,10 +38,15 @@ from .messages import CTransaction, CTxOut, SigHashType, TxSerializeType
|
||||||
from .script import push_script_data, OP_HASH160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG
|
from .script import push_script_data, OP_HASH160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG
|
||||||
|
|
||||||
from coincurve.keys import (
|
from coincurve.keys import (
|
||||||
PrivateKey
|
PrivateKey,
|
||||||
|
PublicKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
||||||
|
SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
|
||||||
|
SEQUENCE_LOCKTIME_MASK = 0x0000f
|
||||||
|
|
||||||
SigHashSerializePrefix: int = 1
|
SigHashSerializePrefix: int = 1
|
||||||
SigHashSerializeWitness: int = 3
|
SigHashSerializeWitness: int = 3
|
||||||
|
|
||||||
|
@ -136,6 +154,20 @@ class DCRInterface(Secp256k1Interface):
|
||||||
def txoType():
|
def txoType():
|
||||||
return CTxOut
|
return CTxOut
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getExpectedSequence(lockType: int, lockVal: int) -> int:
|
||||||
|
ensure(lockVal >= 1, 'Bad lockVal')
|
||||||
|
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
||||||
|
return lockVal
|
||||||
|
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
|
||||||
|
secondsLocked = lockVal
|
||||||
|
# Ensure the locked time is never less than lockVal
|
||||||
|
if secondsLocked % (1 << SEQUENCE_LOCKTIME_GRANULARITY) != 0:
|
||||||
|
secondsLocked += (1 << SEQUENCE_LOCKTIME_GRANULARITY)
|
||||||
|
secondsLocked >>= SEQUENCE_LOCKTIME_GRANULARITY
|
||||||
|
return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG
|
||||||
|
raise ValueError('Unknown lock type')
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(network)
|
super().__init__(network)
|
||||||
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
||||||
|
@ -148,6 +180,8 @@ class DCRInterface(Secp256k1Interface):
|
||||||
self.rpc_wallet = make_rpc_func(coin_settings['walletrpcport'], self._rpcauth, host=self._rpc_host)
|
self.rpc_wallet = make_rpc_func(coin_settings['walletrpcport'], self._rpcauth, host=self._rpc_host)
|
||||||
else:
|
else:
|
||||||
self.rpc_wallet = None
|
self.rpc_wallet = None
|
||||||
|
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||||
|
self.setConfTarget(coin_settings['conf_target'])
|
||||||
|
|
||||||
self._use_segwit = coin_settings['use_segwit']
|
self._use_segwit = coin_settings['use_segwit']
|
||||||
|
|
||||||
|
@ -161,6 +195,13 @@ class DCRInterface(Secp256k1Interface):
|
||||||
checksum = blake256(blake256(data))
|
checksum = blake256(blake256(data))
|
||||||
return b58encode(data + checksum[0:4])
|
return b58encode(data + checksum[0:4])
|
||||||
|
|
||||||
|
def sh_to_address(self, sh: bytes) -> str:
|
||||||
|
assert (len(sh) == 20)
|
||||||
|
prefix = self.chainparams_network()['script_address']
|
||||||
|
data = prefix.to_bytes(2, 'big') + sh
|
||||||
|
checksum = blake256(blake256(data))
|
||||||
|
return b58encode(data + checksum[0:4])
|
||||||
|
|
||||||
def decode_address(self, address: str) -> bytes:
|
def decode_address(self, address: str) -> bytes:
|
||||||
addr_data = b58decode(address)
|
addr_data = b58decode(address)
|
||||||
if addr_data is None:
|
if addr_data is None:
|
||||||
|
@ -198,7 +239,18 @@ class DCRInterface(Secp256k1Interface):
|
||||||
return self._use_segwit
|
return self._use_segwit
|
||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
|
rv = {}
|
||||||
rv = self.rpc_wallet('getinfo')
|
rv = self.rpc_wallet('getinfo')
|
||||||
|
wi = self.rpc_wallet('walletinfo')
|
||||||
|
balances = self.rpc_wallet('getbalance')
|
||||||
|
|
||||||
|
default_account_bal = balances['balances'][0] # 0 always default?
|
||||||
|
rv['balance'] = default_account_bal['spendable']
|
||||||
|
rv['unconfirmed_balance'] = default_account_bal['unconfirmed']
|
||||||
|
rv['immature_balance'] = default_account_bal['immaturecoinbaserewards'] + default_account_bal['immaturestakegeneration']
|
||||||
|
rv['encrypted'] = True
|
||||||
|
rv['locked'] = True if wi['unlocked'] is False else False
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def getSeedHash(self, seed: bytes) -> bytes:
|
def getSeedHash(self, seed: bytes) -> bytes:
|
||||||
|
@ -251,6 +303,13 @@ class DCRInterface(Secp256k1Interface):
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
return tx.serialize(TxSerializeType.NoWitness)
|
return tx.serialize(TxSerializeType.NoWitness)
|
||||||
|
|
||||||
|
def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str:
|
||||||
|
sig_type, key = self.decodeKey(key_wif)
|
||||||
|
redeem_script = bytes.fromhex(prevout_data['redeemScript'])
|
||||||
|
sig = self.signTx(key, bytes.fromhex(tx_hex), 0, redeem_script, self.make_int(prevout_data['amount']))
|
||||||
|
|
||||||
|
return sig.hex()
|
||||||
|
|
||||||
def getScriptDest(self, script: bytes) -> bytes:
|
def getScriptDest(self, script: bytes) -> bytes:
|
||||||
# P2SH
|
# P2SH
|
||||||
script_hash = self.pkh(script)
|
script_hash = self.pkh(script)
|
||||||
|
@ -258,8 +317,205 @@ class DCRInterface(Secp256k1Interface):
|
||||||
|
|
||||||
return OP_HASH160.to_bytes(1) + len(script_hash).to_bytes(1) + script_hash + OP_EQUAL.to_bytes(1)
|
return OP_HASH160.to_bytes(1) + len(script_hash).to_bytes(1) + script_hash + OP_EQUAL.to_bytes(1)
|
||||||
|
|
||||||
|
def encodeScriptDest(self, script_dest: bytes) -> str:
|
||||||
|
script_hash = script_dest[2:-1] # Extract hash from script
|
||||||
|
return self.sh_to_address(script_hash)
|
||||||
|
|
||||||
def getPubkeyHashDest(self, pkh: bytes) -> bytes:
|
def getPubkeyHashDest(self, pkh: bytes) -> bytes:
|
||||||
# P2PKH
|
# P2PKH
|
||||||
|
|
||||||
assert len(pkh) == 20
|
assert len(pkh) == 20
|
||||||
return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
|
return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
|
||||||
|
|
||||||
|
def get_fee_rate(self, conf_target: int = 2) -> (float, str):
|
||||||
|
chain_client_settings = self._sc.getChainClientSettings(self.coin_type()) # basicswap.json
|
||||||
|
override_feerate = chain_client_settings.get('override_feerate', None)
|
||||||
|
if override_feerate:
|
||||||
|
self._log.debug('Fee rate override used for %s: %f', self.coin_name(), override_feerate)
|
||||||
|
return override_feerate, 'override_feerate'
|
||||||
|
|
||||||
|
min_relay_fee = chain_client_settings.get('min_relay_fee', None)
|
||||||
|
|
||||||
|
def try_get_fee_rate(self, conf_target):
|
||||||
|
# TODO: How to estimate required fee?
|
||||||
|
try:
|
||||||
|
fee_rate: float = self.rpc_wallet('walletinfo')['txfee']
|
||||||
|
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||||
|
return fee_rate, 'paytxfee'
|
||||||
|
except Exception:
|
||||||
|
fee_rate: float = self.rpc('getnetworkinfo')['relayfee']
|
||||||
|
return fee_rate, 'relayfee'
|
||||||
|
|
||||||
|
fee_rate, rate_src = try_get_fee_rate(self, conf_target)
|
||||||
|
if min_relay_fee and min_relay_fee > fee_rate:
|
||||||
|
self._log.warning('Feerate {} ({}) is below min relay fee {} for {}'.format(self.format_amount(fee_rate, True, 1), rate_src, self.format_amount(min_relay_fee, True, 1), self.coin_name()))
|
||||||
|
return min_relay_fee, 'min_relay_fee'
|
||||||
|
return fee_rate, rate_src
|
||||||
|
|
||||||
|
def getNewAddress(self, use_segwit: bool = True, label: str = 'swap_receive') -> str:
|
||||||
|
return self.rpc_wallet('getnewaddress')
|
||||||
|
|
||||||
|
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_wallet('listunspent')
|
||||||
|
if unspents is None:
|
||||||
|
unspents = []
|
||||||
|
for u in unspents:
|
||||||
|
if u['spendable'] is not True:
|
||||||
|
continue
|
||||||
|
if u['address'] not in unspents_by_addr:
|
||||||
|
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []}
|
||||||
|
utxo_amount: int = self.make_int(u['amount'], r=1)
|
||||||
|
unspents_by_addr[u['address']]['total'] += utxo_amount
|
||||||
|
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'], u['tree']))
|
||||||
|
|
||||||
|
max_utxos: int = 4
|
||||||
|
|
||||||
|
viable_addrs = []
|
||||||
|
for addr, data in unspents_by_addr.items():
|
||||||
|
if data['total'] >= amount_for:
|
||||||
|
# Sort from largest to smallest amount
|
||||||
|
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0])
|
||||||
|
|
||||||
|
# Max outputs required to reach amount_for
|
||||||
|
utxos_req: int = 0
|
||||||
|
sum_value: int = 0
|
||||||
|
for utxo in sorted_utxos:
|
||||||
|
sum_value += utxo[0]
|
||||||
|
utxos_req += 1
|
||||||
|
if sum_value >= amount_for:
|
||||||
|
break
|
||||||
|
|
||||||
|
if utxos_req <= max_utxos:
|
||||||
|
viable_addrs.append(addr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof')
|
||||||
|
|
||||||
|
sign_for_addr: str = random.choice(viable_addrs)
|
||||||
|
self._log.debug('sign_for_addr %s', sign_for_addr)
|
||||||
|
|
||||||
|
prove_utxos = []
|
||||||
|
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0])
|
||||||
|
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
sum_value: int = 0
|
||||||
|
for utxo in sorted_utxos:
|
||||||
|
sum_value += utxo[0]
|
||||||
|
outpoint = (bytes.fromhex(utxo[1]), utxo[2], utxo[3])
|
||||||
|
prove_utxos.append(outpoint)
|
||||||
|
hasher.update(outpoint[0])
|
||||||
|
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
||||||
|
hasher.update(outpoint[2].to_bytes(1))
|
||||||
|
if sum_value >= amount_for:
|
||||||
|
break
|
||||||
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
|
signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||||
|
|
||||||
|
return (sign_for_addr, signature, prove_utxos)
|
||||||
|
|
||||||
|
def withdrawCoin(self, value: float, addr_to: str, subfee: bool = False) -> str:
|
||||||
|
if subfee:
|
||||||
|
raise ValueError('TODO')
|
||||||
|
params = [addr_to, value]
|
||||||
|
return self.rpc_wallet('sendtoaddress', params)
|
||||||
|
|
||||||
|
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||||
|
addr_info = self.rpc('validateaddress', [address])
|
||||||
|
return addr_info.get('ismine', False)
|
||||||
|
|
||||||
|
def encodeProofUtxos(self, proof_utxos):
|
||||||
|
packed_utxos = bytes()
|
||||||
|
for utxo in proof_utxos:
|
||||||
|
packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big') + utxo[2].to_bytes(1)
|
||||||
|
return packed_utxos
|
||||||
|
|
||||||
|
def decodeProofUtxos(self, msg_utxos):
|
||||||
|
proof_utxos = []
|
||||||
|
if len(msg_utxos) > 0:
|
||||||
|
num_utxos = len(msg_utxos) // 34
|
||||||
|
p: int = 0
|
||||||
|
for i in range(num_utxos):
|
||||||
|
proof_utxos.append((msg_utxos[p: p + 32], int.from_bytes(msg_utxos[p + 32: p + 34], 'big'), msg_utxos[p + 34]))
|
||||||
|
p += 35
|
||||||
|
return proof_utxos
|
||||||
|
|
||||||
|
def verifyProofOfFunds(self, address: str, signature: bytes, utxos, extra_commit_bytes: bytes):
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
sum_value: int = 0
|
||||||
|
for outpoint in utxos:
|
||||||
|
hasher.update(outpoint[0])
|
||||||
|
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
||||||
|
hasher.update(outpoint[2].to_bytes(1))
|
||||||
|
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')
|
||||||
|
|
||||||
|
sum_value: int = 0
|
||||||
|
for outpoint in utxos:
|
||||||
|
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1], outpoint[2]])
|
||||||
|
sum_value += self.make_int(txout['value'])
|
||||||
|
|
||||||
|
return sum_value
|
||||||
|
|
||||||
|
def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool:
|
||||||
|
if message_magic is None:
|
||||||
|
message_magic = self.chainparams()['message_magic']
|
||||||
|
|
||||||
|
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
|
||||||
|
message_hash = blake256(message_bytes)
|
||||||
|
signature_bytes = base64.b64decode(signature)
|
||||||
|
rec_id = (signature_bytes[0] - 27) & 3
|
||||||
|
signature_bytes = signature_bytes[1:] + bytes((rec_id,))
|
||||||
|
try:
|
||||||
|
pubkey = PublicKey.from_signature_and_message(signature_bytes, message_hash, hasher=None)
|
||||||
|
except Exception as e:
|
||||||
|
self._log.info('verifyMessage failed: ' + str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
address_hash = self.decode_address(address)[2:]
|
||||||
|
pubkey_hash = ripemd160(blake256(pubkey.format()))
|
||||||
|
|
||||||
|
return True if address_hash == pubkey_hash else False
|
||||||
|
|
||||||
|
def signTxWithWallet(self, tx) -> bytes:
|
||||||
|
return bytes.fromhex(self.rpc('signrawtransaction', [tx.hex()])['hex'])
|
||||||
|
|
||||||
|
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||||
|
|
||||||
|
# amount can't be a string, else: Failed to parse request: parameter #2 'amounts' must be type float64 (got string)
|
||||||
|
float_amount = float(self.format_amount(amount))
|
||||||
|
txn = self.rpc('createrawtransaction', [[], {addr_to: float_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}')
|
||||||
|
options = {
|
||||||
|
'lockUnspents': lock_unspents,
|
||||||
|
'feeRate': fee_rate,
|
||||||
|
}
|
||||||
|
if sub_fee:
|
||||||
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
|
return self.rpc_wallet('fundrawtransaction', [txn, 'default', options])['hex']
|
||||||
|
|
||||||
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
|
return self.rpc_wallet('signrawtransaction', [txn_funded])['hex']
|
||||||
|
|
||||||
|
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||||
|
self._log.debug('TODO: getLockTxHeight')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
|
||||||
|
txjs = self.rpc('decoderawtransaction', [txn_hex])
|
||||||
|
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'txid': txjs['txid'],
|
||||||
|
'vout': n,
|
||||||
|
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
|
||||||
|
'redeemScript': txn_script.hex(),
|
||||||
|
'amount': txjs['vout'][n]['value']
|
||||||
|
}
|
||||||
|
|
|
@ -42,5 +42,4 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
|
||||||
else:
|
else:
|
||||||
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
|
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
|
||||||
|
|
||||||
print('[rm] data_array', (data_array + data).hex())
|
|
||||||
data_array += data
|
data_array += data
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import random
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import random
|
||||||
|
|
||||||
from .btc import BTCInterface, find_vout_for_address_from_txobj
|
from .btc import BTCInterface, find_vout_for_address_from_txobj
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
|
|
|
@ -24,8 +24,9 @@ from coincurve.dleag import (
|
||||||
verify_ed25519_point,
|
verify_ed25519_point,
|
||||||
)
|
)
|
||||||
|
|
||||||
from basicswap.interface import (
|
from basicswap.interface.base import (
|
||||||
Curves)
|
Curves,
|
||||||
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
i2b, b2i, b2h,
|
i2b, b2i, b2h,
|
||||||
dumpj,
|
dumpj,
|
||||||
|
@ -36,7 +37,8 @@ from basicswap.util.network import (
|
||||||
from basicswap.rpc_xmr import (
|
from basicswap.rpc_xmr import (
|
||||||
make_xmr_rpc_func,
|
make_xmr_rpc_func,
|
||||||
make_xmr_rpc2_func)
|
make_xmr_rpc2_func)
|
||||||
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
|
from basicswap.chainparams import XMR_COIN, Coins
|
||||||
|
from basicswap.interface.base import CoinInterface
|
||||||
|
|
||||||
|
|
||||||
class XMRInterface(CoinInterface):
|
class XMRInterface(CoinInterface):
|
||||||
|
@ -239,9 +241,6 @@ class XMRInterface(CoinInterface):
|
||||||
rv['locked'] = False
|
rv['locked'] = False
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def walletRestoreHeight(self):
|
|
||||||
return self._restore_height
|
|
||||||
|
|
||||||
def getMainWalletAddress(self) -> str:
|
def getMainWalletAddress(self) -> str:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy.orm import scoped_session
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
ensure,
|
ensure,
|
||||||
)
|
)
|
||||||
from basicswap.interface import Curves
|
from basicswap.interface.base import Curves
|
||||||
from basicswap.chainparams import (
|
from basicswap.chainparams import (
|
||||||
Coins,
|
Coins,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1182,7 +1182,7 @@ def finalise_daemon(d):
|
||||||
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
||||||
d.handle.wait(timeout=120)
|
d.handle.wait(timeout=120)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(f'Error {e} for process {d.pid}')
|
logging.info(f'Error {e} for process {d.handle.pid}')
|
||||||
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
||||||
if fp:
|
if fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
|
@ -1262,7 +1262,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||||
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
|
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
|
||||||
|
|
||||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
|
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
|
||||||
swap_client.setDaemonPID(c, daemons[-1].pid)
|
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
||||||
swap_client.setCoinRunParams(c)
|
swap_client.setCoinRunParams(c)
|
||||||
swap_client.createCoinInterface(c)
|
swap_client.createCoinInterface(c)
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import select
|
import select
|
||||||
|
import subprocess
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
|
@ -29,11 +29,12 @@ from basicswap.interface.dcr.messages import (
|
||||||
from tests.basicswap.common import (
|
from tests.basicswap.common import (
|
||||||
stopDaemons,
|
stopDaemons,
|
||||||
waitForRPC,
|
waitForRPC,
|
||||||
|
wait_for_balance,
|
||||||
)
|
)
|
||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
|
read_json_api,
|
||||||
REQUIRED_SETTINGS,
|
REQUIRED_SETTINGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.basicswap.test_xmr import BaseTest, test_delay_event
|
from tests.basicswap.test_xmr import BaseTest, test_delay_event
|
||||||
from basicswap.interface.dcr import DCRInterface
|
from basicswap.interface.dcr import DCRInterface
|
||||||
from basicswap.interface.dcr.messages import CTransaction, CTxIn, COutPoint
|
from basicswap.interface.dcr.messages import CTransaction, CTxIn, COutPoint
|
||||||
|
@ -107,7 +108,7 @@ def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
|
||||||
|
|
||||||
class Test(BaseTest):
|
class Test(BaseTest):
|
||||||
__test__ = True
|
__test__ = True
|
||||||
test_coin_from = Coins.DCR
|
test_coin = Coins.DCR
|
||||||
dcr_daemons = []
|
dcr_daemons = []
|
||||||
start_ltc_nodes = False
|
start_ltc_nodes = False
|
||||||
start_xmr_nodes = False
|
start_xmr_nodes = False
|
||||||
|
@ -122,7 +123,7 @@ class Test(BaseTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
if not cls.restore_instance:
|
if not cls.restore_instance:
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin_from)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
assert (ci0.rpc_wallet('getnewaddress') == cls.dcr_mining_addr)
|
assert (ci0.rpc_wallet('getnewaddress') == cls.dcr_mining_addr)
|
||||||
cls.dcr_ticket_account = ci0.rpc_wallet('getaccount', [cls.dcr_mining_addr, ])
|
cls.dcr_ticket_account = ci0.rpc_wallet('getaccount', [cls.dcr_mining_addr, ])
|
||||||
ci0.rpc('generate', [110,])
|
ci0.rpc('generate', [110,])
|
||||||
|
@ -140,7 +141,7 @@ class Test(BaseTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def coins_loop(cls):
|
def coins_loop(cls):
|
||||||
super(Test, cls).coins_loop()
|
super(Test, cls).coins_loop()
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin_from)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
|
|
||||||
num_passed: int = 0
|
num_passed: int = 0
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
|
@ -148,6 +149,9 @@ class Test(BaseTest):
|
||||||
ci0.rpc_wallet('purchaseticket', [cls.dcr_ticket_account, 0.1, 0])
|
ci0.rpc_wallet('purchaseticket', [cls.dcr_ticket_account, 0.1, 0])
|
||||||
num_passed += 1
|
num_passed += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if 'double spend' in str(e):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
logging.warning('coins_loop purchaseticket {}'.format(e))
|
logging.warning('coins_loop purchaseticket {}'.format(e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -230,8 +234,30 @@ class Test(BaseTest):
|
||||||
'blocks_confirmed': 1,
|
'blocks_confirmed': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def prepare_balance(self, coin, amount: float, port_target_node: int, port_take_from_node: int, test_balance: bool = True) -> None:
|
||||||
|
delay_iterations = 20
|
||||||
|
delay_time = 3
|
||||||
|
coin_ticker: str = coin.name
|
||||||
|
balance_type: str = 'balance'
|
||||||
|
address_type: str = 'deposit_address'
|
||||||
|
js_w = read_json_api(port_target_node, 'wallets')
|
||||||
|
current_balance: float = float(js_w[coin_ticker][balance_type])
|
||||||
|
if test_balance and current_balance >= amount:
|
||||||
|
return
|
||||||
|
post_json = {
|
||||||
|
'value': amount,
|
||||||
|
'address': js_w[coin_ticker][address_type],
|
||||||
|
'subfee': False,
|
||||||
|
}
|
||||||
|
json_rv = read_json_api(port_take_from_node, 'wallets/{}/withdraw'.format(coin_ticker.lower()), post_json)
|
||||||
|
assert (len(json_rv['txid']) == 64)
|
||||||
|
wait_for_amount: float = amount
|
||||||
|
if not test_balance:
|
||||||
|
wait_for_amount += current_balance
|
||||||
|
wait_for_balance(test_delay_event, 'http://127.0.0.1:{}/json/wallets/{}'.format(port_target_node, coin_ticker.lower()), balance_type, wait_for_amount, iterations=delay_iterations, delay_time=delay_time)
|
||||||
|
|
||||||
def test_0001_decred_address(self):
|
def test_0001_decred_address(self):
|
||||||
logging.info('---------- Test {}'.format(self.test_coin_from.name))
|
logging.info('---------- Test {}'.format(self.test_coin.name))
|
||||||
|
|
||||||
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
|
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}
|
||||||
coin_settings.update(REQUIRED_SETTINGS)
|
coin_settings.update(REQUIRED_SETTINGS)
|
||||||
|
@ -249,7 +275,7 @@ class Test(BaseTest):
|
||||||
assert (data[2:] == pkh)
|
assert (data[2:] == pkh)
|
||||||
|
|
||||||
for i, sc in enumerate(self.swap_clients):
|
for i, sc in enumerate(self.swap_clients):
|
||||||
loop_ci = sc.ci(self.test_coin_from)
|
loop_ci = sc.ci(self.test_coin)
|
||||||
root_key = sc.getWalletKey(Coins.DCR, 1)
|
root_key = sc.getWalletKey(Coins.DCR, 1)
|
||||||
masterpubkey = loop_ci.rpc_wallet('getmasterpubkey')
|
masterpubkey = loop_ci.rpc_wallet('getmasterpubkey')
|
||||||
masterpubkey_data = loop_ci.decode_address(masterpubkey)[4:]
|
masterpubkey_data = loop_ci.decode_address(masterpubkey)[4:]
|
||||||
|
@ -261,13 +287,13 @@ class Test(BaseTest):
|
||||||
assert (seed_hash == hash160(masterpubkey_data))
|
assert (seed_hash == hash160(masterpubkey_data))
|
||||||
|
|
||||||
def test_001_segwit(self):
|
def test_001_segwit(self):
|
||||||
logging.info('---------- Test {} segwit'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} segwit'.format(self.test_coin.name))
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci0 = swap_clients[0].ci(self.test_coin_from)
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
assert (ci0.using_segwit() is True)
|
assert (ci0.using_segwit() is True)
|
||||||
|
|
||||||
addr_out = ci0.rpc_wallet('getnewaddress')
|
addr_out = ci0.getNewAddress()
|
||||||
addr_info = ci0.rpc_wallet('validateaddress', [addr_out,])
|
addr_info = ci0.rpc_wallet('validateaddress', [addr_out,])
|
||||||
assert (addr_info['isvalid'] is True)
|
assert (addr_info['isvalid'] is True)
|
||||||
assert (addr_info['ismine'] is True)
|
assert (addr_info['ismine'] is True)
|
||||||
|
@ -294,13 +320,13 @@ class Test(BaseTest):
|
||||||
assert (f_decoded['txid'] == ctx.TxHash().hex())
|
assert (f_decoded['txid'] == ctx.TxHash().hex())
|
||||||
|
|
||||||
def test_003_signature_hash(self):
|
def test_003_signature_hash(self):
|
||||||
logging.info('---------- Test {} signature_hash'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} signature_hash'.format(self.test_coin.name))
|
||||||
# Test that signing a transaction manually produces the same result when signed with the wallet
|
# Test that signing a transaction manually produces the same result when signed with the wallet
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci0 = swap_clients[0].ci(self.test_coin_from)
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
utxos = ci0.rpc_wallet('listunspent')
|
utxos = ci0.getNewAddress()
|
||||||
addr_out = ci0.rpc_wallet('getnewaddress')
|
addr_out = ci0.rpc_wallet('getnewaddress')
|
||||||
rtx = ci0.rpc_wallet('createrawtransaction', [[], {addr_out: 2.0}])
|
rtx = ci0.rpc_wallet('createrawtransaction', [[], {addr_out: 2.0}])
|
||||||
|
|
||||||
|
@ -342,15 +368,18 @@ class Test(BaseTest):
|
||||||
assert (len(sent_txid) == 64)
|
assert (len(sent_txid) == 64)
|
||||||
|
|
||||||
def test_004_csv(self):
|
def test_004_csv(self):
|
||||||
logging.info('---------- Test {} csv'.format(self.test_coin_from.name))
|
logging.info('---------- Test {} csv'.format(self.test_coin.name))
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci0 = swap_clients[0].ci(self.test_coin_from)
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
script = bytearray()
|
script = bytearray()
|
||||||
push_script_data(script, bytes((3,)))
|
push_script_data(script, bytes((3,)))
|
||||||
script += OP_CHECKSEQUENCEVERIFY.to_bytes(1)
|
script += OP_CHECKSEQUENCEVERIFY.to_bytes(1)
|
||||||
|
|
||||||
script_dest = ci0.getScriptDest(script)
|
script_dest = ci0.getScriptDest(script)
|
||||||
|
script_info = ci0.rpc_wallet('decodescript', [script_dest.hex(),])
|
||||||
|
script_addr = ci0.encodeScriptDest(script_dest)
|
||||||
|
assert (script_info['addresses'][0] == script_addr)
|
||||||
|
|
||||||
prevout_amount: int = ci0.make_int(1.1)
|
prevout_amount: int = ci0.make_int(1.1)
|
||||||
tx = CTransaction()
|
tx = CTransaction()
|
||||||
|
@ -388,7 +417,7 @@ class Test(BaseTest):
|
||||||
push_script_data(signature_script, script)
|
push_script_data(signature_script, script)
|
||||||
tx_spend.vin[0].signature_script = signature_script
|
tx_spend.vin[0].signature_script = signature_script
|
||||||
|
|
||||||
addr_out = ci0.rpc_wallet('getnewaddress')
|
addr_out = ci0.getNewAddress()
|
||||||
pkh = ci0.decode_address(addr_out)[2:]
|
pkh = ci0.decode_address(addr_out)[2:]
|
||||||
|
|
||||||
tx_spend.vout.append(ci0.txoType()())
|
tx_spend.vout.append(ci0.txoType()())
|
||||||
|
@ -399,6 +428,7 @@ class Test(BaseTest):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sent_spend_txid = ci0.rpc_wallet('sendrawtransaction', [tx_spend_hex, ])
|
sent_spend_txid = ci0.rpc_wallet('sendrawtransaction', [tx_spend_hex, ])
|
||||||
|
logging.info('Sent tx spending csv output, txid: {}'.format(sent_spend_txid))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
assert ('transaction sequence locks on inputs not met' in str(e))
|
assert ('transaction sequence locks on inputs not met' in str(e))
|
||||||
else:
|
else:
|
||||||
|
@ -415,6 +445,135 @@ class Test(BaseTest):
|
||||||
|
|
||||||
assert (sent_spend_txid is not None)
|
assert (sent_spend_txid is not None)
|
||||||
|
|
||||||
|
def test_005_watchonly(self):
|
||||||
|
logging.info('---------- Test {} watchonly'.format(self.test_coin.name))
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
|
ci1 = swap_clients[1].ci(self.test_coin)
|
||||||
|
|
||||||
|
addr = ci0.getNewAddress()
|
||||||
|
pkh = ci0.decode_address(addr)[2:]
|
||||||
|
addr_info = ci0.rpc_wallet('validateaddress', [addr,])
|
||||||
|
|
||||||
|
addr_script = ci0.getPubkeyHashDest(pkh).hex()
|
||||||
|
script_info = ci0.rpc_wallet('decodescript', [addr_script,])
|
||||||
|
assert (addr in script_info['addresses'])
|
||||||
|
|
||||||
|
# Importscript doesn't import an address
|
||||||
|
ci1.rpc_wallet('importscript', [addr_script,])
|
||||||
|
addr_info1 = ci1.rpc_wallet('validateaddress', [addr,])
|
||||||
|
assert (addr_info1.get('ismine', False) is False)
|
||||||
|
|
||||||
|
# Would need to run a second wallet daemon?
|
||||||
|
try:
|
||||||
|
ro = ci1.rpc_wallet('importpubkey', [addr_info['pubkey'],])
|
||||||
|
except Exception as e:
|
||||||
|
assert ('public keys may only be imported by watching-only wallets' in str(e))
|
||||||
|
else:
|
||||||
|
logging.info('Expected importpubkey to fail on non watching-only wallet')
|
||||||
|
|
||||||
|
chain_height_last = ci1.getChainHeight()
|
||||||
|
txid = ci0.rpc_wallet('sendtoaddress', [addr, 1])
|
||||||
|
|
||||||
|
found_txid = None
|
||||||
|
for i in range(20):
|
||||||
|
if found_txid is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
chain_height_now = ci1.getChainHeight()
|
||||||
|
while chain_height_last <= chain_height_now:
|
||||||
|
if found_txid is not None:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
check_hash = ci1.rpc('getblockhash', [chain_height_last + 1, ])
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning('getblockhash {} failed {}'.format(chain_height_last + 1, e))
|
||||||
|
test_delay_event.wait(1)
|
||||||
|
break
|
||||||
|
|
||||||
|
chain_height_last += 1
|
||||||
|
check_hash = ci1.rpc('getblockhash', [chain_height_last, ])
|
||||||
|
block_tx = ci1.rpc('getblock', [check_hash, True, True])
|
||||||
|
for tx in block_tx['rawtx']:
|
||||||
|
if found_txid is not None:
|
||||||
|
break
|
||||||
|
for txo in tx['vout']:
|
||||||
|
if addr_script == txo['scriptPubKey']['hex']:
|
||||||
|
found_txid = tx['txid']
|
||||||
|
logging.info('found_txid {}'.format(found_txid))
|
||||||
|
break
|
||||||
|
|
||||||
|
test_delay_event.wait(1)
|
||||||
|
|
||||||
|
assert (found_txid is not None)
|
||||||
|
|
||||||
|
def test_008_gettxout(self):
|
||||||
|
logging.info('---------- Test {} gettxout'.format(self.test_coin.name))
|
||||||
|
|
||||||
|
ci0 = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
|
addr = ci0.getNewAddress()
|
||||||
|
|
||||||
|
test_amount: float = 1.0
|
||||||
|
txid = ci0.withdrawCoin(test_amount, addr)
|
||||||
|
assert len(txid) == 64
|
||||||
|
|
||||||
|
unspents = None
|
||||||
|
for i in range(30):
|
||||||
|
unspents = ci0.rpc_wallet('listunspent', [0, 999999999, [addr,]])
|
||||||
|
if unspents is None:
|
||||||
|
unspents = []
|
||||||
|
if len(unspents) > 0:
|
||||||
|
break
|
||||||
|
test_delay_event.wait(1)
|
||||||
|
assert (len(unspents) == 1)
|
||||||
|
utxo = unspents[0]
|
||||||
|
|
||||||
|
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||||
|
|
||||||
|
# Lock utxo so it's not spent for tickets, while waiting for depth
|
||||||
|
rv = ci0.rpc_wallet('lockunspent', [False, [utxo, ]])
|
||||||
|
|
||||||
|
def wait_for_depth():
|
||||||
|
for i in range(20):
|
||||||
|
logging.info('Waiting for txout depth, iter {}'.format(i))
|
||||||
|
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||||
|
if txout['confirmations'] > 0:
|
||||||
|
return txout
|
||||||
|
test_delay_event.wait(1)
|
||||||
|
raise ValueError('prevout not confirmed')
|
||||||
|
txout = wait_for_depth()
|
||||||
|
assert (txout['confirmations'] > 0)
|
||||||
|
assert (addr in txout['scriptPubKey']['addresses'])
|
||||||
|
|
||||||
|
addr_out = ci0.getNewAddress()
|
||||||
|
rtx = ci0.rpc_wallet('createrawtransaction', [[utxo, ], {addr_out: test_amount - 0.0001}])
|
||||||
|
stx = ci0.rpc_wallet('signrawtransaction', [rtx])
|
||||||
|
|
||||||
|
chain_height_before_send = ci0.getChainHeight()
|
||||||
|
sent_txid = ci0.rpc_wallet('sendrawtransaction', [stx['hex'], ])
|
||||||
|
|
||||||
|
# NOTE: UTXO is still found when spent in the mempool (tested in loop, not delay from wallet to core)
|
||||||
|
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||||
|
assert (addr in txout['scriptPubKey']['addresses'])
|
||||||
|
|
||||||
|
for i in range(20):
|
||||||
|
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||||
|
if txout is None:
|
||||||
|
logging.info('txout spent, height before spent {}, height spent {}'.format(chain_height_before_send, ci0.getChainHeight()))
|
||||||
|
break
|
||||||
|
test_delay_event.wait(1)
|
||||||
|
assert (txout is None)
|
||||||
|
|
||||||
|
logging.info('Testing getProofOfFunds')
|
||||||
|
require_amount: int = ci0.make_int(1)
|
||||||
|
funds_proof = ci0.getProofOfFunds(require_amount, 'test'.encode('utf-8'))
|
||||||
|
|
||||||
|
logging.info('Testing verifyProofOfFunds')
|
||||||
|
amount_proved = ci0.verifyProofOfFunds(funds_proof[0], funds_proof[1], funds_proof[2], 'test'.encode('utf-8'))
|
||||||
|
assert (amount_proved >= require_amount)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -13,10 +13,10 @@ from basicswap.db import (
|
||||||
Concepts,
|
Concepts,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
Coins,
|
|
||||||
SwapTypes,
|
|
||||||
BidStates,
|
BidStates,
|
||||||
|
Coins,
|
||||||
DebugTypes,
|
DebugTypes,
|
||||||
|
SwapTypes,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
|
|
Loading…
Reference in a new issue