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

[7/?] StaticAddr: Loop-In #786

Merged
merged 14 commits into from
Nov 5, 2024

Conversation

hieblmi
Copy link
Collaborator

@hieblmi hieblmi commented Jun 28, 2024

This PR introduces loop-ins of static address deposits.
The client/server interaction is depicted below:

image

Open tasks that need resolution until this PR can leave draft mode:

  • Refactorings, unify code with reservations/instantout
  • DB storage & fsm recovery
  • Manual quote acceptance
  • FSM transition consistency checks
  • unit + integration tests
  • signing multiple fee-version htlcs, potential anchor output

Previously merged PRs:

#642
#650
#677
#719
#721
#750

master...static-addr-staging

@hieblmi hieblmi self-assigned this Jun 28, 2024
@hieblmi hieblmi marked this pull request as draft June 28, 2024 12:59
@hieblmi hieblmi force-pushed the static-addr-staging branch 2 times, most recently from d807a95 to e635d63 Compare July 1, 2024 10:51
@hieblmi hieblmi force-pushed the static-addr-7 branch 2 times, most recently from d813b20 to 447d70d Compare July 9, 2024 10:13
@hieblmi hieblmi force-pushed the static-addr-7 branch 3 times, most recently from 7ee61f6 to 3eb5bd2 Compare July 17, 2024 07:11
@hieblmi hieblmi force-pushed the static-addr-staging branch from e635d63 to 2c94110 Compare July 17, 2024 10:13
@hieblmi hieblmi force-pushed the static-addr-7 branch 4 times, most recently from a151ffd to d69e9d5 Compare July 18, 2024 08:19
@hieblmi hieblmi force-pushed the static-addr-staging branch from 2c94110 to ae3a983 Compare July 22, 2024 08:43
@hieblmi hieblmi force-pushed the static-addr-staging branch from ae3a983 to 0e92035 Compare July 29, 2024 07:29
@hieblmi hieblmi force-pushed the static-addr-7 branch 6 times, most recently from 127d76c to a9d9e6f Compare July 30, 2024 14:38
@hieblmi hieblmi marked this pull request as ready for review July 30, 2024 14:38
@hieblmi hieblmi requested review from bhandras and starius July 30, 2024 14:38
Copy link
Collaborator

@starius starius left a comment

Choose a reason for hiding this comment

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

Reviewed only the first commit with protobuf types "looprpc: static address loop-ins".

looprpc/client.proto Show resolved Hide resolved

/*
EXPIRED indicates that the deposit has expired and the sweep transaction
has been sufficiently confirmed.
*/
EXPIRED = 6;
EXPIRED = 10;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe rename this state to EXPIRY_SWEEP_CONFIRMED?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These states were introduced in a different PR, so I'd add create a separate refactor PR to fix these up.

looprpc/client.proto Outdated Show resolved Hide resolved
looprpc/client.proto Show resolved Hide resolved
swapserverrpc/server.proto Outdated Show resolved Hide resolved
swapserverrpc/server.proto Show resolved Hide resolved
swapserverrpc/server.proto Show resolved Hide resolved
swapserverrpc/server.proto Outdated Show resolved Hide resolved
swapserverrpc/server.proto Outdated Show resolved Hide resolved
Copy link
Collaborator

@starius starius left a comment

Choose a reason for hiding this comment

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

Reviewed another 3 commits:

  • sqlc: loop-in tables and queries
  • log: static address loop-in logging
  • staticaddr: deposit changes to be squashed

loopdb/sqlc/queries/static_address_loopin.sql Outdated Show resolved Hide resolved
loopdb/sqlc/queries/static_address_loopin.sql Outdated Show resolved Hide resolved
staticaddr/deposit/fsm.go Outdated Show resolved Hide resolved
staticaddr/deposit/fsm.go Show resolved Hide resolved
staticaddr/deposit/fsm.go Outdated Show resolved Hide resolved
staticaddr/deposit/fsm.go Outdated Show resolved Hide resolved
Copy link
Member

@bhandras bhandras left a comment

