Skip to content

Support group keys for RFQ negotiation flows #1382

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

Merged
merged 7 commits into from
Mar 11, 2025
Merged

Conversation

GeorgeTsagk
Copy link
Member

@GeorgeTsagk GeorgeTsagk commented Feb 12, 2025

Description

This PR introduces the minimum functionality for two nodes to negotiate and register a buy or sell quote, which identifies the underlying asset via a group key instead of an ID.

It includes a basic itest which lets Alice & Bob negotiate a quote based on a specifier which only includes a group key.

Related to #1028, but does not close it.


This change is Reviewable

@GeorgeTsagk
Copy link
Member Author

Rebased on main to include latest changes from #1357

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Great set of commits! Minimal, yet impactful diff.

I think I've spotted a bug where we'll start to compare sats balance instead of assets balance when trying to select the "best" channel. We should follow up there with a unit test to confirm the error, then correct it (if it does indeed exist).

In order to properly test this e2e, I think we may need to expose a group key arg during asset invoice creation. I don't think this requires the full on multi asset group channels (we just want to verify that we can make invoices based no the group key).

Reviewed 1 of 1 files at r1, 2 of 2 files at r2, 2 of 2 files at r3, 1 of 1 files at r4, 1 of 1 files at r5, 1 of 1 files at r6, 2 of 2 files at r7, all commit messages.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @GeorgeTsagk)


rfq/manager.go line 963 at r3 (raw file):

	// Store the result for future calls.
	m.assetIDToGroup.Store(id, groupKeyBytes)

Not something we need to worry about today, but eventually we'll want have some sort of eviction policy here.


