Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add multi pay invoice method #218

Closed
wants to merge 13 commits into from
Closed

Conversation

im-adithya
Copy link
Member

Adds multi_pay_invoice method

@rolznz
Copy link
Contributor

rolznz commented Jan 17, 2024

Hmm, I am not sure this is the best way to do it because of the introduced conditional below and now we have to handle resp and resps, and now there is a lot of array handling and returning an array of resps which we should try to avoid because it forces us to wait for all responses to come back before sending them.

if resp != nil {
		svc.publishEvent(ctx, sub, event, resp)
	}
	if resps != nil {
		...
	}

Also HandleMultiPayInvoiceEvent has it's own waitgroup so that function will not return until all payments have been sent, which means responses won't be sent as soon as they are available.

Maybe we can move svc.publishEvent to the individual handlers (and maybe create a new handler for HandleNotImplemented for the default case in the switch), then the multi_x ones can call svc.publishEvent once per element in the array.

Then we should also remove createAndAppendResponse and not deal with returning an array of responses.

Does that make sense? do you see any issues doing it this way?

service.go Outdated Show resolved Hide resolved
service.go Show resolved Hide resolved
}).Errorf("Failed to process event: %v", err)
return
}
dTag := []string{"a", fmt.Sprintf("%d:%s:%d", NIP_47_RESPONSE_KIND, event.PubKey, invoiceInfo.Id)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this d tag, what is the value? it doesn't seem to match the spec.

Also you construct it in 4 different places, can't you just do it once?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's also why createAndAppendResponse existed.

Then we should also remove createAndAppendResponse and not deal with returning an array of responses.

But I thought you weren't happy with it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this d tag, what is the value? it doesn't seem to match the spec.

I thought d tag here meant this, am I wrong?

Copy link
Contributor

@rolznz rolznz Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought d tag here meant this, am I wrong?

I think it should be a simple tag like ["d", "ID_OR_PAYMENT_HASH HERE"],

Yeah that's also why createAndAppendResponse existed.

I mean, can't you create a variable to store the d tag at the start of the loop and then use it wherever you need it, rather than declare it 4 times? just to keep it DRY (or have a function that does it, but createAndAppendResponse did something different, right? we do not append a response any more)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's also why createAndAppendResponse existed.

Sorry ^one of the reasons why

But changed with the new commit now (although could only dry up dTag declarations)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be a simple tag like ["d", "ID_OR_PAYMENT_HASH HERE"],

Should we confirm with Ben?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a d tag not an a tag. I think you got them mixed up. Can you check the spec again?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also use d tags in the way I mentioned in the PoS app. I will fix this.

models.go Outdated Show resolved Hide resolved
}
payment.Preimage = &preimage
// TODO: What to do here?
nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we need to review these. They do not apply well to the multi methods.

I would probably set it to executed if at least one payment succeeds, otherwise error. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @bumi

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably set it to executed if at least one payment succeeds, otherwise error.

One thought I had was to set a counter to see how many were successful and update this according to that

@frnandu
Copy link
Contributor

frnandu commented Jan 17, 2024

After this is ready for merge, we should also make the changes (cherry pick maybe?) on https://github.com/getAlby/nwc.getalby.com so that Alby backend for NWC also has this methods implemented.

}
dTag := []string{"a", fmt.Sprintf("%d:%s:%s", NIP_47_RESPONSE_KIND, event.PubKey, id)}

hasPermission, code, message := svc.hasPermission(&app, event, request.Method, paymentRequest.MSatoshi)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using request.Method does not work here. We use the NIP_47_PAY_INVOICE_METHOD permission for any payment request

}).Errorf("Failed to process event: %v", err)
return
}
resp.Tags = append(resp.Tags, dTag)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot change the response after it has been signed. Setting tags like this does not work.

I am fixing it.

- fix permission check
- do not change tags after decrypting
@@ -145,11 +80,65 @@ func (svc *Service) StartSubscription(ctx context.Context, sub *nostr.Subscripti
}
}

func (svc *Service) HandleEvent(ctx context.Context, event *nostr.Event) (result *nostr.Event, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the tests still reference this method. Can you update them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am looking at this

alby.go Outdated Show resolved Hide resolved
Code: NIP_47_ERROR_NOT_IMPLEMENTED,
Message: fmt.Sprintf("Unknown method: %s", nip47Request.Method),
}}, nostr.Tags{}, ss)
resp, err = svc.handleUnknownMethod(ctx, nip47Request, event, app, ss)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

@rolznz
Copy link
Contributor

rolznz commented Jan 23, 2024

Tests

  • No test was written yet for the new multi_pay_invoice method
  • The tests are all broken due to the changes in this PR
  • I think the tests are hard to work with right now because they always expect a finished nostr event back which needs to be decoded each time. This is incompatible with multi_* methods.
  • The tests are more complicated because they always need to decrypt the event to check the content. I think it is better to test encryption/decryption once and not deal with it in the other tests, and split up the event handling so the individual functionality can be tested (plus all tests in one file like this is very hard to maintain)

But this will take some time to fix. Do you guys have any thoughts / opinions here? @im-adithya @bumi

@rolznz
Copy link
Contributor

rolznz commented Jan 23, 2024

PR to address some of the TODOs / issues in this PR:

#230

CC @im-adithya @bumi

service_test.go Outdated
// TODO: split up into individual tests
func TestHandleEvent(t *testing.T) {
func TestBackwardsCompatibility(t *testing.T) {
// Test without adding a single permission
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be done in this PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is going to be cleaned up in the self-custodial version. I added a TODO in the Wails PR. Nice!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I just remove this completely then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

@rolznz
Copy link
Contributor

rolznz commented Jan 25, 2024

@im-adithya can you change the merge branch to feat/wails-v2 and merge feat/wails-v2 into this branch?

We decided we will not add the multi_* support to nwc.getalby.com for now.

}

reqEvent.ID = "test_get_balance_without_permission"
res, err := svc.HandleGetBalanceEvent(ctx, request, reqEvent, *app, ss)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the individual handle_x events should deal with encrypting the response. Then we can test encryption/decryption separately in one place. But maybe this could be added as a TODO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can just return the Nip47Response, that would also DRY it up and we can avoid sending ss for no reason to those handlers

assert.NoError(t, err)
assert.Equal(t, received.Error.Code, NIP_47_ERROR_UNAUTHORIZED)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this assert removed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do not have a test for this any more, it would be good to make sure using an unknown pubkey returns unauthorized

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might not be able to do that because that part is handled by HandleEvent which doesn't return anything

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can extract the app finding logic to a new method

@im-adithya im-adithya changed the base branch from main to feat/wails-v2 January 25, 2024 07:34
}).Error(result.Error)
return
}
nostrEvent.ReplyId = resp.ID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we address #231 in this PR?

@rolznz
Copy link
Contributor

rolznz commented Feb 2, 2024

Moved to getAlby#3

@rolznz rolznz closed this Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants