Skip to content

Commit

Permalink
Merge branch 'ElementsProject:master' into add-testnet4-support
Browse files Browse the repository at this point in the history
  • Loading branch information
2seaq authored Dec 2, 2024
2 parents 48d797c + b5d1ace commit 8e98ad3
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 7 deletions.
4 changes: 2 additions & 2 deletions channeld/full_channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1658,13 +1658,13 @@ bool channel_force_htlcs(struct channel *channel,

const char *channel_add_err_name(enum channel_add_err e)
{
static char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)];
static char invalidbuf[sizeof("UNKNOWN ") + STR_MAX_CHARS(e)];

for (size_t i = 0; enum_channel_add_err_names[i].name; i++) {
if (enum_channel_add_err_names[i].v == e)
return enum_channel_add_err_names[i].name;
}
snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e);
snprintf(invalidbuf, sizeof(invalidbuf), "UNKNOWN %i", e);
return invalidbuf;
}

Expand Down
2 changes: 1 addition & 1 deletion lightningd/pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ json_add_routefail_info(struct json_stream *js,
json_add_num(js, "erring_index", erring_index);
json_add_num(js, "failcode", failcode);
/* FIXME: Better way to detect this? */
if (!strstarts(failcodename, "INVALID "))
if (!strstarts(failcodename, "UNKNOWN "))
json_add_string(js, "failcodename", failcodename);

if (erring_node != NULL)
Expand Down
2 changes: 1 addition & 1 deletion lightningd/subd.c
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ void subd_send_msg(struct subd *sd, const u8 *msg_out)
u16 type = fromwire_peektype(msg_out);
/* FIXME: We should use unique upper bits for each daemon, then
* have generate-wire.py add them, just assert here. */
if (strstarts(sd->msgname(type), "INVALID"))
if (strstarts(sd->msgname(type), "UNKNOWN"))
fatal("Sending %s an invalid message %s", sd->name, tal_hex(tmpctx, msg_out));
msg_enqueue(sd->outq, msg_out);
}
Expand Down
32 changes: 31 additions & 1 deletion plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,25 @@ static struct amount_msat total_being_sent(const struct payment *payment)
return sum;
}

static struct amount_msat total_fees_being_sent(const struct payment *payment)
{
struct attempt *attempt;
struct amount_msat sum = AMOUNT_MSAT(0);

list_for_each(&payment->current_attempts, attempt, list) {
struct amount_msat fee;
if (tal_count(attempt->hops) == 0)
continue;
if (!amount_msat_sub(&fee,
attempt->hops[0].amount_in,
attempt->delivers))
abort();
if (!amount_msat_accumulate(&sum, fee))
abort();
}
return sum;
}

static struct command_result *injectpaymentonion_succeeded(struct command *aux_cmd,
const char *method,
const char *buf,
Expand Down Expand Up @@ -1075,6 +1094,9 @@ static struct command_result *getroutes_done_err(struct command *aux_cmd,
return command_still_pending(aux_cmd);
}

/* FIXME: If we fail due to exceeding maxfee, we *could* try waiting for
* any outstanding payments to fail and then try again? */

/* More elaborate explanation. */
if (amount_msat_eq(payment->amount_being_routed, payment->amount))
complaint = "Then routing failed";
Expand All @@ -1097,6 +1119,7 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
struct xpay *xpay = xpay_of(aux_cmd->plugin);
struct out_req *req;
const struct pubkey *dst;
struct amount_msat maxfee;

/* If we get injectpaymentonion responses, they can wait */
payment->amount_being_routed = deliver;
Expand All @@ -1112,6 +1135,13 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
return do_inject(aux_cmd, attempt);
}

if (!amount_msat_sub(&maxfee, payment->maxfee, total_fees_being_sent(payment))) {
payment_log(payment, LOG_BROKEN, "more fees (%s) in flight than allowed (%s)!",
fmt_amount_msat(tmpctx, total_fees_being_sent(payment)),
fmt_amount_msat(tmpctx, payment->maxfee));
maxfee = AMOUNT_MSAT(0);
}