rpcserver.go line 7865 at r6 (raw file):

		// the best local balance.
		fn.ForEach(balances, func(b channelWithSpecifier) {
			if b.channelInfo.LocalBalance >

Is .LocalBalance here actually the asset, or sats balance? I ask as it's lnclient.ChannelInfo, so I assume it would be a sats balance.

We should ensure that for the best balance here, we're looking at the asset balance


rpcserver.go line 7909 at r6 (raw file):

		for assetIdx := range assets {
			assetOutput := assets[assetIdx]

Looks like the inner loop here could be an actual Filter, you'd need to contend with the extra return error value, but that's where the Result type can be nice when working with higher order functions/types.


itest/rfq_test.go line 429 at r7 (raw file):

// testRfqNegotiationGroupKey checks that two nodes can negotiate and register
// quotes based on a specifier that only uses a group key.
func testRfqNegotiationGroupKey(t *harnessTest) {

We should complement this test with one on litd that makes RFQ quotes with a group key.

Think aloud, in order to properly test that, I think we may need to support creating asset invoices with a group key at the very least.


rpcserver.go line 7886 at r6 (raw file):

type channelWithAsset struct {
	// assetInfo is the information about one of the assets in a channel.
	assetInfo rfqmsg.JsonAssetChanInfo

Interesting how this seems to duplicate the information in the attribute below it?

Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Just a drive-by review, will take a closer look once marked as ready for review by assigning reviewers.

asset/asset.go Outdated
@@ -245,6 +245,11 @@ func (i ID) String() string {
return hex.EncodeToString(i[:])
}

// IsEqual returns true if the ID matches the provided ID.
func (i ID) IsEqual(a ID) bool {
Copy link
Member

Choose a reason for hiding this comment

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

just a drive-by comment: asset.ID is an array, so it supports the == operator.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see. I'd argue that we still should keep this method, it's a top-level way of comparing without having to dive into the type and see what it's made of

Copy link
Member Author

@GeorgeTsagk GeorgeTsagk left a comment

Choose a reason for hiding this comment

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

Reviewable status: 7 of 8 files reviewed, 5 unresolved discussions (waiting on @guggero and @Roasbeef)


rpcserver.go line 7865 at r6 (raw file):

Previously, Roasbeef (Olaoluwa Osuntokun) wrote…

Is .LocalBalance here actually the asset, or sats balance? I ask as it's lnclient.ChannelInfo, so I assume it would be a sats balance.

We should ensure that for the best balance here, we're looking at the asset balance

good catch, it's actually the sats balance


rpcserver.go line 7886 at r6 (raw file):

Previously, Roasbeef (Olaoluwa Osuntokun) wrote…

Interesting how this seems to duplicate the information in the attribute below it?

this should not be removed, it includes the decoded data of the CustomChannelData field of lndclient.ChannelInfo


rpcserver.go line 7909 at r6 (raw file):

Previously, Roasbeef (Olaoluwa Osuntokun) wrote…

Looks like the inner loop here could be an actual Filter, you'd need to contend with the extra return error value, but that's where the Result type can be nice when working with higher order functions/types.

you mean an fn.Filter? Seems like it would have to obfuscate some errors we want to send upstream, will see if I can pull it through


itest/rfq_test.go line 429 at r7 (raw file):

Previously, Roasbeef (Olaoluwa Osuntokun) wrote…

We should complement this test with one on litd that makes RFQ quotes with a group key.

Think aloud, in order to properly test that, I think we may need to support creating asset invoices with a group key at the very least.

Trying to keep the scope of this PR small, focusing on the establishment of quotes on group keys, before involving send/receive flows anyhow.

Could implement your recommendation in a follow up PR, taking advantage of the imported LitD oracle harness too


rfq/manager.go line 963 at r3 (raw file):

Previously, Roasbeef (Olaoluwa Osuntokun) wrote…

Not something we need to worry about today, but eventually we'll want have some sort of eviction policy here.

True, will throw a todo, or just do something simple like flushing on intervals

asset/asset.go Outdated
@@ -245,6 +245,11 @@ func (i ID) String() string {
return hex.EncodeToString(i[:])
}

// IsEqual returns true if the ID matches the provided ID.
func (i ID) IsEqual(a ID) bool {
Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see. I'd argue that we still should keep this method, it's a top-level way of comparing without having to dive into the type and see what it's made of

@GeorgeTsagk GeorgeTsagk force-pushed the rfq-negotiation-groupkey branch from a76775a to 269ad4b Compare February 24, 2025 18:02
@coveralls
Copy link

coveralls commented Feb 24, 2025

Pull Request Test Coverage Report for Build 13768108410

Details

  • 20 of 123 (16.26%) changed or added relevant lines in 6 files are covered.
  • 27 unchanged lines in 11 files lost coverage.
  • Overall coverage increased (+0.06%) to 54.644%

Changes Missing Coverage Covered Lines Changed/Added Lines %
rfqmsg/buy_request.go 3 4 75.0%
rfqmsg/sell_request.go 3 4 75.0%
asset/asset.go 3 5 60.0%
rpcserver.go 9 38 23.68%
rfq/manager.go 1 71 1.41%
Files with Coverage Reduction New Missed Lines %
commitment/tap.go 1 85.0%
rfq/manager.go 1 58.74%
rfqmsg/buy_request.go 1 57.81%
rfqmsg/sell_request.go 1 59.83%
rpcserver.go 1 64.12%
asset/group_key.go 2 72.65%
asset/mock.go 3 72.3%
itest/assertions.go 3 90.36%
tapgarden/caretaker.go 4 77.73%
asset/asset.go 5 80.14%
Totals Coverage Status
Change from base Build 13731570279: 0.06%
Covered Lines: 49503
Relevant Lines: 90592

💛 - Coveralls

Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Nice work! Have a refactor/simplification suggestion but nothing major.

rpcserver.go Outdated
Comment on lines 7913 to 7930
assetGen := assetOutput.AssetInfo.AssetGenesis
assetIDBytes, err := hex.DecodeString(
assetGen.AssetID,
)
if err != nil {
return false, fmt.Errorf("error "+
"decoding asset ID: %w", err)
}

var assetID asset.ID
copy(assetID[:], assetIDBytes)

match, err := r.cfg.RfqManager.AssetMatchesSpecifier(
ctx, specifier, assetID,
)
if err != nil {
return false, err
}
Copy link
Member

Choose a reason for hiding this comment

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

This is the exact same code as in the previous commit. Perhaps we should extract that into a re-usable method in the Manager:

func (m *Manager) ChannelCompatible(ctx context.Context,
	jsonAsset rfqmsg.JsonAssetChanInfo, specifier asset.Specifier) (bool,
	error) {

	gen := jsonAsset.AssetInfo.AssetGenesis
	assetIDBytes, err := hex.DecodeString(
		gen.AssetID,
	)
	if err != nil {
		return false, fmt.Errorf("error decoding asset ID: %w", err)
	}

	var assetID asset.ID
	copy(assetID[:], assetIDBytes)

	match, err := m.AssetMatchesSpecifier(ctx, specifier, assetID)
	if err != nil {
		return false, err
	}

	// TODO(george): Instead of returning the first result,
	// try to pick the best channel for what we're trying to
	// do (receive/send). Binding a baseSCID means we're
	// also binding the asset liquidity on that channel.
	return match, nil
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Great recommendation, will add it. Although, I think the correct function signature should be

func (m *Manager) ChannelCompatible(ctx context.Context,
	jsonAssets []rfqmsg.JsonAssetChanInfo, specifier asset.Specifier) (bool,
	error)

as we need to check against all assets, to see if the channel satisfies the specifier

We add a helper to the asset specifier which checks whether it is empty,
returning an error if empty.
Previously our sell & buy request message validators would consider the
message invalid if the included specifier didn't set an ID. Now we want
to support group keys, so having either a group key or ID defined is
fine.
@GeorgeTsagk GeorgeTsagk force-pushed the rfq-negotiation-groupkey branch from 269ad4b to 7c660bd Compare March 4, 2025 14:23
@GeorgeTsagk GeorgeTsagk requested a review from guggero March 4, 2025 16:05
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM ⛵

Reviewed 1 of 1 files at r6, 8 of 8 files at r8, 2 of 2 files at r9, 2 of 2 files at r10, 1 of 1 files at r11, 1 of 1 files at r12, 1 of 1 files at r13, 2 of 2 files at r14, all commit messages.
Reviewable status: all files reviewed, 15 unresolved discussions (waiting on @GeorgeTsagk and @guggero)


rpcserver.go line 8017 at r13 (raw file):

		// the best local balance.
		fn.ForEach(balances, func(b channelWithSpecifier) {
			if b.assetInfo.LocalBalance >

👍

Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Almost there!

This commit adds a new interface to the rfq manager which helps us look
up the group an asset ID may belong to. The reason we can't reuse the
same interface from tapsend is a circular import issue. We also
introduce an in memory look-up map to skip a roundtrip to the db, since
this information is static.
We add some asset specifier related helpers, for checking whether an
asset matches a specifier, and also another helper that checks if a
channels' assets satisfy a specifier. These methods will be used in a
following commit.
When trying to add a local scid alias we would look up all channels and
filter them based on whether the asset ID matches the one of the
specifier. We now call the previously introduced helper method to
easily check the asset against the specifier.
When querying for channels we now provide the specifier, allowing the
helper methods to check against the specifier with all the helpers that
we introduced in the previous commits. Now a channel with an asset ID
that belongs to a group, may qualify as valid "balance" of that group.
This commit adds a simple itest which checks that Alice and Bob can
negotiate an rfq quote, using a group key as the specifier.
@GeorgeTsagk GeorgeTsagk force-pushed the rfq-negotiation-groupkey branch from 7c660bd to 6cf7190 Compare March 10, 2025 15:12
@GeorgeTsagk GeorgeTsagk requested a review from guggero March 10, 2025 15:34
@GeorgeTsagk
Copy link
Member Author

Added TODOs, will work on them when related PRs are merged & after this is merged

Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

LGTM 🎉

@GeorgeTsagk GeorgeTsagk added this pull request to the merge queue Mar 11, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Mar 11, 2025
@GeorgeTsagk GeorgeTsagk added this pull request to the merge queue Mar 11, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Mar 11, 2025
@guggero guggero merged commit af847c4 into main Mar 11, 2025
18 checks passed
@github-project-automation github-project-automation bot moved this from 👀 In review to ✅ Done in Taproot-Assets Project Board Mar 11, 2025
@guggero guggero deleted the rfq-negotiation-groupkey branch March 11, 2025 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

4 participants