|
| 1 | +package itest |
| 2 | + |
| 3 | +import ( |
| 4 | + "github.com/btcsuite/btcd/btcutil" |
| 5 | + "github.com/lightningnetwork/lnd/lnrpc" |
| 6 | + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" |
| 7 | + "github.com/lightningnetwork/lnd/lntest" |
| 8 | +) |
| 9 | + |
| 10 | +// testLocalReputationLogOnly verifies that enabling the experimental, |
| 11 | +// read-only local reputation subsystem on a forwarding node does not affect |
| 12 | +// routing. It exercises the end-to-end log-only invariant across the paths the |
| 13 | +// switch hooks observe — a successful forward, a failed forward, and a warm |
| 14 | +// restart — asserting forwarding behaviour is unchanged in every case. |
| 15 | +// |
| 16 | +// Beyond non-interference, it also verifies the subsystem actually computes |
| 17 | +// reputation: after a successful forward, Bob's log must contain the |
| 18 | +// "Reputation gained" summary (emitted only when the resolution yields a |
| 19 | +// positive effective fee), confirming the hooks fed a real, positive |
| 20 | +// reputation update. (Asserting exact per-channel values over RPC is deferred |
| 21 | +// to the P10 debug read-out; the log assertion gives behavioural coverage now.) |
| 22 | +func testLocalReputationLogOnly(ht *lntest.HarnessTest) { |
| 23 | + const chanAmt = btcutil.Amount(100_000) |
| 24 | + const paymentAmt = 1000 |
| 25 | + |
| 26 | + // Alice -> Bob -> Carol, with Bob (the forwarding node) running the |
| 27 | + // experimental read-only reputation subsystem. |
| 28 | + alice := ht.NewNodeWithCoins("Alice", nil) |
| 29 | + bob := ht.NewNodeWithCoins("Bob", []string{"--routing.reputation"}) |
| 30 | + carol := ht.NewNode("Carol", nil) |
| 31 | + |
| 32 | + ht.ConnectNodes(alice, bob) |
| 33 | + ht.ConnectNodes(bob, carol) |
| 34 | + |
| 35 | + // Open Alice -> Bob and Bob -> Carol. |
| 36 | + chanPointAB := ht.OpenChannel( |
| 37 | + alice, bob, lntest.OpenChannelParams{Amt: chanAmt}, |
| 38 | + ) |
| 39 | + chanPointBC := ht.OpenChannel( |
| 40 | + bob, carol, lntest.OpenChannelParams{Amt: chanAmt}, |
| 41 | + ) |
| 42 | + |
| 43 | + // Make sure Alice has learned of the Bob -> Carol channel so she can |
| 44 | + // route the multi-hop payment. |
| 45 | + ht.AssertChannelInGraph(alice, chanPointBC) |
| 46 | + |
| 47 | + // 1. Successful forward. Carol invoices, Alice pays via Bob. With Bob's |
| 48 | + // reputation subsystem in log-only mode this must succeed exactly as it |
| 49 | + // would without it (Bob observes OnForward + OnSettle). |
| 50 | + payReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, 1) |
| 51 | + ht.CompletePaymentRequests(alice, payReqs) |
| 52 | + |
| 53 | + // The forward earned Bob a fee, which his reputation subsystem must |
| 54 | + // record as a positive reputation gain for the outgoing (Bob -> Carol) |
| 55 | + // channel. The "Reputation gained" summary is logged at Info only when |
| 56 | + // the effective fee is positive, so its presence confirms the subsystem |
| 57 | + // observed the forward+settle and computed a real, positive update. |
| 58 | + ht.AssertNodeLogContains(bob, "Reputation gained: outgoing=") |
| 59 | + |
| 60 | + // 2. Failed forward. A payment to Carol with an unknown payment hash is |
| 61 | + // routed Alice -> Bob -> Carol and rejected at Carol, so Bob observes |
| 62 | + // the forward and its downstream failure (OnFail). Bob must remain |
| 63 | + // unaffected and the payment must fail cleanly. |
| 64 | + failReq := &routerrpc.SendPaymentRequest{ |
| 65 | + Dest: carol.PubKey[:], |
| 66 | + Amt: paymentAmt, |
| 67 | + PaymentHash: ht.Random32Bytes(), |
| 68 | + FinalCltvDelta: finalCltvDelta, |
| 69 | + FeeLimitMsat: noFeeLimitMsat, |
| 70 | + } |
| 71 | + ht.SendPaymentAssertFail( |
| 72 | + alice, failReq, |
| 73 | + lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:ll |
| 74 | + ) |
| 75 | + |
| 76 | + // 3. Warm restart. Restarting Bob reloads persisted reputation |
| 77 | + // state and discards any in-flight HTLC tracking (the documented |
| 78 | + // log-only behaviour). Bob must come back and keep forwarding. |
| 79 | + ht.RestartNode(bob) |
| 80 | + ht.EnsureConnected(alice, bob) |
| 81 | + ht.EnsureConnected(bob, carol) |
| 82 | + ht.AssertNodeNumChannels(bob, 2) |
| 83 | + ht.AssertChannelActive(bob, chanPointAB) |
| 84 | + ht.AssertChannelActive(bob, chanPointBC) |
| 85 | + |
| 86 | + // A subsequent payment must still forward successfully after the warm |
| 87 | + // restart. |
| 88 | + payReqs2, _, _ := ht.CreatePayReqs(carol, paymentAmt, 1) |
| 89 | + ht.CompletePaymentRequests(alice, payReqs2) |
| 90 | + |
| 91 | + // The post-restart forward must again be observed and scored, |
| 92 | + // confirming the subsystem resumes computing reputation after a |
| 93 | + // warm restart. |
| 94 | + ht.AssertNodeLogContains(bob, "Reputation gained: outgoing=") |
| 95 | + |
| 96 | + ht.CloseChannel(alice, chanPointAB) |
| 97 | + ht.CloseChannel(bob, chanPointBC) |
| 98 | +} |
0 commit comments