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 <rusty@rustcorp.com.au>
This commit is contained in:
@@ -60,6 +60,8 @@ struct info {
|
||||
struct timers timers;
|
||||
/* Seed for channel feature determination (e.g. delay time) */
|
||||
struct siphash_seed seed;
|
||||
/* Currently used channels */
|
||||
struct reservation **reservations;
|
||||
|
||||
/* Fake stuff we feed into lightningd */
|
||||
struct fee_states *fee_states;
|
||||
@@ -98,6 +100,12 @@ struct multi_payment {
|
||||
struct payment **payments;
|
||||
};
|
||||
|
||||
/* We've taken up some part of a channel */
|
||||
struct reservation {
|
||||
struct short_channel_id_dir scidd;
|
||||
struct amount_msat amount;
|
||||
};
|
||||
|
||||
static void make_privkey(size_t idx, struct privkey *pk)
|
||||
{
|
||||
/* pyln-testing uses 'lightning-N' then all zeroes as hsm_secret. */
|
||||
@@ -573,6 +581,72 @@ static void add_mpp(struct info *info,
|
||||
tal_free(mp);
|
||||
}
|
||||
|
||||
static void destroy_reservation(struct reservation *r,
|
||||
struct info *info)
|
||||
{
|
||||
for (size_t i = 0; i < tal_count(info->reservations); i++) {
|
||||
if (info->reservations[i] == r) {
|
||||
tal_arr_remove(&info->reservations, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
static void add_reservation(const tal_t *ctx,
|
||||
struct info *info,
|
||||
const struct short_channel_id_dir *scidd,
|
||||
struct amount_msat amount)
|
||||
{
|
||||
struct reservation *r = tal(ctx, struct reservation);
|
||||
r->scidd = *scidd;
|
||||
r->amount = amount;
|
||||
tal_arr_expand(&info->reservations, r);
|
||||
tal_add_destructor2(r, destroy_reservation, info);
|
||||
}
|
||||
|
||||
/* We determine capacity for one side, then we derive the other side.
|
||||
* Reservations, however, do *not* credit the other side, since
|
||||
* they're htlcs in flight. (We don't update after payments, either!) */
|
||||
static struct amount_msat calc_capacity(struct info *info,
|
||||
const struct gossmap_chan *c,
|
||||
const struct short_channel_id_dir *scidd)
|
||||
{
|
||||
struct short_channel_id_dir base_scidd;
|
||||
struct amount_msat base_capacity, dynamic_capacity;
|
||||
|
||||
base_scidd.scid = scidd->scid;
|
||||
base_scidd.dir = 0;
|
||||
base_capacity = gossmap_chan_get_capacity(info->gossmap, c);
|
||||
dynamic_capacity = amount_msat(channel_range(info, &base_scidd,
|
||||
0, base_capacity.millisatoshis)); /* Raw: rand function */
|
||||
/* Invert capacity if that is backwards */
|
||||
if (scidd->dir != base_scidd.dir) {
|
||||
if (!amount_msat_sub(&dynamic_capacity, base_capacity, dynamic_capacity))
|
||||
abort();
|
||||
}
|
||||
|
||||
status_debug("Capacity for %s is %s, dynamic capacity is %s",
|
||||
fmt_short_channel_id_dir(tmpctx, scidd),
|
||||
fmt_amount_msat(tmpctx, base_capacity),
|
||||
fmt_amount_msat(tmpctx, dynamic_capacity));
|
||||
|
||||
/* Take away any reservations */
|
||||
for (size_t i = 0; i < tal_count(info->reservations); i++) {
|
||||
if (!short_channel_id_dir_eq(&info->reservations[i]->scidd, scidd))
|
||||
continue;
|
||||
/* We should never use more that we have! */
|
||||
if (!amount_msat_sub(&dynamic_capacity,
|
||||
dynamic_capacity,
|
||||
info->reservations[i]->amount))
|
||||
abort();
|
||||
status_debug("... minus reservation %s",
|
||||
fmt_amount_msat(tmpctx, info->reservations[i]->amount));
|
||||
}
|
||||
|
||||
return dynamic_capacity;
|
||||
}
|
||||
|
||||
/* Mutual recursion via timer */
|
||||
struct delayed_forward {
|
||||
struct info *info;
|
||||
@@ -704,6 +778,14 @@ found_next:
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount_msat_greater(amount, calc_capacity(info, c, &scidd))) {
|
||||
fail(info, htlc, payload, WIRE_TEMPORARY_CHANNEL_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* When we resolve the HTLC, we'll cancel the reservations */
|
||||
add_reservation(htlc, info, &scidd, amount);
|
||||
|
||||
if (payload->path_key) {
|
||||
struct sha256 sha;
|
||||
blinding_hash_e_and_ss(payload->path_key,
|
||||
@@ -1172,6 +1254,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
info->cached_node_idx = tal_arr(info, size_t, 0);
|
||||
info->multi_payments = tal_arr(info, struct multi_payment *, 0);
|
||||
info->reservations = tal_arr(info, struct reservation *, 0);
|
||||
timers_init(&info->timers, time_mono());
|
||||
info->commit_num = 1;
|
||||
info->fakesig.sighash_type = SIGHASH_ALL;
|
||||
|
||||
@@ -997,6 +997,7 @@ def test_real_data(node_factory, bitcoind):
|
||||
assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == (8, 95, 6007785, 564997, 91)
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_askrene_fake_channeld(node_factory, bitcoind):
|
||||
outfile = tempfile.NamedTemporaryFile(prefix='gossip-store-')
|
||||
nodeids = subprocess.check_output(['devtools/gossmap-compress',
|
||||
@@ -1024,36 +1025,71 @@ def test_askrene_fake_channeld(node_factory, bitcoind):
|
||||
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)
|
||||
|
||||
TEMPORARY_CHANNEL_FAILURE = 0x1007
|
||||
MPP_TIMEOUT = 0x17
|
||||
|
||||
l1.rpc.askrene_create_layer('test_askrene_fake_channeld')
|
||||
for n in range(0, 100):
|
||||
if n in (62, 76, 80, 97):
|
||||
continue
|
||||
|
||||
routes = l1.rpc.getroutes(source=l1.info['id'],
|
||||
destination=nodeids[n],
|
||||
amount_msat=AMOUNT,
|
||||
layers=['auto.sourcefree', 'auto.localchans'],
|
||||
maxfee_msat=AMOUNT,
|
||||
final_cltv=18)
|
||||
print(f"PAYING Node #{n}")
|
||||
success = False
|
||||
while not success:
|
||||
routes = l1.rpc.getroutes(source=l1.info['id'],
|
||||
destination=nodeids[n],
|
||||
amount_msat=AMOUNT,
|
||||
layers=['auto.sourcefree', 'auto.localchans'],
|
||||
maxfee_msat=AMOUNT,
|
||||
final_cltv=18)
|
||||
|
||||
preimage_hex = f'{n:02}' + '00' * 31
|
||||
hash_hex = sha256(bytes.fromhex(preimage_hex)).hexdigest()
|
||||
preimage_hex = f'{n:02}' + '00' * 31
|
||||
hash_hex = sha256(bytes.fromhex(preimage_hex)).hexdigest()
|
||||
|
||||
# Sendpay wants a different format, so we convert.
|
||||
for i, r in enumerate(routes['routes']):
|
||||
hops = [{'id': h['next_node_id'],
|
||||
'channel': h['short_channel_id_dir'].split('/')[0]}
|
||||
for h in r['path']]
|
||||
# delay and amount_msat for sendpay are amounts at *end* of hop, not start!
|
||||
with_end = r['path'] + [{'amount_msat': r['amount_msat'], 'delay': r['final_cltv']}]
|
||||
for n, h in enumerate(hops):
|
||||
h['delay'] = with_end[n + 1]['delay']
|
||||
h['amount_msat'] = with_end[n + 1]['amount_msat']
|
||||
paths = {}
|
||||
# Sendpay wants a different format, so we convert.
|
||||
for i, r in enumerate(routes['routes']):
|
||||
paths[i] = [{'id': h['next_node_id'],
|
||||
'channel': h['short_channel_id_dir'].split('/')[0],
|
||||
'direction': int(h['short_channel_id_dir'].split('/')[1])}
|
||||
for h in r['path']]
|
||||
|
||||
l1.rpc.sendpay(hops, hash_hex,
|
||||
amount_msat=AMOUNT,
|
||||
payment_secret='00' * 32,
|
||||
partid=i + 1, groupid=1)
|
||||
# delay and amount_msat for sendpay are amounts at *end* of hop, not start!
|
||||
with_end = r['path'] + [{'amount_msat': r['amount_msat'], 'delay': r['final_cltv']}]
|
||||
for n, h in enumerate(paths[i]):
|
||||
h['delay'] = with_end[n + 1]['delay']
|
||||
h['amount_msat'] = with_end[n + 1]['amount_msat']
|
||||
|
||||
for i, r in enumerate(routes['routes']):
|
||||
# Worst-case timeout is 1 second per hop.
|
||||
assert l1.rpc.waitsendpay(hash_hex, timeout=TIMEOUT + len(r['path']), partid=i + 1, groupid=1)['payment_preimage'] == preimage_hex
|
||||
l1.rpc.sendpay(paths[i], hash_hex,
|
||||
amount_msat=AMOUNT,
|
||||
payment_secret='00' * 32,
|
||||
partid=i + 1, groupid=1)
|
||||
|
||||
for i, p in paths.items():
|
||||
# Worst-case timeout is 1 second per hop, + 60 seconds if MPP timeout!
|
||||
try:
|
||||
if l1.rpc.waitsendpay(hash_hex, timeout=TIMEOUT + len(p) + 60, partid=i + 1, groupid=1):
|
||||
success = True
|
||||
except RpcError as err:
|
||||
# Timeout means this one succeeded!
|
||||
if err.error['data']['failcode'] == MPP_TIMEOUT:
|
||||
for h in p:
|
||||
l1.rpc.askrene_inform_channel('test_askrene_fake_channeld',
|
||||
f"{h['channel']}/{h['direction']}",
|
||||
h['amount_msat'],
|
||||
'unconstrained')
|
||||
elif err.error['data']['failcode'] == TEMPORARY_CHANNEL_FAILURE:
|
||||
# We succeeded up to here
|
||||
failpoint = err.error['data']['erring_index']
|
||||
for h in p[:failpoint]:
|
||||
l1.rpc.askrene_inform_channel('test_askrene_fake_channeld',
|
||||
f"{h['channel']}/{h['direction']}",
|
||||
h['amount_msat'],
|
||||
'unconstrained')
|
||||
h = p[failpoint]
|
||||
l1.rpc.askrene_inform_channel('test_askrene_fake_channeld',
|
||||
f"{h['channel']}/{h['direction']}",
|
||||
h['amount_msat'],
|
||||
'constrained')
|
||||
else:
|
||||
raise err
|
||||
|
||||
Reference in New Issue
Block a user