use two trampolines: fix blacklisting, use local variables for trampoline_fee_level and use_two_trampolines
This commit is contained in:
@@ -142,27 +142,7 @@ FALLBACK_NODE_LIST_MAINNET = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# hardcoded list
|
from .trampoline import trampolines_by_id, hardcoded_trampoline_nodes, is_hardcoded_trampoline
|
||||||
# TODO for some pubkeys, there are multiple network addresses we could try
|
|
||||||
TRAMPOLINE_NODES_MAINNET = {
|
|
||||||
'ACINQ': LNPeerAddr(host='34.239.230.56', port=9735, pubkey=bfh('03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f')),
|
|
||||||
'Electrum trampoline': LNPeerAddr(host='144.76.99.209', port=9740, pubkey=bfh('03ecef675be448b615e6176424070673ef8284e0fd19d8be062a6cb5b130a0a0d1')),
|
|
||||||
}
|
|
||||||
TRAMPOLINE_NODES_TESTNET = {
|
|
||||||
'endurance': LNPeerAddr(host='34.250.234.192', port=9735, pubkey=bfh('03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134')),
|
|
||||||
}
|
|
||||||
|
|
||||||
def hardcoded_trampoline_nodes():
|
|
||||||
if constants.net in (constants.BitcoinMainnet, ):
|
|
||||||
return TRAMPOLINE_NODES_MAINNET
|
|
||||||
if constants.net in (constants.BitcoinTestnet, ):
|
|
||||||
return TRAMPOLINE_NODES_TESTNET
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def trampolines_by_id():
|
|
||||||
return dict([(x.pubkey, x) for x in hardcoded_trampoline_nodes().values()])
|
|
||||||
|
|
||||||
is_hardcoded_trampoline = lambda node_id: node_id in trampolines_by_id().keys()
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentInfo(NamedTuple):
|
class PaymentInfo(NamedTuple):
|
||||||
@@ -983,6 +963,8 @@ class LNWallet(LNWorker):
|
|||||||
r_tags=decoded_invoice.get_routing_info('r'),
|
r_tags=decoded_invoice.get_routing_info('r'),
|
||||||
t_tags=decoded_invoice.get_routing_info('t'),
|
t_tags=decoded_invoice.get_routing_info('t'),
|
||||||
invoice_features=decoded_invoice.get_tag('9') or 0,
|
invoice_features=decoded_invoice.get_tag('9') or 0,
|
||||||
|
trampoline_fee_level=0,
|
||||||
|
use_two_trampolines=False,
|
||||||
payment_hash=decoded_invoice.paymenthash,
|
payment_hash=decoded_invoice.paymenthash,
|
||||||
payment_secret=decoded_invoice.payment_secret,
|
payment_secret=decoded_invoice.payment_secret,
|
||||||
full_path=full_path)
|
full_path=full_path)
|
||||||
@@ -1015,11 +997,6 @@ class LNWallet(LNWorker):
|
|||||||
self.save_payment_info(info)
|
self.save_payment_info(info)
|
||||||
self.wallet.set_label(key, lnaddr.get_description())
|
self.wallet.set_label(key, lnaddr.get_description())
|
||||||
|
|
||||||
if self.channel_db is None:
|
|
||||||
self.trampoline_fee_level = 0
|
|
||||||
self.trampoline2_list = list(trampolines_by_id().keys())
|
|
||||||
random.shuffle(self.trampoline2_list)
|
|
||||||
|
|
||||||
self.set_invoice_status(key, PR_INFLIGHT)
|
self.set_invoice_status(key, PR_INFLIGHT)
|
||||||
try:
|
try:
|
||||||
await self.pay_to_node(
|
await self.pay_to_node(
|
||||||
@@ -1060,17 +1037,20 @@ class LNWallet(LNWorker):
|
|||||||
attempts: int = 1,
|
attempts: int = 1,
|
||||||
full_path: LNPaymentPath = None,
|
full_path: LNPaymentPath = None,
|
||||||
fwd_trampoline_onion=None,
|
fwd_trampoline_onion=None,
|
||||||
trampoline_fee=None,
|
fwd_trampoline_fee=None,
|
||||||
trampoline_cltv_delta=None) -> None:
|
fwd_trampoline_cltv_delta=None) -> None:
|
||||||
|
|
||||||
if fwd_trampoline_onion:
|
if fwd_trampoline_onion:
|
||||||
# todo: compare to the fee of the actual route we found
|
# todo: compare to the fee of the actual route we found
|
||||||
if trampoline_fee < 1000:
|
if fwd_trampoline_fee < 1000:
|
||||||
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'')
|
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'')
|
||||||
if trampoline_cltv_delta < 576:
|
if fwd_trampoline_cltv_delta < 576:
|
||||||
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON, data=b'')
|
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON, data=b'')
|
||||||
|
|
||||||
self.logs[payment_hash.hex()] = log = []
|
self.logs[payment_hash.hex()] = log = []
|
||||||
|
trampoline_fee_level = 0 # only used for trampoline payments
|
||||||
|
use_two_trampolines = True # only used for pay to legacy
|
||||||
|
|
||||||
amount_inflight = 0 # what we sent in htlcs
|
amount_inflight = 0 # what we sent in htlcs
|
||||||
while True:
|
while True:
|
||||||
amount_to_send = amount_to_pay - amount_inflight
|
amount_to_send = amount_to_pay - amount_inflight
|
||||||
@@ -1090,6 +1070,8 @@ class LNWallet(LNWorker):
|
|||||||
full_path=full_path,
|
full_path=full_path,
|
||||||
payment_hash=payment_hash,
|
payment_hash=payment_hash,
|
||||||
payment_secret=payment_secret,
|
payment_secret=payment_secret,
|
||||||
|
trampoline_fee_level=trampoline_fee_level,
|
||||||
|
use_two_trampolines=use_two_trampolines,
|
||||||
fwd_trampoline_onion=fwd_trampoline_onion))
|
fwd_trampoline_onion=fwd_trampoline_onion))
|
||||||
# 2. send htlcs
|
# 2. send htlcs
|
||||||
for route, amount_msat, total_msat, cltv_delta, bucket_payment_secret, trampoline_onion in routes:
|
for route, amount_msat, total_msat, cltv_delta, bucket_payment_secret, trampoline_onion in routes:
|
||||||
@@ -1115,7 +1097,26 @@ class LNWallet(LNWorker):
|
|||||||
raise PaymentFailure('Giving up after %d attempts'%len(log))
|
raise PaymentFailure('Giving up after %d attempts'%len(log))
|
||||||
# if we get a tmp channel failure, it might work to split the amount and try more routes
|
# if we get a tmp channel failure, it might work to split the amount and try more routes
|
||||||
# if we get a channel update, we might retry the same route and amount
|
# if we get a channel update, we might retry the same route and amount
|
||||||
self.handle_error_code_from_failed_htlc(htlc_log)
|
route = htlc_log.route
|
||||||
|
sender_idx = htlc_log.sender_idx
|
||||||
|
failure_msg = htlc_log.failure_msg
|
||||||
|
code, data = failure_msg.code, failure_msg.data
|
||||||
|
self.logger.info(f"UPDATE_FAIL_HTLC {repr(code)} {data}")
|
||||||
|
self.logger.info(f"error reported by {bh2u(route[sender_idx].node_id)}")
|
||||||
|
if code == OnionFailureCode.MPP_TIMEOUT:
|
||||||
|
raise PaymentFailure(failure_msg.code_name())
|
||||||
|
# trampoline
|
||||||
|
if self.channel_db is None:
|
||||||
|
if code == OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT:
|
||||||
|
# todo: parse the node parameters here (not returned by eclair yet)
|
||||||
|
trampoline_fee_level += 1
|
||||||
|
continue
|
||||||
|
elif use_two_trampolines:
|
||||||
|
use_two_trampolines = False
|
||||||
|
else:
|
||||||
|
raise PaymentFailure(failure_msg.code_name())
|
||||||
|
else:
|
||||||
|
self.handle_error_code_from_failed_htlc(route, sender_idx, failure_msg, code, data)
|
||||||
|
|
||||||
async def pay_to_route(
|
async def pay_to_route(
|
||||||
self, *,
|
self, *,
|
||||||
@@ -1152,28 +1153,8 @@ class LNWallet(LNWorker):
|
|||||||
self.sent_buckets[payment_secret] = total_msat
|
self.sent_buckets[payment_secret] = total_msat
|
||||||
util.trigger_callback('htlc_added', chan, htlc, SENT)
|
util.trigger_callback('htlc_added', chan, htlc, SENT)
|
||||||
|
|
||||||
def handle_error_code_from_failed_htlc(self, htlc_log):
|
|
||||||
route = htlc_log.route
|
def handle_error_code_from_failed_htlc(self, route, sender_idx, failure_msg, code, data):
|
||||||
sender_idx = htlc_log.sender_idx
|
|
||||||
failure_msg = htlc_log.failure_msg
|
|
||||||
code, data = failure_msg.code, failure_msg.data
|
|
||||||
self.logger.info(f"UPDATE_FAIL_HTLC {repr(code)} {data}")
|
|
||||||
self.logger.info(f"error reported by {bh2u(route[sender_idx].node_id)}")
|
|
||||||
if code == OnionFailureCode.MPP_TIMEOUT:
|
|
||||||
raise PaymentFailure(failure_msg.code_name())
|
|
||||||
# trampoline
|
|
||||||
if self.channel_db is None:
|
|
||||||
if code == OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT:
|
|
||||||
# todo: parse the node parameters here (not returned by eclair yet)
|
|
||||||
self.trampoline_fee_level += 1
|
|
||||||
return
|
|
||||||
elif len(route) > 2:
|
|
||||||
edge = route[2]
|
|
||||||
if edge.is_trampoline() and edge.node_id in self.trampoline2_list:
|
|
||||||
self.logger.info(f"blacklisting second trampoline {edge.node_id.hex()}")
|
|
||||||
self.trampoline2_list.remove(edge.node_id)
|
|
||||||
return
|
|
||||||
raise PaymentFailure(failure_msg.code_name())
|
|
||||||
# handle some specific error codes
|
# handle some specific error codes
|
||||||
failure_codes = {
|
failure_codes = {
|
||||||
OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 0,
|
OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 0,
|
||||||
@@ -1223,8 +1204,8 @@ class LNWallet(LNWorker):
|
|||||||
# blacklist channel after reporter node
|
# blacklist channel after reporter node
|
||||||
# TODO this should depend on the error (even more granularity)
|
# TODO this should depend on the error (even more granularity)
|
||||||
# also, we need finer blacklisting (directed edges; nodes)
|
# also, we need finer blacklisting (directed edges; nodes)
|
||||||
if htlc_log.sender_idx is None:
|
if sender_idx is None:
|
||||||
raise PaymentFailure(htlc_log.failure_msg.code_name())
|
raise PaymentFailure(failure_msg.code_name())
|
||||||
try:
|
try:
|
||||||
short_chan_id = route[sender_idx + 1].short_channel_id
|
short_chan_id = route[sender_idx + 1].short_channel_id
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@@ -1236,7 +1217,7 @@ class LNWallet(LNWorker):
|
|||||||
|
|
||||||
# we should not continue if we did not blacklist or update anything
|
# we should not continue if we did not blacklist or update anything
|
||||||
if not (blacklist or update):
|
if not (blacklist or update):
|
||||||
raise PaymentFailure(htlc_log.failure_msg.code_name())
|
raise PaymentFailure(failure_msg.code_name())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _decode_channel_update_msg(cls, chan_upd_msg: bytes) -> Optional[Dict[str, Any]]:
|
def _decode_channel_update_msg(cls, chan_upd_msg: bytes) -> Optional[Dict[str, Any]]:
|
||||||
@@ -1302,7 +1283,9 @@ class LNWallet(LNWorker):
|
|||||||
invoice_features: int,
|
invoice_features: int,
|
||||||
payment_hash,
|
payment_hash,
|
||||||
payment_secret,
|
payment_secret,
|
||||||
fwd_trampoline_onion=None,
|
trampoline_fee_level: int,
|
||||||
|
use_two_trampolines: bool,
|
||||||
|
fwd_trampoline_onion = None,
|
||||||
full_path: LNPaymentPath = None) -> Sequence[Tuple[LNPaymentRoute, int]]:
|
full_path: LNPaymentPath = None) -> Sequence[Tuple[LNPaymentRoute, int]]:
|
||||||
|
|
||||||
"""Creates multiple routes for splitting a payment over the available
|
"""Creates multiple routes for splitting a payment over the available
|
||||||
@@ -1338,8 +1321,8 @@ class LNWallet(LNWorker):
|
|||||||
payment_hash=payment_hash,
|
payment_hash=payment_hash,
|
||||||
payment_secret=payment_secret,
|
payment_secret=payment_secret,
|
||||||
local_height=local_height,
|
local_height=local_height,
|
||||||
trampoline_fee_level=self.trampoline_fee_level,
|
trampoline_fee_level=trampoline_fee_level,
|
||||||
trampoline2_list=self.trampoline2_list)
|
use_two_trampolines=use_two_trampolines)
|
||||||
trampoline_payment_secret = os.urandom(32)
|
trampoline_payment_secret = os.urandom(32)
|
||||||
amount_to_send = amount_with_fees + trampoline_fee
|
amount_to_send = amount_with_fees + trampoline_fee
|
||||||
if chan.available_to_spend(LOCAL, strict=True) < amount_to_send:
|
if chan.available_to_spend(LOCAL, strict=True) < amount_to_send:
|
||||||
@@ -1403,8 +1386,8 @@ class LNWallet(LNWorker):
|
|||||||
payment_hash=payment_hash,
|
payment_hash=payment_hash,
|
||||||
payment_secret=payment_secret,
|
payment_secret=payment_secret,
|
||||||
local_height=local_height,
|
local_height=local_height,
|
||||||
trampoline_fee_level=self.trampoline_fee_level,
|
trampoline_fee_level=trampoline_fee_level,
|
||||||
trampoline2_list=self.trampoline2_list)
|
use_two_trampolines=use_two_trampolines)
|
||||||
self.logger.info(f'trampoline fee {trampoline_fee}')
|
self.logger.info(f'trampoline fee {trampoline_fee}')
|
||||||
# node_features is only used to determine is_tlv
|
# node_features is only used to determine is_tlv
|
||||||
bucket_payment_secret = os.urandom(32)
|
bucket_payment_secret = os.urandom(32)
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
import bitstring
|
import bitstring
|
||||||
|
import random
|
||||||
|
|
||||||
|
from .logging import get_logger, Logger
|
||||||
from .lnutil import LnFeatures
|
from .lnutil import LnFeatures
|
||||||
from .lnonion import calc_hops_data_for_payment, new_onion_packet
|
from .lnonion import calc_hops_data_for_payment, new_onion_packet
|
||||||
from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_sane_to_use
|
from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_sane_to_use
|
||||||
from .lnutil import NoPathFound
|
from .lnutil import NoPathFound, LNPeerAddr
|
||||||
|
from . import constants
|
||||||
|
|
||||||
from .logging import get_logger, Logger
|
|
||||||
|
|
||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -49,6 +51,28 @@ TRAMPOLINE_FEES = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# hardcoded list
|
||||||
|
# TODO for some pubkeys, there are multiple network addresses we could try
|
||||||
|
TRAMPOLINE_NODES_MAINNET = {
|
||||||
|
'ACINQ': LNPeerAddr(host='34.239.230.56', port=9735, pubkey=bytes.fromhex('03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f')),
|
||||||
|
#'Electrum trampoline': LNPeerAddr(host='144.76.99.209', port=9740, pubkey=bytes.fromhex('03ecef675be448b615e6176424070673ef8284e0fd19d8be062a6cb5b130a0a0d1')),
|
||||||
|
'blah': LNPeerAddr(host='34.236.113.58', port=9735, pubkey=bytes.fromhex('02fa50c72ee1e2eb5f1b6d9c3032080c4c864373c4201dfa2966aa34eee1051f97')),
|
||||||
|
}
|
||||||
|
TRAMPOLINE_NODES_TESTNET = {
|
||||||
|
'endurance': LNPeerAddr(host='34.250.234.192', port=9735, pubkey=bytes.fromhex('03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134')),
|
||||||
|
}
|
||||||
|
|
||||||
|
def hardcoded_trampoline_nodes():
|
||||||
|
if constants.net in (constants.BitcoinMainnet, ):
|
||||||
|
return TRAMPOLINE_NODES_MAINNET
|
||||||
|
if constants.net in (constants.BitcoinTestnet, ):
|
||||||
|
return TRAMPOLINE_NODES_TESTNET
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def trampolines_by_id():
|
||||||
|
return dict([(x.pubkey, x) for x in hardcoded_trampoline_nodes().values()])
|
||||||
|
|
||||||
|
is_hardcoded_trampoline = lambda node_id: node_id in trampolines_by_id().keys()
|
||||||
|
|
||||||
def encode_routing_info(r_tags):
|
def encode_routing_info(r_tags):
|
||||||
result = bitstring.BitArray()
|
result = bitstring.BitArray()
|
||||||
@@ -69,8 +93,8 @@ def create_trampoline_route(
|
|||||||
my_pubkey: bytes,
|
my_pubkey: bytes,
|
||||||
trampoline_node_id,
|
trampoline_node_id,
|
||||||
r_tags, t_tags,
|
r_tags, t_tags,
|
||||||
trampoline_fee_level,
|
trampoline_fee_level: int,
|
||||||
trampoline2_list) -> LNPaymentRoute:
|
use_two_trampolines: bool) -> LNPaymentRoute:
|
||||||
|
|
||||||
invoice_features = LnFeatures(invoice_features)
|
invoice_features = LnFeatures(invoice_features)
|
||||||
# We do not set trampoline_routing_opt in our invoices, because the spec is not ready
|
# We do not set trampoline_routing_opt in our invoices, because the spec is not ready
|
||||||
@@ -95,7 +119,9 @@ def create_trampoline_route(
|
|||||||
raise NoPathFound()
|
raise NoPathFound()
|
||||||
# add optional second trampoline
|
# add optional second trampoline
|
||||||
trampoline2 = None
|
trampoline2 = None
|
||||||
if is_legacy:
|
if is_legacy and use_two_trampolines:
|
||||||
|
trampoline2_list = list(trampolines_by_id().keys())
|
||||||
|
random.shuffle(trampoline2_list)
|
||||||
for node_id in trampoline2_list:
|
for node_id in trampoline2_list:
|
||||||
if node_id != trampoline_node_id:
|
if node_id != trampoline_node_id:
|
||||||
trampoline2 = node_id
|
trampoline2 = node_id
|
||||||
@@ -214,8 +240,8 @@ def create_trampoline_route_and_onion(
|
|||||||
payment_hash,
|
payment_hash,
|
||||||
payment_secret,
|
payment_secret,
|
||||||
local_height:int,
|
local_height:int,
|
||||||
trampoline_fee_level,
|
trampoline_fee_level: int,
|
||||||
trampoline2_list):
|
use_two_trampolines: bool):
|
||||||
# create route for the trampoline_onion
|
# create route for the trampoline_onion
|
||||||
trampoline_route = create_trampoline_route(
|
trampoline_route = create_trampoline_route(
|
||||||
amount_msat=amount_msat,
|
amount_msat=amount_msat,
|
||||||
@@ -227,7 +253,7 @@ def create_trampoline_route_and_onion(
|
|||||||
r_tags=r_tags,
|
r_tags=r_tags,
|
||||||
t_tags=t_tags,
|
t_tags=t_tags,
|
||||||
trampoline_fee_level=trampoline_fee_level,
|
trampoline_fee_level=trampoline_fee_level,
|
||||||
trampoline2_list=trampoline2_list)
|
use_two_trampolines=use_two_trampolines)
|
||||||
# compute onion and fees
|
# compute onion and fees
|
||||||
final_cltv = local_height + min_cltv_expiry
|
final_cltv = local_height + min_cltv_expiry
|
||||||
trampoline_onion, amount_with_fees, bucket_cltv = create_trampoline_onion(
|
trampoline_onion, amount_with_fees, bucket_cltv = create_trampoline_onion(
|
||||||
|
|||||||
Reference in New Issue
Block a user