From c01a66783f89cd99c0cef635ac648629ec8821a5 Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Tue, 10 Jun 2025 11:35:16 -0300 Subject: [PATCH] routerrpc: inject clock for testable expiry validation Refactors payment request expiry validation to use an injected clock dependency instead of calling time.Now() directly. --- lnrpc/routerrpc/router_backend.go | 11 ++++++++--- lnrpc/routerrpc/router_backend_test.go | 6 ++++++ rpcserver.go | 5 ++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index b74484d5c..6c0fe8294 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -15,6 +15,7 @@ import ( "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" @@ -118,6 +119,10 @@ type RouterBackend struct { // ShouldSetExpEndorsement returns a boolean indicating whether the // experimental endorsement bit should be set. ShouldSetExpEndorsement func() bool + + // Clock is the clock used to validate payment requests expiry. + // It is useful for testing. + Clock clock.Clock } // MissionControl defines the mission control dependencies of routerrpc. @@ -980,7 +985,7 @@ func (r *RouterBackend) extractIntentFromSendRequest( } // Next, we'll ensure that this payreq hasn't already expired. - err = ValidatePayReqExpiry(payReq) + err = ValidatePayReqExpiry(r.Clock, payReq) if err != nil { return nil, err } @@ -1370,10 +1375,10 @@ func UnmarshalFeatures( // ValidatePayReqExpiry checks if the passed payment request has expired. In // the case it has expired, an error will be returned. -func ValidatePayReqExpiry(payReq *zpay32.Invoice) error { +func ValidatePayReqExpiry(clock clock.Clock, payReq *zpay32.Invoice) error { expiry := payReq.Expiry() validUntil := payReq.Timestamp.Add(expiry) - if time.Now().After(validUntil) { + if clock.Now().After(validUntil) { return fmt.Errorf("invoice expired. Valid until %v", validUntil) } diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index 5a633d9e6..962ea05b9 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -5,9 +5,11 @@ import ( "context" "encoding/hex" "testing" + "time" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/lnmock" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -509,6 +511,9 @@ func TestExtractIntentFromSendRequest(t *testing.T) { target, err := route.NewVertexFromBytes(destNodeBytes) require.NoError(t, err) + mockClock := &lnmock.MockClock{} + mockClock.On("Now").Return(time.Date(2025, 3, 1, 13, 0, 0, 0, time.UTC)) + testCases := []extractIntentTestCase{ { name: "Time preference out of range", @@ -706,6 +711,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { return false }, ActiveNetParams: &chaincfg.RegressionNetParams, + Clock: mockClock, }, sendReq: &SendPaymentRequest{ Amt: int64(paymentAmount), diff --git a/rpcserver.go b/rpcserver.go index 3c72d3459..6b293626e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -696,6 +696,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, routerBackend := &routerrpc.RouterBackend{ SelfNode: selfNode.PubKeyBytes, + Clock: clock.NewDefaultClock(), FetchChannelCapacity: func(chanID uint64) (btcutil.Amount, error) { @@ -5609,7 +5610,9 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme } // Next, we'll ensure that this payreq hasn't already expired. - err = routerrpc.ValidatePayReqExpiry(payReq) + err = routerrpc.ValidatePayReqExpiry( + r.routerBackend.Clock, payReq, + ) if err != nil { return payIntent, err }