XMR withdrawals work.

spendBLockTx uses sweep_all.
This commit is contained in:
tecnovert
2020-12-05 13:22:22 +02:00
parent 669a465262
commit 4c200fe8d4
16 changed files with 181 additions and 65 deletions

View File

@@ -816,14 +816,12 @@ class BasicSwap(BaseApp):
if bid.participate_tx and bid.participate_tx.txid:
self.addWatchedOutput(coin_to, bid.bid_id, bid.participate_tx.txid.hex(), bid.participate_tx.vout, BidStates.SWAP_PARTICIPATING)
# TODO: watch for xmr bid outputs
if self.coin_clients[coin_from]['last_height_checked'] < 1:
if bid.initiate_tx and bid.initiate_tx.chain_height:
self.coin_clients[coin_from]['last_height_checked'] = bid.initiate_tx.chain_height
if self.coin_clients[coin_to]['last_height_checked'] < 1:
if bid.participate_tx and bid.participate_tx.chain_height:
self.coin_clients[coin_to]['last_height_checked'] = bid.participate_tx.chain_height
if self.coin_clients[coin_from]['last_height_checked'] < 1:
if bid.initiate_tx and bid.initiate_tx.chain_height:
self.coin_clients[coin_from]['last_height_checked'] = bid.initiate_tx.chain_height
if self.coin_clients[coin_to]['last_height_checked'] < 1:
if bid.participate_tx and bid.participate_tx.chain_height:
self.coin_clients[coin_to]['last_height_checked'] = bid.participate_tx.chain_height
# TODO process addresspool if bid has previously been abandoned
@@ -980,7 +978,9 @@ class BasicSwap(BaseApp):
# TODO: Dynamic fee selection
xmr_offer.a_fee_rate = make_int(0.00032595, self.ci(coin_from).exp())
xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp())
# xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp())
xmr_offer.b_fee_rate = make_int(0.00002, self.ci(coin_to).exp()) # abs fee
msg_buf.fee_rate_from = xmr_offer.a_fee_rate
msg_buf.fee_rate_to = xmr_offer.b_fee_rate
@@ -1198,12 +1198,19 @@ class BasicSwap(BaseApp):
return self.ci(coin_type).get_fee_rate()
def estimateWithdrawFee(self, coin_type, fee_rate):
if coin_type == Coins.XMR:
self.log.error('TODO: estimateWithdrawFee XMR')
return None
tx_vsize = self.getContractSpendTxVSize(coin_type)
est_fee = (fee_rate * tx_vsize) / 1000
return est_fee
def withdrawCoin(self, coin_type, value, addr_to, subfee):
self.log.info('withdrawCoin %s %s to %s %s', value, self.getTicker(coin_type), addr_to, ' subfee' if subfee else '')
params = [addr_to, value, '', '', subfee, True, self.coin_clients[coin_type]['conf_target']]
if coin_type == Coins.PART:
params.insert(5, '') # narration
return self.callcoinrpc(coin_type, 'sendtoaddress', params)
ci = self.ci(coin_type)
return ci.withdrawCoin(value, addr_to, subfee)
def cacheNewAddressForCoin(self, coin_type):
self.log.debug('cacheNewAddressForCoin %s', coin_type)
@@ -2765,7 +2772,15 @@ class BasicSwap(BaseApp):
def addWatchedOutput(self, coin_type, bid_id, txid_hex, vout, tx_type, swap_type=None):
self.log.debug('Adding watched output %s bid %s tx %s type %s', coin_type, bid_id.hex(), txid_hex, tx_type)
self.coin_clients[coin_type]['watched_outputs'].append(WatchedOutput(bid_id, txid_hex, vout, tx_type, swap_type))
watched = self.coin_clients[coin_type]['watched_outputs']
for wo in watched:
if wo.bid_id == bid_id and wo.txid_hex == txid_hex and wo.vout == vout:
self.log.debug('Output already being watched.')
return
watched.append(WatchedOutput(bid_id, txid_hex, vout, tx_type, swap_type))
def removeWatchedOutput(self, coin_type, bid_id, txid_hex):
# Remove all for bid if txid is None
@@ -2862,6 +2877,8 @@ class BasicSwap(BaseApp):
spending_txid = bytes.fromhex(spend_txid_hex)
if spending_txid == xmr_swap.a_lock_spend_tx_id:
self.log.debug('[rm] state %d', state)
self.log.debug('[rm] BidStates.XMR_SWAP_SECRET_SHARED %d', BidStates.XMR_SWAP_SECRET_SHARED)
if state == BidStates.XMR_SWAP_SECRET_SHARED:
xmr_swap.a_lock_spend_tx = bytes.fromhex(spend_txn['hex'])
bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED) # TODO: Wait for confirmation?
@@ -4232,24 +4249,28 @@ class BasicSwap(BaseApp):
if bid.state != data['bid_state']:
bid.setState(data['bid_state'])
self.log.debug('Set state to %s', strBidState(bid.state))
self.log.debug('[rm] Set bid.state %d', bid.state)
has_changed = True
if has_changed:
session = scoped_session(self.session_factory)
try:
activate_bid = False
if offer.swap_type == SwapTypes.SELLER_FIRST:
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
activate_bid = True
else:
raise ValueError('TODO')
self.log.debug('TODO - determine in-progress for manualBidUpdate')
if offer.swap_type == SwapTypes.XMR_SWAP:
if bid.state and bid.state == BidStates.XMR_SWAP_SECRET_SHARED:
activate_bid = True
if activate_bid:
self.activateBid(session, bid)
else:
self.deactivateBid(session, offer, bid)
self.log.debug('[rm] bid.state %d', bid.state)
self.saveBidInSession(bid_id, bid, session)
session.commit()
finally:

View File

@@ -242,14 +242,13 @@ class HttpHandler(BaseHTTPRequestHandler):
ci = swap_client.ci(k)
fee_rate = swap_client.getFeeRateForCoin(k)
tx_vsize = swap_client.getContractSpendTxVSize(k)
est_fee = (fee_rate * tx_vsize) / 1000
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
wallets_formatted.append({
'name': w['name'],
'version': w['version'],
'cid': str(int(k)),
'fee_rate': ci.format_amount(int(fee_rate * ci.COIN())),
'est_fee': ci.format_amount(int(est_fee * ci.COIN())),
'est_fee': 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())),
'balance': w['balance'],
'blocks': w['blocks'],
'synced': w['synced'],

View File

@@ -123,6 +123,7 @@ class BTCInterface(CoinInterface):
self.txoType = CTxOut
self._network = network
self.blocks_confirmed = coin_settings['blocks_confirmed']
self._conf_target = coin_settings['conf_target']
def testDaemonRPC(self):
self.rpc_callback('getwalletinfo', [])
@@ -881,6 +882,10 @@ class BTCInterface(CoinInterface):
'vout': utxo['vout']})
return rv
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee, True, self._conf_target]
return self.rpc_callback('sendtoaddress', params)
def testBTCInterface():
print('testBTCInterface')

View File

@@ -32,6 +32,7 @@ class PARTInterface(BTCInterface):
self.txoType = CTxOutPart
self._network = network
self.blocks_confirmed = coin_settings['blocks_confirmed']
self._conf_target = coin_settings['conf_target']
def knownWalletSeed(self):
# TODO: Double check
@@ -47,3 +48,7 @@ class PARTInterface(BTCInterface):
def initialiseWallet(self, key):
raise ValueError('TODO')
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee, '', True, self._conf_target]
return self.rpc_callback('sendtoaddress', params)

View File

@@ -21,6 +21,7 @@ from coincurve.dleag import (
from .util import (
dumpj,
make_int,
format_amount)
from .rpc_xmr import (
make_xmr_rpc_func,
@@ -55,14 +56,14 @@ class XMRInterface(CoinInterface):
def __init__(self, coin_settings, network):
super().__init__()
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings['rpchost'])
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', 'localhost'))
rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'])
self.rpc_cb = rpc_cb
self.rpc_wallet_cb = rpc_wallet_cb
self._network = network
self.blocks_confirmed = coin_settings['blocks_confirmed']
self._restore_height = coin_settings['restore_height']
self._restore_height = coin_settings.get('restore_height', 0)
def setWalletFilename(self, wallet_filename):
self._wallet_filename = wallet_filename
@@ -368,14 +369,26 @@ class XMRInterface(CoinInterface):
break
time.sleep(1 + i)
if rv['balance'] < cb_swap_value:
logging.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
raise ValueError('Invalid balance')
params = {'address': address_to}
rv = self.rpc_wallet_cb('sweep_all', params)
print('sweep_all', rv)
return bytes.fromhex(rv['tx_hash_list'][0])
"""
# TODO: need a subfee from output option
b_fee = b_fee_rate * 10 # Guess
# b_fee = b_fee_rate * 10 # Guess
b_fee = b_fee_rate
num_tries = 20
for i in range(1 + num_tries):
try:
params = {'destinations': [{'amount': cb_swap_value - b_fee, 'address': address_to}]}
logging.debug('params', dumpj(params))
rv = self.rpc_wallet_cb('transfer', params)
print('transfer', rv)
break
@@ -387,3 +400,12 @@ class XMRInterface(CoinInterface):
logging.info('Raising fee to %d', b_fee)
return bytes.fromhex(rv['tx_hash'])
"""
def withdrawCoin(self, value, addr_to, subfee):
self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename})
value_sats = make_int(value, self.exp())
params = {'destinations': [{'amount': value_sats, 'address': addr_to}]}
rv = self.rpc_wallet_cb('transfer', params)
return rv['tx_hash']

View File

@@ -14,6 +14,10 @@ OP_16 = 0x60
COIN = 100000000
decimal_ctx = decimal.Context()
decimal_ctx.prec = 20
def assert_cond(v, err='Bad opcode'):
if not v:
raise ValueError(err)
@@ -224,9 +228,15 @@ def getCompactSizeLen(v):
raise ValueError('Value too large')
def float_to_str(f):
# stackoverflow.com/questions/38847690
d1 = decimal_ctx.create_decimal(repr(f))
return format(d1, 'f')
def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r < 0 floor
if type(v) == float:
v = str(v)
v = float_to_str(v)
elif type(v) == int:
return v * 10 ** scale
@@ -239,7 +249,8 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
have_dp = True
continue
if not c.isdigit():
raise ValueError('Invalid char')
raise ValueError('Invalid char: ' + c)
if have_dp:
ep //= 10
if ep <= 0:
@@ -260,7 +271,7 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
def validate_amount(amount, scale=8):
str_amount = str(amount)
str_amount = float_to_str(amount) if type(amount) == float else str(amount)
has_decimal = False
for c in str_amount:
if c == '.' and not has_decimal: