swaps: small refactor and add unit tests for claim tx
This commit is contained in:
@@ -14,7 +14,7 @@ from .bitcoin import (script_to_p2wsh, opcodes, p2wsh_nested_script, push_script
|
||||
is_segwit_address, construct_witness)
|
||||
from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint
|
||||
from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
|
||||
from .util import log_exceptions
|
||||
from .util import log_exceptions, BelowDustLimit
|
||||
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY, ln_dummy_address
|
||||
from .bitcoin import dust_threshold
|
||||
from .logging import Logger
|
||||
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
|
||||
from .wallet import Abstract_Wallet
|
||||
from .lnwatcher import LNWalletWatcher
|
||||
from .lnworker import LNWallet
|
||||
from .simple_config import SimpleConfig
|
||||
|
||||
|
||||
API_URL_MAINNET = 'https://swaps.electrum.org/api'
|
||||
@@ -116,7 +117,6 @@ def create_claim_tx(
|
||||
*,
|
||||
txin: PartialTxInput,
|
||||
witness_script: bytes,
|
||||
preimage: Union[bytes, int], # 0 if timing out forward-swap
|
||||
address: str,
|
||||
amount_sat: int,
|
||||
locktime: int,
|
||||
@@ -124,11 +124,12 @@ def create_claim_tx(
|
||||
"""Create tx to either claim successful reverse-swap,
|
||||
or to get refunded for timed-out forward-swap.
|
||||
"""
|
||||
assert txin.address is not None
|
||||
if is_segwit_address(txin.address):
|
||||
txin.script_type = 'p2wsh'
|
||||
txin.script_sig = b''
|
||||
else:
|
||||
txin.script_type = 'p2wsh-p2sh'
|
||||
txin.script_type = 'p2wsh-p2sh' # TODO rm??
|
||||
txin.redeem_script = bytes.fromhex(p2wsh_nested_script(witness_script.hex()))
|
||||
txin.script_sig = bytes.fromhex(push_script(txin.redeem_script.hex()))
|
||||
txin.witness_script = witness_script
|
||||
@@ -217,33 +218,21 @@ class SwapManager(Logger):
|
||||
if not swap.is_reverse and delta < 0:
|
||||
# too early for refund
|
||||
return
|
||||
# FIXME the mining fee should depend on swap.is_reverse.
|
||||
# the txs are not the same size...
|
||||
amount_sat = txin.value_sats() - self.get_claim_fee()
|
||||
if amount_sat < dust_threshold():
|
||||
try:
|
||||
tx = self._create_and_sign_claim_tx(txin=txin, swap=swap, config=self.wallet.config)
|
||||
except BelowDustLimit:
|
||||
self.logger.info('utxo value below dust threshold')
|
||||
continue
|
||||
if swap.is_reverse: # successful reverse swap
|
||||
preimage = swap.preimage
|
||||
locktime = 0
|
||||
else: # timing out forward swap
|
||||
preimage = 0
|
||||
locktime = swap.locktime
|
||||
tx = create_claim_tx(
|
||||
txin=txin,
|
||||
witness_script=swap.redeem_script,
|
||||
preimage=preimage,
|
||||
address=swap.receive_address,
|
||||
amount_sat=amount_sat,
|
||||
locktime=locktime,
|
||||
)
|
||||
self.sign_tx(tx, swap)
|
||||
self.logger.info(f'adding claim tx {tx.txid()}')
|
||||
self.wallet.adb.add_transaction(tx)
|
||||
swap.spending_txid = tx.txid()
|
||||
|
||||
def get_claim_fee(self):
|
||||
return self.wallet.config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
||||
return self._get_claim_fee(config=self.wallet.config)
|
||||
|
||||
@classmethod
|
||||
def _get_claim_fee(cls, *, config: 'SimpleConfig'):
|
||||
return config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
||||
|
||||
def get_swap(self, payment_hash: bytes) -> Optional[SwapData]:
|
||||
# for history
|
||||
@@ -650,7 +639,8 @@ class SwapManager(Logger):
|
||||
witness = [sig_dummy, preimage, witness_script]
|
||||
txin.witness_sizehint = len(bytes.fromhex(construct_witness(witness)))
|
||||
|
||||
def sign_tx(self, tx: PartialTransaction, swap: SwapData) -> None:
|
||||
@classmethod
|
||||
def sign_tx(cls, tx: PartialTransaction, swap: SwapData) -> None:
|
||||
preimage = swap.preimage if swap.is_reverse else 0
|
||||
witness_script = swap.redeem_script
|
||||
txin = tx.inputs()[0]
|
||||
@@ -663,6 +653,34 @@ class SwapManager(Logger):
|
||||
witness = [sig, preimage, witness_script]
|
||||
txin.witness = bytes.fromhex(construct_witness(witness))
|
||||
|
||||
@classmethod
|
||||
def _create_and_sign_claim_tx(
|
||||
cls,
|
||||
*,
|
||||
txin: PartialTxInput,
|
||||
swap: SwapData,
|
||||
config: 'SimpleConfig',
|
||||
) -> PartialTransaction:
|
||||
# FIXME the mining fee should depend on swap.is_reverse.
|
||||
# the txs are not the same size...
|
||||
amount_sat = txin.value_sats() - cls._get_claim_fee(config=config)
|
||||
if amount_sat < dust_threshold():
|
||||
raise BelowDustLimit()
|
||||
if swap.is_reverse: # successful reverse swap
|
||||
locktime = 0
|
||||
# preimage will be set in sign_tx
|
||||
else: # timing out forward swap
|
||||
locktime = swap.locktime
|
||||
tx = create_claim_tx(
|
||||
txin=txin,
|
||||
witness_script=swap.redeem_script,
|
||||
address=swap.receive_address,
|
||||
amount_sat=amount_sat,
|
||||
locktime=locktime,
|
||||
)
|
||||
cls.sign_tx(tx, swap)
|
||||
return tx
|
||||
|
||||
def max_amount_forward_swap(self) -> Optional[int]:
|
||||
""" returns None if we cannot swap """
|
||||
max_swap_amt_ln = self.get_max_amount()
|
||||
|
||||
78
electrum/tests/test_sswaps.py
Normal file
78
electrum/tests/test_sswaps.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from electrum import SimpleConfig
|
||||
from electrum.util import bfh
|
||||
from electrum.transaction import PartialTxInput, TxOutpoint
|
||||
from electrum.submarine_swaps import SwapManager, SwapData
|
||||
|
||||
from . import TestCaseForTestnet
|
||||
|
||||
|
||||
class TestSwapTxs(TestCaseForTestnet):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.config = SimpleConfig({'electrum_path': self.electrum_path})
|
||||
self.config.set_key('dynamic_fees', False)
|
||||
self.config.set_key('fee_per_kb', 1000)
|
||||
|
||||
def test_claim_tx_for_successful_reverse_swap(self):
|
||||
swap_data = SwapData(
|
||||
is_reverse=True,
|
||||
locktime=2420532,
|
||||
onchain_amount=198694,
|
||||
lightning_amount=200000,
|
||||
redeem_script=bytes.fromhex('8201208763a914d7a62ef0270960fe23f0f351b28caadab62c21838821030bfd61153816df786036ea293edce851d3a4b9f4a1c66bdc1a17f00ffef3d6b167750334ef24b1752102fc8128f17f9e666ea281c702171ab16c1dd2a4337b71f08970f5aa10c608a93268ac'),
|
||||
preimage=bytes.fromhex('f1939b5723155713855d7ebea6e174f77d41d669269e7f138856c3de190e7a36'),
|
||||
prepay_hash=None,
|
||||
privkey=bytes.fromhex('58fd0018a9a2737d1d6b81d380df96bf0c858473a9592015508a270a7c9b1d8d'),
|
||||
lockup_address='tb1q2pvugjl4w56rqw4c7zg0q6mmmev0t5jjy3qzg7sl766phh9fxjxsrtl77t',
|
||||
receive_address='tb1ql0adrj58g88xgz375yct63rclhv29hv03u0mel',
|
||||
funding_txid='897eea7f53e917323e7472d7a2e3099173f7836c57f1b6850f5cbdfe8085dbf9',
|
||||
spending_txid=None,
|
||||
is_redeemed=False,
|
||||
)
|
||||
txin = PartialTxInput(
|
||||
prevout=TxOutpoint(txid=bfh(swap_data.funding_txid), out_idx=0),
|
||||
)
|
||||
txin._trusted_value_sats = swap_data.onchain_amount
|
||||
txin._trusted_address = swap_data.lockup_address
|
||||
tx = SwapManager._create_and_sign_claim_tx(
|
||||
txin=txin,
|
||||
swap=swap_data,
|
||||
config=self.config,
|
||||
)
|
||||
self.assertEqual(
|
||||
"02000000000101f9db8580febd5c0f85b6f1576c83f7739109e3a2d772743e3217e9537fea7e890000000000fdffffff019e07030000000000160014fbfad1ca8741ce640a3ea130bd4478fdd8a2dd8f034730440220156d62534a4e8247eef6bb185c89c4013353c017e45d41ce634976b9d7122c6202202ddb593983fd789cf2166038411425c119d087bc37ec7f8b51bebf603e428fbb0120f1939b5723155713855d7ebea6e174f77d41d669269e7f138856c3de190e7a366a8201208763a914d7a62ef0270960fe23f0f351b28caadab62c21838821030bfd61153816df786036ea293edce851d3a4b9f4a1c66bdc1a17f00ffef3d6b167750334ef24b1752102fc8128f17f9e666ea281c702171ab16c1dd2a4337b71f08970f5aa10c608a93268ac00000000",
|
||||
str(tx)
|
||||
)
|
||||
|
||||
def test_claim_tx_for_timing_out_forward_swap(self):
|
||||
swap_data = SwapData(
|
||||
is_reverse=False,
|
||||
locktime=2420537,
|
||||
onchain_amount=130000,
|
||||
lightning_amount=129014,
|
||||
redeem_script=bytes.fromhex('a914b12bd886ef4fd9ef1c03e899123f2c4b96cec0878763210267ca676c2ed05bb6c380880f1e50b6ef91025dfa963dc49d6c5cb9848f2acf7d670339ef24b1752103d8190cdfcc7dd929a583b7ea8fa8eb1d8463195d336be2f2df94f950ce8b659968ac'),
|
||||
preimage=bytes.fromhex('116f62c3283e4eb0b947a9cb672f1de7321d2c2373d12cd010500adffc32b1f2'),
|
||||
prepay_hash=None,
|
||||
privkey=bytes.fromhex('8d30dead21f5a7a6eeab7456a9a9d449511e942abef9302153cfff84e436614c'),
|
||||
lockup_address='tb1qte2qwev6qvmrhsddac82tnskmjg02ntn73xqg2rjt0qx2xpz693sw2ljzg',
|
||||
receive_address='tb1qj76twx886pkfcs7d808n0yzsgxm33wqlwe0dt0',
|
||||
funding_txid='08ecdcb19ab38fc1288c97da546b8c90549be2348ef306f476dcf6e505158706',
|
||||
spending_txid=None,
|
||||
is_redeemed=False,
|
||||
)
|
||||
txin = PartialTxInput(
|
||||
prevout=TxOutpoint(txid=bfh(swap_data.funding_txid), out_idx=0),
|
||||
)
|
||||
txin._trusted_value_sats = swap_data.onchain_amount
|
||||
txin._trusted_address = swap_data.lockup_address
|
||||
tx = SwapManager._create_and_sign_claim_tx(
|
||||
txin=txin,
|
||||
swap=swap_data,
|
||||
config=self.config,
|
||||
)
|
||||
self.assertEqual(
|
||||
"0200000000010106871505e5f6dc76f406f38e34e29b54908c6b54da978c28c18fb39ab1dcec080000000000fdffffff0148fb01000000000016001497b4b718e7d06c9c43cd3bcf37905041b718b81f034730440220254e054fc195801aca3d62641a0f27d888f44d1dd66760ae5c3418502e82c141022014305da98daa27d665310115845d2fa6d4dc612d910a186db2624aa558bff9fe010065a914b12bd886ef4fd9ef1c03e899123f2c4b96cec0878763210267ca676c2ed05bb6c380880f1e50b6ef91025dfa963dc49d6c5cb9848f2acf7d670339ef24b1752103d8190cdfcc7dd929a583b7ea8fa8eb1d8463195d336be2f2df94f950ce8b659968ac39ef2400",
|
||||
str(tx)
|
||||
)
|
||||
|
||||
@@ -144,6 +144,10 @@ class NoDynamicFeeEstimates(Exception):
|
||||
return _('Dynamic fee estimates not available')
|
||||
|
||||
|
||||
class BelowDustLimit(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidPassword(Exception):
|
||||
def __init__(self, message: Optional[str] = None):
|
||||
self.message = message
|
||||
|
||||
Reference in New Issue
Block a user