-
Notifications
You must be signed in to change notification settings - Fork 118
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
Add simple asset autoloop #886
base: master
Are you sure you want to change the base?
Conversation
39fbaf6
to
30bf919
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work as usual @sputn1ck. In my first pass I've left a few suggestions, comments and nits.
assets/client.go
Outdated
AssetIdStr: assetId, | ||
}, | ||
}, | ||
PaymentMaxAmt: uint64(satMinAmt), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we leave a comment here why the minimum swap amount has to match the maximum amount the edge node has to pay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the var. this is just to signal the edge node what the maximum payment amount would be, but essentially unused as we just want to gauge the price. This whole function will be changed once lightninglabs/taproot-assets#1397 lands
|
||
acceptedRes := rfq.GetAcceptedQuote() | ||
if acceptedRes == nil { | ||
return 0, fmt.Errorf("no accepted quote") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "quote wasn't accepted", maybe with details?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually isn't "quote wasn't accepted" as that would be rfq.GetRejectedQuote()
. This would be an internal error in the grpc call so pretty unlikely and a check for the .proto oneof
message type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func (x *AddAssetSellOrderResponse) GetAcceptedQuote() *PeerAcceptedSellQuote {
if x, ok := x.GetResponse().(*AddAssetSellOrderResponse_AcceptedQuote); ok {
return x.AcceptedQuote
}
return nil
}
func (x *AddAssetSellOrderResponse) GetInvalidQuote() *InvalidQuoteResponse {
if x, ok := x.GetResponse().(*AddAssetSellOrderResponse_InvalidQuote); ok {
return x.InvalidQuote
}
return nil
}
func (x *AddAssetSellOrderResponse) GetRejectedQuote() *RejectedQuoteResponse {
if x, ok := x.GetResponse().(*AddAssetSellOrderResponse_RejectedQuote); ok {
return x.RejectedQuote
}
return nil
}
These are the 3 possibilities
60c3a87
to
8c6e77a
Compare
e3dc42f
to
5f72083
Compare
5f72083
to
4a0b1eb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking quite good! I think some separation of concerns within the easy autoloop where we iterate over channels would be simple to add and would greatly enhance testability of the the code. I'd also recommend covering the new functionality with unit tests.
looprpc/client.proto
Outdated
map<string, EasyAssetParams> easy_asset_params = 25; | ||
} | ||
|
||
message EasyAssetParams { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: would be better to call it EasyAssetAutloopParams
?
liquidity/parameters.go
Outdated
|
||
// EasyAutoloopTargetAmount is the target amount of liquidity that we | ||
// want to maintain in our channels. | ||
EasyAutoloopTargetAmount uint64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: why not call this LocalTargetAssetAmt
?
return nil, err | ||
} | ||
assetRfqRequest = &loop.AssetRFQRequest{ | ||
AssetId: assetIDBytes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in this PR but I think we need to start using an asset specifier instead (which is a union of asset id and group key).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ will greatly help with future group key support
liquidity/liquidity.go
Outdated
return nil | ||
} | ||
|
||
log.Debugf("easy autoloop: local_total=%v, target=%v, "+ | ||
"attempting to loop out %v", localTotal, | ||
m.params.EasyAutoloopTarget, amount) | ||
localTarget, amount) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just flagging that we should log the satAmount
here iiuc (though if not renamed, the log is fine).
liquidity/liquidity.go
Outdated
localTotal := btcutil.Amount(0) | ||
for _, channel := range channels { | ||
if channelIsCustom(channel) { | ||
continue | ||
if assetID == "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we should separate the asset/btc per channel logic here, so it's more readable as well as testable.
|
||
// We'll allow a short rfq expiry as we'll only use this rfq to | ||
// gauge a price. | ||
rfqExpiry := time.Now().Add(time.Minute).Unix() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to specify expiry? Is there a default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sell order expiry is actually unused in tapd. I think we should keep this sane value though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rfq expiry is checked when this function is called on intercepted incoming HTLCs
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err) | ||
} | ||
|
||
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we hold the assumption that an asset unit is at least a millisatoshi. What about fractions? Should we scale both asset units and the rate so we can represent fractions correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you consider this to be assumed?
6d8bb95
to
d4eff31
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
liquidity/loopout_builder.go
Outdated
initatior := getInitiator(params) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name 'initatior' appears to be misspelled; consider renaming it to 'initiator' for clarity.
initatior := getInitiator(params) | |
initiator := getInitiator(params) |
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.
4bc6f00
to
e2bc1c8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracting the asset logic from easy autoloop looks good. I've left some more comments.
liquidity/parameters.go
Outdated
@@ -413,6 +427,14 @@ func RpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters, | |||
addrType = walletrpc.AddressType_TAPROOT_PUBKEY | |||
} | |||
|
|||
easyAssetMap := make(map[string]AssetParams) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this reads better if its EasyAssetParams
.
liquidity/parameters.go
Outdated
@@ -528,6 +551,17 @@ func ParametersToRpc(cfg Parameters) (*clientrpc.LiquidityParameters, | |||
addrType = clientrpc.AddressType_ADDRESS_TYPE_UNKNOWN | |||
} | |||
|
|||
easyAssetMap := make(map[string]*clientrpc.EasyAssetAutoloopParams, len( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: formatting
easyAssetMap := make(
map[string]*clientrpc.EasyAssetAutoloopParams,
len(cfg.AssetAutoloopParams),
)
assetData.LocalBalance, | ||
) | ||
|
||
assetPeerPubkey = channel.PubKeyBytes[:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if there are multiple asset channels this overwrites the assetPeerPubkey
. If only one asset channel is allowed initially we should filter it out from the list of channels first, then apply the logic that's here in the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The peer is only used initially to get a first price check for how many sats we request for in the swap request. This is not the peer that is asked for rfq if we actually swap out.
liquidity/liquidity.go
Outdated
|
||
// We'll overwrite the channel local balance in order to | ||
// reuse channel selection logic. | ||
channel.LocalBalance = btcutil.Amount(localTotal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are setting the LocalBalance
already above, is it correct to overwrite it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, thanks
2b227c8
to
4bb5843
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Overview
This PR adds support for a simple asset autoloop mode by extending the asset client and liquidity manager logic. Key changes include adding a new asset pricing method and helper functions in the asset client, extending client and configuration structures to support an asset client, and updating the liquidity manager and swap builder logic for asset-specific autoloop functionality.
Reviewed Changes
File | Description |
---|---|
cmd/loop/debug.go | Adds a new debug command to force an autoloop tick in development builds. |
assets/client.go & client_test.go | Introduces new methods for retrieving asset prices and converting asset amounts. |
cmd/loop/liquidity.go | Updates command flags and parameter handling to support asset ID input. |
looprpc/client.proto | Adds a new message for asset-specific easy autoloop parameters. |
liquidity/parameters.go | Adds asset-specific autoloop parameters to liquidity configuration. |
liquidity/loopout_builder.go | Integrates asset swap information into the swap builder with new options. |
client.go | Updates client construction and asset client field naming for consistency. |
loopd/utils.go | Adds support for passing the asset pricing method to the liquidity manager. |
liquidity/interface.go & loopin_builder.go | Adjusts function signatures to accept additional swap options. |
liquidity/liquidity.go | Implements asset autoloop procedures in the liquidity manager including channel selection and swap dispatch logic. |
liquidity/autoloop_test.go | Introduces tests for asset autoloop, ensuring correct channel selection and swap dispatch behavior. |
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
liquidity/loopout_builder.go:151
- [nitpick] Concatenating the assetID directly to the initiator string may lead to ambiguity or formatting issues. Consider using a more explicit separator or refactoring the initiator composition to clearly distinguish asset-related information.
initiator += "-" + assetSwap.assetID
liquidity/liquidity.go
Outdated
func (m *Manager) dispatchBestAssetEasyAutoloopSwap(ctx context.Context, | ||
assetID string, localTarget uint64) error { | ||
|
||
if len(assetID) != 32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assetID length check assumes that a valid asset ID has a length of 32 characters; however, if assetIDs are hex-encoded from 32-byte values, the expected length is 64. Please verify the intended format and update the validation accordingly.
if len(assetID) != 32 { | |
if len(assetID) != 64 { |
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.
4bb5843
to
5ecc620
Compare
5ecc620
to
9736660
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good just a couple of comments and suggestions. Also I think would be nice to add more test coverage with more conditions: multiple asset/non-asset channels, multiple amounts, min/max etc so we hit more code paths.
liquidity/autoloop_test.go
Outdated
} | ||
|
||
var ( | ||
channels = []lndclient.ChannelInfo{assetChan} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An easy win would be to add more channels here so we can test that the selection criteria works correctly.
93bf3aa
to
0a12675
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cACK!
most of my feedback is concentrated around the approach and considerations for future groupkey support, but overall LGTM
I'll leave the impl details up to the loop experts
@@ -1207,6 +1207,29 @@ message LiquidityParameters { | |||
The address type of the account specified in the account field. | |||
*/ | |||
AddressType account_addr_type = 24; | |||
|
|||
/* | |||
A map of asset parameters to use for swaps. The key is the asset id and the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should think about how this will change in the future to support group keys.
With group channels there's going to be multiple asset IDs in a single channel so I assume the user will want to define the liquidity parameter based on the group key. Not sure if there's any RPC helper to check whether a channel's assets "satisfy" a groupkey, but could be exposed in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something like the internal helper rfq.Manager.ChannelCompatible
return nil, err | ||
} | ||
assetRfqRequest = &loop.AssetRFQRequest{ | ||
AssetId: assetIDBytes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ will greatly help with future group key support
@@ -305,6 +313,15 @@ func (m *Manager) Run(ctx context.Context) error { | |||
} | |||
} | |||
|
|||
for assetID := range m.params.AssetAutoloopParams { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a global flag like EasyAssetAutoloopActive
would help with users switching off all asset swaps, without editing each liquidity parameter individually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a recommendation
liquidity/liquidity.go
Outdated
} | ||
|
||
// Dispatch a sticky loop out. | ||
go m.dispatchStickyLoopOut( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may have been discussed in a prior PR, but I wonder if there's extra risks with making this a sticky loop out
IMO if every attempt grabs a new quote then it should be fine
// If we use a custom channel, the local balance is | ||
// denominated in the asset's unit, so we don't need to | ||
// check the minimum. | ||
!channelIsCustom(channel) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could provide the rfq / price hint here in order to have a more realistic image of what the assets evaluate to. Is this effectively bypassing the minimum restriction?
return nil | ||
} | ||
|
||
for _, asset := range assetData.Assets { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could add a todo here for group key support, will have to aggregate all the asset balances
|
||
// We'll allow a short rfq expiry as we'll only use this rfq to | ||
// gauge a price. | ||
rfqExpiry := time.Now().Add(time.Minute).Unix() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rfq expiry is checked when this function is called on intercepted incoming HTLCs
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err) | ||
} | ||
|
||
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you consider this to be assumed?
0a12675
to
012baa4
Compare
This PR adds a simple asset autoloop mode by reusing the easy autoloop runloop.
Testing instructions:
In order to test you need a docker-regtest setup with an asset channel.
EDIT(hieblmi) Setup with an asset channel with following steps:
Afterwards set up your loop with the extra flags:
You then need to activate your asset autoloop with
Then you can run
./loop-debug forceautoloop
this should dispatch a loop outAI generated summary:
This pull request introduces significant changes to the
assets/client.go
andliquidity/liquidity.go
files, adding new functionality for handling asset prices and improving the overall asset management. The most important changes include the addition of a new method to get asset prices, modifications to the client configuration to include an asset client, and updates to the liquidity manager to support easy autoloop functionality for assets.Asset Management Enhancements:
assets/client.go
: Added a new methodGetAssetPrice
to retrieve the price of an asset in satoshis using the RFQ process. Also, added a helper functiongetSatsFromAssetAmt
to convert asset amounts to satoshis.assets/client_test.go
: Added tests for the newgetSatsFromAssetAmt
function to ensure correct conversion of asset amounts to satoshis.Client Configuration Updates:
client.go
: Updated theClient
struct and related methods to useAssetClient
instead ofassetClient
, ensuring consistency and proper initialization. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]config.go
: AddedAssetClient
to theclientConfig
struct to support asset-related operations.Liquidity Manager Enhancements:
liquidity/liquidity.go
: Introduced new functionality for easy autoloop operations for assets, including methods to handle asset prices and dispatch swaps based on asset balances. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]These changes collectively enhance the system's ability to manage and utilize assets more effectively, providing better support for asset-related operations and improving the overall functionality of the liquidity manager.