Merge pull request #10169 from SomberNight/202508_swaps_more_cleanup
swaps: more cleanup
This commit is contained in:
@@ -910,7 +910,8 @@ class LNWallet(LNWorker):
|
||||
|
||||
# payment_hash -> callback:
|
||||
self.hold_invoice_callbacks = {} # type: Dict[bytes, Callable[[bytes], Awaitable[None]]]
|
||||
self.payment_bundles = [] # lists of hashes. todo:persist
|
||||
self._payment_bundles_pkey_to_canon = {} # type: Dict[bytes, bytes] # TODO: persist
|
||||
self._payment_bundles_canon_to_pkeylist = {} # type: Dict[bytes, Sequence[bytes]] # TODO: persist
|
||||
|
||||
self.nostr_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NOSTR_KEY)
|
||||
self.swap_manager = SwapManager(wallet=self.wallet, lnworker=self)
|
||||
@@ -2308,22 +2309,42 @@ class LNWallet(LNWorker):
|
||||
self.wallet.save_db()
|
||||
return payment_hash
|
||||
|
||||
def bundle_payments(self, hash_list):
|
||||
def bundle_payments(self, hash_list: Sequence[bytes]) -> None:
|
||||
"""Bundle together a list of payment_hashes, for atomicity, so that either
|
||||
- all gets fulfilled, or
|
||||
- none of them gets fulfilled.
|
||||
(we are the recipient of this payment)
|
||||
"""
|
||||
payment_keys = [self._get_payment_key(x) for x in hash_list]
|
||||
self.payment_bundles.append(payment_keys)
|
||||
with self.lock:
|
||||
# We maintain two maps.
|
||||
# map1: payment_key -> bundle_key=canon_pkey (canonically smallest among pkeys)
|
||||
# map2: bundle_key -> list of pkeys in bundle
|
||||
# assumption: bundles are immutable, so no adding extra pkeys after-the-fact
|
||||
canon_pkey = min(payment_keys)
|
||||
for pkey in payment_keys:
|
||||
assert pkey not in self._payment_bundles_pkey_to_canon
|
||||
for pkey in payment_keys:
|
||||
self._payment_bundles_pkey_to_canon[pkey] = canon_pkey
|
||||
self._payment_bundles_canon_to_pkeylist[canon_pkey] = tuple(payment_keys)
|
||||
|
||||
def get_payment_bundle(self, payment_key: bytes) -> Sequence[bytes]:
|
||||
for key_list in self.payment_bundles:
|
||||
if payment_key in key_list:
|
||||
return key_list
|
||||
return []
|
||||
with self.lock:
|
||||
canon_pkey = self._payment_bundles_pkey_to_canon.get(payment_key)
|
||||
if canon_pkey is None:
|
||||
return []
|
||||
return self._payment_bundles_canon_to_pkeylist[canon_pkey]
|
||||
|
||||
def delete_payment_bundle(self, payment_hash: bytes) -> None:
|
||||
payment_key = self._get_payment_key(payment_hash)
|
||||
for key_list in self.payment_bundles:
|
||||
if payment_key in key_list:
|
||||
self.payment_bundles.remove(key_list)
|
||||
with self.lock:
|
||||
canon_pkey = self._payment_bundles_pkey_to_canon.get(payment_key)
|
||||
if canon_pkey is None: # is it ok for bundle to be missing??
|
||||
return
|
||||
pkey_list = self._payment_bundles_canon_to_pkeylist[canon_pkey]
|
||||
for pkey in pkey_list:
|
||||
del self._payment_bundles_pkey_to_canon[pkey]
|
||||
del self._payment_bundles_canon_to_pkeylist[canon_pkey]
|
||||
|
||||
def save_preimage(self, payment_hash: bytes, preimage: bytes, *, write_to_disk: bool = True):
|
||||
if sha256(preimage) != payment_hash:
|
||||
@@ -2370,9 +2391,15 @@ class LNWallet(LNWorker):
|
||||
self.hold_invoice_callbacks.pop(payment_hash)
|
||||
|
||||
def save_payment_info(self, info: PaymentInfo, *, write_to_disk: bool = True) -> None:
|
||||
key = info.payment_hash.hex()
|
||||
assert info.status in SAVED_PR_STATUS
|
||||
with self.lock:
|
||||
if old_info := self.get_payment_info(payment_hash=info.payment_hash):
|
||||
if info == old_info:
|
||||
return # already saved
|
||||
if info != old_info._replace(status=info.status):
|
||||
# differs more than in status. let's fail
|
||||
raise Exception("payment_hash already in use")
|
||||
key = info.payment_hash.hex()
|
||||
self.payment_info[key] = info.amount_msat, info.direction, info.status
|
||||
if write_to_disk:
|
||||
self.wallet.save_db()
|
||||
|
||||
@@ -77,48 +77,76 @@ assert MAX_LOCKTIME_DELTA < MIN_FINAL_CLTV_DELTA_FOR_CLIENT
|
||||
# different length which would still allow for claiming the onchain
|
||||
# coins but the invoice couldn't be settled
|
||||
|
||||
WITNESS_TEMPLATE_REVERSE_SWAP = [
|
||||
# Unified witness-script for all swaps. Historically with Boltz-backend, this was the reverse-swap script.
|
||||
WITNESS_TEMPLATE_SWAP = [
|
||||
opcodes.OP_SIZE,
|
||||
OPPushDataGeneric(None),
|
||||
OPPushDataGeneric(None), # idx 1. length of preimage
|
||||
opcodes.OP_EQUAL,
|
||||
opcodes.OP_IF,
|
||||
opcodes.OP_HASH160,
|
||||
OPPushDataGeneric(lambda x: x == 20),
|
||||
OPPushDataGeneric(lambda x: x == 20), # idx 5. payment_hash
|
||||
opcodes.OP_EQUALVERIFY,
|
||||
OPPushDataPubkey,
|
||||
OPPushDataPubkey, # idx 7. claim_pubkey
|
||||
opcodes.OP_ELSE,
|
||||
opcodes.OP_DROP,
|
||||
OPPushDataGeneric(None),
|
||||
OPPushDataGeneric(None), # idx 10. locktime
|
||||
opcodes.OP_CHECKLOCKTIMEVERIFY,
|
||||
opcodes.OP_DROP,
|
||||
OPPushDataPubkey,
|
||||
OPPushDataPubkey, # idx 13. refund_pubkey
|
||||
opcodes.OP_ENDIF,
|
||||
opcodes.OP_CHECKSIG
|
||||
]
|
||||
|
||||
|
||||
def check_reverse_redeem_script(
|
||||
def _check_swap_scriptcode(
|
||||
*,
|
||||
redeem_script: bytes,
|
||||
lockup_address: str,
|
||||
payment_hash: bytes,
|
||||
locktime: int,
|
||||
refund_pubkey: bytes = None,
|
||||
claim_pubkey: bytes = None,
|
||||
refund_pubkey: Optional[bytes], # note: We don't need to check the counterparty's key.
|
||||
claim_pubkey: Optional[bytes], # Can use None in that case.
|
||||
) -> None:
|
||||
assert (refund_pubkey is not None) or (claim_pubkey is not None), "at least one pubkey must be set"
|
||||
parsed_script = [x for x in script_GetOp(redeem_script)]
|
||||
if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP):
|
||||
if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_SWAP):
|
||||
raise Exception("rswap check failed: scriptcode does not match template")
|
||||
if script_to_p2wsh(redeem_script) != lockup_address:
|
||||
raise Exception("rswap check failed: inconsistent scriptcode and address")
|
||||
if ripemd(payment_hash) != parsed_script[5][1]:
|
||||
raise Exception("rswap check failed: our preimage not in script")
|
||||
if claim_pubkey and claim_pubkey != parsed_script[7][1]:
|
||||
claim_pubkey = claim_pubkey or parsed_script[7][1]
|
||||
if claim_pubkey != parsed_script[7][1]:
|
||||
raise Exception("rswap check failed: our pubkey not in script")
|
||||
if refund_pubkey and refund_pubkey != parsed_script[13][1]:
|
||||
refund_pubkey = refund_pubkey or parsed_script[13][1]
|
||||
if refund_pubkey != parsed_script[13][1]:
|
||||
raise Exception("rswap check failed: our pubkey not in script")
|
||||
if locktime != int.from_bytes(parsed_script[10][1], byteorder='little'):
|
||||
raise Exception("rswap check failed: inconsistent locktime and script")
|
||||
# let's just rebuild the full script from scratch...
|
||||
if redeem_script != _construct_swap_scriptcode(
|
||||
payment_hash=payment_hash,
|
||||
locktime=locktime,
|
||||
refund_pubkey=refund_pubkey,
|
||||
claim_pubkey=claim_pubkey,
|
||||
):
|
||||
raise Exception("failed to rebuild swap script from scratch")
|
||||
|
||||
|
||||
def _construct_swap_scriptcode(
|
||||
payment_hash: bytes,
|
||||
locktime: int,
|
||||
refund_pubkey: bytes,
|
||||
claim_pubkey: bytes,
|
||||
) -> bytes:
|
||||
assert isinstance(payment_hash, bytes) and len(payment_hash) == 32
|
||||
assert isinstance(locktime, int) and (0 <= locktime <= bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX)
|
||||
assert isinstance(refund_pubkey, bytes) and len(refund_pubkey) == 33
|
||||
assert isinstance(claim_pubkey, bytes) and len(claim_pubkey) == 33
|
||||
return construct_script(
|
||||
WITNESS_TEMPLATE_SWAP,
|
||||
values={1: 32, 5: ripemd(payment_hash), 7: claim_pubkey, 10: locktime, 13: refund_pubkey}
|
||||
)
|
||||
|
||||
|
||||
class SwapServerError(Exception):
|
||||
@@ -226,7 +254,7 @@ class SwapManager(Logger):
|
||||
for payment_hash_hex, swap in self._swaps.items():
|
||||
payment_hash = bytes.fromhex(payment_hash_hex)
|
||||
swap._payment_hash = payment_hash
|
||||
self._add_or_reindex_swap(swap)
|
||||
self._add_or_reindex_swap(swap, is_new=False)
|
||||
if not swap.is_reverse and not swap.is_redeemed:
|
||||
self.lnworker.register_hold_invoice(payment_hash, self.hold_invoice_callback)
|
||||
|
||||
@@ -427,7 +455,7 @@ class SwapManager(Logger):
|
||||
# note: swap.funding_txid can change due to RBF, it will get updated here:
|
||||
swap.funding_txid = txin.prevout.txid.hex()
|
||||
swap._funding_prevout = txin.prevout
|
||||
self._add_or_reindex_swap(swap) # to update _swaps_by_funding_outpoint
|
||||
self._add_or_reindex_swap(swap, is_new=False) # to update _swaps_by_funding_outpoint
|
||||
funding_height = self.lnwatcher.adb.get_tx_height(txin.prevout.txid.hex())
|
||||
spent_height = txin.spent_height
|
||||
# set spending_txid (even if tx is local), for GUI grouping
|
||||
@@ -565,15 +593,21 @@ class SwapManager(Logger):
|
||||
def create_normal_swap(self, *, lightning_amount_sat: int, payment_hash: bytes, their_pubkey: bytes = None):
|
||||
""" server method """
|
||||
assert lightning_amount_sat
|
||||
if payment_hash.hex() in self._swaps:
|
||||
raise Exception("payment_hash already in use")
|
||||
locktime = self.network.get_local_height() + LOCKTIME_DELTA_REFUND
|
||||
if self.network.blockchain().is_tip_stale():
|
||||
raise Exception("our blockchain tip is stale")
|
||||
our_privkey = os.urandom(32)
|
||||
our_pubkey = ECPrivkey(our_privkey).get_public_key_bytes(compressed=True)
|
||||
onchain_amount_sat = self._get_recv_amount(lightning_amount_sat, is_reverse=True) # what the client is going to receive
|
||||
if not onchain_amount_sat:
|
||||
raise Exception("no onchain amount")
|
||||
redeem_script = construct_script(
|
||||
WITNESS_TEMPLATE_REVERSE_SWAP,
|
||||
values={1:32, 5:ripemd(payment_hash), 7:their_pubkey, 10:locktime, 13:our_pubkey}
|
||||
redeem_script = _construct_swap_scriptcode(
|
||||
payment_hash=payment_hash,
|
||||
locktime=locktime,
|
||||
refund_pubkey=our_pubkey,
|
||||
claim_pubkey=their_pubkey,
|
||||
)
|
||||
swap, invoice, prepay_invoice = self.add_normal_swap(
|
||||
redeem_script=redeem_script,
|
||||
@@ -600,6 +634,8 @@ class SwapManager(Logger):
|
||||
min_final_cltv_expiry_delta: Optional[int] = None,
|
||||
) -> Tuple[SwapData, str, Optional[str]]:
|
||||
"""creates a hold invoice"""
|
||||
if payment_hash.hex() in self._swaps:
|
||||
raise Exception("payment_hash already in use")
|
||||
if prepay:
|
||||
# server requests 2 * the mining fee as instantly settled prepayment so that the mining
|
||||
# fees of the funding tx and potential timeout refund tx are always covered
|
||||
@@ -655,7 +691,7 @@ class SwapManager(Logger):
|
||||
spending_txid=None,
|
||||
)
|
||||
swap._payment_hash = payment_hash
|
||||
self._add_or_reindex_swap(swap)
|
||||
self._add_or_reindex_swap(swap, is_new=True)
|
||||
self.add_lnwatcher_callback(swap)
|
||||
return swap, invoice, prepay_invoice
|
||||
|
||||
@@ -663,6 +699,8 @@ class SwapManager(Logger):
|
||||
""" server method. """
|
||||
assert lightning_amount_sat is not None
|
||||
locktime = self.network.get_local_height() + LOCKTIME_DELTA_REFUND
|
||||
if self.network.blockchain().is_tip_stale():
|
||||
raise Exception("our blockchain tip is stale")
|
||||
privkey = os.urandom(32)
|
||||
our_pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
|
||||
onchain_amount_sat = self._get_send_amount(lightning_amount_sat, is_reverse=False)
|
||||
@@ -670,9 +708,11 @@ class SwapManager(Logger):
|
||||
raise Exception("no onchain amount")
|
||||
preimage = os.urandom(32)
|
||||
payment_hash = sha256(preimage)
|
||||
redeem_script = construct_script(
|
||||
WITNESS_TEMPLATE_REVERSE_SWAP,
|
||||
values={1:32, 5:ripemd(payment_hash), 7:our_pubkey, 10:locktime, 13:their_pubkey}
|
||||
redeem_script = _construct_swap_scriptcode(
|
||||
payment_hash=payment_hash,
|
||||
locktime=locktime,
|
||||
refund_pubkey=their_pubkey,
|
||||
claim_pubkey=our_pubkey,
|
||||
)
|
||||
swap = self.add_reverse_swap(
|
||||
redeem_script=redeem_script,
|
||||
@@ -697,6 +737,9 @@ class SwapManager(Logger):
|
||||
payment_hash: bytes,
|
||||
prepay_hash: Optional[bytes] = None,
|
||||
) -> SwapData:
|
||||
if payment_hash.hex() in self._swaps:
|
||||
raise Exception("payment_hash already in use")
|
||||
assert sha256(preimage) == payment_hash
|
||||
lockup_address = script_to_p2wsh(redeem_script)
|
||||
receive_address = self.wallet.get_receiving_address()
|
||||
swap = SwapData(
|
||||
@@ -715,26 +758,41 @@ class SwapManager(Logger):
|
||||
spending_txid=None,
|
||||
)
|
||||
if prepay_hash:
|
||||
if prepay_hash in self._prepayments:
|
||||
raise Exception("prepay_hash already in use")
|
||||
self._prepayments[prepay_hash] = payment_hash
|
||||
swap._payment_hash = payment_hash
|
||||
self._add_or_reindex_swap(swap)
|
||||
self._add_or_reindex_swap(swap, is_new=True)
|
||||
self.add_lnwatcher_callback(swap)
|
||||
return swap
|
||||
|
||||
def server_add_swap_invoice(self, request):
|
||||
def server_add_swap_invoice(self, request: dict) -> dict:
|
||||
""" server method.
|
||||
(client-forward-swap phase2)
|
||||
"""
|
||||
invoice = request['invoice']
|
||||
invoice = Invoice.from_bech32(invoice)
|
||||
key = invoice.rhash
|
||||
payment_hash = bytes.fromhex(key)
|
||||
their_pubkey = bytes.fromhex(request['refundPublicKey'])
|
||||
with self.swaps_lock:
|
||||
assert key in self._swaps
|
||||
swap = self._swaps[key]
|
||||
assert swap.lightning_amount == int(invoice.get_amount_sat())
|
||||
self.wallet.save_invoice(invoice)
|
||||
# check that we have the preimage
|
||||
assert sha256(swap.preimage) == payment_hash
|
||||
assert swap.spending_txid is None
|
||||
self.invoices_to_pay[key] = 0
|
||||
assert swap.lightning_amount == int(invoice.get_amount_sat())
|
||||
assert swap.is_reverse is True
|
||||
# check that we have the preimage
|
||||
assert sha256(swap.preimage) == payment_hash
|
||||
assert swap.spending_txid is None
|
||||
# check their_pubkey by recalculating redeem_script
|
||||
our_pubkey = ECPrivkey(swap.privkey).get_public_key_bytes(compressed=True)
|
||||
redeem_script = _construct_swap_scriptcode(
|
||||
payment_hash=payment_hash, locktime=swap.locktime, refund_pubkey=their_pubkey, claim_pubkey=our_pubkey,
|
||||
)
|
||||
assert swap.redeem_script == redeem_script
|
||||
assert key not in self.invoices_to_pay
|
||||
self.invoices_to_pay[key] = 0
|
||||
assert self.wallet.get_invoice(invoice.get_id()) is None
|
||||
self.wallet.save_invoice(invoice)
|
||||
return {}
|
||||
|
||||
async def normal_swap(
|
||||
@@ -757,9 +815,9 @@ class SwapManager(Logger):
|
||||
cltv safety requirement: (onchain_locktime > LN_locktime), otherwise server is vulnerable
|
||||
|
||||
New flow:
|
||||
- User requests swap
|
||||
- User requests swap (RPC 'createnormalswap')
|
||||
- Server creates preimage, sends RHASH to user
|
||||
- User creates hold invoice, sends it to server
|
||||
- User creates hold invoice, sends it to server (RPC 'addswapinvoice')
|
||||
- Server sends HTLC, user holds it
|
||||
- User creates on-chain output locked to RHASH
|
||||
- Server spends the on-chain output using preimage (revealing the preimage)
|
||||
@@ -812,12 +870,13 @@ class SwapManager(Logger):
|
||||
raise SwapServerError("failed to parse response from swapserver for createnormalswap") from e
|
||||
del data # parsing done
|
||||
# verify redeem_script is built with our pubkey and preimage
|
||||
check_reverse_redeem_script(
|
||||
_check_swap_scriptcode(
|
||||
redeem_script=redeem_script,
|
||||
lockup_address=lockup_address,
|
||||
payment_hash=payment_hash,
|
||||
locktime=locktime,
|
||||
refund_pubkey=refund_pubkey,
|
||||
claim_pubkey=None,
|
||||
)
|
||||
|
||||
# check that onchain_amount is not more than what we estimated
|
||||
@@ -827,6 +886,8 @@ class SwapManager(Logger):
|
||||
# verify that they are not locking up funds for too long
|
||||
if locktime - self.network.get_local_height() > MAX_LOCKTIME_DELTA:
|
||||
raise Exception("fswap check failed: locktime too far in future")
|
||||
if self.network.blockchain().is_tip_stale():
|
||||
raise Exception("our blockchain tip is stale")
|
||||
|
||||
swap, invoice, _ = self.add_normal_swap(
|
||||
redeem_script=redeem_script,
|
||||
@@ -866,8 +927,10 @@ class SwapManager(Logger):
|
||||
self.lnworker.register_hold_invoice(payment_hash, callback)
|
||||
|
||||
# send invoice to server and wait for htlcs
|
||||
# note: server will link this RPC to our previous 'createnormalswap' RPC
|
||||
# - using the RHASH from invoice, and using refundPublicKey
|
||||
# - FIXME it would be safer to use a proper session-secret?!
|
||||
request_data = {
|
||||
"preimageHash": payment_hash.hex(),
|
||||
"invoice": invoice,
|
||||
"refundPublicKey": refund_pubkey.hex(),
|
||||
}
|
||||
@@ -941,7 +1004,7 @@ class SwapManager(Logger):
|
||||
) -> Optional[str]:
|
||||
"""send on Lightning, receive on-chain
|
||||
|
||||
- User generates preimage, RHASH. Sends RHASH to server.
|
||||
- User generates preimage, RHASH. Sends RHASH to server. (RPC 'createswap')
|
||||
- Server creates an LN invoice for RHASH.
|
||||
- User pays LN invoice - except server needs to hold the HTLC as preimage is unknown.
|
||||
- if the server requested a fee prepayment (using 'minerFeeInvoice'),
|
||||
@@ -968,10 +1031,9 @@ class SwapManager(Logger):
|
||||
request_data = {
|
||||
"type": "reversesubmarine",
|
||||
"pairId": "BTC/BTC",
|
||||
"orderSide": "buy",
|
||||
"invoiceAmount": lightning_amount_sat,
|
||||
"preimageHash": payment_hash.hex(),
|
||||
"claimPublicKey": our_pubkey.hex()
|
||||
"claimPublicKey": our_pubkey.hex(),
|
||||
}
|
||||
self.logger.debug(f'rswap: sending request for {lightning_amount_sat}')
|
||||
data = await transport.send_request_to_server('createswap', request_data)
|
||||
@@ -995,7 +1057,7 @@ class SwapManager(Logger):
|
||||
del data # parsing done
|
||||
self.logger.debug(f'rswap: {response_id=}')
|
||||
# verify redeem_script is built with our pubkey and preimage
|
||||
check_reverse_redeem_script(
|
||||
_check_swap_scriptcode(
|
||||
redeem_script=redeem_script,
|
||||
lockup_address=lockup_address,
|
||||
payment_hash=payment_hash,
|
||||
@@ -1010,6 +1072,8 @@ class SwapManager(Logger):
|
||||
# verify that we will have enough time to get our tx confirmed
|
||||
if locktime - self.network.get_local_height() <= MIN_LOCKTIME_DELTA:
|
||||
raise Exception("rswap check failed: locktime too close")
|
||||
if self.network.blockchain().is_tip_stale():
|
||||
raise Exception("our blockchain tip is stale")
|
||||
# verify invoice payment_hash
|
||||
lnaddr = self.lnworker._check_bolt11_invoice(invoice)
|
||||
invoice_amount = int(lnaddr.get_amount_sat())
|
||||
@@ -1052,8 +1116,9 @@ class SwapManager(Logger):
|
||||
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
return swap.funding_txid
|
||||
|
||||
def _add_or_reindex_swap(self, swap: SwapData) -> None:
|
||||
def _add_or_reindex_swap(self, swap: SwapData, *, is_new: bool) -> None:
|
||||
with self.swaps_lock:
|
||||
assert is_new == (swap.payment_hash.hex() not in self._swaps), is_new
|
||||
if swap.payment_hash.hex() not in self._swaps:
|
||||
self._swaps[swap.payment_hash.hex()] = swap
|
||||
if swap._funding_prevout:
|
||||
@@ -1837,11 +1902,11 @@ class NostrTransport(SwapServerTransport):
|
||||
try:
|
||||
method = request.pop('method')
|
||||
self.logger.info(f'handle_request: id={event_id} {method} {request}')
|
||||
if method == 'addswapinvoice':
|
||||
if method == 'addswapinvoice': # client-forward-swap phase2
|
||||
r = self.sm.server_add_swap_invoice(request)
|
||||
elif method == 'createswap':
|
||||
elif method == 'createswap': # client-reverse-swap
|
||||
r = self.sm.server_create_swap(request)
|
||||
elif method == 'createnormalswap':
|
||||
elif method == 'createnormalswap': # client-forward-swap phase1
|
||||
r = self.sm.server_create_normal_swap(request)
|
||||
else:
|
||||
raise Exception(method)
|
||||
|
||||
@@ -10,7 +10,7 @@ import logging
|
||||
import concurrent
|
||||
from concurrent import futures
|
||||
from unittest import mock
|
||||
from typing import Iterable, NamedTuple, Tuple, List, Dict
|
||||
from typing import Iterable, NamedTuple, Tuple, List, Dict, Sequence
|
||||
|
||||
from aiorpcx import timeout_after, TaskTimeout
|
||||
from electrum_ecc import ECPrivkey
|
||||
@@ -215,7 +215,8 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
self.downstream_to_upstream_htlc = {}
|
||||
self.dont_settle_htlcs = {}
|
||||
self.hold_invoice_callbacks = {}
|
||||
self.payment_bundles = [] # lists of hashes. todo:persist
|
||||
self._payment_bundles_pkey_to_canon = {} # type: Dict[bytes, bytes]
|
||||
self._payment_bundles_canon_to_pkeylist = {} # type: Dict[bytes, Sequence[bytes]]
|
||||
self.config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0
|
||||
|
||||
self.logger.info(f"created LNWallet[{name}] with nodeID={local_keypair.pubkey.hex()}")
|
||||
|
||||
Reference in New Issue
Block a user