system: Allow preselecting inputs for atomic swaps.

This commit is contained in:
tecnovert
2022-12-05 17:04:23 +02:00
parent 23e89882a4
commit c90fa6f2c6
16 changed files with 334 additions and 72 deletions

View File

@@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.11.51"
__version__ = "0.11.52"

View File

@@ -26,6 +26,8 @@ import sqlalchemy as sa
import collections
import concurrent.futures
from typing import Optional
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm.session import close_all_sessions
@@ -92,6 +94,7 @@ from .db import (
Offer,
Bid,
SwapTx,
PrefundedTx,
PooledAddress,
SentOffer,
SmsgAddress,
@@ -116,6 +119,7 @@ from .explorers import (
import basicswap.config as cfg
import basicswap.network as bsn
import basicswap.protocols.atomic_swap_1 as atomic_swap_1
import basicswap.protocols.xmr_swap_1 as xmr_swap_1
from .basicswap_util import (
KeyTypes,
TxLockTypes,
@@ -140,9 +144,6 @@ from .basicswap_util import (
isActiveBidState,
NotificationTypes as NT,
)
from .protocols.xmr_swap_1 import (
addLockRefundSigs,
recoverNoScriptTxnWithKey)
non_script_type_coins = (Coins.XMR, Coins.PART_ANON)
@@ -218,6 +219,10 @@ class WatchedTransaction():
class BasicSwap(BaseApp):
ws_server = None
protocolInterfaces = {
SwapTypes.SELLER_FIRST: atomic_swap_1.AtomicSwapInterface(),
SwapTypes.XMR_SWAP: xmr_swap_1.XmrSwapInterface(),
}
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
super().__init__(fp, data_dir, settings, chain, log_name)
@@ -548,6 +553,11 @@ class BasicSwap(BaseApp):
return self.coin_clients[use_coinid][interface_ind]
def pi(self, protocol_ind):
if protocol_ind not in self.protocolInterfaces:
raise ValueError('Unknown protocol_ind {}'.format(int(protocol_ind)))
return self.protocolInterfaces[protocol_ind]
def createInterface(self, coin):
if coin == Coins.PART:
return PARTInterface(self.coin_clients[coin], self.chain, self)
@@ -651,10 +661,8 @@ class BasicSwap(BaseApp):
self.log.info('%s Core version %d', ci.coin_name(), core_version)
self.coin_clients[c]['core_version'] = core_version
if c == Coins.XMR:
t = threading.Thread(target=threadPollXMRChainState, args=(self, c))
else:
t = threading.Thread(target=threadPollChainState, args=(self, c))
thread_func = threadPollXMRChainState if c == Coins.XMR else threadPollChainState
t = threading.Thread(target=thread_func, args=(self, c))
self.threads.append(t)
t.start()
@@ -851,7 +859,7 @@ class BasicSwap(BaseApp):
finally:
self.closeSession(session)
def updateIdentityBidState(self, session, address, bid):
def updateIdentityBidState(self, session, address: str, bid) -> None:
identity_stats = session.query(KnownIdentity).filter_by(address=address).first()
if not identity_stats:
identity_stats = KnownIdentity(address=address, created_at=int(time.time()))
@@ -870,7 +878,7 @@ class BasicSwap(BaseApp):
identity_stats.updated_at = int(time.time())
session.add(identity_stats)
def setIntKVInSession(self, str_key, int_val, session):
def setIntKVInSession(self, str_key: str, int_val: int, session) -> None:
kv = session.query(DBKVInt).filter_by(key=str_key).first()
if not kv:
kv = DBKVInt(key=str_key, value=int_val)
@@ -878,7 +886,7 @@ class BasicSwap(BaseApp):
kv.value = int_val
session.add(kv)
def setIntKV(self, str_key, int_val):
def setIntKV(self, str_key: str, int_val: int) -> None:
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
@@ -889,7 +897,7 @@ class BasicSwap(BaseApp):
session.remove()
self.mxDB.release()
def setStringKV(self, str_key, str_val, session=None):
def setStringKV(self, str_key: str, str_val: str, session=None) -> None:
try:
use_session = self.openSession(session)
kv = use_session.query(DBKVString).filter_by(key=str_key).first()
@@ -902,7 +910,7 @@ class BasicSwap(BaseApp):
if session is None:
self.closeSession(use_session)
def getStringKV(self, str_key):
def getStringKV(self, str_key: str) -> Optional[str]:
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
@@ -915,7 +923,7 @@ class BasicSwap(BaseApp):
session.remove()
self.mxDB.release()
def clearStringKV(self, str_key, str_val):
def clearStringKV(self, str_key: str, str_val: str) -> None:
with self.mxDB:
try:
session = scoped_session(self.session_factory)
@@ -925,6 +933,19 @@ class BasicSwap(BaseApp):
session.close()
session.remove()
def getPreFundedTx(self, linked_type: int, linked_id: bytes, tx_type: int, session=None) -> Optional[bytes]:
try:
use_session = self.openSession(session)
tx = use_session.query(PrefundedTx).filter_by(linked_type=linked_type, linked_id=linked_id, tx_type=tx_type, used_by=None).first()
if not tx:
return None
tx.used_by = linked_id
use_session.add(tx)
return tx.tx_data
finally:
if session is None:
self.closeSession(use_session)
def activateBid(self, session, bid):
if bid.bid_id in self.swaps_in_progress:
self.log.debug('Bid %s is already in progress', bid.bid_id.hex())
@@ -1366,6 +1387,16 @@ class BasicSwap(BaseApp):
repeat_count=0)
session.add(auto_link)
if 'prefunded_itx' in extra_options:
prefunded_tx = PrefundedTx(
active_ind=1,
created_at=offer_created_at,
linked_type=Concepts.OFFER,
linked_id=offer_id,
tx_type=TxTypes.ITX_PRE_FUNDED,
tx_data=extra_options['prefunded_itx'])
session.add(prefunded_tx)
session.add(offer)
session.add(SentOffer(offer_id=offer_id))
session.commit()
@@ -2147,7 +2178,8 @@ class BasicSwap(BaseApp):
bid.pkhash_seller = pkhash_refund
txn = self.createInitiateTxn(coin_from, bid_id, bid, script)
prefunded_tx = self.getPreFundedTx(Concepts.OFFER, offer.offer_id, TxTypes.ITX_PRE_FUNDED)
txn = self.createInitiateTxn(coin_from, bid_id, bid, script, prefunded_tx)
# Store the signed refund txn in case wallet is locked when refund is possible
refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script)
@@ -2532,14 +2564,14 @@ class BasicSwap(BaseApp):
session.remove()
self.mxDB.release()
def setBidError(self, bid_id, bid, error_str, save_bid=True, xmr_swap=None):
def setBidError(self, bid_id, bid, error_str, save_bid=True, xmr_swap=None) -> None:
self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
bid.setState(BidStates.BID_ERROR)
bid.state_note = 'error msg: ' + error_str
if save_bid:
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script):
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script, prefunded_tx=None) -> Optional[str]:
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return None
ci = self.ci(coin_type)
@@ -2550,7 +2582,11 @@ class BasicSwap(BaseApp):
addr_to = ci.encode_p2sh(initiate_script)
self.log.debug('Create initiate txn for coin %s to %s for bid %s', str(coin_type), addr_to, bid_id.hex())
txn_signed = ci.createRawSignedTransaction(addr_to, bid.amount)
if prefunded_tx:
pi = self.pi(SwapTypes.SELLER_FIRST)
txn_signed = pi.promoteMockTx(ci, prefunded_tx, initiate_script).hex()
else:
txn_signed = ci.createRawSignedTransaction(addr_to, bid.amount)
return txn_signed
def deriveParticipateScript(self, bid_id, bid, offer):
@@ -4560,7 +4596,7 @@ class BasicSwap(BaseApp):
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(kaf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount)
addLockRefundSigs(self, xmr_swap, ci_from)
xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from)
msg_buf = XmrBidLockTxSigsMessage(
bid_msg_id=bid_id,
@@ -4988,7 +5024,7 @@ class BasicSwap(BaseApp):
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount)
ensure(v, 'Invalid signature for lock refund spend txn')
addLockRefundSigs(self, xmr_swap, ci_from)
xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from)
delay = random.randrange(self.min_delay_event, self.max_delay_event)
self.log.info('Sending coin A lock tx for xmr bid %s in %d seconds', bid_id.hex(), delay)
@@ -5268,7 +5304,7 @@ class BasicSwap(BaseApp):
has_changed = True
if data['kbs_other'] is not None:
return recoverNoScriptTxnWithKey(self, bid_id, data['kbs_other'])
return xmr_swap_1.recoverNoScriptTxnWithKey(self, bid_id, data['kbs_other'])
if has_changed:
session = scoped_session(self.session_factory)