req = jsonrpc_request_start(aux_cmd, "getroutes",
getroutes_done,
getroutes_done_err,
Expand Down Expand Up @@ -1139,7 +1169,7 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
for (size_t i = 0; i < tal_count(payment->layers); i++)
json_add_string(req->js, NULL, payment->layers[i]);
json_array_end(req->js);
json_add_amount_msat(req->js, "maxfee_msat", payment->maxfee);
json_add_amount_msat(req->js, "maxfee_msat", maxfee);
json_add_u32(req->js, "final_cltv", payment->final_cltv);

return send_payment_req(aux_cmd, payment, req);
Expand Down
48 changes: 48 additions & 0 deletions tests/test_xpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,51 @@ def test_xpay_preapprove(node_factory):

with pytest.raises(RpcError, match=r"invoice was declined"):
l1.rpc.xpay(inv)


@unittest.skipIf(TEST_NETWORK != 'regtest', 'too dusty on elements')
def test_xpay_maxfee(node_factory, bitcoind, chainparams):
"""Test which shows that we don't excees maxfee"""
outfile = tempfile.NamedTemporaryFile(prefix='gossip-store-')
subprocess.check_output(['devtools/gossmap-compress',
'decompress',
'--node-map=3301=022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59',
'tests/data/gossip-store-2024-09-22.compressed',
outfile.name]).decode('utf-8').splitlines()
AMOUNT = 100_000_000

# l2 will warn l1 about its invalid gossip: ignore.
# We throttle l1's gossip to avoid massive log spam.
l1, l2 = node_factory.line_graph(2,
# This is in sats, so 1000x amount we send.
fundamount=AMOUNT,
opts=[{'gossip_store_file': outfile.name,
'subdaemon': 'channeld:../tests/plugins/channeld_fakenet',
'allow_warning': True,
'dev-throttle-gossip': None},
{'allow_bad_gossip': True}])

# l1 needs to know l2's shaseed for the channel so it can make revocations
hsmfile = os.path.join(l2.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
# Needs peer node id and channel dbid (1, it's the first channel), prints out:
# "shaseed: xxxxxxx\n"
shaseed = subprocess.check_output(["tools/hsmtool", "dumpcommitments", l1.info['id'], "1", "0", hsmfile]).decode('utf-8').strip().partition(": ")[2]
l1.rpc.dev_peer_shachain(l2.info['id'], shaseed)

# This one triggers the bug!
n = 59
maxfee = 57966
preimage_hex = bytes([n + 100]).hex() + '00' * 31
hash_hex = sha256(bytes.fromhex(preimage_hex)).hexdigest()
inv = subprocess.check_output(["devtools/bolt11-cli",
"encode",
n.to_bytes(length=8, byteorder=sys.byteorder).hex() + '01' * 24,
f"currency={chainparams['bip173_prefix']}",
f"p={hash_hex}",
f"s={'00' * 32}",
f"d=Paying node {n} with maxfee",
f"amount={AMOUNT}msat"]).decode('utf-8').strip()

ret = l1.rpc.xpay(invstring=inv, maxfee=maxfee)
fee = ret['amount_sent_msat'] - ret['amount_msat']
assert fee <= maxfee
4 changes: 2 additions & 2 deletions tools/gen/impl_template
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ ${i}

const char *${enum_set['name']}_name(int e)
{
static char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)];
static char invalidbuf[sizeof("UNKNOWN ") + STR_MAX_CHARS(e)];

switch ((enum ${enum_set['name']})e) {
% for msg in enum_set['set']:
case ${msg.enum_name()}: return "${msg.enum_name()}";
% endfor
}

snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e);
snprintf(invalidbuf, sizeof(invalidbuf), "UNKNOWN %i", e);
return invalidbuf;
}

Expand Down

0 comments on commit 8e98ad3

Please sign in to comment.