ui: Improve fee estimation.
This commit is contained in:
parent
9888c4ebe1
commit
0432fae5b5
@ -155,9 +155,13 @@ class BTCInterface(CoinInterface):
|
|||||||
return abs(a - b) < 20
|
return abs(a - b) < 20
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_alock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
return 147
|
return 147
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
|
return 110
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def txoType():
|
def txoType():
|
||||||
return CTxOut
|
return CTxOut
|
||||||
@ -986,14 +990,14 @@ class BTCInterface(CoinInterface):
|
|||||||
def scanTxOutset(self, dest):
|
def scanTxOutset(self, dest):
|
||||||
return self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
return self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
||||||
|
|
||||||
def getTransaction(self, txid):
|
def getTransaction(self, txid: bytes):
|
||||||
try:
|
try:
|
||||||
return bytes.fromhex(self.rpc_callback('getrawtransaction', [txid.hex()]))
|
return bytes.fromhex(self.rpc_callback('getrawtransaction', [txid.hex()]))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# TODO: filter errors
|
# TODO: filter errors
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getWalletTransaction(self, txid):
|
def getWalletTransaction(self, txid: bytes):
|
||||||
try:
|
try:
|
||||||
return bytes.fromhex(self.rpc_callback('gettransaction', [txid.hex()]))
|
return bytes.fromhex(self.rpc_callback('gettransaction', [txid.hex()]))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@ -1460,7 +1464,7 @@ class BTCInterface(CoinInterface):
|
|||||||
tx.rehash()
|
tx.rehash()
|
||||||
return tx.serialize().hex()
|
return tx.serialize().hex()
|
||||||
|
|
||||||
def ensureFunds(self, amount):
|
def ensureFunds(self, amount: int) -> None:
|
||||||
if self.getSpendableBalance() < amount:
|
if self.getSpendableBalance() < amount:
|
||||||
raise ValueError('Balance too low')
|
raise ValueError('Balance too low')
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ class FIROInterface(BTCInterface):
|
|||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def createSCLockTx(self, value: int, script: bytearray, vkbv=None) -> bytes:
|
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
||||||
tx = CTransaction()
|
tx = CTransaction()
|
||||||
tx.nVersion = self.txVersion()
|
tx.nVersion = self.txVersion()
|
||||||
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
||||||
|
@ -59,8 +59,12 @@ class PARTInterface(BTCInterface):
|
|||||||
return 0xa0
|
return 0xa0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_alock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
return 213
|
return 200
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
|
return 138
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def txoType():
|
def txoType():
|
||||||
@ -111,8 +115,8 @@ class PARTInterface(BTCInterface):
|
|||||||
|
|
||||||
return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey)
|
return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey)
|
||||||
|
|
||||||
def getWitnessStackSerialisedLength(self, witness_stack):
|
def getWitnessStackSerialisedLength(self, witness_stack) -> int:
|
||||||
length = getCompactSizeLen(len(witness_stack))
|
length: int = getCompactSizeLen(len(witness_stack))
|
||||||
for e in witness_stack:
|
for e in witness_stack:
|
||||||
length += getWitnessElementLen(len(e))
|
length += getWitnessElementLen(len(e))
|
||||||
return length
|
return length
|
||||||
@ -138,7 +142,15 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
def balance_type():
|
def balance_type():
|
||||||
return BalanceTypes.BLIND
|
return BalanceTypes.BLIND
|
||||||
|
|
||||||
def coin_name(self):
|
@staticmethod
|
||||||
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
|
return 1032
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
|
return 980
|
||||||
|
|
||||||
|
def coin_name(self) -> str:
|
||||||
return super().coin_name() + ' Blind'
|
return super().coin_name() + ' Blind'
|
||||||
|
|
||||||
def getScriptLockTxNonce(self, data):
|
def getScriptLockTxNonce(self, data):
|
||||||
@ -167,7 +179,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
ensure(v['result'] is True, 'verifycommitment failed')
|
ensure(v['result'] is True, 'verifycommitment failed')
|
||||||
return output_n, blinded_info
|
return output_n, blinded_info
|
||||||
|
|
||||||
def createSCLockTx(self, value: int, script: bytearray, vkbv) -> bytes:
|
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes) -> bytes:
|
||||||
|
|
||||||
# Nonce is derived from vkbv, ephemeral_key isn't used
|
# Nonce is derived from vkbv, ephemeral_key isn't used
|
||||||
ephemeral_key = self.getNewSecretKey()
|
ephemeral_key = self.getNewSecretKey()
|
||||||
@ -183,7 +195,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
tx_bytes = bytes.fromhex(rv['hex'])
|
tx_bytes = bytes.fromhex(rv['hex'])
|
||||||
return tx_bytes
|
return tx_bytes
|
||||||
|
|
||||||
def fundSCLockTx(self, tx_bytes, feerate, vkbv):
|
def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes:
|
||||||
feerate_str = self.format_amount(feerate)
|
feerate_str = self.format_amount(feerate)
|
||||||
# TODO: unlock unspents if bid cancelled
|
# TODO: unlock unspents if bid cancelled
|
||||||
|
|
||||||
@ -462,7 +474,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
ensure(output_n is not None, 'Output not found in tx')
|
ensure(output_n is not None, 'Output not found in tx')
|
||||||
return output_n
|
return output_n
|
||||||
|
|
||||||
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv):
|
def createSCLockSpendTx(self, tx_lock_bytes: bytes, script_lock: bytes, pk_dest: bytes, tx_fee_rate: int, vkbv: bytes, fee_info={}) -> bytes:
|
||||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||||
lock_txid_hex = lock_tx_obj['txid']
|
lock_txid_hex = lock_tx_obj['txid']
|
||||||
|
|
||||||
@ -499,13 +511,20 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
||||||
lock_spend_tx_hex = rv['hex']
|
lock_spend_tx_hex = rv['hex']
|
||||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [lock_spend_tx_hex])
|
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [lock_spend_tx_hex])
|
||||||
|
|
||||||
vsize = lock_spend_tx_obj['vsize']
|
|
||||||
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||||
|
|
||||||
|
# lock_spend_tx_hex does not include the dummy witness stack
|
||||||
|
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||||
|
vsize = self.getTxVSize(self.loadTx(bytes.fromhex(lock_spend_tx_hex)), add_witness_bytes=witness_bytes)
|
||||||
actual_tx_fee_rate = pay_fee * 1000 // vsize
|
actual_tx_fee_rate = pay_fee * 1000 // vsize
|
||||||
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||||
lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee)
|
lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee)
|
||||||
|
|
||||||
|
fee_info['vsize'] = vsize
|
||||||
|
fee_info['fee_paid'] = pay_fee
|
||||||
|
fee_info['rate_input'] = tx_fee_rate
|
||||||
|
fee_info['rate_actual'] = actual_tx_fee_rate
|
||||||
|
|
||||||
return bytes.fromhex(lock_spend_tx_hex)
|
return bytes.fromhex(lock_spend_tx_hex)
|
||||||
|
|
||||||
def verifySCLockSpendTx(self, tx_bytes,
|
def verifySCLockSpendTx(self, tx_bytes,
|
||||||
@ -629,7 +648,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_callback('getbalances')['mine']['blind_trusted'])
|
return self.make_int(self.rpc_callback('getbalances')['mine']['blind_trusted'])
|
||||||
|
|
||||||
def publishBLockTx(self, vkbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
def publishBLockTx(self, vkbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||||
Kbv = self.getPubkey(vkbv)
|
Kbv = self.getPubkey(vkbv)
|
||||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||||
self._log.debug('sx_addr: {}'.format(sx_addr))
|
self._log.debug('sx_addr: {}'.format(sx_addr))
|
||||||
@ -751,14 +770,23 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
def balance_type():
|
def balance_type():
|
||||||
return BalanceTypes.ANON
|
return BalanceTypes.ANON
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
|
raise ValueError('Not possible')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
|
# TODO: Estimate with ringsize
|
||||||
|
return 1153
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def depth_spendable() -> int:
|
def depth_spendable() -> int:
|
||||||
return 12
|
return 12
|
||||||
|
|
||||||
def coin_name(self):
|
def coin_name(self) -> str:
|
||||||
return super().coin_name() + ' Anon'
|
return super().coin_name() + ' Anon'
|
||||||
|
|
||||||
def publishBLockTx(self, kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||||
|
|
||||||
|
@ -69,6 +69,15 @@ class XMRInterface(CoinInterface):
|
|||||||
def depth_spendable() -> int:
|
def depth_spendable() -> int:
|
||||||
return 10
|
return 10
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
|
raise ValueError('Not possible')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
|
# TODO: Estimate with ringsize
|
||||||
|
return 1507
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(network)
|
super().__init__(network)
|
||||||
daemon_login = None
|
daemon_login = None
|
||||||
@ -266,7 +275,7 @@ class XMRInterface(CoinInterface):
|
|||||||
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
||||||
return xmr_util.encode_address(Kbv, Kbs)
|
return xmr_util.encode_address(Kbv, Kbs)
|
||||||
|
|
||||||
def publishBLockTx(self, kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet_cb('refresh')
|
self.rpc_wallet_cb('refresh')
|
||||||
@ -368,7 +377,7 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height, spend_actual_balance=False):
|
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) -> bytes:
|
||||||
'''
|
'''
|
||||||
Notes:
|
Notes:
|
||||||
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
|
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
|
||||||
@ -520,6 +529,9 @@ class XMRInterface(CoinInterface):
|
|||||||
# TODO
|
# TODO
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def ensureFunds(self, amount):
|
def ensureFunds(self, amount: int) -> None:
|
||||||
if self.getSpendableBalance() < amount:
|
if self.getSpendableBalance() < amount:
|
||||||
raise ValueError('Balance too low')
|
raise ValueError('Balance too low')
|
||||||
|
|
||||||
|
def getTransaction(self, txid: bytes):
|
||||||
|
return self.rpc_cb2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
||||||
|
@ -203,6 +203,18 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes:
|
|||||||
if with_extra_info:
|
if with_extra_info:
|
||||||
offer_data['amount_negotiable'] = o.amount_negotiable
|
offer_data['amount_negotiable'] = o.amount_negotiable
|
||||||
offer_data['rate_negotiable'] = o.rate_negotiable
|
offer_data['rate_negotiable'] = o.rate_negotiable
|
||||||
|
|
||||||
|
if o.swap_type == SwapTypes.XMR_SWAP:
|
||||||
|
_, xmr_offer = swap_client.getXmrOffer(o.offer_id)
|
||||||
|
offer_data['lock_time_1'] = xmr_offer.lock_time_1
|
||||||
|
offer_data['lock_time_2'] = xmr_offer.lock_time_2
|
||||||
|
|
||||||
|
offer_data['feerate_from'] = xmr_offer.a_fee_rate
|
||||||
|
offer_data['feerate_to'] = xmr_offer.b_fee_rate
|
||||||
|
else:
|
||||||
|
offer_data['feerate_from'] = o.from_feerate
|
||||||
|
offer_data['feerate_to'] = o.to_feerate
|
||||||
|
|
||||||
rv.append(offer_data)
|
rv.append(offer_data)
|
||||||
return bytes(json.dumps(rv), 'UTF-8')
|
return bytes(json.dumps(rv), 'UTF-8')
|
||||||
|
|
||||||
|
@ -37,12 +37,15 @@ class JsonrpcDigest():
|
|||||||
self.__verbose = verbose
|
self.__verbose = verbose
|
||||||
self.__allow_none = allow_none
|
self.__allow_none = allow_none
|
||||||
|
|
||||||
self.__request_id = 1
|
self.__request_id = 0
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self.__transport is not None:
|
if self.__transport is not None:
|
||||||
self.__transport.close()
|
self.__transport.close()
|
||||||
|
|
||||||
|
def request_id(self):
|
||||||
|
return self.__request_id
|
||||||
|
|
||||||
def post_request(self, method, params, timeout=None):
|
def post_request(self, method, params, timeout=None):
|
||||||
try:
|
try:
|
||||||
connection = self.__transport.make_connection(self.__host)
|
connection = self.__transport.make_connection(self.__host)
|
||||||
@ -66,7 +69,7 @@ class JsonrpcDigest():
|
|||||||
self.__transport.close()
|
self.__transport.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def json_request(self, method, params, username='', password='', timeout=None):
|
def json_request(self, request_body, username='', password='', timeout=None):
|
||||||
try:
|
try:
|
||||||
connection = self.__transport.make_connection(self.__host)
|
connection = self.__transport.make_connection(self.__host)
|
||||||
if timeout:
|
if timeout:
|
||||||
@ -74,18 +77,11 @@ class JsonrpcDigest():
|
|||||||
|
|
||||||
headers = self.__transport._extra_headers[:]
|
headers = self.__transport._extra_headers[:]
|
||||||
|
|
||||||
request_body = {
|
|
||||||
'method': method,
|
|
||||||
'params': params,
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': self.__request_id
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.putrequest('POST', self.__handler)
|
connection.putrequest('POST', self.__handler)
|
||||||
headers.append(('Content-Type', 'application/json'))
|
headers.append(('Content-Type', 'application/json'))
|
||||||
headers.append(('Connection', 'keep-alive'))
|
headers.append(('Connection', 'keep-alive'))
|
||||||
self.__transport.send_headers(connection, headers)
|
self.__transport.send_headers(connection, headers)
|
||||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8'))
|
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
||||||
resp = connection.getresponse()
|
resp = connection.getresponse()
|
||||||
|
|
||||||
if resp.status == 401:
|
if resp.status == 401:
|
||||||
@ -135,18 +131,11 @@ class JsonrpcDigest():
|
|||||||
headers = self.__transport._extra_headers[:]
|
headers = self.__transport._extra_headers[:]
|
||||||
headers.append(('Authorization', header_value))
|
headers.append(('Authorization', header_value))
|
||||||
|
|
||||||
request_body = {
|
|
||||||
'method': method,
|
|
||||||
'params': params,
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': self.__request_id
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.putrequest('POST', self.__handler)
|
connection.putrequest('POST', self.__handler)
|
||||||
headers.append(('Content-Type', 'application/json'))
|
headers.append(('Content-Type', 'application/json'))
|
||||||
headers.append(('Connection', 'keep-alive'))
|
headers.append(('Connection', 'keep-alive'))
|
||||||
self.__transport.send_headers(connection, headers)
|
self.__transport.send_headers(connection, headers)
|
||||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8'))
|
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
||||||
resp = connection.getresponse()
|
resp = connection.getresponse()
|
||||||
|
|
||||||
self.__request_id += 1
|
self.__request_id += 1
|
||||||
@ -168,10 +157,16 @@ def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rp
|
|||||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
||||||
|
|
||||||
x = JsonrpcDigest(url)
|
x = JsonrpcDigest(url)
|
||||||
|
request_body = {
|
||||||
|
'method': method,
|
||||||
|
'params': params,
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': x.request_id()
|
||||||
|
}
|
||||||
if auth:
|
if auth:
|
||||||
v = x.json_request(method, params, username=auth[0], password=auth[1], timeout=timeout)
|
v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout)
|
||||||
else:
|
else:
|
||||||
v = x.json_request(method, params, timeout=timeout)
|
v = x.json_request(request_body, timeout=timeout)
|
||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode('utf-8'))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@ -183,7 +178,7 @@ def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rp
|
|||||||
return r['result']
|
return r['result']
|
||||||
|
|
||||||
|
|
||||||
def callrpc_xmr2(rpc_port, method, params=None, auth=None, rpc_host='127.0.0.1', timeout=120):
|
def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120):
|
||||||
try:
|
try:
|
||||||
if rpc_host.count('://') > 0:
|
if rpc_host.count('://') > 0:
|
||||||
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
||||||
@ -192,9 +187,9 @@ def callrpc_xmr2(rpc_port, method, params=None, auth=None, rpc_host='127.0.0.1',
|
|||||||
|
|
||||||
x = JsonrpcDigest(url)
|
x = JsonrpcDigest(url)
|
||||||
if auth:
|
if auth:
|
||||||
v = x.json_request(method, params, username=auth[0], password=auth[1], timeout=timeout)
|
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
|
||||||
else:
|
else:
|
||||||
v = x.json_request(method, params, timeout=timeout)
|
v = x.json_request(params, timeout=timeout)
|
||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode('utf-8'))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
@ -420,7 +420,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600 h-20">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600 h-20">
|
||||||
<td class="py-3 px-6">Amount you will get <span class="bold" id="bid_amt_from">{{ data.amt_from }}</span> {{ data.tla_from }}
|
<td class="py-3 px-6">Amount you will get <span class="bold" id="bid_amt_from">{{ data.amt_from }}</span> {{ data.tla_from }}
|
||||||
{% if data.xmr_type == true %} (excluding {{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }} in tx fees).
|
{% if data.xmr_type == true %} (excluding estimated {{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }} in tx fees).
|
||||||
{% else %} (excluding a tx fee).
|
{% else %} (excluding a tx fee).
|
||||||
{% endif %}</td>
|
{% endif %}</td>
|
||||||
<td class="py-3 px-6">Amount you will send <span class="bold" id="bid_amt_to">{{ data.amt_to }}</span> {{ data.tla_to }}
|
<td class="py-3 px-6">Amount you will send <span class="bold" id="bid_amt_to">{{ data.amt_to }}</span> {{ data.tla_to }}
|
||||||
|
@ -231,12 +231,12 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
page_data['from_fee_override'] = ci_from.format_amount(ci_from.make_int(from_fee_override, r=1))
|
page_data['from_fee_override'] = ci_from.format_amount(ci_from.make_int(from_fee_override, r=1))
|
||||||
parsed_data['from_fee_override'] = page_data['from_fee_override']
|
parsed_data['from_fee_override'] = page_data['from_fee_override']
|
||||||
|
|
||||||
lock_spend_tx_vsize = ci_leader.xmr_swap_alock_spend_tx_vsize()
|
lock_spend_tx_vsize = ci_from.xmr_swap_b_lock_spend_tx_vsize() if reverse_bid else ci_from.xmr_swap_a_lock_spend_tx_vsize()
|
||||||
lock_spend_tx_fee = ci_leader.make_int(ci_from.make_int(from_fee_override, r=1) * lock_spend_tx_vsize / 1000, r=1)
|
lock_spend_tx_fee = ci_from.make_int(ci_from.make_int(from_fee_override, r=1) * lock_spend_tx_vsize / 1000, r=1)
|
||||||
page_data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_leader.COIN())
|
page_data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_from.COIN())
|
||||||
page_data['tla_from'] = ci_leader.ticker()
|
page_data['tla_from'] = ci_from.ticker()
|
||||||
|
|
||||||
if ci_follower == Coins.XMR:
|
if ci_to == Coins.XMR:
|
||||||
if have_data_entry(form_data, 'fee_rate_to'):
|
if have_data_entry(form_data, 'fee_rate_to'):
|
||||||
page_data['to_fee_override'] = get_data_entry(form_data, 'fee_rate_to')
|
page_data['to_fee_override'] = get_data_entry(form_data, 'fee_rate_to')
|
||||||
parsed_data['to_fee_override'] = page_data['to_fee_override']
|
parsed_data['to_fee_override'] = page_data['to_fee_override']
|
||||||
@ -244,7 +244,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
to_fee_override, page_data['to_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_to'], page_data['fee_to_conf'])
|
to_fee_override, page_data['to_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_to'], page_data['fee_to_conf'])
|
||||||
if page_data['fee_to_extra'] > 0:
|
if page_data['fee_to_extra'] > 0:
|
||||||
to_fee_override += to_fee_override * (float(page_data['fee_to_extra']) / 100.0)
|
to_fee_override += to_fee_override * (float(page_data['fee_to_extra']) / 100.0)
|
||||||
page_data['to_fee_override'] = ci_follower.format_amount(ci_follower.make_int(to_fee_override, r=1))
|
page_data['to_fee_override'] = ci_to.format_amount(ci_to.make_int(to_fee_override, r=1))
|
||||||
parsed_data['to_fee_override'] = page_data['to_fee_override']
|
parsed_data['to_fee_override'] = page_data['to_fee_override']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Error setting fee', str(e)) # Expected if missing fields
|
print('Error setting fee', str(e)) # Expected if missing fields
|
||||||
@ -612,14 +612,15 @@ def page_offer(self, url_split, post_string):
|
|||||||
int_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
int_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
||||||
|
|
||||||
data['xmr_type'] = True
|
data['xmr_type'] = True
|
||||||
data['a_fee_rate'] = ci_leader.format_amount(xmr_offer.a_fee_rate)
|
data['a_fee_rate'] = ci_from.format_amount(xmr_offer.a_fee_rate)
|
||||||
data['a_fee_rate_verify'] = ci_leader.format_amount(int_fee_rate_now, conv_int=True)
|
data['a_fee_rate_verify'] = ci_leader.format_amount(int_fee_rate_now, conv_int=True)
|
||||||
data['a_fee_rate_verify_src'] = fee_source
|
data['a_fee_rate_verify_src'] = fee_source
|
||||||
data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now
|
data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now
|
||||||
|
|
||||||
lock_spend_tx_vsize = ci_leader.xmr_swap_alock_spend_tx_vsize()
|
from_fee_rate = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate
|
||||||
lock_spend_tx_fee = ci_leader.make_int(xmr_offer.a_fee_rate * lock_spend_tx_vsize / 1000, r=1)
|
lock_spend_tx_vsize = ci_from.xmr_swap_b_lock_spend_tx_vsize() if reverse_bid else ci_from.xmr_swap_a_lock_spend_tx_vsize()
|
||||||
data['amt_from_lock_spend_tx_fee'] = ci_leader.format_amount(lock_spend_tx_fee // ci_leader.COIN())
|
lock_spend_tx_fee = ci_from.make_int(from_fee_rate * lock_spend_tx_vsize / 1000, r=1)
|
||||||
|
data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_from.COIN())
|
||||||
|
|
||||||
if offer.was_sent:
|
if offer.was_sent:
|
||||||
try:
|
try:
|
||||||
@ -633,7 +634,9 @@ def page_offer(self, url_split, post_string):
|
|||||||
formatted_bids = []
|
formatted_bids = []
|
||||||
amt_swapped = 0
|
amt_swapped = 0
|
||||||
for b in bids:
|
for b in bids:
|
||||||
amt_swapped += b[4]
|
amount_from = b[4]
|
||||||
|
rate = b[10]
|
||||||
|
amt_swapped += amount_from
|
||||||
formatted_bids.append((b[2].hex(), ci_from.format_amount(amount_from), strBidState(b[5]), ci_to.format_amount(rate), b[11]))
|
formatted_bids.append((b[2].hex(), ci_from.format_amount(amount_from), strBidState(b[5]), ci_to.format_amount(rate), b[11]))
|
||||||
data['amt_swapped'] = ci_from.format_amount(amt_swapped)
|
data['amt_swapped'] = ci_from.format_amount(amt_swapped)
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ To instead use Monero public nodes and not run a local Monero daemon<br>(it can
|
|||||||
|
|
||||||
|
|
||||||
**Record the mnemonic from the output of the above command.**
|
**Record the mnemonic from the output of the above command.**
|
||||||
|
**Mnemonics should be stored encrypted and/or air-gapped.**
|
||||||
And the output of `echo $CURRENT_XMR_HEIGHT` for use if you need to later restore your wallet.
|
And the output of `echo $CURRENT_XMR_HEIGHT` for use if you need to later restore your wallet.
|
||||||
|
|
||||||
#### Make COINDATA_PATH permanent (optional):
|
#### Make COINDATA_PATH permanent (optional):
|
||||||
|
@ -143,6 +143,15 @@ class TestFunctions(BaseTest):
|
|||||||
offer = swap_clients[id_bidder].listOffers(filters={'offer_id': offer_id})[0]
|
offer = swap_clients[id_bidder].listOffers(filters={'offer_id': offer_id})[0]
|
||||||
assert (offer.offer_id == offer_id)
|
assert (offer.offer_id == offer_id)
|
||||||
|
|
||||||
|
post_json = {'with_extra_info': True}
|
||||||
|
offer0 = read_json_api(1800, f'offers/{offer_id.hex()}', post_json)[0]
|
||||||
|
offer1 = read_json_api(1800, f'offers/{offer_id.hex()}', post_json)[0]
|
||||||
|
from basicswap.util import dumpj
|
||||||
|
logging.info('offer0 {} '.format(dumpj(offer0)))
|
||||||
|
logging.info('offer1 {} '.format(dumpj(offer1)))
|
||||||
|
assert ('lock_time_1' in offer0)
|
||||||
|
assert ('lock_time_1' in offer1)
|
||||||
|
|
||||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
|
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
|
||||||
@ -207,6 +216,27 @@ class TestFunctions(BaseTest):
|
|||||||
assert (node0_sent_messages == (3 + split_msgs if reverse_bid else 4 + split_msgs))
|
assert (node0_sent_messages == (3 + split_msgs if reverse_bid else 4 + split_msgs))
|
||||||
assert (node1_sent_messages == (4 + split_msgs if reverse_bid else 2 + split_msgs))
|
assert (node1_sent_messages == (4 + split_msgs if reverse_bid else 2 + split_msgs))
|
||||||
|
|
||||||
|
post_json = {'show_extra': True}
|
||||||
|
bid0 = read_json_api(1800, f'bids/{bid_id.hex()}', post_json)
|
||||||
|
bid1 = read_json_api(1801, f'bids/{bid_id.hex()}', post_json)
|
||||||
|
logging.info('bid0 {} '.format(dumpj(bid0)))
|
||||||
|
logging.info('bid1 {} '.format(dumpj(bid1)))
|
||||||
|
|
||||||
|
chain_a_lock_txid = None
|
||||||
|
chain_b_lock_txid = None
|
||||||
|
for tx in bid0['txns']:
|
||||||
|
if tx['type'] == 'Chain A Lock Spend':
|
||||||
|
chain_a_lock_txid = tx['txid']
|
||||||
|
elif tx['type'] == 'Chain B Lock Spend':
|
||||||
|
chain_b_lock_txid = tx['txid']
|
||||||
|
for tx in bid1['txns']:
|
||||||
|
if not chain_a_lock_txid and tx['type'] == 'Chain A Lock Spend':
|
||||||
|
chain_a_lock_txid = tx['txid']
|
||||||
|
elif not chain_b_lock_txid and tx['type'] == 'Chain B Lock Spend':
|
||||||
|
chain_b_lock_txid = tx['txid']
|
||||||
|
assert (chain_a_lock_txid is not None)
|
||||||
|
assert (chain_b_lock_txid is not None)
|
||||||
|
|
||||||
def do_test_02_leader_recover_a_lock_tx(self, coin_from: Coins, coin_to: Coins) -> None:
|
def do_test_02_leader_recover_a_lock_tx(self, coin_from: Coins, coin_to: Coins) -> None:
|
||||||
logging.info('---------- Test {} to {} leader recovers coin a lock tx'.format(coin_from.name, coin_to.name))
|
logging.info('---------- Test {} to {} leader recovers coin a lock tx'.format(coin_from.name, coin_to.name))
|
||||||
|
|
||||||
@ -656,6 +686,27 @@ class BasicSwapTest(TestFunctions):
|
|||||||
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4)
|
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4)
|
||||||
assert (self.callnoderpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
|
assert (self.callnoderpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
|
||||||
|
|
||||||
|
expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
|
||||||
|
assert (expect_vsize >= vsize_actual)
|
||||||
|
assert (expect_vsize - vsize_actual < 10)
|
||||||
|
|
||||||
|
# Test chain b (no-script) lock tx size
|
||||||
|
v = ci.getNewSecretKey()
|
||||||
|
s = ci.getNewSecretKey()
|
||||||
|
S = ci.getPubkey(s)
|
||||||
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
|
|
||||||
|
addr_out = ci.getNewAddress(True)
|
||||||
|
lock_tx_b_spend_txid = ci.spendBLockTx(lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0)
|
||||||
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
||||||
|
if lock_tx_b_spend is None:
|
||||||
|
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
|
||||||
|
lock_tx_b_spend_decoded = self.callnoderpc('decoderawtransaction', [lock_tx_b_spend.hex()])
|
||||||
|
|
||||||
|
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
|
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
|
||||||
|
assert (expect_vsize - lock_tx_b_spend_decoded['vsize'] < 10)
|
||||||
|
|
||||||
def test_01_a_full_swap(self):
|
def test_01_a_full_swap(self):
|
||||||
if not self.has_segwit:
|
if not self.has_segwit:
|
||||||
return
|
return
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2022 tecnovert
|
# Copyright (c) 2021-2023 tecnovert
|
||||||
# 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.
|
||||||
|
|
||||||
@ -66,7 +66,6 @@ class Test(BaseTest):
|
|||||||
logging.info('Waiting for blind balance')
|
logging.info('Waiting for blind balance')
|
||||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1800/json/wallets/part', 'blind_balance', 100.0 + node0_blind_before)
|
wait_for_balance(test_delay_event, 'http://127.0.0.1:1800/json/wallets/part', 'blind_balance', 100.0 + node0_blind_before)
|
||||||
js_0 = read_json_api(1800, 'wallets/part')
|
js_0 = read_json_api(1800, 'wallets/part')
|
||||||
node0_blind_before = js_0['blind_balance'] + js_0['blind_unconfirmed']
|
|
||||||
|
|
||||||
def ensure_balance(self, coin_type, node_id, amount):
|
def ensure_balance(self, coin_type, node_id, amount):
|
||||||
tla = 'PART'
|
tla = 'PART'
|
||||||
@ -89,6 +88,122 @@ class Test(BaseTest):
|
|||||||
def getXmrBalance(self, js_wallets):
|
def getXmrBalance(self, js_wallets):
|
||||||
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])
|
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])
|
||||||
|
|
||||||
|
def test_010_txn_size(self):
|
||||||
|
logging.info('---------- Test {} txn_size'.format(self.test_coin_from.name))
|
||||||
|
|
||||||
|
self.ensure_balance(self.test_coin_from, 0, 100.0)
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci = swap_clients[0].ci(self.test_coin_from)
|
||||||
|
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
|
||||||
|
|
||||||
|
def wait_for_unspents(delay_event, iterations=20, delay_time=0.5):
|
||||||
|
nonlocal ci
|
||||||
|
i = 0
|
||||||
|
while not delay_event.is_set():
|
||||||
|
unspents = ci.rpc_callback('listunspentblind')
|
||||||
|
if len(unspents) >= 1:
|
||||||
|
return
|
||||||
|
delay_event.wait(delay_time)
|
||||||
|
i += 1
|
||||||
|
if i > iterations:
|
||||||
|
raise ValueError('wait_for_unspents timed out')
|
||||||
|
wait_for_unspents(test_delay_event)
|
||||||
|
|
||||||
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
|
||||||
|
# Record unspents before createSCLockTx as the used ones will be locked
|
||||||
|
unspents = ci.rpc_callback('listunspentblind')
|
||||||
|
locked_utxos_before = ci.rpc_callback('listlockunspent')
|
||||||
|
|
||||||
|
# fee_rate is in sats/kvB
|
||||||
|
fee_rate: int = 1000
|
||||||
|
|
||||||
|
vkbv = ci.getNewSecretKey()
|
||||||
|
a = ci.getNewSecretKey()
|
||||||
|
b = ci.getNewSecretKey()
|
||||||
|
|
||||||
|
A = ci.getPubkey(a)
|
||||||
|
B = ci.getPubkey(b)
|
||||||
|
lock_tx_script = pi.genScriptLockTxScript(ci, A, B)
|
||||||
|
|
||||||
|
lock_tx = ci.createSCLockTx(amount, lock_tx_script, vkbv)
|
||||||
|
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate, vkbv)
|
||||||
|
lock_tx = ci.signTxWithWallet(lock_tx)
|
||||||
|
|
||||||
|
unspents_after = ci.rpc_callback('listunspentblind')
|
||||||
|
locked_utxos_after = ci.rpc_callback('listlockunspent')
|
||||||
|
|
||||||
|
assert (len(unspents) > len(unspents_after))
|
||||||
|
assert (len(locked_utxos_after) > len(locked_utxos_before))
|
||||||
|
lock_tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx.hex()])
|
||||||
|
txid = lock_tx_decoded['txid']
|
||||||
|
|
||||||
|
vsize = lock_tx_decoded['vsize']
|
||||||
|
expect_fee_int = round(fee_rate * vsize / 1000)
|
||||||
|
expect_fee = ci.format_amount(expect_fee_int)
|
||||||
|
|
||||||
|
ci.rpc_callback('sendrawtransaction', [lock_tx.hex()])
|
||||||
|
rv = ci.rpc_callback('gettransaction', [txid])
|
||||||
|
wallet_tx_fee = -ci.make_int(rv['details'][0]['fee'])
|
||||||
|
|
||||||
|
assert (wallet_tx_fee >= expect_fee_int)
|
||||||
|
assert (wallet_tx_fee - expect_fee_int < 20)
|
||||||
|
|
||||||
|
addr_out = ci.getNewAddress(True)
|
||||||
|
addrinfo = ci.rpc_callback('getaddressinfo', [addr_out,])
|
||||||
|
pk_out = bytes.fromhex(addrinfo['pubkey'])
|
||||||
|
fee_info = {}
|
||||||
|
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pk_out, fee_rate, vkbv, fee_info=fee_info)
|
||||||
|
vsize_estimated: int = fee_info['vsize']
|
||||||
|
|
||||||
|
spend_tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
|
||||||
|
txid = spend_tx_decoded['txid']
|
||||||
|
|
||||||
|
nonce = ci.getScriptLockTxNonce(vkbv)
|
||||||
|
output_n, _ = ci.findOutputByNonce(lock_tx_decoded, nonce)
|
||||||
|
assert (output_n is not None)
|
||||||
|
valueCommitment = bytes.fromhex(lock_tx_decoded['vout'][output_n]['valueCommitment'])
|
||||||
|
|
||||||
|
witness_stack = [
|
||||||
|
b'',
|
||||||
|
ci.signTx(a, lock_spend_tx, 0, lock_tx_script, valueCommitment),
|
||||||
|
ci.signTx(b, lock_spend_tx, 0, lock_tx_script, valueCommitment),
|
||||||
|
lock_tx_script,
|
||||||
|
]
|
||||||
|
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack)
|
||||||
|
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
|
||||||
|
vsize_actual: int = tx_decoded['vsize']
|
||||||
|
|
||||||
|
# Note: The fee is set allowing 9 bytes for the encoded fee amount, causing a small overestimate
|
||||||
|
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 10)
|
||||||
|
assert (ci.rpc_callback('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
|
||||||
|
|
||||||
|
# Test chain b (no-script) lock tx size
|
||||||
|
v = ci.getNewSecretKey()
|
||||||
|
s = ci.getNewSecretKey()
|
||||||
|
S = ci.getPubkey(s)
|
||||||
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
|
|
||||||
|
addr_out = ci.getNewStealthAddress()
|
||||||
|
lock_tx_b_spend_txid = None
|
||||||
|
for i in range(20):
|
||||||
|
try:
|
||||||
|
lock_tx_b_spend_txid = ci.spendBLockTx(lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print('spendBLockTx failed', str(e))
|
||||||
|
test_delay_event.wait(2)
|
||||||
|
assert (lock_tx_b_spend_txid is not None)
|
||||||
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
||||||
|
if lock_tx_b_spend is None:
|
||||||
|
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
|
||||||
|
lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()])
|
||||||
|
|
||||||
|
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
|
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
|
||||||
|
assert (expect_vsize - lock_tx_b_spend_decoded['vsize'] < 10)
|
||||||
|
|
||||||
def test_01_part_xmr(self):
|
def test_01_part_xmr(self):
|
||||||
logging.info('---------- Test PARTct to XMR')
|
logging.info('---------- Test PARTct to XMR')
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
@ -669,6 +669,130 @@ class Test(BaseTest):
|
|||||||
def notest_00_delay(self):
|
def notest_00_delay(self):
|
||||||
test_delay_event.wait(100000)
|
test_delay_event.wait(100000)
|
||||||
|
|
||||||
|
def test_010_txn_size(self):
|
||||||
|
logging.info('---------- Test {} txn_size'.format(Coins.PART))
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci = swap_clients[0].ci(Coins.PART)
|
||||||
|
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
|
||||||
|
|
||||||
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
|
||||||
|
# Record unspents before createSCLockTx as the used ones will be locked
|
||||||
|
unspents = ci.rpc_callback('listunspent')
|
||||||
|
|
||||||
|
# fee_rate is in sats/kvB
|
||||||
|
fee_rate: int = 1000
|
||||||
|
|
||||||
|
a = ci.getNewSecretKey()
|
||||||
|
b = ci.getNewSecretKey()
|
||||||
|
|
||||||
|
A = ci.getPubkey(a)
|
||||||
|
B = ci.getPubkey(b)
|
||||||
|
lock_tx_script = pi.genScriptLockTxScript(ci, A, B)
|
||||||
|
|
||||||
|
lock_tx = ci.createSCLockTx(amount, lock_tx_script)
|
||||||
|
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate)
|
||||||
|
lock_tx = ci.signTxWithWallet(lock_tx)
|
||||||
|
|
||||||
|
unspents_after = ci.rpc_callback('listunspent')
|
||||||
|
assert (len(unspents) > len(unspents_after))
|
||||||
|
|
||||||
|
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx.hex()])
|
||||||
|
txid = tx_decoded['txid']
|
||||||
|
|
||||||
|
vsize = tx_decoded['vsize']
|
||||||
|
expect_fee_int = round(fee_rate * vsize / 1000)
|
||||||
|
expect_fee = ci.format_amount(expect_fee_int)
|
||||||
|
|
||||||
|
out_value: int = 0
|
||||||
|
for txo in tx_decoded['vout']:
|
||||||
|
if 'value' in txo:
|
||||||
|
out_value += ci.make_int(txo['value'])
|
||||||
|
in_value: int = 0
|
||||||
|
for txi in tx_decoded['vin']:
|
||||||
|
for utxo in unspents:
|
||||||
|
if 'vout' not in utxo:
|
||||||
|
continue
|
||||||
|
if utxo['txid'] == txi['txid'] and utxo['vout'] == txi['vout']:
|
||||||
|
in_value += ci.make_int(utxo['amount'])
|
||||||
|
break
|
||||||
|
fee_value = in_value - out_value
|
||||||
|
|
||||||
|
ci.rpc_callback('sendrawtransaction', [lock_tx.hex()])
|
||||||
|
rv = ci.rpc_callback('gettransaction', [txid])
|
||||||
|
wallet_tx_fee = -ci.make_int(rv['fee'])
|
||||||
|
|
||||||
|
assert (wallet_tx_fee == fee_value)
|
||||||
|
assert (wallet_tx_fee == expect_fee_int)
|
||||||
|
|
||||||
|
addr_out = ci.getNewAddress(True)
|
||||||
|
pkh_out = ci.decodeAddress(addr_out)
|
||||||
|
fee_info = {}
|
||||||
|
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info)
|
||||||
|
vsize_estimated: int = fee_info['vsize']
|
||||||
|
|
||||||
|
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
|
||||||
|
txid = tx_decoded['txid']
|
||||||
|
|
||||||
|
witness_stack = [
|
||||||
|
b'',
|
||||||
|
ci.signTx(a, lock_spend_tx, 0, lock_tx_script, amount),
|
||||||
|
ci.signTx(b, lock_spend_tx, 0, lock_tx_script, amount),
|
||||||
|
lock_tx_script,
|
||||||
|
]
|
||||||
|
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack)
|
||||||
|
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
|
||||||
|
vsize_actual: int = tx_decoded['vsize']
|
||||||
|
|
||||||
|
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4)
|
||||||
|
assert (ci.rpc_callback('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
|
||||||
|
|
||||||
|
expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
|
||||||
|
assert (expect_vsize >= vsize_actual)
|
||||||
|
assert (expect_vsize - vsize_actual < 10)
|
||||||
|
|
||||||
|
# Test chain b (no-script) lock tx size
|
||||||
|
v = ci.getNewSecretKey()
|
||||||
|
s = ci.getNewSecretKey()
|
||||||
|
S = ci.getPubkey(s)
|
||||||
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
|
|
||||||
|
addr_out = ci.getNewAddress(True)
|
||||||
|
lock_tx_b_spend_txid = ci.spendBLockTx(lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0)
|
||||||
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
||||||
|
if lock_tx_b_spend is None:
|
||||||
|
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
|
||||||
|
lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()])
|
||||||
|
|
||||||
|
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
|
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
|
||||||
|
assert (expect_vsize - lock_tx_b_spend_decoded['vsize'] < 10)
|
||||||
|
|
||||||
|
def test_010_xmr_txn_size(self):
|
||||||
|
logging.info('---------- Test {} txn_size'.format(Coins.XMR))
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci = swap_clients[1].ci(Coins.XMR)
|
||||||
|
pi = swap_clients[1].pi(SwapTypes.XMR_SWAP)
|
||||||
|
|
||||||
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
fee_rate: int = 1000 # TODO: How to set feerate for rpc functions?
|
||||||
|
|
||||||
|
v = ci.getNewSecretKey()
|
||||||
|
s = ci.getNewSecretKey()
|
||||||
|
S = ci.getPubkey(s)
|
||||||
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
|
|
||||||
|
addr_out = ci.getNewAddress(True)
|
||||||
|
lock_tx_b_spend_txid = ci.spendBLockTx(lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0)
|
||||||
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
||||||
|
|
||||||
|
actual_size: int = len(lock_tx_b_spend['txs_as_hex'][0]) // 2
|
||||||
|
expect_size: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
|
assert (expect_size >= actual_size)
|
||||||
|
assert (expect_size - actual_size < 10)
|
||||||
|
|
||||||
def test_01_part_xmr(self):
|
def test_01_part_xmr(self):
|
||||||
logging.info('---------- Test PART to XMR')
|
logging.info('---------- Test PART to XMR')
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
@ -1197,6 +1321,36 @@ class Test(BaseTest):
|
|||||||
js_0 = read_json_api(1800, 'wallets/part')
|
js_0 = read_json_api(1800, 'wallets/part')
|
||||||
assert (js_0['anon_balance'] + js_0['anon_pending'] > node0_anon_before + (amount_to - 0.05))
|
assert (js_0['anon_balance'] + js_0['anon_pending'] > node0_anon_before + (amount_to - 0.05))
|
||||||
|
|
||||||
|
# Test chain b (no-script) lock tx size
|
||||||
|
ci = swap_clients[1].ci(Coins.PART_ANON)
|
||||||
|
pi = swap_clients[1].pi(SwapTypes.XMR_SWAP)
|
||||||
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
fee_rate: int = 1000
|
||||||
|
v = ci.getNewSecretKey()
|
||||||
|
s = ci.getNewSecretKey()
|
||||||
|
S = ci.getPubkey(s)
|
||||||
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
|
|
||||||
|
addr_out = ci.getNewStealthAddress()
|
||||||
|
lock_tx_b_spend_txid = None
|
||||||
|
for i in range(20):
|
||||||
|
try:
|
||||||
|
lock_tx_b_spend_txid = ci.spendBLockTx(lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print('spendBLockTx failed', str(e))
|
||||||
|
test_delay_event.wait(2)
|
||||||
|
assert (lock_tx_b_spend_txid is not None)
|
||||||
|
|
||||||
|
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
|
||||||
|
if lock_tx_b_spend is None:
|
||||||
|
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
|
||||||
|
lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()])
|
||||||
|
|
||||||
|
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
|
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
|
||||||
|
assert (expect_vsize - lock_tx_b_spend_decoded['vsize'] < 10)
|
||||||
|
|
||||||
def test_12_particl_blind(self):
|
def test_12_particl_blind(self):
|
||||||
logging.info('---------- Test Particl blind transactions')
|
logging.info('---------- Test Particl blind transactions')
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
Loading…
Reference in New Issue
Block a user