diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 1897c50..756ce04 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -140,7 +140,6 @@ from .basicswap_util import ( canAcceptBidState, describeEventEntry, getLastBidState, - getOfferProofOfFundsHash, getVoutByAddress, getVoutByScriptPubKey, inactive_states, @@ -2117,21 +2116,27 @@ class BasicSwap(BaseApp): msg_buf.fee_rate_to ) # Unused: TODO - Set priority? + ensure_balance: int = int(amount) if coin_from in self.scriptless_coins: - ci_from.ensureFunds(msg_buf.amount_from) + # TODO: Better tx size estimate, xmr_swap_b_lock_tx_vsize could be larger than xmr_swap_b_lock_spend_tx_vsize + estimated_fee: int = ( + msg_buf.fee_rate_from + * ci_from.xmr_swap_b_lock_spend_tx_vsize() + / 1000 + ) + ci_from.ensureFunds(msg_buf.amount_from + estimated_fee) else: - proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr) - ensure_balance: int = int(amount) # If a prefunded txn is not used, check that the wallet balance can cover the tx fee. if "prefunded_itx" not in extra_options: pi = self.pi(SwapTypes.XMR_SWAP) _ = pi.getFundedInitiateTxTemplate(ci_from, ensure_balance, False) # TODO: Save the prefunded tx so the fee can't change, complicates multiple offers at the same time. - proof_addr, proof_sig, proof_utxos = self.getProofOfFunds( - coin_from_t, ensure_balance, proof_of_funds_hash - ) - # TODO: For now proof_of_funds is just a client side check, may need to be sent with offers in future however. + # TODO: Send proof of funds with offer + # proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr) + # proof_addr, proof_sig, proof_utxos = self.getProofOfFunds( + # coin_from_t, ensure_balance, proof_of_funds_hash + # ) offer_bytes = msg_buf.to_bytes() payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex() diff --git a/basicswap/interface/part.py b/basicswap/interface/part.py index df5c79f..d7ef2b5 100644 --- a/basicswap/interface/part.py +++ b/basicswap/interface/part.py @@ -261,9 +261,7 @@ class PARTInterfaceBlind(PARTInterface): ] params = [inputs, outputs] rv = self.rpc_wallet("createrawparttransaction", params) - - tx_bytes = bytes.fromhex(rv["hex"]) - return tx_bytes + return bytes.fromhex(rv["hex"]) def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes: feerate_str = self.format_amount(feerate) @@ -292,7 +290,7 @@ class PARTInterfaceBlind(PARTInterface): "lockUnspents": True, "feeRate": feerate_str, } - rv = self.rpc( + rv = self.rpc_wallet( "fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options] ) return bytes.fromhex(rv["hex"]) @@ -1162,10 +1160,44 @@ class PARTInterfaceBlind(PARTInterface): sub_fee: bool = False, lock_unspents: bool = True, ) -> str: - txn = self.rpc_wallet( - "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + # Estimate lock tx size / fee + + # self.createSCLockTx + vkbv = self.getNewRandomKey() + ephemeral_key = self.getNewRandomKey() + ephemeral_pubkey = self.getPubkey(ephemeral_key) + assert len(ephemeral_pubkey) == 33 + nonce = self.getScriptLockTxNonce(vkbv) + inputs = [] + outputs = [ + { + "type": "blind", + "amount": self.format_amount(amount), + "address": addr_to, + "nonce": nonce.hex(), + "data": ephemeral_pubkey.hex(), + } + ] + params = [inputs, outputs] + tx_hex = self.rpc_wallet("createrawparttransaction", params)["hex"] + + # self.fundSCLockTx + tx_obj = self.rpc("decoderawtransaction", [tx_hex]) + + assert len(tx_obj["vout"]) == 1 + txo = tx_obj["vout"][0] + blinded_info = self.rpc( + "rewindrangeproof", [txo["rangeproof"], txo["valueCommitment"], nonce.hex()] ) + outputs_info = { + 0: { + "value": blinded_info["amount"], + "blind": blinded_info["blind"], + "nonce": nonce.hex(), + } + } + options = { "lockUnspents": lock_unspents, "conf_target": self._conf_target, @@ -1174,7 +1206,9 @@ class PARTInterfaceBlind(PARTInterface): options["subtractFeeFromOutputs"] = [ 0, ] - return self.rpc_wallet("fundrawtransactionfrom", ["blind", txn, options])["hex"] + return self.rpc_wallet( + "fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options] + )["hex"] class PARTInterfaceAnon(PARTInterface): diff --git a/basicswap/js_server.py b/basicswap/js_server.py index fb0cc7a..7ffd1e9 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -841,7 +841,7 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes: key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True) address = ci.getAddressFromKeys(key_view, key_spend) - expect_address = self.getCachedMainWalletAddress(ci) + expect_address = swap_client.getCachedMainWalletAddress(ci) rv.update( { "key_view": ci.encodeKey(key_view), diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index c2de768..a7e4444 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -1734,6 +1734,22 @@ class BasicSwapTest(TestFunctions): amt_swap: int = ci_from.make_int(balance_from_before, r=1) rate_swap: int = ci_to.make_int(2.0, r=1) + + try: + offer_id = swap_clients[id_offerer].postOffer( + coin_from, + coin_to, + amt_swap, + rate_swap, + amt_swap, + SwapTypes.XMR_SWAP, + auto_accept_bids=True, + ) + except Exception as e: + assert "Insufficient funds" in str(e) + else: + assert False, "Should fail" + amt_swap -= ci_from.make_int(1) offer_id = swap_clients[id_offerer].postOffer( coin_from, coin_to, @@ -1745,26 +1761,32 @@ class BasicSwapTest(TestFunctions): ) wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id) + # First bid should work bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap) - - event = wait_for_event( - test_delay_event, - swap_clients[id_offerer], - Concepts.BID, - bid_id, - event_type=EventLogTypes.ERROR, - wait_for=60, - ) - assert "Insufficient funds" in event.event_msg - wait_for_bid( test_delay_event, swap_clients[id_offerer], bid_id, - BidStates.BID_RECEIVED, - wait_for=20, + (BidStates.BID_ACCEPTED, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED), + wait_for=40, ) + # Should be out of funds for second bid (over remaining offer value causes a hard auto accept fail) + bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap) + wait_for_bid( + test_delay_event, + swap_clients[id_offerer], + bid_id, + BidStates.BID_AACCEPT_FAIL, + wait_for=40, + ) + try: + swap_clients[id_offerer].acceptBid(bid_id) + except Exception as e: + assert "Insufficient funds" in str(e) + else: + assert False, "Should fail" + def test_08_insufficient_funds_rev(self): tla_from = self.test_coin_from.name logging.info("---------- Test {} Insufficient Funds (reverse)".format(tla_from)) diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 79feda8..4effdfc 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -1793,7 +1793,9 @@ class Test(BaseTest): self.prepare_balance(Coins.XMR, 20.0, 1800, 1801) js_w1_before = read_json_api(1801, "wallets") ci1_btc = swap_clients[1].ci(Coins.BTC) - btc_total = ci1_btc.make_int(js_w1_before["BTC"]["balance"]) + ci1_btc.make_int(js_w1_before["BTC"]["unconfirmed"]) + btc_total = ci1_btc.make_int(js_w1_before["BTC"]["balance"]) + ci1_btc.make_int( + js_w1_before["BTC"]["unconfirmed"] + ) try: offer_id = swap_clients[1].postOffer( @@ -1803,7 +1805,7 @@ class Test(BaseTest): 0, 10 * COIN, SwapTypes.XMR_SWAP, - extra_options={"amount_to": 10 * XMR_COIN} + extra_options={"amount_to": 10 * XMR_COIN}, ) except Exception as e: assert "Insufficient funds" in str(e) @@ -1817,7 +1819,7 @@ class Test(BaseTest): 0, 10 * COIN, SwapTypes.XMR_SWAP, - extra_options={"amount_to": 10 * XMR_COIN} + extra_options={"amount_to": 10 * XMR_COIN}, ) wait_for_offer(test_delay_event, swap_clients[0], offer_id)