View File

@@ -123,6 +123,8 @@ class TxTypes(IntEnum):
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
XMR_SWAP_B_LOCK = auto()
ITX_PRE_FUNDED = auto()
class ActionTypes(IntEnum):
ACCEPT_BID = auto()
@@ -289,6 +291,8 @@ def strTxType(tx_type):
return 'Chain A Lock Refund Swipe Tx'
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
return 'Chain B Lock Tx'
if tx_type == TxTypes.ITX_PRE_FUNDED:
return 'Funded mock initiate tx'
return 'Unknown'

View File

@@ -12,7 +12,7 @@ from enum import IntEnum, auto
from sqlalchemy.ext.declarative import declarative_base
CURRENT_DB_VERSION = 16
CURRENT_DB_VERSION = 17
CURRENT_DB_DATA_VERSION = 2
Base = declarative_base()
@@ -221,6 +221,19 @@ class SwapTx(Base):
self.states = (self.states if self.states is not None else bytes()) + struct.pack('<iq', new_state, int(time.time()))
class PrefundedTx(Base):
__tablename__ = 'prefunded_transactions'
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)
tx_type = sa.Column(sa.Integer) # TxTypes
tx_data = sa.Column(sa.LargeBinary)
used_by = sa.Column(sa.LargeBinary)
class PooledAddress(Base):
__tablename__ = 'addresspool'

