bump fee of swap claim transactions
Note: This adds a new field, spent_txid, to PartialTxOutput
This commit is contained in:
@@ -761,10 +761,9 @@ class AddressSynchronizer(Logger):
|
|||||||
for tx_hash, height in h:
|
for tx_hash, height in h:
|
||||||
l = self.db.get_txi_addr(tx_hash, address)
|
l = self.db.get_txi_addr(tx_hash, address)
|
||||||
for txi, v in l:
|
for txi, v in l:
|
||||||
sent[txi] = height
|
sent[txi] = tx_hash, height
|
||||||
return received, sent
|
return received, sent
|
||||||
|
|
||||||
|
|
||||||
def get_addr_outputs(self, address: str) -> Dict[TxOutpoint, PartialTxInput]:
|
def get_addr_outputs(self, address: str) -> Dict[TxOutpoint, PartialTxInput]:
|
||||||
coins, spent = self.get_addr_io(address)
|
coins, spent = self.get_addr_io(address)
|
||||||
out = {}
|
out = {}
|
||||||
@@ -775,7 +774,13 @@ class AddressSynchronizer(Logger):
|
|||||||
utxo._trusted_address = address
|
utxo._trusted_address = address
|
||||||
utxo._trusted_value_sats = value
|
utxo._trusted_value_sats = value
|
||||||
utxo.block_height = tx_height
|
utxo.block_height = tx_height
|
||||||
utxo.spent_height = spent.get(prevout_str, None)
|
if prevout_str in spent:
|
||||||
|
txid, height = spent[prevout_str]
|
||||||
|
utxo.spent_txid = txid
|
||||||
|
utxo.spent_height = height
|
||||||
|
else:
|
||||||
|
utxo.spent_txid = None
|
||||||
|
utxo.spent_height = None
|
||||||
out[prevout] = utxo
|
out[prevout] = utxo
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -816,7 +821,8 @@ class AddressSynchronizer(Logger):
|
|||||||
else:
|
else:
|
||||||
u += v
|
u += v
|
||||||
if txo in sent:
|
if txo in sent:
|
||||||
if sent[txo] > 0:
|
sent_txid, sent_height = sent[txo]
|
||||||
|
if sent_height > 0:
|
||||||
c -= v
|
c -= v
|
||||||
else:
|
else:
|
||||||
u -= v
|
u -= v
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ def create_claim_tx(
|
|||||||
txin: PartialTxInput,
|
txin: PartialTxInput,
|
||||||
witness_script: bytes,
|
witness_script: bytes,
|
||||||
preimage: Union[bytes, int], # 0 if timing out forward-swap
|
preimage: Union[bytes, int], # 0 if timing out forward-swap
|
||||||
privkey: bytes,
|
|
||||||
address: str,
|
address: str,
|
||||||
amount_sat: int,
|
amount_sat: int,
|
||||||
locktime: int,
|
locktime: int,
|
||||||
@@ -117,10 +116,7 @@ def create_claim_tx(
|
|||||||
txin.witness_script = witness_script
|
txin.witness_script = witness_script
|
||||||
txout = PartialTxOutput.from_address_and_value(address, amount_sat)
|
txout = PartialTxOutput.from_address_and_value(address, amount_sat)
|
||||||
tx = PartialTransaction.from_io([txin], [txout], version=2, locktime=locktime)
|
tx = PartialTransaction.from_io([txin], [txout], version=2, locktime=locktime)
|
||||||
#tx.set_rbf(True)
|
tx.set_rbf(True)
|
||||||
sig = bytes.fromhex(tx.sign_txin(0, privkey))
|
|
||||||
witness = [sig, preimage, witness_script]
|
|
||||||
txin.witness = bytes.fromhex(construct_witness(witness))
|
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
|
||||||
@@ -169,21 +165,23 @@ class SwapManager(Logger):
|
|||||||
return
|
return
|
||||||
current_height = self.network.get_local_height()
|
current_height = self.network.get_local_height()
|
||||||
delta = current_height - swap.locktime
|
delta = current_height - swap.locktime
|
||||||
if not swap.is_reverse and delta < 0:
|
|
||||||
# too early for refund
|
|
||||||
return
|
|
||||||
txos = self.lnwatcher.get_addr_outputs(swap.lockup_address)
|
txos = self.lnwatcher.get_addr_outputs(swap.lockup_address)
|
||||||
for txin in txos.values():
|
for txin in txos.values():
|
||||||
if swap.is_reverse and txin.value_sats() < swap.onchain_amount:
|
if swap.is_reverse and txin.value_sats() < swap.onchain_amount:
|
||||||
self.logger.info('amount too low, we should not reveal the preimage')
|
self.logger.info('amount too low, we should not reveal the preimage')
|
||||||
continue
|
continue
|
||||||
|
swap.funding_txid = txin.prevout.txid.hex()
|
||||||
spent_height = txin.spent_height
|
spent_height = txin.spent_height
|
||||||
if spent_height is not None:
|
if spent_height is not None:
|
||||||
|
swap.spending_txid = txin.spent_txid
|
||||||
if spent_height > 0 and current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY:
|
if spent_height > 0 and current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY:
|
||||||
self.logger.info(f'stop watching swap {swap.lockup_address}')
|
self.logger.info(f'stop watching swap {swap.lockup_address}')
|
||||||
self.lnwatcher.remove_callback(swap.lockup_address)
|
self.lnwatcher.remove_callback(swap.lockup_address)
|
||||||
swap.is_redeemed = True
|
swap.is_redeemed = True
|
||||||
continue
|
continue
|
||||||
|
if not swap.is_reverse and delta < 0:
|
||||||
|
# too early for refund
|
||||||
|
return
|
||||||
# FIXME the mining fee should depend on swap.is_reverse.
|
# FIXME the mining fee should depend on swap.is_reverse.
|
||||||
# the txs are not the same size...
|
# the txs are not the same size...
|
||||||
amount_sat = txin.value_sats() - self.get_claim_fee()
|
amount_sat = txin.value_sats() - self.get_claim_fee()
|
||||||
@@ -201,17 +199,12 @@ class SwapManager(Logger):
|
|||||||
txin=txin,
|
txin=txin,
|
||||||
witness_script=swap.redeem_script,
|
witness_script=swap.redeem_script,
|
||||||
preimage=preimage,
|
preimage=preimage,
|
||||||
privkey=swap.privkey,
|
|
||||||
address=address,
|
address=address,
|
||||||
amount_sat=amount_sat,
|
amount_sat=amount_sat,
|
||||||
locktime=locktime,
|
locktime=locktime,
|
||||||
)
|
)
|
||||||
|
self.sign_tx(tx, swap)
|
||||||
await self.network.broadcast_transaction(tx)
|
await self.network.broadcast_transaction(tx)
|
||||||
# save txid
|
|
||||||
if swap.is_reverse:
|
|
||||||
swap.spending_txid = tx.txid()
|
|
||||||
else:
|
|
||||||
self.wallet.set_label(tx.txid(), 'Swap refund')
|
|
||||||
|
|
||||||
def get_claim_fee(self):
|
def get_claim_fee(self):
|
||||||
return self.wallet.config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
return self.wallet.config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
||||||
@@ -307,7 +300,7 @@ class SwapManager(Logger):
|
|||||||
dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), expected_onchain_amount_sat)
|
dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), expected_onchain_amount_sat)
|
||||||
tx.outputs().remove(dummy_output)
|
tx.outputs().remove(dummy_output)
|
||||||
tx.add_outputs([funding_output])
|
tx.add_outputs([funding_output])
|
||||||
tx.set_rbf(False)
|
tx.set_rbf(True) # rbf must not decrease payment
|
||||||
self.wallet.sign_transaction(tx, password)
|
self.wallet.sign_transaction(tx, password)
|
||||||
# save swap data in wallet in case we need a refund
|
# save swap data in wallet in case we need a refund
|
||||||
swap = SwapData(
|
swap = SwapData(
|
||||||
@@ -321,7 +314,7 @@ class SwapManager(Logger):
|
|||||||
lightning_amount = lightning_amount_sat,
|
lightning_amount = lightning_amount_sat,
|
||||||
is_reverse = False,
|
is_reverse = False,
|
||||||
is_redeemed = False,
|
is_redeemed = False,
|
||||||
funding_txid = tx.txid(),
|
funding_txid = None,
|
||||||
spending_txid = None,
|
spending_txid = None,
|
||||||
)
|
)
|
||||||
self.swaps[payment_hash.hex()] = swap
|
self.swaps[payment_hash.hex()] = swap
|
||||||
@@ -541,3 +534,22 @@ class SwapManager(Logger):
|
|||||||
if is_reverse and send_amount is not None:
|
if is_reverse and send_amount is not None:
|
||||||
send_amount += self.get_claim_fee()
|
send_amount += self.get_claim_fee()
|
||||||
return send_amount
|
return send_amount
|
||||||
|
|
||||||
|
def get_swap_by_tx(self, tx):
|
||||||
|
# determine if tx is spending from a swap
|
||||||
|
txin = tx.inputs()[0]
|
||||||
|
for key, swap in self.swaps.items():
|
||||||
|
if txin.prevout.txid.hex() == swap.funding_txid:
|
||||||
|
return swap
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sign_tx(self, tx, swap):
|
||||||
|
preimage = swap.preimage if swap.is_reverse else 0
|
||||||
|
witness_script = swap.redeem_script
|
||||||
|
txin = tx.inputs()[0]
|
||||||
|
txin.script_type = 'p2wsh'
|
||||||
|
txin.script_sig = b''
|
||||||
|
txin.witness_script = witness_script
|
||||||
|
sig = bytes.fromhex(tx.sign_txin(0, swap.privkey))
|
||||||
|
witness = [sig, preimage, witness_script]
|
||||||
|
txin.witness = bytes.fromhex(construct_witness(witness))
|
||||||
|
|||||||
@@ -1210,6 +1210,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
|||||||
self._trusted_address = None # type: Optional[str]
|
self._trusted_address = None # type: Optional[str]
|
||||||
self.block_height = None # type: Optional[int] # height at which the TXO is mined; None means unknown
|
self.block_height = None # type: Optional[int] # height at which the TXO is mined; None means unknown
|
||||||
self.spent_height = None # type: Optional[int] # height at which the TXO got spent
|
self.spent_height = None # type: Optional[int] # height at which the TXO got spent
|
||||||
|
self.spent_txid = None # type: Optional[str] # txid of the spender
|
||||||
self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown
|
self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown
|
||||||
self._is_native_segwit = None # type: Optional[bool] # None means unknown
|
self._is_native_segwit = None # type: Optional[bool] # None means unknown
|
||||||
|
|
||||||
|
|||||||
@@ -591,10 +591,14 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
return any([chan.funding_outpoint.txid == txid
|
return any([chan.funding_outpoint.txid == txid
|
||||||
for chan in self.lnworker.channels.values()])
|
for chan in self.lnworker.channels.values()])
|
||||||
|
|
||||||
|
def is_swap_tx(self, tx: Transaction) -> bool:
|
||||||
|
return bool(self.lnworker.swap_manager.get_swap_by_tx(tx)) if self.lnworker else False
|
||||||
|
|
||||||
def get_tx_info(self, tx: Transaction) -> TxWalletDetails:
|
def get_tx_info(self, tx: Transaction) -> TxWalletDetails:
|
||||||
tx_wallet_delta = self.get_wallet_delta(tx)
|
tx_wallet_delta = self.get_wallet_delta(tx)
|
||||||
is_relevant = tx_wallet_delta.is_relevant
|
is_relevant = tx_wallet_delta.is_relevant
|
||||||
is_any_input_ismine = tx_wallet_delta.is_any_input_ismine
|
is_any_input_ismine = tx_wallet_delta.is_any_input_ismine
|
||||||
|
is_swap = self.is_swap_tx(tx)
|
||||||
fee = tx_wallet_delta.fee
|
fee = tx_wallet_delta.fee
|
||||||
exp_n = None
|
exp_n = None
|
||||||
can_broadcast = False
|
can_broadcast = False
|
||||||
@@ -629,7 +633,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
size = tx.estimated_size()
|
size = tx.estimated_size()
|
||||||
fee_per_byte = fee / size
|
fee_per_byte = fee / size
|
||||||
exp_n = self.config.fee_to_depth(fee_per_byte)
|
exp_n = self.config.fee_to_depth(fee_per_byte)
|
||||||
can_bump = is_any_input_ismine and not tx.is_final()
|
can_bump = (is_any_input_ismine or is_swap) and not tx.is_final()
|
||||||
can_dscancel = (is_any_input_ismine and not tx.is_final()
|
can_dscancel = (is_any_input_ismine and not tx.is_final()
|
||||||
and not all([self.is_mine(txout.address) for txout in tx.outputs()]))
|
and not all([self.is_mine(txout.address) for txout in tx.outputs()]))
|
||||||
try:
|
try:
|
||||||
@@ -640,7 +644,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
else:
|
else:
|
||||||
status = _('Local')
|
status = _('Local')
|
||||||
can_broadcast = self.network is not None
|
can_broadcast = self.network is not None
|
||||||
can_bump = is_any_input_ismine and not tx.is_final()
|
can_bump = (is_any_input_ismine or is_swap) and not tx.is_final()
|
||||||
else:
|
else:
|
||||||
status = _("Signed")
|
status = _("Signed")
|
||||||
can_broadcast = self.network is not None
|
can_broadcast = self.network is not None
|
||||||
@@ -1961,6 +1965,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
for k in self.get_keystores():
|
for k in self.get_keystores():
|
||||||
if k.can_sign_txin(txin):
|
if k.can_sign_txin(txin):
|
||||||
return True
|
return True
|
||||||
|
if self.is_swap_tx(tx):
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_input_tx(self, tx_hash: str, *, ignore_network_issues=False) -> Optional[Transaction]:
|
def get_input_tx(self, tx_hash: str, *, ignore_network_issues=False) -> Optional[Transaction]:
|
||||||
@@ -2013,6 +2019,11 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
return
|
return
|
||||||
if not isinstance(tx, PartialTransaction):
|
if not isinstance(tx, PartialTransaction):
|
||||||
return
|
return
|
||||||
|
# note: swap signing does not require the password
|
||||||
|
swap = self.lnworker.swap_manager.get_swap_by_tx(tx) if self.lnworker else None
|
||||||
|
if swap:
|
||||||
|
self.lnworker.swap_manager.sign_tx(tx, swap)
|
||||||
|
return
|
||||||
# add info to a temporary tx copy; including xpubs
|
# add info to a temporary tx copy; including xpubs
|
||||||
# and full derivation paths as hw keystores might want them
|
# and full derivation paths as hw keystores might want them
|
||||||
tmp_tx = copy.deepcopy(tx)
|
tmp_tx = copy.deepcopy(tx)
|
||||||
|
|||||||
Reference in New Issue
Block a user