From 8423896285eebc6a2e1c1fd30c61bc3b01a37d35 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Thu, 21 Jul 2022 06:19:47 -0700 Subject: [PATCH] Tight CLTV expiry dynamically --- .env-sample | 10 ++++++---- api/lightning/node.py | 4 +--- api/logics.py | 39 ++++++++++++++++++++++++++++++++------- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/.env-sample b/.env-sample index 55c9d7cc..43f2f61f 100644 --- a/.env-sample +++ b/.env-sample @@ -77,9 +77,11 @@ MIN_TRADE = 20000 MAX_TRADE = 3000000 MAX_TRADE_BONDLESS_TAKER = 50000 -# Expiration (CLTV_expiry) time for HODL invoices in HOURS // 7 min/block assumed -BOND_EXPIRY = 54 -ESCROW_EXPIRY = 48 +# For CLTV_expiry calculation +# Assume 8 min/block assumed +BLOCK_TIME = 8 +# Safety multiplier in case of mining speed up (CLTV expiry will be times X larger than real time needs for locked bonds/escrow) +MAX_MINING_NETWORK_SPEEDUP_EXPECTED = 1.7 # Expiration time for locking collateral in SECONDS EXP_MAKER_BOND_INVOICE = 300 @@ -123,7 +125,7 @@ MAX_SWAP_FEE = 0.1 # Liquidity split point (LN/onchain) at which we use MAX_SWAP_FEE MAX_SWAP_POINT = 0 # Min amount allowed for Swap -MIN_SWAP_AMOUNT = 50000 +MIN_SWAP_AMOUNT = 10000 # Reward tip. Reward for every finished trade in the referral program (Satoshis) REWARD_TIP = 100 diff --git a/api/lightning/node.py b/api/lightning/node.py index af38db30..f4739ad4 100644 --- a/api/lightning/node.py +++ b/api/lightning/node.py @@ -160,7 +160,7 @@ class LNNode: @classmethod def gen_hold_invoice(cls, num_satoshis, description, invoice_expiry, - cltv_expiry_secs): + cltv_expiry_blocks): """Generates hold invoice""" hold_payment = {} @@ -170,8 +170,6 @@ class LNNode: # Its hash is used to generate the hold invoice r_hash = hashlib.sha256(preimage).digest() - # timelock expiry for the last hop, computed based on a 10 minutes block with 30% padding (~7 min block) - cltv_expiry_blocks = int(cltv_expiry_secs / (7 * 60)) request = invoicesrpc.AddHoldInvoiceRequest( memo=description, value=num_satoshis, diff --git a/api/logics.py b/api/logics.py index f011b3df..f5cb1bbb 100644 --- a/api/logics.py +++ b/api/logics.py @@ -27,13 +27,12 @@ MAX_TRADE = int(config("MAX_TRADE")) EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE")) EXP_TAKER_BOND_INVOICE = int(config("EXP_TAKER_BOND_INVOICE")) -BOND_EXPIRY = int(config("BOND_EXPIRY")) -ESCROW_EXPIRY = int(config("ESCROW_EXPIRY")) +BLOCK_TIME = float(config("BLOCK_TIME")) +MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float(config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED")) INVOICE_AND_ESCROW_DURATION = int(config("INVOICE_AND_ESCROW_DURATION")) FIAT_EXCHANGE_DURATION = int(config("FIAT_EXCHANGE_DURATION")) - class Logics: @classmethod @@ -987,6 +986,31 @@ class Logics: # send_message.delay(order.id,'order_published') # too spammy return + def compute_cltv_expiry_blocks(order, invoice_concept): + ''' Computes timelock CLTV expiry of the last hop in blocks for hodl invoices + + invoice_concepts (str): maker_bond, taker_bond, trade_escrow + ''' + # Every invoice_concept must be locked by at least the fiat exchange duration + cltv_expiry_secs = order.t_to_expire(Order.Status.CHA) + + # Both fidelity bonds must also be locked for deposit_time (escrow duration or WFE status) + if invoice_concept in ["taker_bond", "maker_bond"]: + cltv_expiry_secs += order.t_to_expire(Order.Status.WFE) + + # Maker bond must also be locked for the full public duration plus the taker bond locking time + if invoice_concept == "maker_bond": + cltv_expiry_secs += order.t_to_expire(Order.Status.PUB) + cltv_expiry_secs += order.t_to_expire(Order.Status.TAK) + + # Add a safety marging by multiplying by the maxium expected mining network speed up + safe_cltv_expiry_secs = cltv_expiry_secs * MAX_MINING_NETWORK_SPEEDUP_EXPECTED + # Convert to blocks using assummed average block time (~8 mins/block) + cltv_expiry_blocks = int(safe_cltv_expiry_secs / (BLOCK_TIME * 60)) + print(invoice_concept," cltv_expiry_hours:",cltv_expiry_secs/3600," cltv_expiry_blocks:",cltv_expiry_blocks) + + return cltv_expiry_blocks + @classmethod def is_maker_bond_locked(cls, order): if order.maker_bond.status == LNPayment.Status.LOCKED: @@ -1031,7 +1055,7 @@ class Logics: bond_satoshis, description, invoice_expiry=order.t_to_expire(Order.Status.WFB), - cltv_expiry_secs=BOND_EXPIRY * 3600, + cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "maker_bond") ) except Exception as e: print(str(e)) @@ -1040,7 +1064,7 @@ class Logics: "bad_request": "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware." } - if "wallet locked" in str(e): + elif "wallet locked" in str(e): return False, { "bad_request": "This is weird, RoboSats' lightning wallet is locked. Check in the Telegram group, maybe the staff has died." @@ -1147,7 +1171,7 @@ class Logics: bond_satoshis, description, invoice_expiry=order.t_to_expire(Order.Status.TAK), - cltv_expiry_secs=BOND_EXPIRY * 3600, + cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "taker_bond") ) except Exception as e: @@ -1235,7 +1259,7 @@ class Logics: escrow_satoshis, description, invoice_expiry=order.t_to_expire(Order.Status.WF2), - cltv_expiry_secs=ESCROW_EXPIRY * 3600, + cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "trade_escrow") ) except Exception as e: @@ -1610,6 +1634,7 @@ class Logics: summary['mining_fee_sats'] = order.payout_tx.mining_fee_sats summary['swap_fee_sats'] = round(order.payout_tx.num_satoshis - order.payout_tx.mining_fee_sats - order.payout_tx.sent_satoshis) summary['swap_fee_percent'] = order.payout_tx.swap_fee_rate + summary['trade_fee_sats'] = round(order.last_satoshis - summary['received_sats'] - summary['mining_fee_sats'] - summary['swap_fee_sats']) else: summary['sent_sats'] = order.trade_escrow.num_satoshis summary['received_fiat'] = order.amount