Skip to content

Commit 10461a6

Browse files
committed
channeld_fakenet: add capacity information.
Start with a random capacity (linear prob), and remember in-progess payments so we can simulate them using capacity properly. Signed-off-by: Rusty Russell <[email protected]>
1 parent 42c817a commit 10461a6

File tree

2 files changed

+148
-29
lines changed

2 files changed

+148
-29
lines changed

tests/plugins/channeld_fakenet.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ struct info {
6060
struct timers timers;
6161
/* Seed for channel feature determination (e.g. delay time) */
6262
struct siphash_seed seed;
63+
/* Currently used channels */
64+
struct reservation **reservations;
6365

6466
/* Fake stuff we feed into lightningd */
6567
struct fee_states *fee_states;
@@ -98,6 +100,12 @@ struct multi_payment {
98100
struct payment **payments;
99101
};
100102

103+
/* We've taken up some part of a channel */
104+
struct reservation {
105+
struct short_channel_id_dir scidd;
106+
struct amount_msat amount;
107+
};
108+
101109
static void make_privkey(size_t idx, struct privkey *pk)
102110
{
103111
/* pyln-testing uses 'lightning-N' then all zeroes as hsm_secret. */
@@ -573,6 +581,72 @@ static void add_mpp(struct info *info,
573581
tal_free(mp);
574582
}
575583

584+
static void destroy_reservation(struct reservation *r,
585+
struct info *info)
586+
{
587+
for (size_t i = 0; i < tal_count(info->reservations); i++) {
588+
if (info->reservations[i] == r) {
589+
tal_arr_remove(&info->reservations, i);
590+
return;
591+
}
592+
}
593+
abort();
594+
}
595+
596+
static void add_reservation(const tal_t *ctx,
597+
struct info *info,
598+
const struct short_channel_id_dir *scidd,
599+
struct amount_msat amount)
600+
{
601+
struct reservation *r = tal(ctx, struct reservation);
602+
r->scidd = *scidd;
603+
r->amount = amount;
604+
tal_arr_expand(&info->reservations, r);
605+
tal_add_destructor2(r, destroy_reservation, info);
606+
}
607+
608+
/* We determine capacity for one side, then we derive the other side.
609+
* Reservations, however, do *not* credit the other side, since
610+
* they're htlcs in flight. (We don't update after payments, either!) */
611+
static struct amount_msat calc_capacity(struct info *info,
612+
const struct gossmap_chan *c,
613+
const struct short_channel_id_dir *scidd)
614+
{
615+
struct short_channel_id_dir base_scidd;
616+
struct amount_msat base_capacity, dynamic_capacity;
617+
618+
base_scidd.scid = scidd->scid;
619+
base_scidd.dir = 0;
620+
base_capacity = gossmap_chan_get_capacity(info->gossmap, c);
621+
dynamic_capacity = amount_msat(channel_range(info, &base_scidd,
622+
0, base_capacity.millisatoshis)); /* Raw: rand function */
623+
/* Invert capacity if that is backwards */
624+
if (scidd->dir != base_scidd.dir) {
625+
if (!amount_msat_sub(&dynamic_capacity, base_capacity, dynamic_capacity))
626+
abort();
627+
}
628+
629+
status_debug("Capacity for %s is %s, dynamic capacity is %s",
630+
fmt_short_channel_id_dir(tmpctx, scidd),
631+
fmt_amount_msat(tmpctx, base_capacity),
632+
fmt_amount_msat(tmpctx, dynamic_capacity));
633+
634+
/* Take away any reservations */
635+
for (size_t i = 0; i < tal_count(info->reservations); i++) {
636+
if (!short_channel_id_dir_eq(&info->reservations[i]->scidd, scidd))
637+
continue;
638+
/* We should never use more that we have! */
639+
if (!amount_msat_sub(&dynamic_capacity,
640+
dynamic_capacity,
641+
info->reservations[i]->amount))
642+
abort();
643+
status_debug("... minus reservation %s",
644+
fmt_amount_msat(tmpctx, info->reservations[i]->amount));
645+
}
646+
647+
return dynamic_capacity;
648+
}
649+
576650
/* Mutual recursion via timer */
577651
struct delayed_forward {
578652
struct info *info;
@@ -704,6 +778,14 @@ static void forward_htlc(struct info *info,
704778
return;
705779
}
706780

781+
if (amount_msat_greater(amount, calc_capacity(info, c, &scidd))) {
782+
fail(info, htlc, payload, WIRE_TEMPORARY_CHANNEL_FAILURE);
783+
return;
784+
}
785+
786+
/* When we resolve the HTLC, we'll cancel the reservations */
787+
add_reservation(htlc, info, &scidd, amount);
788+
707789
if (payload->path_key) {
708790
struct sha256 sha;
709791
blinding_hash_e_and_ss(payload->path_key,
@@ -1172,6 +1254,7 @@ int main(int argc, char *argv[])
11721254

11731255
info->cached_node_idx = tal_arr(info, size_t, 0);
11741256
info->multi_payments = tal_arr(info, struct multi_payment *, 0);
1257+
info->reservations = tal_arr(info, struct reservation *, 0);
11751258
timers_init(&info->timers, time_mono());
11761259
info->commit_num = 1;
11771260
info->fakesig.sighash_type = SIGHASH_ALL;

tests/test_askrene.py

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,7 @@ def test_real_data(node_factory, bitcoind):
997997
assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == (8, 95, 6007785, 564997, 91)
998998

999999

1000+
@pytest.mark.slow_test
10001001
def test_askrene_fake_channeld(node_factory, bitcoind):
10011002
outfile = tempfile.NamedTemporaryFile(prefix='gossip-store-')
10021003
nodeids = subprocess.check_output(['devtools/gossmap-compress',
@@ -1024,36 +1025,71 @@ def test_askrene_fake_channeld(node_factory, bitcoind):
10241025
shaseed = subprocess.check_output(["tools/hsmtool", "dumpcommitments", l1.info['id'], "1", "0", hsmfile]).decode('utf-8').strip().partition(": ")[2]
10251026
l1.rpc.dev_peer_shachain(l2.info['id'], shaseed)
10261027

1028+
TEMPORARY_CHANNEL_FAILURE = 0x1007
1029+
MPP_TIMEOUT = 0x17
1030+
1031+
l1.rpc.askrene_create_layer('test_askrene_fake_channeld')
10271032
for n in range(0, 100):
10281033
if n in (62, 76, 80, 97):
10291034
continue
10301035

1031-
routes = l1.rpc.getroutes(source=l1.info['id'],
1032-
destination=nodeids[n],
1033-
amount_msat=AMOUNT,
1034-
layers=['auto.sourcefree', 'auto.localchans'],
1035-
maxfee_msat=AMOUNT,
1036-
final_cltv=18)
1037-
1038-
preimage_hex = f'{n:02}' + '00' * 31
1039-
hash_hex = sha256(bytes.fromhex(preimage_hex)).hexdigest()
1040-
1041-
# Sendpay wants a different format, so we convert.
1042-
for i, r in enumerate(routes['routes']):
1043-
hops = [{'id': h['next_node_id'],
1044-
'channel': h['short_channel_id_dir'].split('/')[0]}
1045-
for h in r['path']]
1046-
# delay and amount_msat for sendpay are amounts at *end* of hop, not start!
1047-
with_end = r['path'] + [{'amount_msat': r['amount_msat'], 'delay': r['final_cltv']}]
1048-
for n, h in enumerate(hops):
1049-
h['delay'] = with_end[n + 1]['delay']
1050-
h['amount_msat'] = with_end[n + 1]['amount_msat']
1051-
1052-
l1.rpc.sendpay(hops, hash_hex,
1053-
amount_msat=AMOUNT,
1054-
payment_secret='00' * 32,
1055-
partid=i + 1, groupid=1)
1056-
1057-
for i, r in enumerate(routes['routes']):
1058-
# Worst-case timeout is 1 second per hop.
1059-
assert l1.rpc.waitsendpay(hash_hex, timeout=TIMEOUT + len(r['path']), partid=i + 1, groupid=1)['payment_preimage'] == preimage_hex
1036+
print(f"PAYING Node #{n}")
1037+
success = False
1038+
while not success:
1039+
routes = l1.rpc.getroutes(source=l1.info['id'],
1040+
destination=nodeids[n],
1041+
amount_msat=AMOUNT,
1042+
layers=['auto.sourcefree', 'auto.localchans'],
1043+
maxfee_msat=AMOUNT,
1044+
final_cltv=18)
1045+
1046+
preimage_hex = f'{n:02}' + '00' * 31
1047+
hash_hex = sha256(bytes.fromhex(preimage_hex)).hexdigest()
1048+
1049+
paths = {}
1050+
# Sendpay wants a different format, so we convert.
1051+
for i, r in enumerate(routes['routes']):
1052+
paths[i] = [{'id': h['next_node_id'],
1053+
'channel': h['short_channel_id_dir'].split('/')[0],
1054+
'direction': int(h['short_channel_id_dir'].split('/')[1])}
1055+
for h in r['path']]
1056+
1057+
# delay and amount_msat for sendpay are amounts at *end* of hop, not start!
1058+
with_end = r['path'] + [{'amount_msat': r['amount_msat'], 'delay': r['final_cltv']}]
1059+
for n, h in enumerate(paths[i]):
1060+
h['delay'] = with_end[n + 1]['delay']
1061+
h['amount_msat'] = with_end[n + 1]['amount_msat']
1062+
1063+
l1.rpc.sendpay(paths[i], hash_hex,
1064+
amount_msat=AMOUNT,
1065+
payment_secret='00' * 32,
1066+
partid=i + 1, groupid=1)
1067+
1068+
for i, p in paths.items():
1069+
# Worst-case timeout is 1 second per hop, + 60 seconds if MPP timeout!
1070+
try:
1071+
if l1.rpc.waitsendpay(hash_hex, timeout=TIMEOUT + len(p) + 60, partid=i + 1, groupid=1):
1072+
success = True
1073+
except RpcError as err:
1074+
# Timeout means this one succeeded!
1075+
if err.error['data']['failcode'] == MPP_TIMEOUT:
1076+
for h in p:
1077+
l1.rpc.askrene_inform_channel('test_askrene_fake_channeld',
1078+
f"{h['channel']}/{h['direction']}",
1079+
h['amount_msat'],
1080+
'unconstrained')
1081+
elif err.error['data']['failcode'] == TEMPORARY_CHANNEL_FAILURE:
1082+
# We succeeded up to here
1083+
failpoint = err.error['data']['erring_index']
1084+
for h in p[:failpoint]:
1085+
l1.rpc.askrene_inform_channel('test_askrene_fake_channeld',
1086+
f"{h['channel']}/{h['direction']}",
1087+
h['amount_msat'],
1088+
'unconstrained')
1089+
h = p[failpoint]
1090+
l1.rpc.askrene_inform_channel('test_askrene_fake_channeld',
1091+
f"{h['channel']}/{h['direction']}",
1092+
h['amount_msat'],
1093+
'constrained')
1094+
else:
1095+
raise err

0 commit comments

Comments
 (0)