Skip to content

Commit

Permalink
Make paying invoice more robust (#86)
Browse files Browse the repository at this point in the history
* feat: make paying invoice more robust

* test: basic GetHashFromInvoice test

* test: fix tests

* test: fix the failing test
  • Loading branch information
fiksn authored May 22, 2023
1 parent 732f097 commit bbe2bb7
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 62 deletions.
5 changes: 5 additions & 0 deletions cmd/bolt-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ func getApp() *cli.App {
Hidden: true,
Value: 5 * time.Minute,
},
&cli.BoolFlag{
Name: "smooth",
Usage: "smooth htlcs",
Hidden: true,
},
}

app.Flags = append(app.Flags, entities.GlogFlags...)
Expand Down
2 changes: 1 addition & 1 deletion lightning/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ type LightingAPICalls interface {
GetOnChainFunds(ctx context.Context) (*Funds, error)
SendToOnChainAddress(ctx context.Context, address string, sats int64, useUnconfirmed bool, urgency Urgency) (string, error)
PayInvoice(ctx context.Context, paymentRequest string, sats int64, outgoingChanIds []uint64) (*PaymentResp, error)
GetPaymentStatus(ctx context.Context, paymentHash string) (*PaymentResp, error)
GetPaymentStatus(ctx context.Context, paymentRequest string) (*PaymentResp, error)
CreateInvoice(ctx context.Context, sats int64, preimage string, memo string, expiry time.Duration) (*InvoiceResp, error)
IsInvoicePaid(ctx context.Context, paymentHash string) (bool, error)

Expand Down
23 changes: 21 additions & 2 deletions lightning/api_clnraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -943,9 +943,28 @@ func (l *ClnRawLightningAPI) calculateExclusions(ctx context.Context, outgoingCh
}

// GetPaymentStatus - API call.
func (l *ClnRawLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash string) (*PaymentResp, error) {
func (l *ClnRawLightningAPI) GetPaymentStatus(ctx context.Context, paymentRequest string) (*PaymentResp, error) {
var reply ClnPaymentEntries
err := l.connection.Call(ctx, ListPays, []interface{}{nil, paymentHash}, &reply, DefaultDuration)

if paymentRequest == "" {
return nil, fmt.Errorf("missing payment request")
}

var (
err error
paymentHash string
)

if strings.HasPrefix(paymentRequest, "ln") {
paymentHash = GetHashFromInvoice(paymentRequest)
if paymentHash == "" {
return nil, fmt.Errorf("bad payment request")
}
} else {
paymentHash = paymentRequest
}

err = l.connection.Call(ctx, ListPays, []interface{}{nil, paymentHash}, &reply, DefaultDuration)
if err != nil {
return nil, err
}
Expand Down
21 changes: 18 additions & 3 deletions lightning/api_clnsocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,27 @@ func TestClnGetPaymentStatus(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(Deadline))
defer cancel()

resp, err := api.GetPaymentStatus(ctx, "d4b75c00becfb3d50b98e296a3f8f4f590af2093fddf870c01ad2e19c8e13994")
resp, err := api.GetPaymentStatus(ctx, "lnbc13370n1pjx3794pp56jm4cq97e7ea2zucu2t287857kg27gynlh0cwrqp45hpnj8p8x2qdqqcqzzsxqyz5vqsp57587usq4ph5axdvjgvxhfqh64p295utlarcn88tna6q73audza0q9qyyssq6584t5datgf0kgw3thk5ny85dd3wjgmckrqs9w7ja03l8pqd643rt6zdlhc5pf6ktc6m6mrgn2d3tjesw6ups2yr96g4jp0dm9s4tmgp359yyu")

assert.NoError(t, err)
assert.NotEqual(t, 0, resp.Preimage)
}

func TestClnGetPaymentStatusLegacy(t *testing.T) {
data := clnData(t, "cln_listpay")

_, err = api.GetPaymentStatus(ctx, "d4b75c00becfb3d50b98e296a3f8f4f590af2093fddf870c01ad2e19c8e13995")
assert.Error(t, err)
_, api, closer := clnCommon(t, func(c net.Conn) {
clnCannedResponse(t, c, "listpay", data)
})
defer closer()

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(Deadline))
defer cancel()

resp, err := api.GetPaymentStatus(ctx, "d4b75c00becfb3d50b98e296a3f8f4f590af2093fddf870c01ad2e19c8e13994")

assert.NoError(t, err)
assert.NotEqual(t, 0, resp.Preimage)
}

func TestClnCreateInvoice(t *testing.T) {
Expand Down
76 changes: 60 additions & 16 deletions lightning/api_grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ func GetMsatsFromInvoice(bolt11 string) uint64 {
}

firstNumber := strings.IndexAny(bolt11, "1234567890")
if firstNumber == -1 {
if firstNumber <= 2 {
return 0
}

Expand All @@ -880,6 +880,33 @@ func GetMsatsFromInvoice(bolt11 string) uint64 {
return 0
}

func GetHashFromInvoice(bolt11 string) string {
if len(bolt11) < 2 {
return ""
}

firstNumber := strings.IndexAny(bolt11, "1234567890")
if firstNumber <= 2 {
return ""
}

chainPrefix := bolt11[2:firstNumber]
chain := &chaincfg.Params{
Bech32HRPSegwit: chainPrefix,
}

inv, err := zpay32.Decode(bolt11, chain)
if err != nil {
return ""
}

if inv.PaymentHash == nil {
return ""
}

return hex.EncodeToString(inv.PaymentHash[:])
}

// PayInvoice API.
func (l *LndGrpcLightningAPI) PayInvoice(ctx context.Context, paymentRequest string, sats int64, outgoingChanIds []uint64) (*PaymentResp, error) {
req := &routerrpc.SendPaymentRequest{}
Expand Down Expand Up @@ -909,14 +936,6 @@ func (l *LndGrpcLightningAPI) PayInvoice(ctx context.Context, paymentRequest str
resp, err := l.RouterClient.SendPaymentV2(ctx, req)

if err != nil {
// TODO: we don't know the hash here, else we could return l.GetPaymentStatus()
if strings.Contains(err.Error(), "invoice is already paid") {
return nil, nil

} else if strings.Contains(err.Error(), "AlreadyExists desc = payment is in transition") {
return nil, nil
}

return nil, err
}

Expand All @@ -925,12 +944,11 @@ func (l *LndGrpcLightningAPI) PayInvoice(ctx context.Context, paymentRequest str
return nil, ctx.Err()
}
event, err := resp.Recv()
if err == io.EOF {
break
}

if err != nil {
if strings.Contains(err.Error(), "AlreadyExists desc = payment is in transition") {
return nil, nil
}

return nil, err
}

Expand Down Expand Up @@ -958,16 +976,37 @@ func (l *LndGrpcLightningAPI) PayInvoice(ctx context.Context, paymentRequest str
}

case lnrpc.Payment_FAILED:
return nil, fmt.Errorf("failed payment")
return &PaymentResp{
Hash: event.PaymentHash,
Status: Failed,
}, nil
}
}

return nil, fmt.Errorf("eof")
}

// GetPaymentStatus API.
func (l *LndGrpcLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash string) (*PaymentResp, error) {
func (l *LndGrpcLightningAPI) GetPaymentStatus(ctx context.Context, paymentRequest string) (*PaymentResp, error) {
var err error
req := &routerrpc.TrackPaymentRequest{}
req.PaymentHash, err = hex.DecodeString(paymentHash)
if paymentRequest == "" {
return nil, fmt.Errorf("missing payment request")
}

if strings.HasPrefix(paymentRequest, "ln") {
paymentHash := GetHashFromInvoice(paymentRequest)
if paymentHash == "" {
return nil, fmt.Errorf("bad payment request")
}

req.PaymentHash, err = hex.DecodeString(paymentHash)
if err != nil {
return nil, fmt.Errorf("bad payment request")
}
} else {
req.PaymentHash = []byte(paymentRequest)
}
if err != nil {
return nil, err
}
Expand All @@ -985,6 +1024,9 @@ func (l *LndGrpcLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash
}

event, err := resp.Recv()
if err == io.EOF {
break
}

if err != nil {
return nil, err
Expand All @@ -1011,6 +1053,8 @@ func (l *LndGrpcLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash
}, nil
}
}

return nil, fmt.Errorf("eof")
}

// CreateInvoice API.
Expand Down
14 changes: 12 additions & 2 deletions lightning/api_grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ type PayInvoiceRespFunc func(resp *PaymentResp, err error)
func TestPayInvoiceGrpc(t *testing.T) {
for _, currentCase := range []Pair[string, PayInvoiceRespFunc]{
{First: "payinvoice_noroute", Second: func(resp *PaymentResp, err error) {
assert.Error(t, err)
assert.NoError(t, err)
assert.Equal(t, Failed, resp.Status)
}},
{First: "payinvoice_inflight", Second: func(resp *PaymentResp, err error) {
assert.NoError(t, err)
Expand Down Expand Up @@ -543,7 +544,7 @@ func TestGetPaymentStatusGrpc(t *testing.T) {
TrackPaymentV2(gomock.Any(), gomock.Any()).
Return(c, nil)

resp, err := api.GetPaymentStatus(context.Background(), "12581685c1f79db4049d7757ba51fc6170c33eec2dd1cb19bae3c20036f86937")
resp, err := api.GetPaymentStatus(context.Background(), "lnbcrt13370n1p3lwhhnpp5zfvpdpwp77wmgpyawatm550uv9cvx0hv9hgukxd6u0pqqdhcdymsdqqcqzpgxqyz5vqsp5xsum9s4cpkvw27f6smk4daxqkpjgmah8xxhs8ty34fm4srmwfvjs9qyyssqlx5zk7j8lfeelzmpsk0mmwp3583tl52j8us2q9nt05vrmtp3sasxzc8wyjchrum67sllzr52gjz26rcrye6y4vrlpr6pyv9jhhlrp2cq52rxf5")
assert.NoError(t, err)
assert.Equal(t, Success, resp.Status)
}
Expand Down Expand Up @@ -665,4 +666,13 @@ func TestGetMsatsFromInvoice(t *testing.T) {
assert.Equal(t, expected, GetMsatsFromInvoice("lntb1m1pjyxysnpp5x24d0yvj0c7atwhf88l8c7xu7l80xnymhhuaz7j5u4yp6dfcgqcqdqqcqzpgxqyz5vqsp5uq3c68sh2fqrdwl2l90ccxw7qpfn65cgs3n5lrd8nwu3f2hrxk8q9qyyssqzdr5gqj35u30fra74d02utsq3gljkhfj9zpvxp8ljhaclz49zkjpy2gxha53ejyu8am8m0q97ql7algt068tze8p8wwfquc4e7m3z8cp6a2mp9"))
assert.Equal(t, expected, GetMsatsFromInvoice("lnbc1m1pjyxyw7pp5ftl92yhqxnp925ppl7tr7txze2px38ccgr5msekvgru0v9fthzcqdqqcqzpgxqyz5vqsp5xa4zr56xw6fkprpc9w75cyyv4zdv0hxw7em770qetxsuz9ywsy6s9qyyssqpuyv9ft83utw87wv0xlan4r4wju7rd4ktk6cyls9da6w0qjjagn94x5hjlaez0l5tfw9ttkhezh2jw9hgd0vpncfyrpspzatww57pvsq4cx0cg"))
assert.Equal(t, uint64(0), GetMsatsFromInvoice("lnbc"))

assert.Equal(t, uint64(806246000), GetMsatsFromInvoice("lnbc8062460n1pjxgfldpp5e96fqfeahqdlvse0mmmfl3vnv90huhl96cnuwxp7w90nrzdzhe2sdql2djkuepqw3hjqsj5gvsxzerywfjhxuccqzynxqrrsssp5ha03x05dx2s0gyf69dvk2rh0gma7xr009znr9m06tlnqwcf0xweq9qyyssqe5glksza37wee3lgtpste2yt3xgrjwj8en5au0sf28qfhpffmkprnppwk3vl2m6mcumxx9v84jh974a4jqs2a2a92g2mguwt22xpkgqq43qyfs"))
}

func TestGetHashFromInvoice(t *testing.T) {
assert.Equal(t, "32aad791927e3dd5bae939fe7c78dcf7cef34c9bbdf9d17a54e5481d35384030", GetHashFromInvoice("lntb1m1pjyxysnpp5x24d0yvj0c7atwhf88l8c7xu7l80xnymhhuaz7j5u4yp6dfcgqcqdqqcqzpgxqyz5vqsp5uq3c68sh2fqrdwl2l90ccxw7qpfn65cgs3n5lrd8nwu3f2hrxk8q9qyyssqzdr5gqj35u30fra74d02utsq3gljkhfj9zpvxp8ljhaclz49zkjpy2gxha53ejyu8am8m0q97ql7algt068tze8p8wwfquc4e7m3z8cp6a2mp9"))
assert.Equal(t, "c97490273db81bf6432fdef69fc593615f7e5fe5d627c7183e715f3189a2be55", GetHashFromInvoice("lnbc8062460n1pjxgfldpp5e96fqfeahqdlvse0mmmfl3vnv90huhl96cnuwxp7w90nrzdzhe2sdql2djkuepqw3hjqsj5gvsxzerywfjhxuccqzynxqrrsssp5ha03x05dx2s0gyf69dvk2rh0gma7xr009znr9m06tlnqwcf0xweq9qyyssqe5glksza37wee3lgtpste2yt3xgrjwj8en5au0sf28qfhpffmkprnppwk3vl2m6mcumxx9v84jh974a4jqs2a2a92g2mguwt22xpkgqq43qyfs"))
assert.Equal(t, "", GetHashFromInvoice("l"))
assert.Equal(t, "", GetHashFromInvoice("lnbc"))
}
2 changes: 1 addition & 1 deletion lightning/api_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (m *MockLightningAPI) PayInvoice(ctx context.Context, paymentRequest string
panic("not implemented")
}

func (m *MockLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash string) (*PaymentResp, error) {
func (m *MockLightningAPI) GetPaymentStatus(ctx context.Context, paymentRequest string) (*PaymentResp, error) {
panic("not implemented")
}

Expand Down
24 changes: 19 additions & 5 deletions lightning/api_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,25 @@ func (l *LndRestLightningAPI) PayInvoice(ctx context.Context, paymentRequest str
}

// GetPaymentStatus API.
func (l *LndRestLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash string) (*PaymentResp, error) {
func (l *LndRestLightningAPI) GetPaymentStatus(ctx context.Context, paymentRequest string) (*PaymentResp, error) {
req := &TrackPaymentRequestOverride{}
req.PaymentHash = paymentHash
req.NoInflightUpdates = true

if paymentRequest == "" {
return nil, fmt.Errorf("missing payment request")
}

if strings.HasPrefix(paymentRequest, "ln") {
paymentHash := GetHashFromInvoice(paymentRequest)
if paymentHash == "" {
return nil, fmt.Errorf("bad payment request")
}

req.PaymentHash = paymentHash
} else {
req.PaymentHash = paymentRequest
}

resp, err := l.HTTPAPI.HTTPTrackPayment(ctx, l.Request, req)
if err != nil {
return nil, err
Expand All @@ -730,19 +744,19 @@ func (l *LndRestLightningAPI) GetPaymentStatus(ctx context.Context, paymentHash
case "succeeded":
return &PaymentResp{
Preimage: resp.PaymentPreimage,
Hash: paymentHash,
Hash: req.PaymentHash,
Status: Success,
}, nil
case "failed":
return &PaymentResp{
Preimage: "",
Hash: paymentHash,
Hash: req.PaymentHash,
Status: Failed,
}, nil
default:
return &PaymentResp{
Preimage: "",
Hash: paymentHash,
Hash: req.PaymentHash,
Status: Pending,
}, nil
}
Expand Down
11 changes: 3 additions & 8 deletions plugins/boltz/swap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,18 +313,13 @@ func TestPayHodlInvoiceGrpc(t *testing.T) {
api, err := ln()
require.NoError(t, err)

request := "lnbcrt1200n1pjy9dsxpp52nwgn5e2z5cxkccwwlcsr53mn66elv6dum6xd346r5k3xrnwlqzsdqqcqzpgxqyz5vqsp5s7rndl282gtcqn86t5zv2rak4qe2ma0n0dn45saxwajszhkps36s9qyyssqudjn4pan0lrucj0rq82x8e48u9p7lh8nmgjtsn3vs3qeqcwqh7spdsd0tps9erthgydtrgpx8dg8yk5q3nwdp6dglh5c9823fj5tmlqq3rvctw"
channel := uint64(178120883765248)
request := "lnbcrt211pjxwcndpp5ns4kuz97pww5andpd4rvs3allwrtptt2tk0mzzct0rjpffn7lm0sdqqcqzpgxqyz5vqsp5j6ph6ql60y3xrpvdxytsh2hslrg2ztwghh22hjtce5vx7sqdlaus9qyyssq99smdrc625teq0mjsk3zp7dnrk4vejt7x6ayxy99eqxhednanhzptgktzhaayu34dy42jtgutvzehu8lmh3g2z0ezufzt8xrfgkpecqqy0ntpt"
//channel := uint64(178120883765248)

resp, err := api.PayInvoice(context.Background(), request, 0, []uint64{channel})
resp, err := api.PayInvoice(context.Background(), request, 0, []uint64{})
assert.NoError(t, err)

fmt.Printf("%+v\n", resp)

resp2, err := api.PayInvoice(context.Background(), request, 0, []uint64{})
assert.NoError(t, err)

fmt.Printf("%+v\n", resp2)

t.Fail()
}
Loading

0 comments on commit bbe2bb7

Please sign in to comment.