Browse Source

Add bid intent messages.

pull/36/head
tecnovert 12 months ago
parent
commit
f6fb11f452
No known key found for this signature in database GPG Key ID: 8ED6D8750C4E3F93
  1. 14
      basicswap/base.py
  2. 1362
      basicswap/basicswap.py
  3. 8
      basicswap/basicswap_util.py
  4. 31
      basicswap/db.py
  5. 16
      basicswap/db_upgrades.py
  6. 2
      basicswap/db_util.py
  7. 25
      basicswap/interface/btc.py
  8. 2
      basicswap/interface/dash.py
  9. 2
      basicswap/interface/firo.py
  10. 11
      basicswap/interface/part.py
  11. 2
      basicswap/interface/pivx.py
  12. 6
      basicswap/interface/xmr.py
  13. 8
      basicswap/js_server.py
  14. 20
      basicswap/messages.proto
  15. 6
      basicswap/messages_pb2.py
  16. 6
      basicswap/protocols/atomic_swap_1.py
  17. 37
      basicswap/protocols/xmr_swap_1.py
  18. 10
      basicswap/templates/offer_new_1.html
  19. 38
      basicswap/ui/page_offers.py
  20. 34
      basicswap/ui/util.py
  21. 2
      tests/basicswap/common.py
  22. 202
      tests/basicswap/test_btc_xmr.py
  23. 14
      tests/basicswap/test_xmr.py

14
basicswap/base.py

@ -17,8 +17,6 @@ import subprocess
from sockshandler import SocksiPyHandler
import basicswap.config as cfg
from .rpc import (
callrpc,
)
@ -123,18 +121,6 @@ class BaseApp:
cc = self.coin_clients[coin]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])
def calltx(self, cmd):
bindir = self.coin_clients[Coins.PART]['bindir']
args = [os.path.join(bindir, cfg.PARTICL_TX), ]
if self.chain != 'mainnet':
args.append('-' + self.chain)
args += shlex.split(cmd)
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = p.communicate()
if len(out[1]) > 0:
raise ValueError('TX error ' + str(out[1]))
return out[0].decode('utf-8').strip()
def callcoincli(self, coin_type, params, wallet=None, timeout=None):
bindir = self.coin_clients[coin_type]['bindir']
datadir = self.coin_clients[coin_type]['datadir']

1362
basicswap/basicswap.py

File diff suppressed because it is too large

8
basicswap/basicswap_util.py

@ -47,6 +47,9 @@ class MessageTypes(IntEnum):
XMR_BID_LOCK_RELEASE_LF = auto()
OFFER_REVOKE = auto()
ADS_BID_LF = auto()
ADS_BID_ACCEPT_FL = auto()
class AddressTypes(IntEnum):
OFFER = auto()
@ -98,6 +101,7 @@ class BidStates(IntEnum):
BID_STATE_UNKNOWN = 26
XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS = 27 # XmrBidLockTxSigsMessage
XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX = 28 # XmrBidLockSpendTxMessage
BID_REQUEST_SENT = 29
class TxStates(IntEnum):
@ -140,6 +144,7 @@ class ActionTypes(IntEnum):
RECOVER_XMR_SWAP_LOCK_TX_B = auto()
SEND_XMR_SWAP_LOCK_SPEND_MSG = auto()
REDEEM_ITX = auto()
ACCEPT_AS_REV_BID = auto()
class EventLogTypes(IntEnum):
@ -296,6 +301,9 @@ def strBidState(state):
return 'Exchanged script lock tx sigs msg'
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
return 'Exchanged script lock spend tx msg'
if state == BidStates.BID_REQUEST_SENT:
return 'Request sent'
return 'Unknown' + ' ' + str(state)

31
basicswap/db.py

@ -12,7 +12,7 @@ from enum import IntEnum, auto
from sqlalchemy.ext.declarative import declarative_base
CURRENT_DB_VERSION = 20
CURRENT_DB_VERSION = 21
CURRENT_DB_DATA_VERSION = 3
Base = declarative_base()
@ -86,6 +86,7 @@ class Offer(Base):
auto_accept_bids = sa.Column(sa.Boolean)
withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO
security_token = sa.Column(sa.LargeBinary)
bid_reversed = sa.Column(sa.Boolean)
state = sa.Column(sa.Integer)
states = sa.Column(sa.LargeBinary) # Packed states and times
@ -123,7 +124,6 @@ class Bid(Base):
amount = sa.Column(sa.BigInteger)
rate = sa.Column(sa.BigInteger)
accept_msg_id = sa.Column(sa.LargeBinary)
pkhash_seller = sa.Column(sa.LargeBinary)
initiate_txn_redeem = sa.Column(sa.LargeBinary)
@ -290,6 +290,7 @@ class EventLog(Base):
class XmrOffer(Base):
__tablename__ = 'xmr_offers'
# TODO: Merge to Offer
swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id'))
@ -306,16 +307,6 @@ class XmrSwap(Base):
swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey('bids.bid_id'))
bid_msg_id2 = sa.Column(sa.LargeBinary)
bid_msg_id3 = sa.Column(sa.LargeBinary)
bid_accept_msg_id = sa.Column(sa.LargeBinary)
bid_accept_msg_id2 = sa.Column(sa.LargeBinary)
bid_accept_msg_id3 = sa.Column(sa.LargeBinary)
coin_a_lock_tx_sigs_l_msg_id = sa.Column(sa.LargeBinary) # MSG3L F -> L
coin_a_lock_spend_tx_msg_id = sa.Column(sa.LargeBinary) # MSG4F L -> F
coin_a_lock_release_msg_id = sa.Column(sa.LargeBinary) # MSG5F L -> F
contract_count = sa.Column(sa.Integer)
@ -503,3 +494,19 @@ class Notification(Base):
created_at = sa.Column(sa.BigInteger)
event_type = sa.Column(sa.Integer)
event_data = sa.Column(sa.LargeBinary)
class MessageLink(Base):
__tablename__ = 'message_links'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
created_at = sa.Column(sa.BigInteger)
linked_type = sa.Column(sa.Integer)
linked_id = sa.Column(sa.LargeBinary)
# linked_row_id = sa.Column(sa.Integer) # TODO: Find a way to use table rowids
msg_type = sa.Column(sa.Integer)
msg_sequence = sa.Column(sa.Integer)
msg_id = sa.Column(sa.LargeBinary)

