mirror of
https://github.com/basicswap/basicswap.git
synced 2024-11-16 15:58:17 +00:00
MSG3L
Debug mode is disabled by default.
This commit is contained in:
parent
009729aa96
commit
18a5322f10
14 changed files with 503 additions and 91 deletions
|
@ -36,7 +36,7 @@ class BaseApp:
|
|||
self.settings = settings
|
||||
self.coin_clients = {}
|
||||
self.mxDB = threading.RLock()
|
||||
self.debug = self.settings.get('debug', cfg.DEBUG)
|
||||
self.debug = self.settings.get('debug', False)
|
||||
|
||||
self.prepareLogging()
|
||||
self.log.info('Network: {}'.format(self.chain))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019 tecnovert
|
||||
# Copyright (c) 2019-2020 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
@ -57,6 +57,8 @@ from .messages_pb2 import (
|
|||
XmrBidMessage,
|
||||
XmrBidAcceptMessage,
|
||||
XmrSplitMessage,
|
||||
XmrBidLockTxSigsMessage,
|
||||
XmrBidLockSpendTxMessage,
|
||||
)
|
||||
from .db import (
|
||||
CURRENT_DB_VERSION,
|
||||
|
@ -95,6 +97,7 @@ class MessageTypes(IntEnum):
|
|||
XMR_BID = auto()
|
||||
XMR_BID_SPLIT = auto()
|
||||
XMR_BID_ACCEPT = auto()
|
||||
XMR_BID_TXN_SIGS_FL = auto()
|
||||
|
||||
|
||||
class SwapTypes(IntEnum):
|
||||
|
@ -120,6 +123,8 @@ class BidStates(IntEnum):
|
|||
SWAP_INITIATED = auto() # Initiate txn validated
|
||||
SWAP_PARTICIPATING = auto() # Participate txn validated
|
||||
SWAP_COMPLETED = auto() # All swap txns spent
|
||||
XMR_SWAP_SCRIPT_COIN_LOCKED = auto()
|
||||
SWAP_DELAYING = auto()
|
||||
SWAP_TIMEDOUT = auto()
|
||||
BID_ABANDONED = auto() # Bid will no longer be processed
|
||||
BID_ERROR = auto() # An error occurred
|
||||
|
@ -141,9 +146,15 @@ class TxTypes(IntEnum):
|
|||
PTX_REDEEM = auto()
|
||||
PTX_REFUND = auto()
|
||||
|
||||
XMR_SWAP_A_LOCK = auto()
|
||||
XMR_SWAP_A_LOCK_REFUND = auto()
|
||||
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
|
||||
|
||||
|
||||
class EventTypes(IntEnum):
|
||||
ACCEPT_BID = auto()
|
||||
SIGN_XMR_SWAP_LOCK_TX_A = auto()
|
||||
SEND_XMR_SWAP_LOCK_TX_A = auto()
|
||||
|
||||
|
||||
class XmrSplitMsgTypes(IntEnum):
|
||||
|
@ -467,15 +478,15 @@ class BasicSwap(BaseApp):
|
|||
|
||||
def createInterface(self, coin):
|
||||
if coin == Coins.PART:
|
||||
return PARTInterface(self.coin_clients[coin])
|
||||
return PARTInterface(self.coin_clients[coin], self.chain)
|
||||
elif coin == Coins.BTC:
|
||||
return BTCInterface(self.coin_clients[coin])
|
||||
return BTCInterface(self.coin_clients[coin], self.chain)
|
||||
elif coin == Coins.LTC:
|
||||
return LTCInterface(self.coin_clients[coin])
|
||||
return LTCInterface(self.coin_clients[coin], self.chain)
|
||||
elif coin == Coins.NMC:
|
||||
return NMCInterface(self.coin_clients[coin])
|
||||
return NMCInterface(self.coin_clients[coin], self.chain)
|
||||
elif coin == Coins.XMR:
|
||||
return XMRInterface(self.coin_clients[coin])
|
||||
return XMRInterface(self.coin_clients[coin], self.chain)
|
||||
else:
|
||||
raise ValueError('Unknown coin type')
|
||||
|
||||
|
@ -1087,18 +1098,22 @@ class BasicSwap(BaseApp):
|
|||
finally:
|
||||
self.mxDB.release()
|
||||
|
||||
def createEvent(self, delay, event_type, linked_id):
|
||||
def createEventInSession(self, delay, event_type, linked_id, session):
|
||||
self.log.debug('createEvent %d %s', event_type, linked_id.hex())
|
||||
event = EventQueue()
|
||||
event.active_ind = 1
|
||||
event.created_at = int(time.time())
|
||||
event.trigger_at = event.created_at + delay
|
||||
event.event_type = event_type
|
||||
event.linked_id = linked_id
|
||||
session.add(event)
|
||||
|
||||
def createEvent(self, delay, event_type, linked_id):
|
||||
#self.log.debug('createEvent %d %s', event_type, linked_id.hex())
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
event = EventQueue()
|
||||
event.active_ind = 1
|
||||
event.created_at = int(time.time())
|
||||
event.trigger_at = event.created_at + delay
|
||||
event.event_type = event_type
|
||||
event.linked_id = linked_id
|
||||
session.add(event)
|
||||
self.createEventInSession(delay, event_type, linked_id, session)
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
|
@ -1351,11 +1366,6 @@ class BasicSwap(BaseApp):
|
|||
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex())
|
||||
assert(offer.expire_at > int(time.time())), 'Offer has expired'
|
||||
|
||||
msg_buf = XmrBidMessage()
|
||||
msg_buf.offer_msg_id = offer_id
|
||||
msg_buf.time_valid = 60 * 10
|
||||
msg_buf.amount = int(amount) # amount of coin_from
|
||||
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ci_from = self.ci(coin_from)
|
||||
|
@ -1363,6 +1373,14 @@ class BasicSwap(BaseApp):
|
|||
|
||||
self.checkSynced(coin_from, coin_to)
|
||||
|
||||
msg_buf = XmrBidMessage()
|
||||
msg_buf.offer_msg_id = offer_id
|
||||
msg_buf.time_valid = 60 * 10
|
||||
msg_buf.amount = int(amount) # amount of coin_from
|
||||
|
||||
address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK)
|
||||
msg_buf.dest_af = ci_from.decodeAddress(address_out)
|
||||
|
||||
bid_created_at = int(time.time())
|
||||
if offer.swap_type != SwapTypes.XMR_SWAP:
|
||||
raise ValueError('TODO')
|
||||
|
@ -1370,6 +1388,7 @@ class BasicSwap(BaseApp):
|
|||
# Follower to leader
|
||||
xmr_swap = XmrSwap()
|
||||
xmr_swap.contract_count = self.getNewContractId()
|
||||
xmr_swap.dest_af = msg_buf.dest_af
|
||||
xmr_swap.b_restore_height = self.ci(coin_to).getChainHeight()
|
||||
kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_xmr=True)
|
||||
kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 2, for_xmr=True)
|
||||
|
@ -1580,7 +1599,8 @@ class BasicSwap(BaseApp):
|
|||
session.close()
|
||||
session.remove()
|
||||
|
||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||
#self.swaps_in_progress[bid_id] = (bid, offer)
|
||||
# Add to swaps_in_progress only when waiting on txns
|
||||
self.log.info('Sent XMR_BID_ACCEPT %s', bid_id.hex())
|
||||
return bid_id
|
||||
finally:
|
||||
|
@ -1625,6 +1645,7 @@ class BasicSwap(BaseApp):
|
|||
self.mxDB.release()
|
||||
|
||||
def setBidError(self, bid_id, bid, error_str):
|
||||
self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
|
||||
bid.setState(BidStates.BID_ERROR)
|
||||
bid.state_note = 'error msg: ' + error_str
|
||||
self.saveBid(bid_id, bid)
|
||||
|
@ -2435,6 +2456,10 @@ class BasicSwap(BaseApp):
|
|||
for row in q:
|
||||
if row.event_type == EventTypes.ACCEPT_BID:
|
||||
self.acceptBid(row.linked_id)
|
||||
elif row.event_type == EventTypes.SIGN_XMR_SWAP_LOCK_TX_A:
|
||||
self.sendXmrBidTxnSigsFtoL(row.linked_id, session)
|
||||
elif row.event_type == EventTypes.SEND_XMR_SWAP_LOCK_TX_A:
|
||||
self.sendXmrBidCoinALockTx(row.linked_id, session)
|
||||
else:
|
||||
self.log.warning('Unknown event type: %d', row.event_type)
|
||||
|
||||
|
@ -2459,9 +2484,9 @@ class BasicSwap(BaseApp):
|
|||
if num_segments > 1:
|
||||
try:
|
||||
self.receiveXmrBid(bid, session)
|
||||
except Exception as e:
|
||||
self.log.info('Verify xmr bid {} failed: {}'.format(bid.bid_id.hex(), str(e)))
|
||||
bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(e))
|
||||
except Exception as ex:
|
||||
self.log.info('Verify xmr bid {} failed: {}'.format(bid.bid_id.hex(), str(ex)))
|
||||
bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(ex))
|
||||
session.add(bid)
|
||||
continue
|
||||
if bid.created_at + ttl_xmr_split_messages < now:
|
||||
|
@ -2476,9 +2501,9 @@ class BasicSwap(BaseApp):
|
|||
if num_segments > 1:
|
||||
try:
|
||||
self.receiveXmrBidAccept(bid, session)
|
||||
except Exception as e:
|
||||
self.log.info('Verify xmr bid accept {} failed: {}'.format(bid.bid_id.hex(), str(e)))
|
||||
bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(e))
|
||||
except Exception as ex:
|
||||
self.log.info('Verify xmr bid accept {} failed: {}'.format(bid.bid_id.hex(), str(ex)))
|
||||
bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(ex))
|
||||
session.add(bid)
|
||||
continue
|
||||
if bid.created_at + ttl_xmr_split_messages < now:
|
||||
|
@ -2489,6 +2514,7 @@ class BasicSwap(BaseApp):
|
|||
# Expire old records
|
||||
q = session.query(XmrSplitData).filter(XmrSplitData.created_at + ttl_xmr_split_messages < now)
|
||||
q.delete(synchronize_session=False)
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
|
@ -2659,7 +2685,6 @@ class BasicSwap(BaseApp):
|
|||
else:
|
||||
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
|
||||
self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay)
|
||||
|
||||
self.createEvent(delay, EventTypes.ACCEPT_BID, bid_id)
|
||||
|
||||
def processBidAccept(self, msg):
|
||||
|
@ -2760,6 +2785,14 @@ class BasicSwap(BaseApp):
|
|||
if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag):
|
||||
raise ValueError('Invalid DLEAG proof.')
|
||||
|
||||
# Extract pubkeys from MSG1L DLEAG
|
||||
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33]
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkasf):
|
||||
raise ValueError('Invalid coin a pubkey.')
|
||||
xmr_swap.pkbsf = xmr_swap.kbsf_dleag[33: 33 + 32]
|
||||
if not ci_to.verifyPubkey(xmr_swap.pkbsf):
|
||||
raise ValueError('Invalid coin b pubkey.')
|
||||
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkaf):
|
||||
raise ValueError('Invalid pubkey.')
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkarf):
|
||||
|
@ -2794,14 +2827,27 @@ class BasicSwap(BaseApp):
|
|||
if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag):
|
||||
raise ValueError('Invalid DLEAG proof.')
|
||||
|
||||
# Extract pubkeys from MSG1F DLEAG
|
||||
xmr_swap.pkasl = xmr_swap.kbsl_dleag[0: 33]
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkasl):
|
||||
raise ValueError('Invalid coin a pubkey.')
|
||||
xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33: 33 + 32]
|
||||
if not ci_to.verifyPubkey(xmr_swap.pkbsl):
|
||||
raise ValueError('Invalid coin b pubkey.')
|
||||
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkal):
|
||||
raise ValueError('Invalid pubkey.')
|
||||
if not ci_from.verifyPubkey(xmr_swap.pkarl):
|
||||
raise ValueError('Invalid pubkey.')
|
||||
|
||||
bid.setState(BidStates.BID_ACCEPTED)
|
||||
#bid.setState(BidStates.BID_ACCEPTED)
|
||||
bid.setState(BidStates.SWAP_DELAYING)
|
||||
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
|
||||
|
||||
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
|
||||
self.log.info('Responding to xmr bid accept %s in %d seconds', bid.bid_id.hex(), delay)
|
||||
self.createEventInSession(delay, EventTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, session)
|
||||
|
||||
def processXmrBid(self, msg):
|
||||
self.log.debug('Processing xmr bid msg %s', msg['msgid'])
|
||||
now = int(time.time())
|
||||
|
@ -2841,6 +2887,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
xmr_swap = XmrSwap(
|
||||
bid_id=bid_id,
|
||||
dest_af=bid_data.dest_af,
|
||||
pkaf=bid_data.pkaf,
|
||||
pkarf=bid_data.pkarf,
|
||||
vkbvf=bid_data.kbvf,
|
||||
|
@ -2858,7 +2905,7 @@ class BasicSwap(BaseApp):
|
|||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
||||
|
||||
def processXmrBidAccept(self, msg):
|
||||
# F receiving MSG1F
|
||||
# F receiving MSG1F and MSG2F
|
||||
self.log.debug('Processing xmr bid accept msg %s', msg['msgid'])
|
||||
now = int(time.time())
|
||||
msg_bytes = bytes.fromhex(msg['hex'][2:-2])
|
||||
|
@ -2869,23 +2916,192 @@ class BasicSwap(BaseApp):
|
|||
|
||||
self.log.debug('for bid %s', msg_data.bid_msg_id.hex())
|
||||
bid, xmr_swap = self.getXmrBid(msg_data.bid_msg_id)
|
||||
assert(bid), 'Bid not found: {}.'.format(msg_data.bid_id.hex())
|
||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(msg_data.bid_id.hex())
|
||||
|
||||
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True)
|
||||
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
|
||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ci_from = self.ci(coin_from)
|
||||
ci_to = self.ci(coin_to)
|
||||
|
||||
assert(len(msg_data.sh) == 32), 'Bad secret hash length'
|
||||
try:
|
||||
assert(len(msg_data.sh) == 32), 'Bad secret hash length'
|
||||
|
||||
xmr_swap.sh = msg_data.sh
|
||||
xmr_swap.pkal = msg_data.pkal
|
||||
xmr_swap.pkarl = msg_data.pkarl
|
||||
xmr_swap.vkbvl = msg_data.kbvl
|
||||
xmr_swap.kbsl_dleag = msg_data.kbsl_dleag
|
||||
xmr_swap.sh = msg_data.sh
|
||||
xmr_swap.pkal = msg_data.pkal
|
||||
xmr_swap.pkarl = msg_data.pkarl
|
||||
xmr_swap.vkbvl = msg_data.kbvl
|
||||
xmr_swap.kbsl_dleag = msg_data.kbsl_dleag
|
||||
|
||||
bid.setState(BidStates.BID_RECEIVING_ACC)
|
||||
self.saveBid(msg_data.bid_msg_id, bid, xmr_swap=xmr_swap)
|
||||
xmr_swap.a_lock_tx = msg_data.a_lock_tx
|
||||
xmr_swap.a_lock_tx_script = msg_data.a_lock_tx_script
|
||||
xmr_swap.a_lock_refund_tx = msg_data.a_lock_refund_tx
|
||||
xmr_swap.a_lock_refund_tx_script = msg_data.a_lock_refund_tx_script
|
||||
xmr_swap.a_lock_refund_spend_tx = msg_data.a_lock_refund_spend_tx
|
||||
xmr_swap.al_lock_refund_tx_sig = msg_data.al_lock_refund_tx_sig
|
||||
|
||||
check_a_lock_tx_inputs = True
|
||||
xmr_swap.a_lock_tx_id, lock_tx_vout = ci_from.verifyLockTx(
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
bid.amount,
|
||||
xmr_swap.sh,
|
||||
xmr_swap.pkal, xmr_swap.pkaf,
|
||||
xmr_offer.lock_time_1, xmr_offer.a_fee_rate,
|
||||
xmr_swap.pkarl, xmr_swap.pkarf,
|
||||
check_a_lock_tx_inputs
|
||||
)
|
||||
xmr_swap.a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
|
||||
|
||||
lock_refund_tx_id, xmr_swap.a_swap_refund_value = ci_from.verifyLockRefundTx(
|
||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.a_lock_tx_id, lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.pkarl, xmr_swap.pkarf,
|
||||
xmr_offer.lock_time_2,
|
||||
xmr_swap.pkaf,
|
||||
bid.amount, xmr_offer.a_fee_rate
|
||||
)
|
||||
|
||||
ci_from.verifyLockRefundSpendTx(
|
||||
xmr_swap.a_lock_refund_spend_tx,
|
||||
lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
|
||||
xmr_swap.pkal,
|
||||
xmr_swap.a_swap_refund_value, xmr_offer.a_fee_rate
|
||||
)
|
||||
|
||||
logging.info('Checking leader\'s lock refund tx signature')
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkarl, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
|
||||
bid.setState(BidStates.BID_RECEIVING_ACC)
|
||||
self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap)
|
||||
except Exception as ex:
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
self.setBidError(bid.bid_id, bid, str(ex))
|
||||
|
||||
def sendXmrBidTxnSigsFtoL(self, bid_id, session):
|
||||
# F -> L: Sending MSG3L
|
||||
self.log.debug('Signing xmr bid lock txns %s', bid_id.hex())
|
||||
|
||||
bid, xmr_swap = self.getXmrBid(bid_id)
|
||||
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
|
||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
|
||||
|
||||
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False)
|
||||
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
|
||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ci_from = self.ci(coin_from)
|
||||
ci_to = self.ci(coin_to)
|
||||
|
||||
try:
|
||||
karf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4)
|
||||
|
||||
print('[rm] xmr_swap.a_swap_refund_value', xmr_swap.a_swap_refund_value)
|
||||
xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES(karf, xmr_swap.pkasl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||
xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(karf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||
|
||||
msg_buf = XmrBidLockTxSigsMessage(
|
||||
bid_msg_id=bid_id,
|
||||
af_lock_refund_spend_tx_esig=xmr_swap.af_lock_refund_spend_tx_esig,
|
||||
af_lock_refund_tx_sig=xmr_swap.af_lock_refund_tx_sig
|
||||
)
|
||||
|
||||
mag_bytes = msg_buf.SerializeToString()
|
||||
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_TXN_SIGS_FL) + mag_bytes.hex()
|
||||
|
||||
options = {'decodehex': True, 'ttl_is_seconds': True}
|
||||
# TODO: set msg_valid based on bid / offer parameters
|
||||
msg_valid = self.SMSG_SECONDS_IN_HOUR * 48
|
||||
ro = self.callrpc('smsgsend', [bid.bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options])
|
||||
xmr_swap.coin_a_lock_tx_sigs_l_id = bytes.fromhex(ro['msgid'])
|
||||
|
||||
self.log.info('Sent XMR_BID_TXN_SIGS_FL %s', xmr_swap.coin_a_lock_tx_sigs_l_id.hex())
|
||||
|
||||
bid.setState(BidStates.BID_ACCEPTED)
|
||||
session.add(bid)
|
||||
session.add(xmr_swap)
|
||||
|
||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||
except Exception as ex:
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
|
||||
def sendXmrBidCoinALockTx(self, bid_id, session):
|
||||
# Send coin A lock tx and MSG4F L -> F
|
||||
self.log.debug('Sending coin A lock tx for xmr bid %s', bid_id.hex())
|
||||
|
||||
bid, xmr_swap = self.getXmrBid(bid_id)
|
||||
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
|
||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
|
||||
|
||||
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False)
|
||||
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
|
||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ci_from = self.ci(coin_from)
|
||||
ci_to = self.ci(coin_to)
|
||||
|
||||
kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4)
|
||||
|
||||
xmr_swap.a_lock_spend_tx = ci_from.createScriptLockSpendTx(
|
||||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.dest_af,
|
||||
xmr_offer.a_fee_rate)
|
||||
|
||||
xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES(kal, xmr_swap.pkasf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, bid.amount) # self.a_swap_value
|
||||
|
||||
# TODO: Separate MSG4F and txn sending
|
||||
|
||||
|
||||
def processXmrBidCoinALockSigs(self, msg):
|
||||
# Follower processing MSG3L
|
||||
self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid'])
|
||||
now = int(time.time())
|
||||
msg_bytes = bytes.fromhex(msg['hex'][2:-2])
|
||||
msg_data = XmrBidLockTxSigsMessage()
|
||||
msg_data.ParseFromString(msg_bytes)
|
||||
|
||||
assert(len(msg_data.bid_msg_id) == 28), 'Bad bid_msg_id length'
|
||||
bid_id = msg_data.bid_msg_id
|
||||
|
||||
bid, xmr_swap = self.getXmrBid(bid_id)
|
||||
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
|
||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
|
||||
|
||||
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False)
|
||||
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
|
||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ci_from = self.ci(coin_from)
|
||||
ci_to = self.ci(coin_to)
|
||||
|
||||
try:
|
||||
xmr_swap.af_lock_refund_spend_tx_esig = msg_data.af_lock_refund_spend_tx_esig
|
||||
xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig
|
||||
|
||||
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True)
|
||||
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig)
|
||||
|
||||
print('[rm] xmr_swap.a_swap_refund_value', xmr_swap.a_swap_refund_value)
|
||||
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkarf, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||
assert(v), 'Invalid signature for lock refund spend txn'
|
||||
|
||||
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
|
||||
self.log.info('Sending coin A lock tx for xmr bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createEvent(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_A, bid_id)
|
||||
|
||||
bid.setState(BidStates.SWAP_DELAYING)
|
||||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
||||
except Exception as ex:
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
self.setBidError(bid_id, bid, str(ex))
|
||||
|
||||
def processXmrSplitMessage(self, msg):
|
||||
self.log.debug('Processing xmr split msg %s', msg['msgid'])
|
||||
|
@ -2895,7 +3111,6 @@ class BasicSwap(BaseApp):
|
|||
msg_data.ParseFromString(msg_bytes)
|
||||
|
||||
# Validate data
|
||||
print('[rm] msg_data.msg_id', msg_data.msg_id.hex())
|
||||
assert(len(msg_data.msg_id) == 28), 'Bad msg_id length'
|
||||
|
||||
if msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT:
|
||||
|
@ -2923,12 +3138,15 @@ class BasicSwap(BaseApp):
|
|||
self.processXmrBid(msg)
|
||||
elif msg_type == MessageTypes.XMR_BID_ACCEPT:
|
||||
self.processXmrBidAccept(msg)
|
||||
elif msg_type == MessageTypes.XMR_BID_TXN_SIGS_FL:
|
||||
self.processXmrBidCoinALockSigs(msg)
|
||||
elif msg_type == MessageTypes.XMR_BID_SPLIT:
|
||||
self.processXmrSplitMessage(msg)
|
||||
|
||||
except Exception as ex:
|
||||
self.log.error('processMsg %s', str(ex))
|
||||
traceback.print_exc()
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.mxDB.release()
|
||||
|
||||
|
@ -2954,7 +3172,8 @@ class BasicSwap(BaseApp):
|
|||
pass
|
||||
except Exception as ex:
|
||||
self.log.error('smsg zmq %s', str(ex))
|
||||
traceback.print_exc()
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
|
@ -2968,7 +3187,8 @@ class BasicSwap(BaseApp):
|
|||
to_remove.append(bid_id)
|
||||
except Exception as ex:
|
||||
self.log.error('checkBidState %s %s', bid_id.hex(), str(ex))
|
||||
traceback.print_exc()
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
self.setBidError(bid_id, v[0], str(ex))
|
||||
|
||||
for bid_id in to_remove:
|
||||
|
@ -2996,7 +3216,8 @@ class BasicSwap(BaseApp):
|
|||
|
||||
except Exception as ex:
|
||||
self.log.error('update %s', str(ex))
|
||||
traceback.print_exc()
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.mxDB.release()
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import os
|
||||
|
||||
DEBUG = True
|
||||
|
||||
CONFIG_FILENAME = 'basicswap.json'
|
||||
DEFAULT_DATADIR = '~/.basicswap'
|
||||
TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))
|
||||
|
|
|
@ -228,15 +228,21 @@ class XmrSwap(Base):
|
|||
bid_accept_msg_id2 = sa.Column(sa.LargeBinary)
|
||||
bid_accept_msg_id3 = sa.Column(sa.LargeBinary)
|
||||
|
||||
coin_a_lock_tx_sigs_l_id = sa.Column(sa.LargeBinary) # MSG3L F -> L
|
||||
|
||||
contract_count = sa.Column(sa.Integer)
|
||||
|
||||
sh = sa.Column(sa.LargeBinary) # Secret hash
|
||||
|
||||
dest_af = sa.Column(sa.LargeBinary) # Destination for coin A amount to follower when swap completes successfully
|
||||
|
||||
pkal = sa.Column(sa.LargeBinary)
|
||||
pkarl = sa.Column(sa.LargeBinary)
|
||||
pkasl = sa.Column(sa.LargeBinary)
|
||||
|
||||
pkaf = sa.Column(sa.LargeBinary)
|
||||
pkarf = sa.Column(sa.LargeBinary)
|
||||
pkasf = sa.Column(sa.LargeBinary)
|
||||
|
||||
vkbvl = sa.Column(sa.LargeBinary)
|
||||
vkbsl = sa.Column(sa.LargeBinary)
|
||||
|
@ -260,6 +266,13 @@ class XmrSwap(Base):
|
|||
|
||||
a_lock_refund_spend_tx = sa.Column(sa.LargeBinary)
|
||||
|
||||
af_lock_refund_spend_tx_esig = sa.Column(sa.LargeBinary)
|
||||
af_lock_refund_spend_tx_sig = sa.Column(sa.LargeBinary)
|
||||
af_lock_refund_tx_sig = sa.Column(sa.LargeBinary)
|
||||
|
||||
a_lock_spend_tx = sa.Column(sa.LargeBinary)
|
||||
al_lock_spend_tx_esig = sa.Column(sa.LargeBinary)
|
||||
|
||||
b_restore_height = sa.Column(sa.Integer) # Height of xmr chain before the swap
|
||||
|
||||
|
||||
|
|
|
@ -9,18 +9,25 @@ import time
|
|||
import hashlib
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from basicswap.contrib.test_framework import segwit_addr
|
||||
|
||||
|
||||
from .util import (
|
||||
decodeScriptNum,
|
||||
getCompactSizeLen,
|
||||
dumpj,
|
||||
format_amount,
|
||||
make_int
|
||||
)
|
||||
make_int,
|
||||
decodeAddress)
|
||||
from coincurve.keys import (
|
||||
PublicKey)
|
||||
from coincurve.dleag import (
|
||||
verify_secp256k1_point)
|
||||
from coincurve.ecdsaotves import (
|
||||
ecdsaotves_enc_sign,
|
||||
ecdsaotves_enc_verify,
|
||||
ecdsaotves_dec_sig,
|
||||
ecdsaotves_rec_enc_key)
|
||||
|
||||
from .ecc_util import (
|
||||
G, ep,
|
||||
|
@ -59,7 +66,7 @@ from .contrib.test_framework.script import (
|
|||
|
||||
from .contrib.test_framework.key import ECKey, ECPubKey
|
||||
|
||||
from .chainparams import CoinInterface
|
||||
from .chainparams import CoinInterface, Coins, chainparams
|
||||
from .rpc import make_rpc_func
|
||||
from .util import assert_cond
|
||||
|
||||
|
@ -72,6 +79,10 @@ def findOutput(tx, script_pk):
|
|||
|
||||
|
||||
class BTCInterface(CoinInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.BTC
|
||||
|
||||
@staticmethod
|
||||
def exp():
|
||||
return 8
|
||||
|
@ -102,9 +113,10 @@ class BTCInterface(CoinInterface):
|
|||
def compareFeeRates(self, a, b):
|
||||
return abs(a - b) < 20
|
||||
|
||||
def __init__(self, coin_settings):
|
||||
def __init__(self, coin_settings, network):
|
||||
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
||||
self.txoType = CTxOut
|
||||
self._network = network
|
||||
|
||||
def testDaemonRPC(self):
|
||||
self.rpc_callback('getwalletinfo', [])
|
||||
|
@ -124,6 +136,13 @@ class BTCInterface(CoinInterface):
|
|||
args.append('bech32')
|
||||
return self.rpc_callback('getnewaddress', args)
|
||||
|
||||
def decodeAddress(self, address):
|
||||
bech32_prefix = chainparams[self.coin_type()][self._network]['hrp']
|
||||
if address.startswith(bech32_prefix):
|
||||
ignr, pkhash = segwit_addr.decode(bech32_prefix, address)
|
||||
return pkhash
|
||||
return decodeAddress(address)[1:]
|
||||
|
||||
def getNewSecretKey(self):
|
||||
return getSecretInt()
|
||||
|
||||
|
@ -379,8 +398,8 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx.serialize()
|
||||
|
||||
def createScriptLockSpendTx(self, tx_lock, script_lock, pkh_dest, tx_fee_rate):
|
||||
|
||||
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate):
|
||||
tx_lock = self.loadTx(tx_lock_bytes)
|
||||
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
|
||||
locked_n = findOutput(tx_lock, output_script)
|
||||
assert_cond(locked_n is not None, 'Output not found in tx')
|
||||
|
@ -411,7 +430,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx.serialize()
|
||||
|
||||
def verifyLockTx(self, tx, script_out,
|
||||
def verifyLockTx(self, tx_bytes, script_out,
|
||||
swap_value,
|
||||
sh,
|
||||
Kal, Kaf,
|
||||
|
@ -425,6 +444,7 @@ class BTCInterface(CoinInterface):
|
|||
# However by checking early we can avoid wasting time processing unmineable txns
|
||||
# Check fee is reasonable
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
logging.info('Verifying lock tx: {}.'.format(b2h(tx_hash)))
|
||||
|
||||
|
@ -441,11 +461,11 @@ class BTCInterface(CoinInterface):
|
|||
# Check script and values
|
||||
shv, A, B, csv_val, C, D = self.extractScriptLockScriptValues(script_out)
|
||||
assert_cond(shv == sh, 'Bad hash lock')
|
||||
assert_cond(A == self.encodePubkey(Kal), 'Bad script pubkey')
|
||||
assert_cond(B == self.encodePubkey(Kaf), 'Bad script pubkey')
|
||||
assert_cond(A == Kal, 'Bad script pubkey')
|
||||
assert_cond(B == Kaf, 'Bad script pubkey')
|
||||
assert_cond(csv_val == lock_value, 'Bad script csv value')
|
||||
assert_cond(C == self.encodePubkey(Karl), 'Bad script pubkey')
|
||||
assert_cond(D == self.encodePubkey(Karf), 'Bad script pubkey')
|
||||
assert_cond(C == Karl, 'Bad script pubkey')
|
||||
assert_cond(D == Karf, 'Bad script pubkey')
|
||||
|
||||
if check_lock_tx_inputs:
|
||||
# Check that inputs are unspent and verify fee rate
|
||||
|
@ -454,7 +474,6 @@ class BTCInterface(CoinInterface):
|
|||
add_witness_bytes = getCompactSizeLen(len(tx.vin))
|
||||
for pi in tx.vin:
|
||||
ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||
print('ptx', dumpj(ptx))
|
||||
prevout = ptx['vout'][pi.prevout.n]
|
||||
inputs_value += make_int(prevout['value'])
|
||||
|
||||
|
@ -483,7 +502,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx_hash, locked_n
|
||||
|
||||
def verifyLockRefundTx(self, tx, script_out,
|
||||
def verifyLockRefundTx(self, tx_bytes, script_out,
|
||||
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||
Karl, Karf, csv_val_expect, Kaf, swap_value, feerate):
|
||||
# Verify:
|
||||
|
@ -491,6 +510,7 @@ class BTCInterface(CoinInterface):
|
|||
# Must have only one output to the p2wsh of the lock refund script
|
||||
# Output value must be locked_coin - lock tx fee
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
logging.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash)))
|
||||
|
||||
|
@ -511,10 +531,10 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
# Check script and values
|
||||
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||
assert_cond(A == self.encodePubkey(Karl), 'Bad script pubkey')
|
||||
assert_cond(B == self.encodePubkey(Karf), 'Bad script pubkey')
|
||||
assert_cond(A == Karl, 'Bad script pubkey')
|
||||
assert_cond(B == Karf, 'Bad script pubkey')
|
||||
assert_cond(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
assert_cond(C == self.encodePubkey(Kaf), 'Bad script pubkey')
|
||||
assert_cond(C == Kaf, 'Bad script pubkey')
|
||||
|
||||
fee_paid = swap_value - locked_coin
|
||||
assert(fee_paid > 0)
|
||||
|
@ -533,13 +553,14 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return tx_hash, locked_coin
|
||||
|
||||
def verifyLockRefundSpendTx(self, tx,
|
||||
def verifyLockRefundSpendTx(self, tx_bytes,
|
||||
lock_refund_tx_id, prevout_script,
|
||||
Kal,
|
||||
prevout_value, feerate):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
|
||||
tx = self.loadTx(tx_bytes)
|
||||
tx_hash = self.getTxHash(tx)
|
||||
logging.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash)))
|
||||
|
||||
|
@ -553,7 +574,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
|
||||
p2wpkh = CScript([OP_0, hash160(self.encodePubkey(Kal))])
|
||||
p2wpkh = CScript([OP_0, hash160(Kal)])
|
||||
locked_n = findOutput(tx, p2wpkh)
|
||||
assert_cond(locked_n is not None, 'Output not found in lock refund spend tx')
|
||||
tx_value = tx.vout[locked_n].nValue
|
||||
|
@ -632,16 +653,22 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
|
||||
|
||||
def signTxOtVES(self, key_sign, key_encrypt, tx, prevout_n, prevout_script, prevout_value):
|
||||
def signTxOtVES(self, key_sign, pubkey_encrypt, tx_bytes, prevout_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||
return otves.EncSign(key_sign, key_encrypt, sig_hash)
|
||||
|
||||
def verifyTxOtVES(self, tx, sig, Ks, Ke, prevout_n, prevout_script, prevout_value):
|
||||
return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
|
||||
#return otves.EncSign(key_sign, key_encrypt, sig_hash)
|
||||
|
||||
def verifyTxOtVES(self, tx_bytes, sig, Ks, Ke, prevout_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||
return otves.EncVrfy(Ks, Ke, sig_hash, sig)
|
||||
return ecdsaotves_enc_verify(Ks, Ke, sig_hash, sig)
|
||||
#return otves.EncVrfy(Ks, Ke, sig_hash, sig)
|
||||
|
||||
def decryptOtVES(self, k, esig):
|
||||
return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
|
||||
return ecdsaotves_dec_sig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
|
||||
#return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
|
||||
|
||||
def verifyTxSig(self, tx_bytes, sig, K, prevout_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
@ -675,9 +702,9 @@ class BTCInterface(CoinInterface):
|
|||
tx.deserialize(BytesIO(tx_bytes))
|
||||
return tx
|
||||
|
||||
def getTxHash(self, tx_bytes):
|
||||
tx = CTransaction()
|
||||
tx = FromHex(tx, tx_bytes.hex())
|
||||
def getTxHash(self, tx):
|
||||
if isinstance(tx, bytes):
|
||||
tx = self.loadTx(tx)
|
||||
tx.rehash()
|
||||
return i2b(tx.sha256)
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from .interface_btc import BTCInterface
|
||||
|
||||
from .chainparams import Coins
|
||||
|
||||
class LTCInterface(BTCInterface):
|
||||
pass
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.LTC
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from .interface_btc import BTCInterface
|
||||
|
||||
from .chainparams import Coins
|
||||
|
||||
class NMCInterface(BTCInterface):
|
||||
pass
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.NMC
|
||||
|
|
|
@ -10,11 +10,15 @@ from .contrib.test_framework.messages import (
|
|||
)
|
||||
|
||||
from .interface_btc import BTCInterface
|
||||
from .chainparams import CoinInterface
|
||||
from .chainparams import CoinInterface, Coins
|
||||
from .rpc import make_rpc_func
|
||||
|
||||
|
||||
class PARTInterface(BTCInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.PART
|
||||
|
||||
@staticmethod
|
||||
def witnessScaleFactor():
|
||||
return 2
|
||||
|
@ -23,9 +27,10 @@ class PARTInterface(BTCInterface):
|
|||
def txVersion():
|
||||
return 0xa0
|
||||
|
||||
def __init__(self, coin_settings):
|
||||
def __init__(self, coin_settings, network):
|
||||
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
|
||||
self.txoType = CTxOutPart
|
||||
self._network = network
|
||||
|
||||
def getNewAddress(self, use_segwit):
|
||||
return self.rpc_callback('getnewaddress', ['swap_receive'])
|
||||
|
|
|
@ -25,12 +25,16 @@ from .rpc_xmr import (
|
|||
make_xmr_wallet_rpc_func)
|
||||
from .ecc_util import (
|
||||
b2i)
|
||||
from .chainparams import CoinInterface
|
||||
from .chainparams import CoinInterface, Coins
|
||||
|
||||
XMR_COIN = 10 ** 12
|
||||
|
||||
|
||||
class XMRInterface(CoinInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.XMR
|
||||
|
||||
@staticmethod
|
||||
def exp():
|
||||
return 12
|
||||
|
@ -43,12 +47,13 @@ class XMRInterface(CoinInterface):
|
|||
def nbK(): # No. of bytes requires to encode a public key
|
||||
return 32
|
||||
|
||||
def __init__(self, coin_settings):
|
||||
def __init__(self, coin_settings, network):
|
||||
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'])
|
||||
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
|
||||
|
||||
def testDaemonRPC(self):
|
||||
self.rpc_wallet_cb('get_languages')
|
||||
|
|
|
@ -51,9 +51,8 @@ message BidAcceptMessage {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/* Step 2, buyer -> seller */
|
||||
message XmrBidMessage {
|
||||
/* MSG1L, F -> L */
|
||||
bytes offer_msg_id = 1;
|
||||
uint64 time_valid = 2; /* seconds bid is valid for */
|
||||
uint64 amount = 3; /* amount of amount_from bid is for */
|
||||
|
@ -63,6 +62,8 @@ message XmrBidMessage {
|
|||
|
||||
bytes kbvf = 6;
|
||||
bytes kbsf_dleag = 7;
|
||||
|
||||
bytes dest_af = 8;
|
||||
}
|
||||
|
||||
message XmrSplitMessage {
|
||||
|
@ -72,7 +73,6 @@ message XmrSplitMessage {
|
|||
bytes dleag = 4;
|
||||
}
|
||||
|
||||
|
||||
message XmrBidAcceptMessage {
|
||||
bytes bid_msg_id = 1;
|
||||
|
||||
|
@ -92,3 +92,20 @@ message XmrBidAcceptMessage {
|
|||
bytes al_lock_refund_tx_sig = 12;
|
||||
}
|
||||
|
||||
message XmrBidLockTxSigsMessage {
|
||||
/* MSG3L */
|
||||
bytes bid_msg_id = 1;
|
||||
|
||||
bytes af_lock_refund_spend_tx_esig = 2;
|
||||
bytes af_lock_refund_tx_sig = 3;
|
||||
}
|
||||
|
||||
message XmrBidLockSpendTxMessage {
|
||||
/* MSG4F */
|
||||
bytes bid_msg_id = 1;
|
||||
|
||||
bytes a_lock_spend_tx = 2;
|
||||
bytes al_lock_spend_tx_esig = 3;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
|
|||
syntax='proto3',
|
||||
serialized_options=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\x88\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\x62\x06proto3'
|
||||
serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\x99\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"f\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x03 \x01(\x0c\x62\x06proto3'
|
||||
)
|
||||
|
||||
|
||||
|
@ -366,6 +366,13 @@ _XMRBIDMESSAGE = _descriptor.Descriptor(
|
|||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='dest_af', full_name='basicswap.XmrBidMessage.dest_af', index=7,
|
||||
number=8, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
|
@ -379,7 +386,7 @@ _XMRBIDMESSAGE = _descriptor.Descriptor(
|
|||
oneofs=[
|
||||
],
|
||||
serialized_start=736,
|
||||
serialized_end=872,
|
||||
serialized_end=889,
|
||||
)
|
||||
|
||||
|
||||
|
@ -431,8 +438,8 @@ _XMRSPLITMESSAGE = _descriptor.Descriptor(
|
|||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=874,
|
||||
serialized_end=958,
|
||||
serialized_start=891,
|
||||
serialized_end=975,
|
||||
)
|
||||
|
||||
|
||||
|
@ -540,8 +547,100 @@ _XMRBIDACCEPTMESSAGE = _descriptor.Descriptor(
|
|||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=961,
|
||||
serialized_end=1244,
|
||||
serialized_start=978,
|
||||
serialized_end=1261,
|
||||
)
|
||||
|
||||
|
||||
_XMRBIDLOCKTXSIGSMESSAGE = _descriptor.Descriptor(
|
||||
name='XmrBidLockTxSigsMessage',
|
||||
full_name='basicswap.XmrBidLockTxSigsMessage',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='bid_msg_id', full_name='basicswap.XmrBidLockTxSigsMessage.bid_msg_id', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='af_lock_refund_spend_tx_esig', full_name='basicswap.XmrBidLockTxSigsMessage.af_lock_refund_spend_tx_esig', index=1,
|
||||
number=2, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='af_lock_refund_tx_sig', full_name='basicswap.XmrBidLockTxSigsMessage.af_lock_refund_tx_sig', index=2,
|
||||
number=3, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=1263,
|
||||
serialized_end=1377,
|
||||
)
|
||||
|
||||
|
||||
_XMRBIDLOCKSPENDTXMESSAGE = _descriptor.Descriptor(
|
||||
name='XmrBidLockSpendTxMessage',
|
||||
full_name='basicswap.XmrBidLockSpendTxMessage',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='bid_msg_id', full_name='basicswap.XmrBidLockSpendTxMessage.bid_msg_id', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='a_lock_spend_tx', full_name='basicswap.XmrBidLockSpendTxMessage.a_lock_spend_tx', index=1,
|
||||
number=2, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='al_lock_spend_tx_esig', full_name='basicswap.XmrBidLockSpendTxMessage.al_lock_spend_tx_esig', index=2,
|
||||
number=3, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=1379,
|
||||
serialized_end=1481,
|
||||
)
|
||||
|
||||
_OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE
|
||||
|
@ -552,6 +651,8 @@ DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE
|
|||
DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['XmrBidLockTxSigsMessage'] = _XMRBIDLOCKTXSIGSMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['XmrBidLockSpendTxMessage'] = _XMRBIDLOCKSPENDTXMESSAGE
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), {
|
||||
|
@ -596,5 +697,19 @@ XmrBidAcceptMessage = _reflection.GeneratedProtocolMessageType('XmrBidAcceptMess
|
|||
})
|
||||
_sym_db.RegisterMessage(XmrBidAcceptMessage)
|
||||
|
||||
XmrBidLockTxSigsMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockTxSigsMessage', (_message.Message,), {
|
||||
'DESCRIPTOR' : _XMRBIDLOCKTXSIGSMESSAGE,
|
||||
'__module__' : 'messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockTxSigsMessage)
|
||||
})
|
||||
_sym_db.RegisterMessage(XmrBidLockTxSigsMessage)
|
||||
|
||||
XmrBidLockSpendTxMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockSpendTxMessage', (_message.Message,), {
|
||||
'DESCRIPTOR' : _XMRBIDLOCKSPENDTXMESSAGE,
|
||||
'__module__' : 'messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockSpendTxMessage)
|
||||
})
|
||||
_sym_db.RegisterMessage(XmrBidLockSpendTxMessage)
|
||||
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
|
|
@ -135,6 +135,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||
btcdatadir = os.path.join(datadir, str(BTC_NODE))
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
settings = {
|
||||
'debug': True,
|
||||
'zmqhost': 'tcp://127.0.0.1',
|
||||
'zmqport': BASE_ZMQ_PORT + nodeId,
|
||||
'htmlhost': 'localhost',
|
||||
|
|
|
@ -139,6 +139,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||
btcdatadir = os.path.join(datadir, str(BTC_NODE))
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
settings = {
|
||||
'debug': True,
|
||||
'zmqhost': 'tcp://127.0.0.1',
|
||||
'zmqport': BASE_ZMQ_PORT + nodeId,
|
||||
'htmlhost': 'localhost',
|
||||
|
|
|
@ -198,6 +198,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey):
|
|||
|
||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||
settings = {
|
||||
'debug': True,
|
||||
'zmqhost': 'tcp://127.0.0.1',
|
||||
'zmqport': BASE_ZMQ_PORT + node_id,
|
||||
'htmlhost': 'localhost',
|
||||
|
@ -513,9 +514,9 @@ class Test(unittest.TestCase):
|
|||
return
|
||||
raise ValueError('wait_for_offer timed out.')
|
||||
|
||||
def wait_for_bid(self, swap_client, bid_id, state=None, sent=False):
|
||||
def wait_for_bid(self, swap_client, bid_id, state=None, sent=False, wait_for=20):
|
||||
logging.info('wait_for_bid %s', bid_id.hex())
|
||||
for i in range(20):
|
||||
for i in range(wait_for):
|
||||
time.sleep(1)
|
||||
bids = swap_client.listBids(sent=sent)
|
||||
for bid in bids:
|
||||
|
@ -551,7 +552,11 @@ class Test(unittest.TestCase):
|
|||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True)
|
||||
self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True, wait_for=40)
|
||||
|
||||
|
||||
self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, sent=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue