Skip to content

Commit

Permalink
fix: skipped hold invoice plugin tests
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Aug 11, 2023
1 parent 54afef1 commit d3b4614
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 25 deletions.
12 changes: 12 additions & 0 deletions tools/hold/consts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
from enum import Enum


class Network(str, Enum):
Mainnet = "bitcoin"
Testnet = "testnet"
Signet = "signet"
Regtest = "regtest"


PLUGIN_NAME = "hold"

TIMEOUT_CANCEL = 60
TIMEOUT_CANCEL_REGTEST = 5

TIMEOUT_CHECK_INTERVAL = 10
12 changes: 7 additions & 5 deletions tools/hold/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from bolt11 import Bolt11, Feature, Features, FeatureState, encode
from bolt11.types import MilliSatoshi
from consts import Network
from pyln.client import Plugin
from secp256k1 import PrivateKey
from utils import time_now
Expand All @@ -11,10 +12,10 @@


NETWORK_PREFIXES = {
"bitcoin": "bc",
"testnet": "tb",
"signet": "tbs",
"regtest": "bcrt",
Network.Mainnet: "bc",
Network.Testnet: "tb",
Network.Signet: "tbs",
Network.Regtest: "bcrt",
}


Expand All @@ -24,8 +25,9 @@ class Defaults(int, Enum):


def get_network_prefix(network: str) -> str:
# noinspection PyTypeChecker
return NETWORK_PREFIXES[network] if network in NETWORK_PREFIXES \
else NETWORK_PREFIXES["bitcoin"]
else NETWORK_PREFIXES[Network.Mainnet]


def get_payment_secret(val: str | None) -> str:
Expand Down
18 changes: 16 additions & 2 deletions tools/hold/htlc_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from typing import Any
from urllib.request import Request

from consts import TIMEOUT_CHECK_INTERVAL
from consts import (
TIMEOUT_CANCEL,
TIMEOUT_CANCEL_REGTEST,
TIMEOUT_CHECK_INTERVAL,
Network,
)
from datastore import DataStore
from invoice import HoldInvoice, InvoiceState
from pyln.client import Plugin
Expand All @@ -20,9 +25,18 @@ def __init__(self, plugin: Plugin, ds: DataStore, settler: Settler) -> None:
self._plugin = plugin
self._ds = ds
self._settler = settler
self._timeout = TIMEOUT_CANCEL

self._start_timeout_interval()

def init(self) -> None:
if self._plugin.rpc.getinfo()["network"] == Network.Regtest:
self._timeout = TIMEOUT_CANCEL_REGTEST
self._plugin.log(
f"Using regtest MPP timeout of {self._timeout} seconds",
level="warn",
)

def handle_htlc(
self,
invoice: HoldInvoice,
Expand Down Expand Up @@ -69,4 +83,4 @@ def _timeout_handler(self) -> None:
with self._lock:
for htlcs in self._settler.htlcs.values():
if not htlcs.is_fully_paid():
htlcs.cancel_expired()
htlcs.cancel_expired(self._timeout)
4 changes: 4 additions & 0 deletions tools/hold/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

# TODO: fix shebang line
# TODO: restart handling
# TODO: docstrings
# TODO: gRPC with subs


pl = Plugin()
Expand All @@ -31,6 +33,7 @@ def init(
plugin: Plugin,
**kwargs: dict[str, Any],
) -> None:
handler.init()
encoder.init()
plugin.log(f"Plugin {PLUGIN_NAME} initialized")

Expand All @@ -40,6 +43,7 @@ def hold_invoice(
plugin: Plugin,
payment_hash: str,
amount_msat: int,
# TODO: remove default when library can handle empty strings
memo: str = "Hold invoice",
expiry: int = Defaults.Expiry,
min_final_cltv_expiry: int = Defaults.MinFinalCltvExpiry,
Expand Down
5 changes: 2 additions & 3 deletions tools/hold/settler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from enum import Enum
from typing import ClassVar

from consts import TIMEOUT_CANCEL
from invoice import HoldInvoice, InvoiceState
from pyln.client.plugin import Request
from utils import partition, time_now
Expand Down Expand Up @@ -37,12 +36,12 @@ def is_fully_paid(self) -> bool:
def requests(self) -> list[Request]:
return [h.request for h in self.htlcs]

def cancel_expired(self) -> None:
def cancel_expired(self, expiry: int) -> None:
expired, not_expired = partition(
self.htlcs,
lambda htlc: (
time_now() - htlc.creation_time
).total_seconds() > TIMEOUT_CANCEL,
).total_seconds() > expiry,
)

self.htlcs = not_expired
Expand Down
39 changes: 25 additions & 14 deletions tools/hold/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
from threading import Thread
from typing import Any

import bolt11
import pytest
from bolt11.types import MilliSatoshi
from cli_utils import CliCaller, cln_con

PLUGIN_PATH = "/tools/hold/plugin.py"
Expand Down Expand Up @@ -81,10 +83,12 @@ def __init__(
invoice: str,
max_shard_size: int | None = None,
outgoing_chan_id: str | None = None,
timeout: int | None = None,
) -> None:
Thread.__init__(self)

self.node = node
self.timeout = timeout
self.invoice = invoice
self.max_shard_size = max_shard_size
self.outgoing_chan_id = outgoing_chan_id
Expand All @@ -98,6 +102,9 @@ def run(self) -> None:
if self.max_shard_size is not None:
cmd += f" --max_shard_size_sat {self.max_shard_size}"

if self.timeout is not None:
cmd += f" --timeout {self.timeout}s"

res = lnd_raw(self.node, f"{cmd} {self.invoice} 2> /dev/null")
res = res[res.find("{"):]
self.res = json.loads(res)
Expand Down Expand Up @@ -171,7 +178,6 @@ def test_list_not_found(self, cln: CliCaller) -> None:
def test_settle_accepted(self, cln: CliCaller) -> None:
payment_preimage, payment_hash, invoice = add_hold_invoice(cln)

print(invoice)
pay = LndPay(LndNode.One, invoice)
pay.start()

Expand Down Expand Up @@ -294,24 +300,30 @@ def test_cancel_non_existent(self, cln: CliCaller) -> None:
assert res["code"] == 2102
assert res["message"] == "hold invoice with that payment hash does not exist"

@pytest.mark.skip()
def test_mpp_timeout(self, cln: CliCaller) -> None:
_, payment_hash, invoice = add_hold_invoice(cln)
amount = lnd(LndNode.One, "decodepayreq", invoice)["num_satoshis"]
cln_node = cln("getinfo")["id"]
dec = bolt11.decode(invoice)
dec.amount_msat = MilliSatoshi(dec.amount_msat - 1000)

routes = lnd(LndNode.One, "queryroutes", cln_node, str(int(amount) - 1))
less_invoice = cln("signinvoice", bolt11.encode(dec))["bolt11"]

res = lnd(
LndNode.One,
"sendtoroute",
"--payment_hash",
pay = LndPay(LndNode.One, less_invoice, timeout=5)
pay.start()

time.sleep(0.5)
assert cln(
"listholdinvoices",
payment_hash,
format_json(routes),
)
)["holdinvoices"][0]["state"] == "unpaid"

assert res["status"] == "FAILED"
assert res["failure"]["code"] == "MPP_TIMEOUT"
pay.join()

assert pay.res["status"] == "FAILED"
assert pay.res["failure_reason"] == "FAILURE_REASON_TIMEOUT"
assert len(pay.res["htlcs"]) == 1

htlc = pay.res["htlcs"][0]
assert htlc["failure"]["code"] == "MPP_TIMEOUT"

def test_htlc_too_little_cltv(self, cln: CliCaller) -> None:
_, payment_hash, invoice = add_hold_invoice(cln)
Expand Down Expand Up @@ -422,7 +434,6 @@ def test_ignore_non_hold(self, cln: CliCaller) -> None:

assert pay.res["status"] == "SUCCEEDED"

@pytest.mark.skip()
def test_ignore_forward(self, cln: CliCaller) -> None:
cln_id = cln("getinfo")["id"]
channels = lnd(LndNode.Two, "listchannels")["channels"]
Expand Down
2 changes: 1 addition & 1 deletion tools/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ select = ["ALL"]
ignore = [
"T201", "D101", "D211", "D213", "INP001", "BLE001", "FBT001", "FBT002", "FBT003", "S605",
"TD002", "TD003", "FIX002", "ANN101", "D102", "D103", "D107", "D100", "SLOT000", "S101",
"PLR2004", "ARG001", "PLR0913", "D104"
"PLR2004", "ARG001", "PLR0913", "D104", "FA102"
]

0 comments on commit d3b4614

Please sign in to comment.