16
basicswap/db_upgrades.py

@ -266,6 +266,22 @@ def upgradeDatabase(self, db_version):
session.execute('ALTER TABLE bidstates ADD COLUMN in_error INTEGER')
session.execute('ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER')
session.execute('ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER')
elif current_version == 20:
db_version += 1
session.execute('''
CREATE TABLE message_links (
record_id INTEGER NOT NULL,
active_ind INTEGER,
created_at BIGINT,
linked_type INTEGER,
linked_id BLOB,
msg_type INTEGER,
msg_sequence INTEGER,
msg_id BLOB,
PRIMARY KEY (record_id))''')
session.execute('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER')
if current_version != db_version:
self.db_version = db_version

2
basicswap/db_util.py

@ -38,6 +38,7 @@ def remove_expired_data(self):
session.execute('DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id', {'bid_id': bid_row[0]})
session.execute('DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id', {'bid_id': bid_row[0]})
session.execute('DELETE FROM bids WHERE bids.bid_id = :bid_id', {'bid_id': bid_row[0]})
session.execute('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id', {'type_ind': int(Concepts.BID), 'linked_id': bid_row[0]})
session.execute('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
session.execute('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
@ -47,6 +48,7 @@ def remove_expired_data(self):
session.execute('DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id', {'offer_id': offer_row[0]})
session.execute('DELETE FROM actions WHERE actions.linked_id = :offer_id', {'offer_id': offer_row[0]})
session.execute('DELETE FROM offers WHERE offers.offer_id = :offer_id', {'offer_id': offer_row[0]})
session.execute('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
self.log.warning(f'Removed data for {num_offers} expired offers and {num_bids} bids.')

25
basicswap/interface/btc.py

@ -318,6 +318,27 @@ class BTCInterface(CoinInterface):
args.append('bech32')
return self.rpc_callback('getnewaddress', args)
def isValidAddress(self, address: str) -> bool:
try:
rv = self.rpc_callback('validateaddress', [address])
if rv['isvalid'] is True:
return True
except Exception as ex:
self._log.debug('validateaddress failed: {}'.format(address))
return False
def isValidAddressHash(self, address_hash: bytes) -> bool:
hash_len = len(address_hash)
if hash_len == 20:
return True
def isValidPubkey(self, pubkey: bytes) -> bool:
try:
self.verifyPubkey(pubkey)
return True
except Exception:
return False
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_callback('getaddressinfo', [address])
if not or_watch_only:
@ -1434,6 +1455,10 @@ class BTCInterface(CoinInterface):
tx.rehash()
return tx.serialize().hex()
def ensureFunds(self, amount):
if self.getSpendableBalance() < amount:
raise ValueError('Balance too low')
def testBTCInterface():
print('TODO: testBTCInterface')

2
basicswap/interface/dash.py

@ -55,7 +55,7 @@ class DASHInterface(BTCInterface):
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
return self.rpc_callback('sendtoaddress', params)
def getSpendableBalance(self):
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:

2
basicswap/interface/firo.py

@ -181,7 +181,7 @@ class FIROInterface(BTCInterface):
def getWalletSeedID(self):
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']
def getSpendableBalance(self):
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:

11
basicswap/interface/part.py

@ -131,15 +131,6 @@ class PARTInterface(BTCInterface):
block_header = self.rpc_callback('getblockheader', [block_hash])
return block_header['height']
def isValidAddress(self, address: str) -> bool:
try:
rv = self.rpc_callback('validateaddress', [address])
if rv['isvalid'] is True:
return True
except Exception as ex:
self._log.debug('validateaddress failed: {}'.format(address))
return False
class PARTInterfaceBlind(PARTInterface):
@staticmethod
@ -648,7 +639,7 @@ class PARTInterfaceBlind(PARTInterface):
txid = self.rpc_callback('sendtypeto', params)
return bytes.fromhex(txid)
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height: int, bid_sender: bool):
Kbv = self.getPubkey(kbv)
sx_addr = self.formatStealthAddress(Kbv, Kbs)

2
basicswap/interface/pivx.py

@ -79,7 +79,7 @@ class PIVXInterface(BTCInterface):
params = [addr_to, value, '', '', subfee]
return self.rpc_callback('sendtoaddress', params)
def getSpendableBalance(self):
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
def loadTx(self, tx_bytes):

6
basicswap/interface/xmr.py

@ -480,7 +480,7 @@ class XMRInterface(CoinInterface):
except Exception as e:
return {'error': str(e)}
def getSpendableBalance(self):
def getSpendableBalance(self) -> int:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
@ -514,3 +514,7 @@ class XMRInterface(CoinInterface):
def isAddressMine(self, address):
# TODO
return True
def ensureFunds(self, amount):
if self.getSpendableBalance() < amount:
raise ValueError('Balance too low')

8
basicswap/js_server.py

@ -382,8 +382,8 @@ def js_revokeoffer(self, url_split, post_string, is_json) -> bytes:
def js_smsgaddresses(self, url_split, post_string, is_json) -> bytes:
swap_client = self.server.swap_client
swap_client.checkSystemStatus()
post_data = {} if post_string == '' else getFormData(post_string, is_json)
if len(url_split) > 3:
post_data = {} if post_string == '' else getFormData(post_string, is_json)
if url_split[3] == 'new':
addressnote = get_data_entry_or(post_data, 'addressnote', '')
new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote)
@ -400,7 +400,11 @@ def js_smsgaddresses(self, url_split, post_string, is_json) -> bytes:
new_addr = swap_client.editSMSGAddress(address, activeind, addressnote)
return bytes(json.dumps({'edited_address': address}), 'UTF-8')
return bytes(json.dumps(swap_client.listAllSMSGAddresses()), 'UTF-8')
filters = {
'exclude_inactive': post_data.get('exclude_inactive', True),
}
return bytes(json.dumps(swap_client.listAllSMSGAddresses(filters)), 'UTF-8')
def js_rates(self, url_split, post_string, is_json) -> bytes:

20
basicswap/messages.proto

@ -131,3 +131,23 @@ message XmrBidLockReleaseMessage {
bytes al_lock_spend_tx_esig = 2;
}
message ADSBidIntentMessage {
/* L -> F Sent from bidder, construct a reverse bid */
bytes offer_msg_id = 1;
uint64 time_valid = 2; /* seconds bid is valid for */
uint64 amount = 3; /* amount of amount_from bid is for */
uint64 rate = 4;
uint32 protocol_version = 5;
}
message ADSBidIntentAcceptMessage {
/* F -> L Sent from offerer, construct a reverse bid */
bytes bid_msg_id = 1;
bytes pkaf = 2;
bytes kbvf = 3;
bytes kbsf_dleag = 4;
bytes dest_af = 5;
}

6
basicswap/messages_pb2.py

@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xa6\x04\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\x12\x18\n\x10protocol_version\x18\x10 \x01(\r\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\"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\"\xb4\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\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\"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\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb2\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\x04rate\x18\x04 \x01(\x04\x12\x0c\n\x04pkaf\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\x12\x18\n\x10protocol_version\x18\t \x01(\r\"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\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x04 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x05 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x08 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\t \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\n \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0b \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\"X\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\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xa6\x04\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\x12\x18\n\x10protocol_version\x18\x10 \x01(\r\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\"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\"\xb4\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\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\"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\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb2\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\x04rate\x18\x04 \x01(\x04\x12\x0c\n\x04pkaf\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\x12\x18\n\x10protocol_version\x18\t \x01(\r\"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\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x04 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x05 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x08 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\t \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\n \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0b \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\"X\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\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"w\n\x13\x41\x44SBidIntentMessage\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\x04rate\x18\x04 \x01(\x04\x12\x18\n\x10protocol_version\x18\x05 \x01(\r\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', globals())
@ -44,4 +44,8 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_XMRBIDLOCKSPENDTXMESSAGE._serialized_end=1707
_XMRBIDLOCKRELEASEMESSAGE._serialized_start=1709
_XMRBIDLOCKRELEASEMESSAGE._serialized_end=1786
_ADSBIDINTENTMESSAGE._serialized_start=1788
_ADSBIDINTENTMESSAGE._serialized_end=1907
_ADSBIDINTENTACCEPTMESSAGE._serialized_start=1909
_ADSBIDINTENTACCEPTMESSAGE._serialized_end=2021
# @@protoc_insertion_point(module_scope)

6
basicswap/protocols/atomic_swap_1.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert
# Copyright (c) 2020-2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -23,7 +23,7 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
ABS_LOCK_TIME_LEEWAY = 10 * 60
def buildContractScript(lock_val, secret_hash, pkh_redeem, pkh_refund, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY):
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY) -> bytearray:
script = bytearray([
OpCodes.OP_IF,
OpCodes.OP_SIZE,
@ -58,7 +58,7 @@ def extractScriptSecretHash(script):
return script[7:39]
def redeemITx(self, bid_id, session):
def redeemITx(self, bid_id: bytes, session):
bid, offer = self.getBidAndOffer(bid_id, session)
ci_from = self.ci(offer.coin_from)

37
basicswap/protocols/xmr_swap_1.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert
# Copyright (c) 2020-2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -9,6 +9,7 @@ from sqlalchemy.orm import scoped_session
from basicswap.util import (
ensure,
)
from basicswap.interface import Curves
from basicswap.chainparams import (
Coins,
)
@ -37,17 +38,17 @@ def addLockRefundSigs(self, xmr_swap, ci):
xmr_swap.a_lock_refund_tx = signed_tx
def recoverNoScriptTxnWithKey(self, bid_id, encoded_key):
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
self.log.info('Manually recovering %s', bid_id.hex())
# Manually recover txn if other key is known
session = scoped_session(self.session_factory)
try:
bid, xmr_swap = self.getXmrBidFromSession(session, bid_id)
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex()))
ensure(xmr_swap, 'XMR swap not found: {}.'.format(bid_id.hex()))
ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex()))
offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False)
ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex()))
ensure(xmr_offer, 'XMR offer not found: {}.'.format(bid.offer_id.hex()))
ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex()))
ci_to = self.ci(offer.coin_to)
for_ed25519 = True if Coins(offer.coin_to) == Coins.XMR else False
@ -109,6 +110,34 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
return None
def reverseBidAmountAndRate(swap_client, bid, offer) -> (int, int):
ci_from = swap_client.ci(offer.coin_to)
ci_to = swap_client.ci(offer.coin_from)
bid_rate = offer.rate if bid.rate is None else bid.rate
amount_from: int = bid.amount
amount_to: int = int((int(amount_from) * bid_rate) // ci_from.COIN())
reversed_rate: int = ci_to.make_int(amount_from / amount_to, r=1)
return amount_to, reversed_rate
def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
if ci_to.curve_type() == Curves.ed25519:
xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf)
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33]
elif ci_to.curve_type() == Curves.secp256k1:
for i in range(10):
xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap')
pk_recovered: bytes = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap')
if pk_recovered == xmr_swap.pkbsf:
break
# self.log.debug('kbsl recovered pubkey mismatch, retrying.')
assert (pk_recovered == xmr_swap.pkbsf)
xmr_swap.pkasf = xmr_swap.pkbsf
else:
raise ValueError('Unknown curve')
class XmrSwapInterface(ProtocolInterface):
swap_type = SwapTypes.XMR_SWAP

10
basicswap/templates/offer_new_1.html

@ -391,7 +391,7 @@
<div class="py-3 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Price in BTC (Rate)</span>
</div>
</th>
</th>
</tr>
</thead>
<tbody id="priceTableBody" class="text-gray-700">
@ -555,14 +555,16 @@ function lookup_rates_table() {
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
const adaptor_sig_only_coins = ['6' /* XMR */, '8' /* PART_ANON */, '7' /* PART_BLIND */];
const secret_hash_only_coins = ['11' /* PIVX */, '12' /* DASH */, '13' /* FIRO */];
let make_hidden = false;
if (coin_to == '6' /* XMR */ || coin_to == '8' /* PART_ANON */ || coin_to == '7' /* PART_BLIND */ || coin_from == '7' /* PART_BLIND */ ) {
if (adaptor_sig_only_coins.includes(coin_from) || adaptor_sig_only_coins.includes(coin_to)) {
swap_type.disabled = true;
swap_type.value = 'xmr_swap';
make_hidden = true;
swap_type.classList.add('select-disabled'); // Add the class to the disabled select
} else
if (coin_from == '11' /* PIVX */ || coin_from == '12' /* DASH */ || coin_from == '13' /* FIRO */ ) {
if (secret_hash_only_coins.includes(coin_from)) {
swap_type.disabled = true;
swap_type.value = 'seller_first';
make_hidden = true;
@ -693,4 +695,4 @@ document.addEventListener('DOMContentLoaded', function() {
</script>
{% endif %}
</body>
</html>
</html>

38
basicswap/ui/page_offers.py

@ -101,6 +101,9 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
except Exception:
errors.append('Unknown Coin To')
if coin_from == coin_to:
errors.append('Coins from and to must be different.')
try:
page_data['amt_from'] = get_data_entry(form_data, 'amt_from')
parsed_data['amt_from'] = inputAmount(page_data['amt_from'], ci_from)
@ -214,6 +217,10 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
try:
if len(errors) == 0 and page_data['swap_style'] == 'xmr':
reverse_bid: bool = ci_from.coin_type() in swap_client.scriptless_coins
ci_leader = ci_to if reverse_bid else ci_from
ci_follower = ci_from if reverse_bid else ci_to
if have_data_entry(form_data, 'fee_rate_from'):
page_data['from_fee_override'] = get_data_entry(form_data, 'fee_rate_from')
parsed_data['from_fee_override'] = page_data['from_fee_override']
@ -224,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))
parsed_data['from_fee_override'] = page_data['from_fee_override']
lock_spend_tx_vsize = ci_from.xmr_swap_alock_spend_tx_vsize()
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_from.COIN())
page_data['tla_from'] = ci_from.ticker()
lock_spend_tx_vsize = ci_leader.xmr_swap_alock_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)
page_data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_leader.COIN())
page_data['tla_from'] = ci_leader.ticker()
if coin_to == Coins.XMR:
if ci_follower == Coins.XMR:
if have_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']
@ -237,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'])
if page_data['fee_to_extra'] > 0:
to_fee_override += to_fee_override * (float(page_data['fee_to_extra']) / 100.0)
page_data['to_fee_override'] = ci_to.format_amount(ci_to.make_int(to_fee_override, r=1))
page_data['to_fee_override'] = ci_follower.format_amount(ci_follower.make_int(to_fee_override, r=1))
parsed_data['to_fee_override'] = page_data['to_fee_override']
except Exception as e:
print('Error setting fee', str(e)) # Expected if missing fields
@ -596,17 +603,22 @@ def page_offer(self, url_split, post_string):
data['debug_ind'] = debugind
data['debug_options'] = [(int(t), t.name) for t in DebugTypes]
reverse_bid: bool = ci_from.coin_type() in swap_client.scriptless_coins
ci_leader = ci_to if reverse_bid else ci_from
ci_follower = ci_from if reverse_bid else ci_to
if xmr_offer:
int_fee_rate_now, fee_source = ci_from.get_fee_rate()
int_fee_rate_now, fee_source = ci_leader.get_fee_rate()
data['xmr_type'] = True
data['a_fee_rate'] = ci_from.format_amount(xmr_offer.a_fee_rate)
data['a_fee_rate_verify'] = ci_from.format_amount(int_fee_rate_now, conv_int=True)
data['a_fee_rate'] = ci_leader.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_src'] = fee_source
data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now
lock_spend_tx_vsize = ci_from.xmr_swap_alock_spend_tx_vsize()
lock_spend_tx_fee = ci_from.make_int(xmr_offer.a_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())
lock_spend_tx_vsize = ci_leader.xmr_swap_alock_spend_tx_vsize()
lock_spend_tx_fee = ci_leader.make_int(xmr_offer.a_fee_rate * lock_spend_tx_vsize / 1000, r=1)
data['amt_from_lock_spend_tx_fee'] = ci_leader.format_amount(lock_spend_tx_fee // ci_leader.COIN())
if offer.was_sent:
try:
@ -621,7 +633,7 @@ def page_offer(self, url_split, post_string):
amt_swapped = 0
for b in bids:
amt_swapped += b[4]
formatted_bids.append((b[2].hex(), ci_from.format_amount(b[4]), strBidState(b[5]), ci_to.format_amount(b[10]), b[11]))
formatted_bids.append((b[2].hex(), ci_leader.format_amount(b[4]), strBidState(b[5]), ci_follower.format_amount(b[10]), b[11]))
data['amt_swapped'] = ci_from.format_amount(amt_swapped)
template = server.env.get_template('offer.html')

34
basicswap/ui/util.py

@ -28,10 +28,10 @@ from basicswap.basicswap_util import (
getLastBidState,
)
from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSplitKey
from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSplitKey, reverseBidAmountAndRate
PAGE_LIMIT = 50
invalid_coins_from = (Coins.XMR, Coins.PART_ANON)
invalid_coins_from = []
def tickerToCoinId(ticker):
@ -151,6 +151,15 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
ticker_from = ci_from.ticker()
ticker_to = ci_to.ticker()
reverse_bid: bool = ci_from.coin_type() in swap_client.scriptless_coins
ci_leader = ci_to if reverse_bid else ci_from
ci_follower = ci_from if reverse_bid else ci_to
bid_amount = bid.amount
bid_rate = offer.rate if bid.rate is None else bid.rate
if reverse_bid:
bid_amount, bid_rate = reverseBidAmountAndRate(swap_client, bid, offer)
state_description = ''
if offer.swap_type == SwapTypes.SELLER_FIRST:
if bid.state == BidStates.BID_SENT:
@ -229,7 +238,6 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
state_description = 'Redeeming output'
addr_label = swap_client.getAddressLabel([bid.bid_addr, ])[0]
bid_rate = offer.rate if bid.rate is None else bid.rate
can_abandon: bool = False
if swap_client.debug and bid.state not in (BidStates.BID_ABANDONED, BidStates.SWAP_COMPLETED):
@ -238,8 +246,8 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
data = {
'coin_from': ci_from.coin_name(),
'coin_to': ci_to.coin_name(),
'amt_from': ci_from.format_amount(bid.amount),
'amt_to': ci_to.format_amount((bid.amount * bid_rate) // ci_from.COIN()),
'amt_from': ci_from.format_amount(bid_amount),
'amt_to': ci_to.format_amount((bid_amount * bid_rate) // ci_from.COIN()),
'bid_rate': ci_to.format_amount(bid_rate),
'ticker_from': ticker_from,
'ticker_to': ticker_to,
@ -335,16 +343,16 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.block_time:
raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_from.decodeSequence(raw_sequence)
raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_leader.decodeSequence(raw_sequence)
data['coin_a_lock_refund_tx_est_final'] = bid.xmr_a_lock_tx.block_time + seconds_locked
data['coin_a_last_median_time'] = swap_client.coin_clients[offer.coin_from]['chain_median_time']
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if refund_tx.block_time is not None:
raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_from.decodeSequence(raw_sequence)
raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_leader.decodeSequence(raw_sequence)
data['coin_a_lock_refund_swipe_tx_est_final'] = refund_tx.block_time + seconds_locked
if view_tx_ind:
@ -367,12 +375,12 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
else:
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if bid.initiate_tx and bid.initiate_tx.block_time is not None:
raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_from.decodeSequence(raw_sequence)
raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_leader.decodeSequence(raw_sequence)
data['itx_refund_tx_est_final'] = bid.initiate_tx.block_time + seconds_locked
if bid.participate_tx and bid.participate_tx.block_time is not None:
raw_sequence = ci_to.getExpectedSequence(offer.lock_type, offer.lock_value // 2)
seconds_locked = ci_to.decodeSequence(raw_sequence)
raw_sequence = ci_follower.getExpectedSequence(offer.lock_type, offer.lock_value // 2)
seconds_locked = ci_follower.decodeSequence(raw_sequence)
data['ptx_refund_tx_est_final'] = bid.participate_tx.block_time + seconds_locked
return data

2
tests/basicswap/common.py

@ -121,7 +121,7 @@ def stopDaemons(daemons):
logging.info('Closing %d, error %s', d.pid, str(e))
def wait_for_bid(delay_event, swap_client, bid_id, state=None, sent=False, wait_for=20):
def wait_for_bid(delay_event, swap_client, bid_id, state=None, sent: bool = False, wait_for: int = 20) -> None:
logging.info('wait_for_bid %s', bid_id.hex())
for i in range(wait_for):
if delay_event.is_set():

202
tests/basicswap/test_btc_xmr.py

@ -63,6 +63,18 @@ class BasicSwapTest(BaseTest):
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr])
def prepare_balance(self, coin_ticker: str, amount: float, port_target_node: int, port_take_from_node: int) -> None:
js_w = read_json_api(port_target_node, 'wallets')
if float(js_w[coin_ticker]['balance']) < amount:
post_json = {
'value': amount,
'address': js_w[coin_ticker]['deposit_address'],
'subfee': False,
}
json_rv = read_json_api(port_take_from_node, 'wallets/{}/withdraw'.format(coin_ticker.lower()), post_json)
assert (len(json_rv['txid']) == 64)
wait_for_balance(test_delay_event, 'http://127.0.0.1:{}/json/wallets/{}'.format(port_target_node, coin_ticker.lower()), 'balance', amount)
def test_001_nested_segwit(self):
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
@ -263,7 +275,7 @@ class BasicSwapTest(BaseTest):
rv = read_json_api(1800, 'getcoinseed', {'coin': 'XMR'})
assert (rv['address'] == '47H7UDLzYEsR28BWttxp59SP1UVSxs4VKDJYSfmz7Wd4Fue5VWuoV9x9eejunwzVSmHWN37gBkaAPNf9VD4bTvwQKsBVWyK')
def do_test_01_full_swap(self, coin_from, coin_to):
def do_test_01_full_swap(self, coin_from: Coins, coin_to: Coins) -> None:
logging.info('---------- Test {} to {}'.format(coin_from.name, coin_to.name))
swap_clients = self.swap_clients
@ -289,8 +301,7 @@ class BasicSwapTest(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
swap_clients[0].acceptXmrBid(bid_id)
swap_clients[0].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
@ -317,63 +328,83 @@ class BasicSwapTest(BaseTest):
if False: # TODO: set stakeaddress and xmr rewards to non wallet addresses
assert (node1_to_after < node1_to_before - amount_to_float)
def test_01_full_swap(self):
def test_01_a_full_swap(self):
if not self.has_segwit:
return
self.do_test_01_full_swap(self.test_coin_from, Coins.XMR)
def test_01_full_swap_to_part(self):
def test_01_b_full_swap_reverse(self):
if not self.has_segwit:
return
self.prepare_balance(Coins.XMR.name, 100.0, 1800, 1801)
self.do_test_01_full_swap(Coins.XMR, self.test_coin_from)
def test_01_c_full_swap_to_part(self):
if not self.has_segwit:
return
self.do_test_01_full_swap(self.test_coin_from, Coins.PART)
def test_01_full_swap_from_part(self):
def test_01_d_full_swap_from_part(self):
self.do_test_01_full_swap(Coins.PART, self.test_coin_from)
def do_test_02_leader_recover_a_lock_tx(self, coin_from, coin_to):
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))
swap_clients = self.swap_clients
reverse_bid: bool = coin_from in swap_clients[0].scriptless_coins
ci_from = swap_clients[0].ci(coin_from)
ci_to = swap_clients[0].ci(coin_to)
js_w0_before = read_json_api(1800, 'wallets')
node0_from_before = self.getBalance(js_w0_before, coin_from)
# Offerer sends the offer
# Bidder sends the bid
id_offerer: int = 0
id_bidder: int = 1
# Leader sends the initial (chain a) lock tx.
# Follower sends the participate (chain b) lock tx.
id_leader: int = 1 if reverse_bid else 0
id_follower: int = 0 if reverse_bid else 1
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
js_wl_before = read_json_api(1800 + id_leader, 'wallets')
wl_from_before = self.getBalance(js_wl_before, coin_from)
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[0].postOffer(
offer_id = swap_clients[id_offerer].postOffer(
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
offer = swap_clients[id_bidder].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
bid_id = swap_clients[id_bidder].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[0], bid_id, BidStates.BID_RECEIVED)
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[id_offerer].acceptBid(bid_id)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert (xmr_swap)
leader_sent_bid: bool = True if reverse_bid else False
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=leader_sent_bid, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, [BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED], sent=(not leader_sent_bid))
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, [BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED], sent=True)
js_w0_after = read_json_api(1800, 'wallets')
node0_from_after = self.getBalance(js_w0_after, coin_from)
js_wl_after = read_json_api(1800 + id_leader, 'wallets')
wl_from_after = self.getBalance(js_wl_after, coin_from)
# TODO: Discard block rewards
# assert (node0_from_before - node0_from_after < 0.02)
def test_02_leader_recover_a_lock_tx(self):
def test_02_a_leader_recover_a_lock_tx(self):
if not self.has_segwit:
return
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.XMR)
def test_02_leader_recover_a_lock_tx_to_part(self):
def test_02_b_leader_recover_a_lock_tx_reverse(self):
if not self.has_segwit:
return
self.prepare_balance(Coins.XMR.name, 100.0, 1800, 1801)
self.do_test_02_leader_recover_a_lock_tx(Coins.XMR, self.test_coin_from)
def test_02_c_leader_recover_a_lock_tx_to_part(self):
if not self.has_segwit:
return
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.PART)
@ -384,34 +415,42 @@ class BasicSwapTest(BaseTest):
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to):
logging.info('---------- Test {} to {} follower recovers coin a lock tx'.format(coin_from.name, coin_to.name))
# Leader is too slow to recover the coin a lock tx and follower swipes it
# coin b lock tx remains unspent
swap_clients = self.swap_clients
reverse_bid: bool = coin_from in swap_clients[0].scriptless_coins
ci_from = swap_clients[0].ci(coin_from)
ci_to = swap_clients[0].ci(coin_to)
id_offerer: int = 0
id_bidder: int = 1
id_leader: int = 1 if reverse_bid else 0
id_follower: int = 0 if reverse_bid else 1
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
js_w0_before = read_json_api(1800, 'wallets')
js_w1_before = read_json_api(1801, 'wallets')
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[0].postOffer(
offer_id = swap_clients[id_offerer].postOffer(
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
offer = swap_clients[id_bidder].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert (xmr_swap)
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[id_leader].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
swap_clients[id_offerer].acceptBid(bid_id)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True)
leader_sent_bid: bool = True if reverse_bid else False
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180, sent=leader_sent_bid)
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=(not leader_sent_bid))
js_w1_after = read_json_api(1801, 'wallets')
@ -426,74 +465,97 @@ class BasicSwapTest(BaseTest):
wait_for_none_active(test_delay_event, 1800)
wait_for_none_active(test_delay_event, 1801)
def test_03_follower_recover_a_lock_tx(self):
def test_03_a_follower_recover_a_lock_tx(self):
if not self.has_segwit:
return
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.XMR)
def test_03_follower_recover_a_lock_tx_to_part(self):
def test_03_b_follower_recover_a_lock_tx_reverse(self):
if not self.has_segwit:
return
self.prepare_balance(Coins.XMR.name, 100.0, 1800, 1801)
self.do_test_03_follower_recover_a_lock_tx(Coins.XMR, self.test_coin_from)
def test_03_c_follower_recover_a_lock_tx_to_part(self):
if not self.has_segwit:
return
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.PART)
def test_03_follower_recover_a_lock_tx_from_part(self):
def test_03_d_follower_recover_a_lock_tx_from_part(self):
self.do_test_03_follower_recover_a_lock_tx(Coins.PART, self.test_coin_from)
def do_test_04_follower_recover_b_lock_tx(self, coin_from, coin_to):
logging.info('---------- Test {} to {} follower recovers coin b lock tx'.format(coin_from.name, coin_to.name))
swap_clients = self.swap_clients
reverse_bid: bool = coin_from in swap_clients[0].scriptless_coins
ci_from = swap_clients[0].ci(coin_from)
ci_to = swap_clients[0].ci(coin_to)
id_offerer: int = 0
id_bidder: int = 1
id_leader: int = 1 if reverse_bid else 0
id_follower: int = 0 if reverse_bid else 1
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
js_w0_before = read_json_api(1800, 'wallets')
js_w1_before = read_json_api(1801, 'wallets')
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[0].postOffer(
offer_id = swap_clients[id_offerer].postOffer(
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
offer = swap_clients[id_bidder].getOffer(offer_id)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert (xmr_swap)
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
swap_clients[0].acceptXmrBid(bid_id)
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
swap_clients[id_offerer].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
leader_sent_bid: bool = True if reverse_bid else False
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=200, sent=leader_sent_bid)
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=(not leader_sent_bid))
js_w0_after = read_json_api(1800, 'wallets')
js_w1_after = read_json_api(1801, 'wallets')
node0_from_before = self.getBalance(js_w0_before, coin_from)
node0_from_after = self.getBalance(js_w0_after, coin_from)
logging.info('End coin_from balance {}, diff {}'.format(node0_from_after, node0_from_after - node0_from_before))
# TODO: Discard block rewards
# assert (node0_from_before - node0_from_after < 0.02)
logging.info('node0 end coin_from balance {}, diff {}'.format(node0_from_after, node0_from_after - node0_from_before))
node0_to_before = self.getBalance(js_w0_before, coin_to)
node0_to_after = self.getBalance(js_w0_after, coin_to)
logging.info('node0 end coin_to balance {}, diff {}'.format(node0_to_after, node0_to_after - node0_to_before))
if coin_from != Coins.PART: # TODO: Discard block rewards
assert (node0_from_before - node0_from_after < 0.02)
node1_coin_to_before = self.getBalance(js_w1_before, coin_to)
node1_coin_to_after = self.getBalance(js_w1_after, coin_to)
logging.info('End coin_to balance {}, diff {}'.format(node1_coin_to_after, node1_coin_to_after - node1_coin_to_before))
assert (node1_coin_to_before - node1_coin_to_after < 0.02)
node1_from_before = self.getBalance(js_w1_before, coin_from)
node1_from_after = self.getBalance(js_w1_after, coin_from)
logging.info('node1 end coin_from balance {}, diff {}'.format(node1_from_after, node1_from_after - node1_from_before))
node1_to_before = self.getBalance(js_w1_before, coin_to)
node1_to_after = self.getBalance(js_w1_after, coin_to)
logging.info('node1 end coin_to balance {}, diff {}'.format(node1_to_after, node1_to_after - node1_to_before))
assert (node1_to_before - node1_to_after < 0.02)
def test_04_follower_recover_b_lock_tx(self):
def test_04_a_follower_recover_b_lock_tx(self):
if not self.has_segwit:
return
self.do_test_04_follower_recover_b_lock_tx(self.test_coin_from, Coins.XMR)
def test_04_follower_recover_b_lock_tx_to_part(self):
def test_04_b_follower_recover_b_lock_tx_reverse(self):
if not self.has_segwit:
return
self.prepare_balance(Coins.XMR.name, 100.0, 1800, 1801)
self.do_test_04_follower_recover_b_lock_tx(Coins.XMR, self.test_coin_from)
def test_04_c_follower_recover_b_lock_tx_to_part(self):
if not self.has_segwit:
return
self.do_test_04_follower_recover_b_lock_tx(self.test_coin_from, Coins.PART)
def test_04_follower_recover_b_lock_tx_from_part(self):
def test_04_d_follower_recover_b_lock_tx_from_part(self):
self.do_test_04_follower_recover_b_lock_tx(Coins.PART, self.test_coin_from)
def do_test_05_self_bid(self, coin_from, coin_to):
@ -528,17 +590,7 @@ class BasicSwapTest(BaseTest):
logging.info('---------- Test {} Preselected inputs'.format(tla_from))
swap_clients = self.swap_clients
# Prepare balance
js_w2 = read_json_api(1802, 'wallets')
if float(js_w2[tla_from]['balance']) < 100.0:
post_json = {
'value': 100,
'address': js_w2[tla_from]['deposit_address'],
'subfee': False,
}
json_rv = read_json_api(1800, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json)
assert (len(json_rv['txid']) == 64)
wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 100.0)
self.prepare_balance(tla_from, 100.0, 1802, 1800)
js_w2 = read_json_api(1802, 'wallets')
assert (float(js_w2[tla_from]['balance']) >= 100.0)

14
tests/basicswap/test_xmr.py

@ -88,6 +88,9 @@ from tests.basicswap.common import (
LTC_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST,
)
from basicswap.db_util import (
remove_expired_data,
)
from bin.basicswap_run import startDaemon, startXmrDaemon
@ -744,6 +747,12 @@ class Test(BaseTest):
assert (json_rv['edited_address'] == new_address)
js_3 = read_json_api(1801, 'smsgaddresses')
assert (len(js_3) == 0)
post_json = {
'exclude_inactive': False,
}
js_3 = json.loads(post_json_req('http://127.0.0.1:1801/json/smsgaddresses', post_json))
found = False
for addr in js_3:
if addr['addr'] == new_address:
@ -1356,7 +1365,7 @@ class Test(BaseTest):
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800, sent=True)
def test_98_withdraw_all(self):
def test_97_withdraw_all(self):
logging.info('---------- Test XMR withdrawal all')
try:
logging.info('Disabling XMR mining')
@ -1384,6 +1393,9 @@ class Test(BaseTest):
logging.info('Restoring XMR mining')
pause_event.set()
def test_98_remove_expired_data(self):
remove_expired_data(self.swap_clients[0])
if __name__ == '__main__':
unittest.main()

Loading…
Cancel
Save