diff --git a/common/json.c b/common/json.c index e305320f4cd2..f800a561d816 100644 --- a/common/json.c +++ b/common/json.c @@ -90,6 +90,21 @@ bool json_to_number(const char *buffer, const jsmntok_t *tok, return true; } +bool json_to_u16(const char *buffer, const jsmntok_t *tok, + short unsigned int *num) +{ + uint64_t u64; + + if (!json_to_u64(buffer, tok, &u64)) + return false; + *num = u64; + + /* Just in case it doesn't fit. */ + if (*num != u64) + return false; + return true; +} + bool json_to_int(const char *buffer, const jsmntok_t *tok, int *num) { char *end; diff --git a/common/json.h b/common/json.h index 6efdc3f90ab1..3206b3d983a4 100644 --- a/common/json.h +++ b/common/json.h @@ -36,6 +36,10 @@ bool json_to_number(const char *buffer, const jsmntok_t *tok, bool json_to_u64(const char *buffer, const jsmntok_t *tok, uint64_t *num); +/* Extract number from this (may be a string, or a number literal) */ +bool json_to_u16(const char *buffer, const jsmntok_t *tok, + uint16_t *num); + /* Extract double from this (must be a number literal) */ bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num); diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index 434fbc0234f3..9e7e45465220 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -434,6 +434,28 @@ def dev_memleak(self): """ return self.call("dev-memleak") + def dev_pay(self, bolt11, msatoshi=None, label=None, riskfactor=None, + description=None, maxfeepercent=None, retry_for=None, + maxdelay=None, exemptfee=None, use_shadow=True): + """ + A developer version of `pay`, with the possibility to deactivate + shadow routing (used for testing). + """ + payload = { + "bolt11": bolt11, + "msatoshi": msatoshi, + "label": label, + "riskfactor": riskfactor, + "maxfeepercent": maxfeepercent, + "retry_for": retry_for, + "maxdelay": maxdelay, + "exemptfee": exemptfee, + "use_shadow": use_shadow, + # Deprecated. + "description": description, + } + return self.call("pay", payload) + def dev_reenable_commit(self, peer_id): """ Re-enable the commit timer on peer {id} @@ -761,7 +783,9 @@ def newaddr(self, addresstype=None): """ return self.call("newaddr", {"addresstype": addresstype}) - def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None, description=None): + def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None, + description=None, maxfeepercent=None, retry_for=None, + maxdelay=None, exemptfee=None): """ Send payment specified by {bolt11} with {msatoshi} (ignored if {bolt11} has an amount), optional {label} @@ -772,6 +796,10 @@ def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None, description=No "msatoshi": msatoshi, "label": label, "riskfactor": riskfactor, + "maxfeepercent": maxfeepercent, + "retry_for": retry_for, + "maxdelay": maxdelay, + "exemptfee": exemptfee, # Deprecated. "description": description, } diff --git a/doc/lightning-pay.7 b/doc/lightning-pay.7 index 2e0fd9ff5a61..714585710b56 100644 --- a/doc/lightning-pay.7 +++ b/doc/lightning-pay.7 @@ -51,19 +51,19 @@ randomization\. 1: Route Randomization -2: Shadow Route - - Route randomization means the payment algorithm does not always use the lowest-fee or shortest route\. This prevents some highly-connected node from learning all of the user payments by reducing their fees below the network average\. -Shadow route means the payment algorithm will virtually extend the time -delays along the route, making it appear to intermediate nodes that the -route is longer than it actually is\. This prevents intermediate nodes -from reliably guessing their distance from the payee\. +2: Shadow Route + + +Shadow route means the payment algorithm will virtually extend the route +by adding delays and fees along it, making it appear to intermediate nodes +that the route is longer than it actually is\. This prevents intermediate +nodes from reliably guessing their distance from the payee\. Route randomization will never exceed \fImaxfeepercent\fR of the payment\. @@ -84,6 +84,7 @@ You can monitor the progress and retries of a payment using the The following error codes may occur: +.RS .IP \[bu] -1: Catchall nonspecific error\. .IP \[bu] @@ -109,6 +110,7 @@ invoice expiration) as UNIX epoch time in seconds\. .IP \[bu] 210: Payment timed out without a payment in progress\. +.RE Error codes 202 and 204 will only get reported at \fBsendpay\fR; in \fBpay\fR we will keep retrying if we would have gotten those errors\. @@ -116,6 +118,7 @@ Error codes 202 and 204 will only get reported at \fBsendpay\fR; in A routing failure object has the fields below: +.RS .IP \[bu] \fIerring_index\fR: The index of the node along the route that reported the error\. 0 for the local node, 1 for the first hop, and so on\. @@ -132,6 +135,7 @@ error, or \fI0:0:0\fR if the destination node raised the error\. received from the remote node\. Only present if error is from the remote node and the \fIfailcode\fR has the UPDATE bit set, as per BOLT #4\. +.RE The \fIdata\fR field of errors will include statistics \fIgetroute_tries\fR and \fIsendpay_tries\fR\. It will also contain a \fIfailures\fR field with detailed @@ -150,7 +154,3 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -.HL - -Last updated 2019-08-01 14:59:36 CEST - diff --git a/doc/lightning-pay.7.md b/doc/lightning-pay.7.md index 1bc12e5346ef..2cbeed3684d4 100644 --- a/doc/lightning-pay.7.md +++ b/doc/lightning-pay.7.md @@ -48,17 +48,17 @@ randomization. 1: Route Randomization -2: Shadow Route - Route randomization means the payment algorithm does not always use the lowest-fee or shortest route. This prevents some highly-connected node from learning all of the user payments by reducing their fees below the network average. -Shadow route means the payment algorithm will virtually extend the time -delays along the route, making it appear to intermediate nodes that the -route is longer than it actually is. This prevents intermediate nodes -from reliably guessing their distance from the payee. +2: Shadow Route + +Shadow route means the payment algorithm will virtually extend the route +by adding delays and fees along it, making it appear to intermediate nodes +that the route is longer than it actually is. This prevents intermediate +nodes from reliably guessing their distance from the payee. Route randomization will never exceed *maxfeepercent* of the payment. Route randomization and shadow routing will not take routes that would diff --git a/plugins/pay.c b/plugins/pay.c index 6b0dfc1e5d9c..3819fd524828 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -103,6 +103,11 @@ struct pay_command { /* Any remaining routehints to try. */ struct route_info **routehints; +#if DEVELOPER + /* Disable the use of shadow route ? */ + double use_shadow; +#endif + /* Current node during shadow route calculation. */ const char *shadow_dest; }; @@ -820,25 +825,30 @@ static struct command_result *add_shadow_route(struct command *cmd, const jsmntok_t *chan, *best = NULL; size_t i; u64 sample = 0; - u32 cltv, best_cltv; + struct route_info *route = tal_arr(NULL, struct route_info, 1); json_for_each_arr(i, chan, channels) { - struct amount_sat sat; - u64 v; + u64 v = pseudorand(UINT64_MAX); - json_to_sat(buf, json_get_member(buf, chan, "satoshis"), &sat); - if (amount_msat_greater_sat(pc->msat, sat)) - continue; + if (!best || v > sample) { + struct amount_sat sat; - /* Don't use if total would exceed 1/4 of our time allowance. */ - json_to_number(buf, json_get_member(buf, chan, "delay"), &cltv); - if ((pc->final_cltv + cltv) * 4 > pc->maxdelay) - continue; + json_to_sat(buf, json_get_member(buf, chan, "satoshis"), &sat); + if (amount_msat_greater_sat(pc->msat, sat)) + continue; + + /* Don't use if total would exceed 1/4 of our time allowance. */ + json_to_u16(buf, json_get_member(buf, chan, "delay"), + &route[0].cltv_expiry_delta); + if ((pc->final_cltv + route[0].cltv_expiry_delta) * 4 > pc->maxdelay) + continue; + + json_to_number(buf, json_get_member(buf, chan, "base_fee_millisatoshi"), + &route[0].fee_base_msat); + json_to_number(buf, json_get_member(buf, chan, "fee_per_millionth"), + &route[0].fee_proportional_millionths); - v = pseudorand(UINT64_MAX); - if (!best || v > sample) { best = chan; - best_cltv = cltv; sample = v; } } @@ -850,18 +860,28 @@ static struct command_result *add_shadow_route(struct command *cmd, return start_pay_attempt(cmd, pc, "Initial attempt"); } - pc->final_cltv += best_cltv; + pc->final_cltv += route[0].cltv_expiry_delta; pc->shadow_dest = json_strdup(pc, buf, json_get_member(buf, best, "destination")); + route_msatoshi(&pc->msat, pc->msat, route, 1); tal_append_fmt(&pc->ps->shadow, - "Added %u cltv delay for shadow to %s. ", - best_cltv, pc->shadow_dest); + "Added %u cltv delay, %u base fee, and %u ppm fee " + "for shadow to %s.", + route[0].cltv_expiry_delta, route[0].fee_base_msat, + route[0].fee_proportional_millionths, + pc->shadow_dest); + tal_free(route); + return shadow_route(cmd, pc); } static struct command_result *shadow_route(struct command *cmd, struct pay_command *pc) { +#if DEVELOPER + if (!pc->use_shadow) + return start_pay_attempt(cmd, pc, "Initial attempt"); +#endif if (pseudorand(2) == 0) return start_pay_attempt(cmd, pc, "Initial attempt"); @@ -1023,6 +1043,9 @@ static struct command_result *json_pay(struct command *cmd, double *maxfeepercent; unsigned int *maxdelay; struct amount_msat *exemptfee; +#if DEVELOPER + bool *use_shadow; +#endif if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str), @@ -1034,6 +1057,9 @@ static struct command_result *json_pay(struct command *cmd, p_opt_def("maxdelay", param_number, &maxdelay, maxdelay_default), p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)), +#if DEVELOPER + p_opt_def("use_shadow", param_bool, &use_shadow, true), +#endif NULL)) return command_param_failed(); @@ -1085,6 +1111,9 @@ static struct command_result *json_pay(struct command *cmd, pc->current_routehint = NULL; pc->routehints = filter_routehints(pc, b11->routes); pc->expensive_route = NULL; +#if DEVELOPER + pc->use_shadow = *use_shadow; +#endif /* Get capacities of local channels (no parameters) */ return send_outreq(cmd, "listpeers", listpeers_done, forward_error, pc, diff --git a/tests/test_misc.py b/tests/test_misc.py index d855e1cbf3e2..54f134355242 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -306,7 +306,7 @@ def test_htlc_sig_persistence(node_factory, bitcoind, executor): assert len(l1.rpc.listfunds()['outputs']) == 3 -@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_htlc_out_timeout(node_factory, bitcoind, executor): """Test that we drop onchain if the peer doesn't time out HTLC""" @@ -328,7 +328,7 @@ def test_htlc_out_timeout(node_factory, bitcoind, executor): inv = l2.rpc.invoice(amt, 'test_htlc_out_timeout', 'desc')['bolt11'] assert only_one(l2.rpc.listinvoices('test_htlc_out_timeout')['invoices'])['status'] == 'unpaid' - executor.submit(l1.rpc.pay, inv) + executor.submit(l1.rpc.dev_pay, inv, use_shadow=False) # l1 will disconnect, and not reconnect. l1.daemon.wait_for_log('dev_disconnect: @WIRE_REVOKE_AND_ACK') @@ -373,7 +373,7 @@ def test_htlc_out_timeout(node_factory, bitcoind, executor): l2.daemon.wait_for_log('onchaind complete, forgetting peer') -@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_htlc_in_timeout(node_factory, bitcoind, executor): """Test that we drop onchain if the peer doesn't accept fulfilled HTLC""" @@ -395,7 +395,7 @@ def test_htlc_in_timeout(node_factory, bitcoind, executor): inv = l2.rpc.invoice(amt, 'test_htlc_in_timeout', 'desc')['bolt11'] assert only_one(l2.rpc.listinvoices('test_htlc_in_timeout')['invoices'])['status'] == 'unpaid' - executor.submit(l1.rpc.pay, inv) + executor.submit(l1.rpc.dev_pay, inv, use_shadow=False) # l1 will disconnect and not reconnect. l1.daemon.wait_for_log('dev_disconnect: -WIRE_REVOKE_AND_ACK') diff --git a/tests/test_pay.py b/tests/test_pay.py index 5bdf0b03d483..4e86120d95d7 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -16,12 +16,13 @@ import unittest +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_pay(node_factory): l1, l2 = node_factory.line_graph(2) inv = l2.rpc.invoice(123000, 'test_pay', 'description')['bolt11'] before = int(time.time()) - details = l1.rpc.pay(inv) + details = l1.rpc.dev_pay(inv, use_shadow=False) after = int(time.time()) preimage = details['payment_preimage'] assert details['status'] == 'complete' @@ -36,7 +37,7 @@ def test_pay(node_factory): assert invoice['status'] == 'paid' and invoice['paid_at'] >= before and invoice['paid_at'] <= after # Repeat payments are NOPs (if valid): we can hand null. - l1.rpc.pay(inv) + l1.rpc.dev_pay(inv, use_shadow=False) # This won't work: can't provide an amount (even if correct!) with pytest.raises(RpcError): l1.rpc.pay(inv, 123000) @@ -54,7 +55,7 @@ def test_pay(node_factory): # Must provide an amount! with pytest.raises(RpcError): l1.rpc.pay(inv2) - l1.rpc.pay(inv2, random.randint(1000, 999999)) + l1.rpc.dev_pay(inv2, random.randint(1000, 999999), use_shadow=False) # Should see 6 completed payments assert len(l1.rpc.listsendpays()['payments']) == 6 @@ -64,6 +65,7 @@ def test_pay(node_factory): assert len(payments) == 1 and payments[0]['payment_preimage'] == preimage +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_pay_amounts(node_factory): l1, l2 = node_factory.line_graph(2) inv = l2.rpc.invoice(Millisatoshi("123sat"), 'test_pay_amounts', 'description')['bolt11'] @@ -73,13 +75,14 @@ def test_pay_amounts(node_factory): assert isinstance(invoice['amount_msat'], Millisatoshi) assert invoice['amount_msat'] == Millisatoshi(123000) - l1.rpc.pay(inv) + l1.rpc.dev_pay(inv, use_shadow=False) invoice = only_one(l2.rpc.listinvoices('test_pay_amounts')['invoices']) assert isinstance(invoice['amount_received_msat'], Millisatoshi) assert invoice['amount_received_msat'] >= Millisatoshi(123000) +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_pay_limits(node_factory): """Test that we enforce fee max percentage and max delay""" l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) @@ -120,7 +123,8 @@ def test_pay_limits(node_factory): assert status[2]['strategy'].startswith("Trying route hint") # This works, because fee is less than exemptfee. - l1.rpc.call('pay', {'bolt11': inv['bolt11'], 'msatoshi': 100000, 'maxfeepercent': 0.0001, 'exemptfee': 2000}) + l1.rpc.dev_pay(inv['bolt11'], msatoshi=100000, maxfeepercent=0.0001, + exemptfee=2000, use_shadow=False) status = l1.rpc.call('paystatus', {'bolt11': inv['bolt11']})['pay'][2]['attempts'] assert len(status) == 1 assert status[0]['strategy'] == "Initial attempt" @@ -287,22 +291,23 @@ def test_pay_get_error_with_update(node_factory): l1.daemon.wait_for_log(r'Received channel_update for channel {}/. now DISABLED'.format(chanid2)) +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_pay_optional_args(node_factory): l1, l2 = node_factory.line_graph(2) inv1 = l2.rpc.invoice(123000, 'test_pay', 'desc')['bolt11'] - l1.rpc.pay(inv1, label='desc') + l1.rpc.dev_pay(inv1, label='desc', use_shadow=False) payment1 = l1.rpc.listsendpays(inv1)['payments'] assert len(payment1) and payment1[0]['msatoshi'] == 123000 assert payment1[0]['label'] == 'desc' inv2 = l2.rpc.invoice(321000, 'test_pay2', 'description')['bolt11'] - l1.rpc.pay(inv2, riskfactor=5.0) + l1.rpc.dev_pay(inv2, riskfactor=5.0, use_shadow=False) payment2 = l1.rpc.listsendpays(inv2)['payments'] assert len(payment2) == 1 and payment2[0]['msatoshi'] == 321000 anyinv = l2.rpc.invoice('any', 'any_pay', 'desc')['bolt11'] - l1.rpc.pay(anyinv, label='desc', msatoshi='500') + l1.rpc.dev_pay(anyinv, label='desc', msatoshi='500', use_shadow=False) payment3 = l1.rpc.listsendpays(anyinv)['payments'] assert len(payment3) == 1 and payment3[0]['msatoshi'] == 500 assert payment3[0]['label'] == 'desc' @@ -311,7 +316,7 @@ def test_pay_optional_args(node_factory): assert len(l1.rpc.listsendpays()['payments']) == 3 -@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_payment_success_persistence(node_factory, bitcoind, executor): # Start two nodes and open a channel.. die during payment. # Feerates identical so we don't get gratuitous commit to update them @@ -327,7 +332,7 @@ def test_payment_success_persistence(node_factory, bitcoind, executor): inv1 = l2.rpc.invoice(1000, 'inv1', 'inv1') # Fire off a pay request, it'll get interrupted by a restart - executor.submit(l1.rpc.pay, inv1['bolt11']) + executor.submit(l1.rpc.dev_pay, inv1['bolt11'], use_shadow=False) l1.daemon.wait_for_log(r'dev_disconnect: \+WIRE_COMMITMENT_SIGNED') @@ -351,7 +356,8 @@ def test_payment_success_persistence(node_factory, bitcoind, executor): l1.wait_channel_active(chanid) # A duplicate should succeed immediately (nop) and return correct preimage. - preimage = l1.rpc.pay(inv1['bolt11'])['payment_preimage'] + preimage = l1.rpc.dev_pay(inv1['bolt11'], + use_shadow=False)['payment_preimage'] assert l1.rpc.dev_rhash(preimage)['rhash'] == inv1['payment_hash'] @@ -1600,7 +1606,7 @@ def listpays_nofail(b11): fut = executor.submit(listpays_nofail, inv['bolt11']) # Pay l1->l5 should succeed via straight line (eventually) - l1.rpc.pay(inv['bolt11']) + l1.rpc.dev_pay(inv['bolt11'], use_shadow=False) # This should be OK. fut.result() @@ -1614,7 +1620,7 @@ def listpays_nofail(b11): # It will try l1->l2->l3->l4->l5, which fails. inv = l5.rpc.invoice(10**8, 'test_retry2', 'test_retry2')['bolt11'] with pytest.raises(RpcError, match=r'3 attempts'): - l1.rpc.pay(inv) + l1.rpc.dev_pay(inv, use_shadow=False) @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 otherwise gossip takes 5 minutes!") @@ -1652,7 +1658,7 @@ def test_pay_routeboost(node_factory, bitcoind): # Now we should be able to pay it. start = time.time() - l1.rpc.pay(inv['bolt11']) + l1.rpc.dev_pay(inv['bolt11'], use_shadow=False) end = time.time() # Status should show all the gory details. @@ -1700,7 +1706,7 @@ def test_pay_routeboost(node_factory, bitcoind): 'label': 'test_pay_routeboost2', 'description': 'test_pay_routeboost2', 'dev-routes': [routel3l4l5]}) - l1.rpc.pay(inv['bolt11']) + l1.rpc.dev_pay(inv['bolt11'], use_shadow=False) status = l1.rpc.call('paystatus', [inv['bolt11']]) assert len(only_one(status['pay'])['attempts']) == 2 assert 'failure' in only_one(status['pay'])['attempts'][0] @@ -1722,7 +1728,8 @@ def test_pay_routeboost(node_factory, bitcoind): 'label': 'test_pay_routeboost5', 'description': 'test_pay_routeboost5', 'dev-routes': [routel3l4l5, routel3l5]}) - l1.rpc.pay(inv['bolt11'], label="paying test_pay_routeboost5") + l1.rpc.dev_pay(inv['bolt11'], label="paying test_pay_routeboost5", + use_shadow=False) status = l1.rpc.call('paystatus', [inv['bolt11']]) assert only_one(status['pay'])['bolt11'] == inv['bolt11'] @@ -1797,7 +1804,7 @@ def test_pay_direct(node_factory, bitcoind): for i in range(8): inv = l3.rpc.invoice(20000000, 'pay{}'.format(i), 'desc')['bolt11'] - l0.rpc.pay(inv) + l0.rpc.dev_pay(inv, use_shadow=False) # We should have gone the direct route, so # l1->l2 channel msatoshi_to_us should not @@ -1964,7 +1971,7 @@ def test_setchannelfee_state(node_factory, bitcoind): bitcoind.generate_block(6) l0.wait_for_route(l2) inv = l2.rpc.invoice(100000, 'test_setchannelfee_state', 'desc')['bolt11'] - result = l0.rpc.pay(inv) + result = l0.rpc.dev_pay(inv, use_shadow=False) assert result['status'] == 'complete' assert result['msatoshi_sent'] == 100042 @@ -2029,7 +2036,7 @@ def test_setchannelfee_routing(node_factory, bitcoind): # do and check actual payment inv = l3.rpc.invoice(4999999, 'test_setchannelfee_1', 'desc')['bolt11'] - result = l1.rpc.pay(inv) + result = l1.rpc.dev_pay(inv, use_shadow=False) assert result['status'] == 'complete' assert result['msatoshi_sent'] == 5002020 @@ -2049,7 +2056,7 @@ def test_setchannelfee_routing(node_factory, bitcoind): # do and check actual payment inv = l3.rpc.invoice(4999999, 'test_setchannelfee_2', 'desc')['bolt11'] - result = l1.rpc.pay(inv) + result = l1.rpc.dev_pay(inv, use_shadow=False) assert result['status'] == 'complete' assert result['msatoshi_sent'] == 5000049 @@ -2086,7 +2093,7 @@ def test_setchannelfee_zero(node_factory, bitcoind): # do and check actual payment inv = l3.rpc.invoice(4999999, 'test_setchannelfee_3', 'desc')['bolt11'] - result = l1.rpc.pay(inv) + result = l1.rpc.dev_pay(inv, use_shadow=False) assert result['status'] == 'complete' assert result['msatoshi_sent'] == 4999999 @@ -2128,7 +2135,7 @@ def test_setchannelfee_restart(node_factory, bitcoind): # l1 can make payment to l3 with custom fees being applied # Note: BOLT #7 math works out to 2021 msat fees inv = l3.rpc.invoice(4999999, 'test_setchannelfee_1', 'desc')['bolt11'] - result = l1.rpc.pay(inv) + result = l1.rpc.dev_pay(inv, use_shadow=False) assert result['status'] == 'complete' assert result['msatoshi_sent'] == 5002020 @@ -2336,3 +2343,28 @@ def test_error_returns_blockheight(node_factory, bitcoind): # * [`u32`:`height`] assert (err.value.error['data']['raw_message'] == '400f{:016x}{:08x}'.format(100, bitcoind.rpc.getblockcount())) + + +@flaky +def test_shadow_routing(node_factory): + """ + Test the value randomization through shadow routing + + Since there is a very low (0.5**10) probability that it fails we mark it + as flaky. + """ + # We need l3 for random walk + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) + + amount = 10000 + total_amount = 0 + n_payments = 10 + for i in range(n_payments): + inv = l3.rpc.invoice(amount, "{}".format(i), "test")["bolt11"] + total_amount += l1.rpc.pay(inv)["amount_msat"] + + assert total_amount.millisatoshis > n_payments * amount + # Test that the added amount isn't absurd + assert total_amount.millisatoshis < (n_payments * amount) * (1 + 0.01) + + # FIXME: Test cltv delta too ? diff --git a/tests/test_plugin.py b/tests/test_plugin.py index b1e6ef84cb84..71f6562efa9d 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -239,8 +239,11 @@ def test_pay_plugin(node_factory): l1.rpc.call('pay') # Make sure usage messages are present. - assert only_one(l1.rpc.help('pay')['help'])['command'] == 'pay bolt11 [msatoshi] [label] [riskfactor] [maxfeepercent] [retry_for] [maxdelay] [exemptfee]' - assert only_one(l1.rpc.help('paystatus')['help'])['command'] == 'paystatus [bolt11]' + msg = 'pay bolt11 [msatoshi] [label] [riskfactor] [maxfeepercent] '\ + '[retry_for] [maxdelay] [exemptfee]' + if DEVELOPER: + msg += ' [use_shadow]' + assert only_one(l1.rpc.help('pay')['help'])['command'] == msg def test_plugin_connected_hook(node_factory): @@ -513,7 +516,7 @@ def test_htlc_accepted_hook_forward_restart(node_factory, executor): ], wait_for_announce=True) i1 = l3.rpc.invoice(msatoshi=1000, label="direct", description="desc")['bolt11'] - f1 = executor.submit(l1.rpc.pay, i1) + f1 = executor.submit(l1.rpc.dev_pay, i1, use_shadow=False) l2.daemon.wait_for_log(r'Holding onto an incoming htlc for 10 seconds') @@ -572,6 +575,7 @@ def test_warning_notification(node_factory): l1.daemon.wait_for_log('plugin-pretend_badlog.py log: Test warning notification\\(for broken event\\)') +@unittest.skipIf(not DEVELOPER, "needs to deactivate shadow routing") def test_invoice_payment_notification(node_factory): """ Test the 'invoice_payment' notification @@ -583,7 +587,7 @@ def test_invoice_payment_notification(node_factory): preimage = '1' * 64 label = "a_descriptive_label" inv1 = l2.rpc.invoice(msats, label, 'description', preimage=preimage) - l1.rpc.pay(inv1['bolt11']) + l1.rpc.dev_pay(inv1['bolt11'], use_shadow=False) l2.daemon.wait_for_log(r"Received invoice_payment event for label {}," " preimage {}, and amount of {}msat"