Choose a reason for hiding this comment

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

Really awesome and huge work! 🥇 I only have a few questions and nit comments left.
tACK, LGTM 🌊

WHERE swap_hash = u.swap_hash
)
WHERE
',' || $1 || ',' LIKE '%,' || u.update_state || ',%'
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 a nice trick! I'd say sqlc.slice is slightly more readable though, I had to test this filter condition a bit to make sure it works as expected.

@@ -34,7 +34,7 @@ const (

// DefaultTransitionTimeout is the default timeout for transitions in
// the deposit state machine.
DefaultTransitionTimeout = 1 * time.Minute
DefaultTransitionTimeout = 5 * time.Second
Copy link
Member

Choose a reason for hiding this comment

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

sgtm

}

// AllOutpointsActiveDeposits checks if all deposits referenced by the outpoints
// are active and in the specified state.
// are in out in-mem active deposits map and in the specified state. If
Copy link
Member

Choose a reason for hiding this comment

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

nit: in out => in our


m.Lock()
defer m.Unlock()
_, deposits := m.toActiveDeposits(&outpoints)
Copy link
Member

Choose a reason for hiding this comment

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

This may not cause obvious problems, but iiuc AllOutpointsActiveDeposits (and other functions using toActiveDeposits(...) may suffer from non-atomicity. Instead of separately fetching active deposits, then locking them if they are in the target state we should be atomically filter and lock them.

Copy link
Collaborator Author

@hieblmi hieblmi Oct 30, 2024

Choose a reason for hiding this comment

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

You are right, there's a gap in coverage. It could be that a deposit is removed from the active map after the toActiveDeposits call.
Instead, we can m.mu.Lock() throughout the AllOutpointsActiveDeposits call and in other calls sites, so the in-mem deposit map is locked during filtering.

Left a comment that the caller of toActiveDeposits should call mu.Lock on the manager.

@@ -1429,12 +1429,17 @@ func (s *swapClientServer) WithdrawDeposits(ctx context.Context,
}
}

err = s.withdrawalManager.WithdrawDeposits(ctx, outpoints)
txhash, pkScript, err := s.withdrawalManager.DeliverWithdrawalRequest(
Copy link
Member

Choose a reason for hiding this comment

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

nit: time to squash these.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was thinking to squash them on the staging branch once this PR is merged to it.

}