View File

@@ -225,6 +225,19 @@ def upgradeDatabase(self, db_version):
event_data BLOB,
created_at BIGINT,
PRIMARY KEY (record_id))''')
elif current_version == 16:
db_version += 1
session.execute('''
CREATE TABLE prefunded_transactions (
record_id INTEGER NOT NULL,
active_ind INTEGER,
created_at BIGINT,
linked_type INTEGER,
linked_id BLOB,
tx_type INTEGER,
tx_data BLOB,
used_by BLOB,
PRIMARY KEY (record_id))''')
if current_version != db_version:
self.db_version = db_version

View File

@@ -12,6 +12,7 @@ import hashlib
import logging
import traceback
from io import BytesIO
from basicswap.contrib.test_framework import segwit_addr
from basicswap.util import (
@@ -64,6 +65,7 @@ from basicswap.contrib.test_framework.script import (
OP_CHECKMULTISIG,
OP_CHECKSEQUENCEVERIFY,
OP_DROP,
OP_HASH160, OP_EQUAL,
SIGHASH_ALL,
SegwitV0SignatureHash,
hash160)
@@ -244,7 +246,7 @@ class BTCInterface(CoinInterface):
def getBlockHeader(self, block_hash):
return self.rpc_callback('getblockheader', [block_hash])
def getBlockHeaderAt(self, time, block_after=False):
def getBlockHeaderAt(self, time: int, block_after=False):
blockchaininfo = self.rpc_callback('getblockchaininfo')
last_block_header = self.rpc_callback('getblockheader', [blockchaininfo['bestblockhash']])
@@ -294,24 +296,24 @@ class BTCInterface(CoinInterface):
finally:
self.close_rpc(rpc_conn)
def getWalletSeedID(self):
def getWalletSeedID(self) -> str:
return self.rpc_callback('getwalletinfo')['hdseedid']
def checkExpectedSeed(self, expect_seedid):
def checkExpectedSeed(self, expect_seedid) -> bool:
self._expect_seedid_hex = expect_seedid
return expect_seedid == self.getWalletSeedID()
def getNewAddress(self, use_segwit, label='swap_receive'):
def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str:
args = [label]
if use_segwit:
args.append('bech32')
return self.rpc_callback('getnewaddress', args)
def isAddressMine(self, address):
def isAddressMine(self, address: str) -> bool:
addr_info = self.rpc_callback('getaddressinfo', [address])
return addr_info['ismine']
def checkAddressMine(self, address):
def checkAddressMine(self, address: str) -> None:
addr_info = self.rpc_callback('getaddressinfo', [address])
ensure(addr_info['ismine'], 'ismine is false')
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
@@ -914,7 +916,7 @@ class BTCInterface(CoinInterface):
def encodeTx(self, tx):
return tx.serialize()
def loadTx(self, tx_bytes):
def loadTx(self, tx_bytes) -> CTransaction:
# Load tx from bytes to internal representation
tx = CTransaction()
tx.deserialize(BytesIO(tx_bytes))
@@ -963,23 +965,23 @@ class BTCInterface(CoinInterface):
# TODO: filter errors
return None
def setTxSignature(self, tx_bytes, stack):
def setTxSignature(self, tx_bytes, stack) -> bytes:
tx = self.loadTx(tx_bytes)
tx.wit.vtxinwit.clear()
tx.wit.vtxinwit.append(CTxInWitness())
tx.wit.vtxinwit[0].scriptWitness.stack = stack
return tx.serialize()
def stripTxSignature(self, tx_bytes):
def stripTxSignature(self, tx_bytes) -> bytes:
tx = self.loadTx(tx_bytes)
tx.wit.vtxinwit.clear()
return tx.serialize()
def extractLeaderSig(self, tx_bytes):
def extractLeaderSig(self, tx_bytes) -> bytes:
tx = self.loadTx(tx_bytes)
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
def extractFollowerSig(self, tx_bytes):
def extractFollowerSig(self, tx_bytes) -> bytes:
tx = self.loadTx(tx_bytes)
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
@@ -1142,7 +1144,7 @@ class BTCInterface(CoinInterface):
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
assert (rv is True)
def verifyMessage(self, address, message, signature, message_magic=None) -> bool:
def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool:
if message_magic is None:
message_magic = self.chainparams()['message_magic']
@@ -1209,13 +1211,13 @@ class BTCInterface(CoinInterface):
length += 1 # flags
return length
def describeTx(self, tx_hex):
def describeTx(self, tx_hex: str):
return self.rpc_callback('decoderawtransaction', [tx_hex])
def getSpendableBalance(self):
return self.make_int(self.rpc_callback('getbalances')['mine']['trusted'])
def createUTXO(self, value_sats):
def createUTXO(self, value_sats: int):
# Create a new address and send value_sats to it
spendable_balance = self.getSpendableBalance()
@@ -1225,18 +1227,22 @@ class BTCInterface(CoinInterface):
address = self.getNewAddress(self._use_segwit, 'create_utxo')
return self.withdrawCoin(self.format_amount(value_sats), address, False), address
def createRawSignedTransaction(self, addr_to, amount):
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
options = {
'lockUnspents': True,
'lockUnspents': lock_unspents,
'conf_target': self._conf_target,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
return txn_signed
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
def getBlockWithTxns(self, block_hash):
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
def getBlockWithTxns(self, block_hash: str):
return self.rpc_callback('getblock', [block_hash, 2])
def getUnspentsByAddr(self):
@@ -1248,7 +1254,7 @@ class BTCInterface(CoinInterface):
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
return unspent_addr
def getUTXOBalance(self, address):
def getUTXOBalance(self, address: str):
num_blocks = self.rpc_callback('getblockcount')
sum_unspent = 0
@@ -1292,11 +1298,11 @@ class BTCInterface(CoinInterface):
return self.getUTXOBalance(address)
def isWalletEncrypted(self):
def isWalletEncrypted(self) -> bool:
wallet_info = self.rpc_callback('getwalletinfo')
return 'unlocked_until' in wallet_info
def isWalletLocked(self):
def isWalletLocked(self) -> bool:
wallet_info = self.rpc_callback('getwalletinfo')
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
return True
@@ -1308,7 +1314,7 @@ class BTCInterface(CoinInterface):
locked = encrypted and wallet_info['unlocked_until'] <= 0
return encrypted, locked
def changeWalletPassword(self, old_password, new_password):
def changeWalletPassword(self, old_password: str, new_password: str):
self._log.info('changeWalletPassword - {}'.format(self.ticker()))
if old_password == '':
if self.isWalletEncrypted():
@@ -1316,7 +1322,7 @@ class BTCInterface(CoinInterface):
return self.rpc_callback('encryptwallet', [new_password])
self.rpc_callback('walletpassphrasechange', [old_password, new_password])
def unlockWallet(self, password):
def unlockWallet(self, password: str):
if password == '':
return
self._log.info('unlockWallet - {}'.format(self.ticker()))
@@ -1327,6 +1333,14 @@ class BTCInterface(CoinInterface):
self._log.info('lockWallet - {}'.format(self.ticker()))
self.rpc_callback('walletlock')
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
script_hash = hash160(script)
assert len(script_hash) == 20
return CScript([OP_HASH160, script_hash, OP_EQUAL])
def get_p2wsh_script_pubkey(self, script: bytearray) -> bytearray:
return CScript([OP_0, hashlib.sha256(script).digest()])
def testBTCInterface():
print('TODO: testBTCInterface')

View File

@@ -184,11 +184,18 @@ def ser_string_vector(l):
return r
# Deserialize from bytes
def FromBytes(obj, tx_bytes):
obj.deserialize(BytesIO(tx_bytes))
return obj
# Deserialize from a hex string representation (eg from RPC)
def FromHex(obj, hex_string):
obj.deserialize(BytesIO(hex_str_to_bytes(hex_string)))
return obj
# Convert a binary-serializable object to hex (eg for submission via RPC)
def ToHex(obj):
return bytes_to_hex_str(obj.serialize())

View File

@@ -132,26 +132,28 @@ class FIROInterface(BTCInterface):
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def createRawSignedTransaction(self, addr_to, amount):
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
'lockUnspents': True,
'lockUnspents': lock_unspents,
'feeRate': fee_rate,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return txn_signed
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
def getScriptForPubkeyHash(self, pkh):
# Return P2WPKH nested in BIP16 P2SH
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
# Return P2PKH
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
def getScriptDest(self, script):
def getScriptDest(self, script: bytearray) -> bytearray:
# P2WSH nested in BIP16_P2SH
script_hash = hashlib.sha256(script).digest()

View File

@@ -1,17 +1,20 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from io import BytesIO
from .btc import BTCInterface
from basicswap.chainparams import Coins
from basicswap.util.address import decodeAddress
from .contrib.pivx_test_framework.messages import (
CBlock,
ToHex,
FromHex)
FromHex,
CTransaction)
class PIVXInterface(BTCInterface):
@@ -19,19 +22,25 @@ class PIVXInterface(BTCInterface):
def coin_type():
return Coins.PIVX
def createRawSignedTransaction(self, addr_to, amount):
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
def signTxWithWallet(self, tx):
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
'lockUnspents': True,
'lockUnspents': lock_unspents,
'feeRate': fee_rate,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return txn_signed
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
def decodeAddress(self, address):
return decodeAddress(address)[1:]
@@ -65,3 +74,9 @@ class PIVXInterface(BTCInterface):
def getSpendableBalance(self):
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
def loadTx(self, tx_bytes):
# Load tx from bytes to internal representation
tx = CTransaction()
tx.deserialize(BytesIO(tx_bytes))
return tx

View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
class ProtocolInterface:
swap_type = None
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
raise ValueError('base class')

View File

@@ -10,12 +10,17 @@ from basicswap.db import (
from basicswap.util import (
SerialiseNum,
)
from basicswap.util.script import (
getP2WSH,
)
from basicswap.script import (
OpCodes,
)
from basicswap.basicswap_util import (
SwapTypes,
EventLogTypes,
)
from . import ProtocolInterface
INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
ABS_LOCK_TIME_LEEWAY = 10 * 60
@@ -66,3 +71,43 @@ def redeemITx(self, bid_id, session):
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex())
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session)
class AtomicSwapInterface(ProtocolInterface):
swap_type = SwapTypes.SELLER_FIRST
def getMockScript(self) -> bytearray:
return bytearray([
OpCodes.OP_RETURN, OpCodes.OP_1])
def getMockScriptScriptPubkey(self, ci) -> bytearray:
script = self.getMockScript()
return ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
mock_txo_script = self.getMockScriptScriptPubkey(ci)
real_txo_script = ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
found: int = 0
ctx = ci.loadTx(mock_tx)
for txo in ctx.vout:
if txo.scriptPubKey == mock_txo_script:
txo.scriptPubKey = real_txo_script
found += 1
if found < 1:
raise ValueError('Mocked output not found')
if found > 1:
raise ValueError('Too many mocked outputs found')
ctx.nLockTime = 0
funded_tx = ctx.serialize()
return ci.signTxWithWallet(funded_tx)
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
script = self.getMockScript()
addr_to = ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script)
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
return bytes.fromhex(funded_tx)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert
# Copyright (c) 2020-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -14,8 +14,10 @@ from basicswap.chainparams import (
)
from basicswap.basicswap_util import (
KeyTypes,
SwapTypes,
EventLogTypes,
)
from . import ProtocolInterface
def addLockRefundSigs(self, xmr_swap, ci):
@@ -84,3 +86,7 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
key_type = KeyTypes.KBSF if bid.was_sent else KeyTypes.KBSL
return ci_to.encodeKey(swap_client.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, key_type, True if offer.coin_to == Coins.XMR else False))
class XmrSwapInterface(ProtocolInterface):
swap_type = SwapTypes.XMR_SWAP

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019 tecnovert
# Copyright (c) 2019-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -15,6 +15,7 @@ class OpCodes(IntEnum):
OP_IF = 0x63,
OP_ELSE = 0x67,
OP_ENDIF = 0x68,
OP_RETURN = 0x6a,
OP_DROP = 0x75,
OP_DUP = 0x76,
OP_SIZE = 0x82,