system: Allow preselecting inputs for atomic swaps.
This commit is contained in:
parent
23e89882a4
commit
c90fa6f2c6
@ -1,3 +1,3 @@
|
|||||||
name = "basicswap"
|
name = "basicswap"
|
||||||
|
|
||||||
__version__ = "0.11.51"
|
__version__ = "0.11.52"
|
||||||
|
@ -26,6 +26,8 @@ import sqlalchemy as sa
|
|||||||
import collections
|
import collections
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
from sqlalchemy.orm.session import close_all_sessions
|
from sqlalchemy.orm.session import close_all_sessions
|
||||||
|
|
||||||
@ -92,6 +94,7 @@ from .db import (
|
|||||||
Offer,
|
Offer,
|
||||||
Bid,
|
Bid,
|
||||||
SwapTx,
|
SwapTx,
|
||||||
|
PrefundedTx,
|
||||||
PooledAddress,
|
PooledAddress,
|
||||||
SentOffer,
|
SentOffer,
|
||||||
SmsgAddress,
|
SmsgAddress,
|
||||||
@ -116,6 +119,7 @@ from .explorers import (
|
|||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
import basicswap.network as bsn
|
import basicswap.network as bsn
|
||||||
import basicswap.protocols.atomic_swap_1 as atomic_swap_1
|
import basicswap.protocols.atomic_swap_1 as atomic_swap_1
|
||||||
|
import basicswap.protocols.xmr_swap_1 as xmr_swap_1
|
||||||
from .basicswap_util import (
|
from .basicswap_util import (
|
||||||
KeyTypes,
|
KeyTypes,
|
||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
@ -140,9 +144,6 @@ from .basicswap_util import (
|
|||||||
isActiveBidState,
|
isActiveBidState,
|
||||||
NotificationTypes as NT,
|
NotificationTypes as NT,
|
||||||
)
|
)
|
||||||
from .protocols.xmr_swap_1 import (
|
|
||||||
addLockRefundSigs,
|
|
||||||
recoverNoScriptTxnWithKey)
|
|
||||||
|
|
||||||
|
|
||||||
non_script_type_coins = (Coins.XMR, Coins.PART_ANON)
|
non_script_type_coins = (Coins.XMR, Coins.PART_ANON)
|
||||||
@ -218,6 +219,10 @@ class WatchedTransaction():
|
|||||||
|
|
||||||
class BasicSwap(BaseApp):
|
class BasicSwap(BaseApp):
|
||||||
ws_server = None
|
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'):
|
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
|
||||||
super().__init__(fp, data_dir, settings, chain, log_name)
|
super().__init__(fp, data_dir, settings, chain, log_name)
|
||||||
@ -548,6 +553,11 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
return self.coin_clients[use_coinid][interface_ind]
|
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):
|
def createInterface(self, coin):
|
||||||
if coin == Coins.PART:
|
if coin == Coins.PART:
|
||||||
return PARTInterface(self.coin_clients[coin], self.chain, self)
|
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.log.info('%s Core version %d', ci.coin_name(), core_version)
|
||||||
self.coin_clients[c]['core_version'] = core_version
|
self.coin_clients[c]['core_version'] = core_version
|
||||||
|
|
||||||
if c == Coins.XMR:
|
thread_func = threadPollXMRChainState if c == Coins.XMR else threadPollChainState
|
||||||
t = threading.Thread(target=threadPollXMRChainState, args=(self, c))
|
t = threading.Thread(target=thread_func, args=(self, c))
|
||||||
else:
|
|
||||||
t = threading.Thread(target=threadPollChainState, args=(self, c))
|
|
||||||
self.threads.append(t)
|
self.threads.append(t)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
@ -851,7 +859,7 @@ class BasicSwap(BaseApp):
|
|||||||
finally:
|
finally:
|
||||||
self.closeSession(session)
|
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()
|
identity_stats = session.query(KnownIdentity).filter_by(address=address).first()
|
||||||
if not identity_stats:
|
if not identity_stats:
|
||||||
identity_stats = KnownIdentity(address=address, created_at=int(time.time()))
|
identity_stats = KnownIdentity(address=address, created_at=int(time.time()))
|
||||||
@ -870,7 +878,7 @@ class BasicSwap(BaseApp):
|
|||||||
identity_stats.updated_at = int(time.time())
|
identity_stats.updated_at = int(time.time())
|
||||||
session.add(identity_stats)
|
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()
|
kv = session.query(DBKVInt).filter_by(key=str_key).first()
|
||||||
if not kv:
|
if not kv:
|
||||||
kv = DBKVInt(key=str_key, value=int_val)
|
kv = DBKVInt(key=str_key, value=int_val)
|
||||||
@ -878,7 +886,7 @@ class BasicSwap(BaseApp):
|
|||||||
kv.value = int_val
|
kv.value = int_val
|
||||||
session.add(kv)
|
session.add(kv)
|
||||||
|
|
||||||
def setIntKV(self, str_key, int_val):
|
def setIntKV(self, str_key: str, int_val: int) -> None:
|
||||||
self.mxDB.acquire()
|
self.mxDB.acquire()
|
||||||
try:
|
try:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
@ -889,7 +897,7 @@ class BasicSwap(BaseApp):
|
|||||||
session.remove()
|
session.remove()
|
||||||
self.mxDB.release()
|
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:
|
try:
|
||||||
use_session = self.openSession(session)
|
use_session = self.openSession(session)
|
||||||
kv = use_session.query(DBKVString).filter_by(key=str_key).first()
|
kv = use_session.query(DBKVString).filter_by(key=str_key).first()
|
||||||
@ -902,7 +910,7 @@ class BasicSwap(BaseApp):
|
|||||||
if session is None:
|
if session is None:
|
||||||
self.closeSession(use_session)
|
self.closeSession(use_session)
|
||||||
|
|
||||||
def getStringKV(self, str_key):
|
def getStringKV(self, str_key: str) -> Optional[str]:
|
||||||
self.mxDB.acquire()
|
self.mxDB.acquire()
|
||||||
try:
|
try:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
@ -915,7 +923,7 @@ class BasicSwap(BaseApp):
|
|||||||
session.remove()
|
session.remove()
|
||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
def clearStringKV(self, str_key, str_val):
|
def clearStringKV(self, str_key: str, str_val: str) -> None:
|
||||||
with self.mxDB:
|
with self.mxDB:
|
||||||
try:
|
try:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
@ -925,6 +933,19 @@ class BasicSwap(BaseApp):
|
|||||||
session.close()
|
session.close()
|
||||||
session.remove()
|
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):
|
def activateBid(self, session, bid):
|
||||||
if bid.bid_id in self.swaps_in_progress:
|
if bid.bid_id in self.swaps_in_progress:
|
||||||
self.log.debug('Bid %s is already in progress', bid.bid_id.hex())
|
self.log.debug('Bid %s is already in progress', bid.bid_id.hex())
|
||||||
@ -1366,6 +1387,16 @@ class BasicSwap(BaseApp):
|
|||||||
repeat_count=0)
|
repeat_count=0)
|
||||||
session.add(auto_link)
|
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(offer)
|
||||||
session.add(SentOffer(offer_id=offer_id))
|
session.add(SentOffer(offer_id=offer_id))
|
||||||
session.commit()
|
session.commit()
|
||||||
@ -2147,7 +2178,8 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
bid.pkhash_seller = pkhash_refund
|
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
|
# Store the signed refund txn in case wallet is locked when refund is possible
|
||||||
refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script)
|
refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script)
|
||||||
@ -2532,14 +2564,14 @@ class BasicSwap(BaseApp):
|
|||||||
session.remove()
|
session.remove()
|
||||||
self.mxDB.release()
|
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)
|
self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
|
||||||
bid.setState(BidStates.BID_ERROR)
|
bid.setState(BidStates.BID_ERROR)
|
||||||
bid.state_note = 'error msg: ' + error_str
|
bid.state_note = 'error msg: ' + error_str
|
||||||
if save_bid:
|
if save_bid:
|
||||||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
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':
|
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
|
||||||
return None
|
return None
|
||||||
ci = self.ci(coin_type)
|
ci = self.ci(coin_type)
|
||||||
@ -2550,7 +2582,11 @@ class BasicSwap(BaseApp):
|
|||||||
addr_to = ci.encode_p2sh(initiate_script)
|
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())
|
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
|
return txn_signed
|
||||||
|
|
||||||
def deriveParticipateScript(self, bid_id, bid, offer):
|
def deriveParticipateScript(self, bid_id, bid, offer):
|
||||||
@ -4560,7 +4596,7 @@ class BasicSwap(BaseApp):
|
|||||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
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)
|
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(
|
msg_buf = XmrBidLockTxSigsMessage(
|
||||||
bid_msg_id=bid_id,
|
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)
|
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')
|
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)
|
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)
|
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
|
has_changed = True
|
||||||
|
|
||||||
if data['kbs_other'] is not None:
|
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:
|
if has_changed:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
|
@ -123,6 +123,8 @@ class TxTypes(IntEnum):
|
|||||||
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
|
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
|
||||||
XMR_SWAP_B_LOCK = auto()
|
XMR_SWAP_B_LOCK = auto()
|
||||||
|
|
||||||
|
ITX_PRE_FUNDED = auto()
|
||||||
|
|
||||||
|
|
||||||
class ActionTypes(IntEnum):
|
class ActionTypes(IntEnum):
|
||||||
ACCEPT_BID = auto()
|
ACCEPT_BID = auto()
|
||||||
@ -289,6 +291,8 @@ def strTxType(tx_type):
|
|||||||
return 'Chain A Lock Refund Swipe Tx'
|
return 'Chain A Lock Refund Swipe Tx'
|
||||||
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
||||||
return 'Chain B Lock Tx'
|
return 'Chain B Lock Tx'
|
||||||
|
if tx_type == TxTypes.ITX_PRE_FUNDED:
|
||||||
|
return 'Funded mock initiate tx'
|
||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from enum import IntEnum, auto
|
|||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
|
||||||
CURRENT_DB_VERSION = 16
|
CURRENT_DB_VERSION = 17
|
||||||
CURRENT_DB_DATA_VERSION = 2
|
CURRENT_DB_DATA_VERSION = 2
|
||||||
Base = declarative_base()
|
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()))
|
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):
|
class PooledAddress(Base):
|
||||||
__tablename__ = 'addresspool'
|
__tablename__ = 'addresspool'
|
||||||
|
|
||||||
|
@ -225,6 +225,19 @@ def upgradeDatabase(self, db_version):
|
|||||||
event_data BLOB,
|
event_data BLOB,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
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:
|
if current_version != db_version:
|
||||||
self.db_version = db_version
|
self.db_version = db_version
|
||||||
|
@ -12,6 +12,7 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from basicswap.contrib.test_framework import segwit_addr
|
from basicswap.contrib.test_framework import segwit_addr
|
||||||
|
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
@ -64,6 +65,7 @@ from basicswap.contrib.test_framework.script import (
|
|||||||
OP_CHECKMULTISIG,
|
OP_CHECKMULTISIG,
|
||||||
OP_CHECKSEQUENCEVERIFY,
|
OP_CHECKSEQUENCEVERIFY,
|
||||||
OP_DROP,
|
OP_DROP,
|
||||||
|
OP_HASH160, OP_EQUAL,
|
||||||
SIGHASH_ALL,
|
SIGHASH_ALL,
|
||||||
SegwitV0SignatureHash,
|
SegwitV0SignatureHash,
|
||||||
hash160)
|
hash160)
|
||||||
@ -244,7 +246,7 @@ class BTCInterface(CoinInterface):
|
|||||||
def getBlockHeader(self, block_hash):
|
def getBlockHeader(self, block_hash):
|
||||||
return self.rpc_callback('getblockheader', [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')
|
blockchaininfo = self.rpc_callback('getblockchaininfo')
|
||||||
last_block_header = self.rpc_callback('getblockheader', [blockchaininfo['bestblockhash']])
|
last_block_header = self.rpc_callback('getblockheader', [blockchaininfo['bestblockhash']])
|
||||||
|
|
||||||
@ -294,24 +296,24 @@ class BTCInterface(CoinInterface):
|
|||||||
finally:
|
finally:
|
||||||
self.close_rpc(rpc_conn)
|
self.close_rpc(rpc_conn)
|
||||||
|
|
||||||
def getWalletSeedID(self):
|
def getWalletSeedID(self) -> str:
|
||||||
return self.rpc_callback('getwalletinfo')['hdseedid']
|
return self.rpc_callback('getwalletinfo')['hdseedid']
|
||||||
|
|
||||||
def checkExpectedSeed(self, expect_seedid):
|
def checkExpectedSeed(self, expect_seedid) -> bool:
|
||||||
self._expect_seedid_hex = expect_seedid
|
self._expect_seedid_hex = expect_seedid
|
||||||
return expect_seedid == self.getWalletSeedID()
|
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]
|
args = [label]
|
||||||
if use_segwit:
|
if use_segwit:
|
||||||
args.append('bech32')
|
args.append('bech32')
|
||||||
return self.rpc_callback('getnewaddress', args)
|
return self.rpc_callback('getnewaddress', args)
|
||||||
|
|
||||||
def isAddressMine(self, address):
|
def isAddressMine(self, address: str) -> bool:
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||||
return addr_info['ismine']
|
return addr_info['ismine']
|
||||||
|
|
||||||
def checkAddressMine(self, address):
|
def checkAddressMine(self, address: str) -> None:
|
||||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||||
ensure(addr_info['ismine'], 'ismine is false')
|
ensure(addr_info['ismine'], 'ismine is false')
|
||||||
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
|
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
|
||||||
@ -914,7 +916,7 @@ class BTCInterface(CoinInterface):
|
|||||||
def encodeTx(self, tx):
|
def encodeTx(self, tx):
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def loadTx(self, tx_bytes):
|
def loadTx(self, tx_bytes) -> CTransaction:
|
||||||
# Load tx from bytes to internal representation
|
# Load tx from bytes to internal representation
|
||||||
tx = CTransaction()
|
tx = CTransaction()
|
||||||
tx.deserialize(BytesIO(tx_bytes))
|
tx.deserialize(BytesIO(tx_bytes))
|
||||||
@ -963,23 +965,23 @@ class BTCInterface(CoinInterface):
|
|||||||
# TODO: filter errors
|
# TODO: filter errors
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def setTxSignature(self, tx_bytes, stack):
|
def setTxSignature(self, tx_bytes, stack) -> bytes:
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
tx.wit.vtxinwit.clear()
|
tx.wit.vtxinwit.clear()
|
||||||
tx.wit.vtxinwit.append(CTxInWitness())
|
tx.wit.vtxinwit.append(CTxInWitness())
|
||||||
tx.wit.vtxinwit[0].scriptWitness.stack = stack
|
tx.wit.vtxinwit[0].scriptWitness.stack = stack
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def stripTxSignature(self, tx_bytes):
|
def stripTxSignature(self, tx_bytes) -> bytes:
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
tx.wit.vtxinwit.clear()
|
tx.wit.vtxinwit.clear()
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def extractLeaderSig(self, tx_bytes):
|
def extractLeaderSig(self, tx_bytes) -> bytes:
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
||||||
|
|
||||||
def extractFollowerSig(self, tx_bytes):
|
def extractFollowerSig(self, tx_bytes) -> bytes:
|
||||||
tx = self.loadTx(tx_bytes)
|
tx = self.loadTx(tx_bytes)
|
||||||
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
|
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
|
||||||
|
|
||||||
@ -1142,7 +1144,7 @@ class BTCInterface(CoinInterface):
|
|||||||
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
|
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
|
||||||
assert (rv is True)
|
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:
|
if message_magic is None:
|
||||||
message_magic = self.chainparams()['message_magic']
|
message_magic = self.chainparams()['message_magic']
|
||||||
|
|
||||||
@ -1209,13 +1211,13 @@ class BTCInterface(CoinInterface):
|
|||||||
length += 1 # flags
|
length += 1 # flags
|
||||||
return length
|
return length
|
||||||
|
|
||||||
def describeTx(self, tx_hex):
|
def describeTx(self, tx_hex: str):
|
||||||
return self.rpc_callback('decoderawtransaction', [tx_hex])
|
return self.rpc_callback('decoderawtransaction', [tx_hex])
|
||||||
|
|
||||||
def getSpendableBalance(self):
|
def getSpendableBalance(self):
|
||||||
return self.make_int(self.rpc_callback('getbalances')['mine']['trusted'])
|
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
|
# Create a new address and send value_sats to it
|
||||||
|
|
||||||
spendable_balance = self.getSpendableBalance()
|
spendable_balance = self.getSpendableBalance()
|
||||||
@ -1225,18 +1227,22 @@ class BTCInterface(CoinInterface):
|
|||||||
address = self.getNewAddress(self._use_segwit, 'create_utxo')
|
address = self.getNewAddress(self._use_segwit, 'create_utxo')
|
||||||
return self.withdrawCoin(self.format_amount(value_sats), address, False), address
|
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)}])
|
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': True,
|
'lockUnspents': lock_unspents,
|
||||||
'conf_target': self._conf_target,
|
'conf_target': self._conf_target,
|
||||||
}
|
}
|
||||||
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
if sub_fee:
|
||||||
txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return txn_signed
|
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])
|
return self.rpc_callback('getblock', [block_hash, 2])
|
||||||
|
|
||||||
def getUnspentsByAddr(self):
|
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)
|
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
|
||||||
return unspent_addr
|
return unspent_addr
|
||||||
|
|
||||||
def getUTXOBalance(self, address):
|
def getUTXOBalance(self, address: str):
|
||||||
num_blocks = self.rpc_callback('getblockcount')
|
num_blocks = self.rpc_callback('getblockcount')
|
||||||
|
|
||||||
sum_unspent = 0
|
sum_unspent = 0
|
||||||
@ -1292,11 +1298,11 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
return self.getUTXOBalance(address)
|
return self.getUTXOBalance(address)
|
||||||
|
|
||||||
def isWalletEncrypted(self):
|
def isWalletEncrypted(self) -> bool:
|
||||||
wallet_info = self.rpc_callback('getwalletinfo')
|
wallet_info = self.rpc_callback('getwalletinfo')
|
||||||
return 'unlocked_until' in wallet_info
|
return 'unlocked_until' in wallet_info
|
||||||
|
|
||||||
def isWalletLocked(self):
|
def isWalletLocked(self) -> bool:
|
||||||
wallet_info = self.rpc_callback('getwalletinfo')
|
wallet_info = self.rpc_callback('getwalletinfo')
|
||||||
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
|
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
|
||||||
return True
|
return True
|
||||||
@ -1308,7 +1314,7 @@ class BTCInterface(CoinInterface):
|
|||||||
locked = encrypted and wallet_info['unlocked_until'] <= 0
|
locked = encrypted and wallet_info['unlocked_until'] <= 0
|
||||||
return encrypted, locked
|
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()))
|
self._log.info('changeWalletPassword - {}'.format(self.ticker()))
|
||||||
if old_password == '':
|
if old_password == '':
|
||||||
if self.isWalletEncrypted():
|
if self.isWalletEncrypted():
|
||||||
@ -1316,7 +1322,7 @@ class BTCInterface(CoinInterface):
|
|||||||
return self.rpc_callback('encryptwallet', [new_password])
|
return self.rpc_callback('encryptwallet', [new_password])
|
||||||
self.rpc_callback('walletpassphrasechange', [old_password, new_password])
|
self.rpc_callback('walletpassphrasechange', [old_password, new_password])
|
||||||
|
|
||||||
def unlockWallet(self, password):
|
def unlockWallet(self, password: str):
|
||||||
if password == '':
|
if password == '':
|
||||||
return
|
return
|
||||||
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
||||||
@ -1327,6 +1333,14 @@ class BTCInterface(CoinInterface):
|
|||||||
self._log.info('lockWallet - {}'.format(self.ticker()))
|
self._log.info('lockWallet - {}'.format(self.ticker()))
|
||||||
self.rpc_callback('walletlock')
|
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():
|
def testBTCInterface():
|
||||||
print('TODO: testBTCInterface')
|
print('TODO: testBTCInterface')
|
||||||
|
@ -184,11 +184,18 @@ def ser_string_vector(l):
|
|||||||
return r
|
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)
|
# Deserialize from a hex string representation (eg from RPC)
|
||||||
def FromHex(obj, hex_string):
|
def FromHex(obj, hex_string):
|
||||||
obj.deserialize(BytesIO(hex_str_to_bytes(hex_string)))
|
obj.deserialize(BytesIO(hex_str_to_bytes(hex_string)))
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
# Convert a binary-serializable object to hex (eg for submission via RPC)
|
# Convert a binary-serializable object to hex (eg for submission via RPC)
|
||||||
def ToHex(obj):
|
def ToHex(obj):
|
||||||
return bytes_to_hex_str(obj.serialize())
|
return bytes_to_hex_str(obj.serialize())
|
||||||
|
@ -132,26 +132,28 @@ class FIROInterface(BTCInterface):
|
|||||||
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
||||||
return bytes.fromhex(rv['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)}])
|
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||||
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
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}')
|
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': True,
|
'lockUnspents': lock_unspents,
|
||||||
'feeRate': fee_rate,
|
'feeRate': fee_rate,
|
||||||
}
|
}
|
||||||
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
if sub_fee:
|
||||||
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return txn_signed
|
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh):
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
# Return P2WPKH nested in BIP16 P2SH
|
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])
|
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
|
# P2WSH nested in BIP16_P2SH
|
||||||
|
|
||||||
script_hash = hashlib.sha256(script).digest()
|
script_hash = hashlib.sha256(script).digest()
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020 tecnovert
|
# Copyright (c) 2022 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from .btc import BTCInterface
|
from .btc import BTCInterface
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress
|
||||||
from .contrib.pivx_test_framework.messages import (
|
from .contrib.pivx_test_framework.messages import (
|
||||||
CBlock,
|
CBlock,
|
||||||
ToHex,
|
ToHex,
|
||||||
FromHex)
|
FromHex,
|
||||||
|
CTransaction)
|
||||||
|
|
||||||
|
|
||||||
class PIVXInterface(BTCInterface):
|
class PIVXInterface(BTCInterface):
|
||||||
@ -19,19 +22,25 @@ class PIVXInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.PIVX
|
return Coins.PIVX
|
||||||
|
|
||||||
def createRawSignedTransaction(self, addr_to, amount):
|
def signTxWithWallet(self, tx):
|
||||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
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)
|
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}')
|
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': True,
|
'lockUnspents': lock_unspents,
|
||||||
'feeRate': fee_rate,
|
'feeRate': fee_rate,
|
||||||
}
|
}
|
||||||
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
if sub_fee:
|
||||||
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
options['subtractFeeFromOutputs'] = [0,]
|
||||||
return txn_signed
|
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):
|
def decodeAddress(self, address):
|
||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
@ -65,3 +74,9 @@ class PIVXInterface(BTCInterface):
|
|||||||
|
|
||||||
def getSpendableBalance(self):
|
def getSpendableBalance(self):
|
||||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
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
|
||||||
|
@ -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')
|
@ -10,12 +10,17 @@ from basicswap.db import (
|
|||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
SerialiseNum,
|
SerialiseNum,
|
||||||
)
|
)
|
||||||
|
from basicswap.util.script import (
|
||||||
|
getP2WSH,
|
||||||
|
)
|
||||||
from basicswap.script import (
|
from basicswap.script import (
|
||||||
OpCodes,
|
OpCodes,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
|
SwapTypes,
|
||||||
EventLogTypes,
|
EventLogTypes,
|
||||||
)
|
)
|
||||||
|
from . import ProtocolInterface
|
||||||
|
|
||||||
INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
|
INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
|
||||||
ABS_LOCK_TIME_LEEWAY = 10 * 60
|
ABS_LOCK_TIME_LEEWAY = 10 * 60
|
||||||
@ -66,3 +71,43 @@ def redeemITx(self, bid_id, session):
|
|||||||
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
|
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.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)
|
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)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020 tecnovert
|
# Copyright (c) 2020-2022 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@ -14,8 +14,10 @@ from basicswap.chainparams import (
|
|||||||
)
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
KeyTypes,
|
KeyTypes,
|
||||||
|
SwapTypes,
|
||||||
EventLogTypes,
|
EventLogTypes,
|
||||||
)
|
)
|
||||||
|
from . import ProtocolInterface
|
||||||
|
|
||||||
|
|
||||||
def addLockRefundSigs(self, xmr_swap, ci):
|
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
|
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))
|
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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019 tecnovert
|
# Copyright (c) 2019-2022 tecnovert
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ class OpCodes(IntEnum):
|
|||||||
OP_IF = 0x63,
|
OP_IF = 0x63,
|
||||||
OP_ELSE = 0x67,
|
OP_ELSE = 0x67,
|
||||||
OP_ENDIF = 0x68,
|
OP_ENDIF = 0x68,
|
||||||
|
OP_RETURN = 0x6a,
|
||||||
OP_DROP = 0x75,
|
OP_DROP = 0x75,
|
||||||
OP_DUP = 0x76,
|
OP_DUP = 0x76,
|
||||||
OP_SIZE = 0x82,
|
OP_SIZE = 0x82,
|
||||||
|
20
doc/notes.md
20
doc/notes.md
@ -50,12 +50,28 @@ In chainclients.monero:
|
|||||||
|
|
||||||
On the remote machine open an ssh tunnel to port 18081:
|
On the remote machine open an ssh tunnel to port 18081:
|
||||||
|
|
||||||
ssh -R 18081:localhost:18081 -N user@LOCAL_NODE_IP
|
ssh -N -R 18081:localhost:18081 user@LOCAL_NODE_IP
|
||||||
|
|
||||||
And start monerod
|
And start monerod
|
||||||
|
|
||||||
|
|
||||||
## Installing on windows natively
|
## SSH Tunnel to Remote BasicSwap Node
|
||||||
|
|
||||||
|
While basicswap can be configured to host on an external interface:
|
||||||
|
|
||||||
|
If not using docker by changing 'htmlhost' and 'wshost' in basicswap.json
|
||||||
|
For docker change 'HTML_PORT' and 'WS_PORT' in the .env file in the same dir as docker-compose.yml
|
||||||
|
|
||||||
|
A better solution is to use ssh to forward the required ports from the machine running bascswap to the client.
|
||||||
|
|
||||||
|
ssh -N -L 5555:localhost:12700 -L 11700:localhost:11700 BASICSWAP_HOST
|
||||||
|
|
||||||
|
Run from the client machine (not running basicswap) will forward the basicswap ui on port 12700 to port 5555
|
||||||
|
on the local machine and also the websocket port at 11700.
|
||||||
|
The ui port on the client machine can be anything but the websocket port must match 'wsport' in basicswap.json.
|
||||||
|
|
||||||
|
|
||||||
|
## Installing on Windows Natively
|
||||||
|
|
||||||
This is not a supported installation method!
|
This is not a supported installation method!
|
||||||
|
|
||||||
|
@ -261,18 +261,32 @@ def waitForNumSwapping(delay_event, port, bids, wait_for=60):
|
|||||||
raise ValueError('waitForNumSwapping failed')
|
raise ValueError('waitForNumSwapping failed')
|
||||||
|
|
||||||
|
|
||||||
def wait_for_balance(delay_event, url, balance_key, expect_amount, iterations=20, delay_time=3):
|
def wait_for_balance(delay_event, url, balance_key, expect_amount, iterations=20, delay_time=3) -> None:
|
||||||
i = 0
|
i = 0
|
||||||
while not delay_event.is_set():
|
while not delay_event.is_set():
|
||||||
rv_js = json.loads(urlopen(url).read())
|
rv_js = json.loads(urlopen(url).read())
|
||||||
if float(rv_js[balance_key]) >= expect_amount:
|
if float(rv_js[balance_key]) >= expect_amount:
|
||||||
break
|
return
|
||||||
delay_event.wait(delay_time)
|
delay_event.wait(delay_time)
|
||||||
i += 1
|
i += 1
|
||||||
if i > iterations:
|
if i > iterations:
|
||||||
raise ValueError('Expect {} {}'.format(balance_key, expect_amount))
|
raise ValueError('Expect {} {}'.format(balance_key, expect_amount))
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_unspent(delay_event, ci, expect_amount, iterations=20, delay_time=1) -> None:
|
||||||
|
logging.info(f'Waiting for unspent balance: {expect_amount}')
|
||||||
|
i = 0
|
||||||
|
while not delay_event.is_set():
|
||||||
|
unspent_addr = ci.getUnspentsByAddr()
|
||||||
|
for _, value in unspent_addr.items():
|
||||||
|
if value >= expect_amount:
|
||||||
|
return
|
||||||
|
delay_event.wait(delay_time)
|
||||||
|
i += 1
|
||||||
|
if i > iterations:
|
||||||
|
raise ValueError('wait_for_unspent {}'.format(expect_amount))
|
||||||
|
|
||||||
|
|
||||||
def delay_for(delay_event, delay_for=60):
|
def delay_for(delay_event, delay_for=60):
|
||||||
logging.info('Delaying for {} seconds.'.format(delay_for))
|
logging.info('Delaying for {} seconds.'.format(delay_for))
|
||||||
delay_event.wait(delay_for)
|
delay_event.wait(delay_for)
|
||||||
|
@ -43,6 +43,7 @@ from tests.basicswap.common import (
|
|||||||
wait_for_offer,
|
wait_for_offer,
|
||||||
wait_for_bid,
|
wait_for_bid,
|
||||||
wait_for_balance,
|
wait_for_balance,
|
||||||
|
wait_for_unspent,
|
||||||
wait_for_bid_tx_state,
|
wait_for_bid_tx_state,
|
||||||
wait_for_in_progress,
|
wait_for_in_progress,
|
||||||
TEST_HTTP_PORT,
|
TEST_HTTP_PORT,
|
||||||
@ -496,6 +497,69 @@ class Test(BaseTest):
|
|||||||
assert (compare_bid_states(offerer_states, self.states_offerer[2]) is True)
|
assert (compare_bid_states(offerer_states, self.states_offerer[2]) is True)
|
||||||
assert (compare_bid_states(bidder_states, self.states_bidder[2], exact_match=False) is True)
|
assert (compare_bid_states(bidder_states, self.states_bidder[2], exact_match=False) is True)
|
||||||
|
|
||||||
|
def test_14_sweep_balance(self):
|
||||||
|
logging.info('---------- Test sweep balance offer')
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
|
# Disable staking
|
||||||
|
walletsettings = callnoderpc(2, 'walletsettings', ['stakingoptions', ])
|
||||||
|
walletsettings['enabled'] = False
|
||||||
|
walletsettings = callnoderpc(2, 'walletsettings', ['stakingoptions', walletsettings])
|
||||||
|
walletsettings = callnoderpc(2, 'walletsettings', ['stakingoptions', ])
|
||||||
|
assert (walletsettings['stakingoptions']['enabled'] is False)
|
||||||
|
|
||||||
|
# Prepare balance
|
||||||
|
js_w2 = read_json_api(1802, 'wallets')
|
||||||
|
if float(js_w2['PART']['balance']) < 100.0:
|
||||||
|
post_json = {
|
||||||
|
'value': 100,
|
||||||
|
'address': js_w2['PART']['deposit_address'],
|
||||||
|
'subfee': False,
|
||||||
|
}
|
||||||
|
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/part/withdraw', post_json)
|
||||||
|
assert (len(json_rv['txid']) == 64)
|
||||||
|
wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/part', 'balance', 100.0)
|
||||||
|
|
||||||
|
js_w2 = read_json_api(1802, 'wallets')
|
||||||
|
assert (float(js_w2['PART']['balance']) >= 100.0)
|
||||||
|
|
||||||
|
js_w2 = read_json_api(1802, 'wallets')
|
||||||
|
post_json = {
|
||||||
|
'value': float(js_w2['PART']['balance']),
|
||||||
|
'address': read_json_api(1802, 'wallets/part/nextdepositaddr'),
|
||||||
|
'subfee': True,
|
||||||
|
}
|
||||||
|
json_rv = read_json_api(TEST_HTTP_PORT + 2, 'wallets/part/withdraw', post_json)
|
||||||
|
wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/part', 'balance', 10.0)
|
||||||
|
assert (len(json_rv['txid']) == 64)
|
||||||
|
|
||||||
|
# Create prefunded ITX
|
||||||
|
ci = swap_clients[2].ci(Coins.PART)
|
||||||
|
pi = swap_clients[2].pi(SwapTypes.SELLER_FIRST)
|
||||||
|
js_w2 = read_json_api(1802, 'wallets')
|
||||||
|
swap_value = ci.make_int(js_w2['PART']['balance'])
|
||||||
|
|
||||||
|
itx = pi.getFundedInitiateTxTemplate(ci, swap_value, True)
|
||||||
|
itx_decoded = ci.describeTx(itx.hex())
|
||||||
|
value_after_subfee = ci.make_int(itx_decoded['vout'][0]['value'])
|
||||||
|
assert (value_after_subfee < swap_value)
|
||||||
|
swap_value = value_after_subfee
|
||||||
|
wait_for_unspent(test_delay_event, ci, swap_value)
|
||||||
|
|
||||||
|
# Create swap with prefunded ITX
|
||||||
|
extra_options = {'prefunded_itx': itx}
|
||||||
|
offer_id = swap_clients[2].postOffer(Coins.PART, Coins.BTC, swap_value, 2 * COIN, swap_value, SwapTypes.SELLER_FIRST, extra_options=extra_options)
|
||||||
|
|
||||||
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||||
|
offer = swap_clients[1].getOffer(offer_id)
|
||||||
|
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
|
wait_for_bid(test_delay_event, swap_clients[2], bid_id)
|
||||||
|
swap_clients[2].acceptBid(bid_id)
|
||||||
|
|
||||||
|
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||||
|
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||||
|
|
||||||
def pass_99_delay(self):
|
def pass_99_delay(self):
|
||||||
logging.info('Delay')
|
logging.info('Delay')
|
||||||
for i in range(60 * 10):
|
for i in range(60 * 10):
|
||||||
|
Loading…
Reference in New Issue
Block a user