// toStaticAddressLoopIn converts sql rows to an instant out struct.
func toStaticAddressLoopIn(_ context.Context, network *chaincfg.Params,
Copy link
Member

Choose a reason for hiding this comment

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

+1, perhaps again this could come as a follow up PR to reduce scope creep.

fee := feeRate.FeeForWeight(f.loopIn.htlcWeight())
if fee > maxHtlcTxFee {
// Abort the swap by pushing empty sigs to the server.
pushEmptySigs()
Copy link
Member

Choose a reason for hiding this comment

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

nit: Why not push empty sigs for the above errors?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great point, if added it in all error cases after the call ServerStaticAddressLoopIn.

if err != nil {
err = fmt.Errorf("unable to derive htlc timeout sweep "+
"address: %w", err)

Copy link
Member

Choose a reason for hiding this comment

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

nit: Why not push empty sigs here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

deposit.SweepHtlcTimeout,
)
if err != nil {
log.Errorf("unable to transition "+
Copy link
Member

Choose a reason for hiding this comment

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

In this case manual intervention would be needed iiuc?

Copy link
Collaborator Author

@hieblmi hieblmi Oct 30, 2024

Choose a reason for hiding this comment

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

The timeout transaction would be published and funds would be safe, but the client would show the deposits in state Deposited(cause the payment deadline hit and reset the deposits). So for sanity the state would have to be changed manually, yes.

log.Errorf("%v: %w", txLabel, err)
f.LastActionError = err
return err
}
Copy link
Member

Choose a reason for hiding this comment

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

Ideally we also want to keep on republishing the timeout sweep (and therefore ignore errors handled here). We don't currently republish unless we recovered and we have one in the mempool already but what happens if it is priced out?

// ServerStaticAddressLoopIn initiates a static address loop-in swap. The
// server will respond with htlc details that the client can use to
// construct and sign the htlc tx.
rpc ServerStaticAddressLoopIn (ServerStaticAddressLoopInRequest)
Copy link
Member

Choose a reason for hiding this comment

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

wdyt about moving this to a seperate file and service?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it makes sense given the number of endpoints and also given the pattern we have with reservation/instantout. I think that's a candidate for a follow-up PR.

Copy link
Member

Choose a reason for hiding this comment

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

For awareness: a new service would be a breaking change


// The info the server used to generate the standard fee partial htlc tx
// sigs.
ServerHtlcSigningInfo standard_htlc_info = 3;
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

@hieblmi hieblmi Oct 31, 2024

Choose a reason for hiding this comment

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

That'd be much cleaner, although these maps don't allow enum keys, just arbitrary <string,string> etc. mappings, so we'd have to setup keys for this map in code rather than in the protobuf definition, which makes it a little less clear what's transported imo.
Can change it ofc if there's strong opinions.

// Once the htlc tx is initiated, we store the loop-in in the database.
err = f.cfg.Store.CreateLoopIn(ctx, f.loopIn)
if err != nil {
err = fmt.Errorf("unable to store loop-in in db: %w", err)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

here as well called pushEmptySigs().

@hieblmi hieblmi force-pushed the static-addr-7 branch 4 times, most recently from 63f4946 to a50d3d7 Compare October 30, 2024 15:58
@hieblmi hieblmi requested a review from sputn1ck October 31, 2024 09:55
Copy link
Member

@sputn1ck sputn1ck left a comment

Choose a reason for hiding this comment

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

LGTM tACK! 🥳 🥳 🥳 🥳

Copy link
Collaborator

@starius starius left a comment

Choose a reason for hiding this comment

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

LGTM 🏆

Few notes

Description: `
Requests a loop-in swap based on static address deposits. After the
creation of a static address funds can be send to it. Once the funds are
confirmed on-chain they can be swapped instantaneously. If deposited
Copy link
Collaborator

Choose a reason for hiding this comment

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

I propose to add the number of confirmations needed here.

// Debugf logs a debug message with the loop-in swap hash.
func (f *FSM) Debugf(format string, args ...interface{}) {
if f.loopIn == nil {
log.Infof(format, args...)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be "log.Infof" I think

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch, should be log.debugf.

)

const (
defaultConfTarget = 3
Copy link
Collaborator

Choose a reason for hiding this comment

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

I propose to change to 6

// initiation time of the swap. If more than the swaps configured payment
// timeout has passed, the remaining time will be negative.
func (l *StaticAddressLoopIn) RemainingPaymentTimeSeconds() int64 {
elapsedSinceInitiation := time.Since(l.InitiationTime).Seconds()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I propose to add lnd/clock instance to StaticAddressLoopIn and use it here, so this place can be fully tested (by passing a test clock). I propose to add a unit test for this method, testing it at various times.

@hieblmi hieblmi force-pushed the static-addr-staging branch from 90c9c2c to f959936 Compare November 5, 2024 08:58
@hieblmi hieblmi force-pushed the static-addr-7 branch 2 times, most recently from 2b5b1c4 to d4967f4 Compare November 5, 2024 09:11
rpcs for loop client and server are added to
facilitate static address swap requests.
We add CRU procedures for static address swaps.
The static address state machine adds states
to reflect the state of a deposit during a
loop-in swap.
In this commit we introduce maximum fee percentages
for the static loop-in htlc transactions. Since
the server has the ability to publish htlc transactions
without settling the swap payment we have to restrict
the amount the server allocates for fees of these
transactions.
In this commit we add the static address
loop-in state machine and its orchestration
through the manager.
@hieblmi hieblmi merged commit d277bff into lightninglabs:static-addr-staging Nov 5, 2024
4 checks passed
@hieblmi hieblmi deleted the static-addr-7 branch November 5, 2024 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants