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

[3/?] StaticAddr: Deposit tracking and timeout sweep #677

Merged
merged 9 commits into from
Apr 25, 2024

Conversation

hieblmi
Copy link
Collaborator

@hieblmi hieblmi commented Dec 20, 2023

It introduces tracking of deposits to a static address and monitoring of the CSV expiry. Instead of using RegisterConfirmationsNtfn to detect spends to a pkScript, we poll the lnd account of the imported static address taproot script and notify the main event loop of the manager about new deposits to a static address.

Todo:

- state machine recovery
- listening for timeout tx confirmations to be sure that the client claimed funds.

@hieblmi hieblmi self-assigned this Dec 20, 2023
@hieblmi hieblmi marked this pull request as draft December 20, 2023 18:50
staticaddr/manager.go Outdated Show resolved Hide resolved
staticaddr/actions.go Outdated Show resolved Hide resolved
staticaddr/manager.go Outdated Show resolved Hide resolved
@hieblmi hieblmi force-pushed the static-addr-3 branch 3 times, most recently from dde94ea to b2f400c Compare December 22, 2023 14:15
@hieblmi hieblmi changed the base branch from master to static-addr-staging February 15, 2024 12:13
@hieblmi hieblmi marked this pull request as ready for review February 16, 2024 10:56
@hieblmi hieblmi requested a review from sputn1ck February 16, 2024 10:57
@hieblmi hieblmi force-pushed the static-addr-3 branch 4 times, most recently from 75feb5c to 298ba8e Compare February 23, 2024 14:13
staticaddr/manager.go Outdated Show resolved Hide resolved
@hieblmi hieblmi force-pushed the static-addr-3 branch 4 times, most recently from 2db3756 to 2b1456b Compare March 4, 2024 13:03
@hieblmi hieblmi removed the request for review from GeorgeTsagk March 4, 2024 13:06
// If the deposit is expired but not yet sufficiently confirmed, we
// republish the expiry sweep transaction.
if f.deposit.isExpired(currentHeight, params.Expiry) {
if f.deposit.State == WaitForExpirySweep {
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we just send the OnExpiry event here an handle it in the fsm, so that it republishes? This would require WaitForExpirySweepAction to be async, but IMO makes the whole flow more sensible me.

Copy link
Member

Choose a reason for hiding this comment

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

Hm I guess what speaks against this, is that republishing might error. Which if we'd need to handle that in the fsm itself would be more complicated. So I'm fine with the current approach

Copy link
Member

Choose a reason for hiding this comment

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

But maybe it's worth to remodel this a bit, as we're currently not saving any sweep txid to the db, which IMO is somewhat important for bookkeeping

Copy link
Member

Choose a reason for hiding this comment

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

We could in WaitForExpirySweepAction, just call something like buildAndPublishTimeoutSweep(f.ctx, f.cfg) which could return a txid and save it to the db.

This would allow merging the PublishExpiredDeposit and WaitForExpirySweep states. in handleBlockNotification we could just send the onexpiry event and in the fsm call the action itself again when the event is sent.

Copy link
Collaborator Author

@hieblmi hieblmi Apr 17, 2024

Choose a reason for hiding this comment

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

we could just send the onexpiry event and in the fsm call the action itself again when the event is sent.

Wouldn't the new merged state action also wait for the confirmation of the sweep and hence block processing of new events until the state action returns a.k.a the expiry got confirmed? So we couldn't call buildAndPublishTimeoutSweep again since we wouldn't receive the event till the sweep is confirmed.

What do you think about extracting the sweep tx once we are in the final state Expired and then update the deposit with it, rather than needing to keep track of replacement txns while the sweep is unconfirmed?

EDIT: In the latest commits I am attaching the confirmed sweep tx id after 3 confirmations.

Copy link
Member

Choose a reason for hiding this comment

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

As a side note this is also not safe as we're running on the block notification goroutine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks yes, I've added a mutex to the deposit and locked all operations on the state.

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.

Looking really good now! Would be nice to add some test coverage too, but can come in a follow up PR as well given the side branch.

looprpc/client.proto Show resolved Hide resolved
$7
);

-- name: UpdateDeposit :exec
Copy link
Member

Choose a reason for hiding this comment

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

It's simpler to use just one UpdateDeposit function but perhaps worth considering a more granular approach where depending on the state change we only update the confirmation height or the expiry txid. WDYT? Obviously this would also mean a slight change in how the FSM works and I could also argue its a bit overly cautious.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could do that. Are you thinking that instead of updating in each states entry function we'd update only relevant fields of the deposit at the end of each action through a state-customized update function?

Or we could switch on the state in updateDeposit and call the related update method for the state transition?

Copy link
Member

Choose a reason for hiding this comment

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

Yes I'm thinking of using a state customized update functions. Also not a blocker for this PR, just an idea for later.

@@ -0,0 +1,29 @@
package address
Copy link
Member

Choose a reason for hiding this comment

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

Another approach to subpackage logging could be to just have one logger in the staticaddr package and then each subpackage just defines a global variable (log) which we assign with a modified UseLogger function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That reminds me that I didn't want to clutter the logs too much with subsystem logs. I think we can just summarize all deposit/withdraw/loop-in operations inside the SADDR logger. I think the log becomes easier to read then.

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 placed a single logger into the staticaddr package. For the loop-in I might add a separate logger, but creation/deposits/withdrawals I think can be under the same sublogger.

staticaddr/deposit/actions.go Show resolved Hide resolved
}

depositStates := depoFsm.DepositStatesV0()
switch params.ProtocolVersion {
Copy link
Member

Choose a reason for hiding this comment

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

nit: this check could go just below where we call GetStaticAddressParameters().

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 has to stay here since if there'd be a V1 state machine we need the depoFsm struct to retrieve those states.

staticaddr/deposit/manager.go Show resolved Hide resolved
// If the deposit is expired but not yet sufficiently confirmed, we
// republish the expiry sweep transaction.
if f.deposit.isExpired(currentHeight, params.Expiry) {
if f.deposit.State == WaitForExpirySweep {
Copy link
Member

Choose a reason for hiding this comment

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

As a side note this is also not safe as we're running on the block notification goroutine.

@hieblmi
Copy link
Collaborator Author

hieblmi commented Apr 19, 2024

Thanks for the review @bhandras, I plan to add some unit tests to increase test coverage.

@hieblmi hieblmi force-pushed the static-addr-3 branch 2 times, most recently from 8ae63de to 3c03fc7 Compare April 23, 2024 17:30
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.

Awesome work on the deposit FSM 🚀 LGTM ⚡

$7
);

-- name: UpdateDeposit :exec
Copy link
Member

Choose a reason for hiding this comment

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

Yes I'm thinking of using a state customized update functions. Also not a blocker for this PR, just an idea for later.

func NewAddressManager(cfg *ManagerConfig) *Manager {
// NewManager creates a new address manager.
func NewManager(cfg *ManagerConfig) *Manager {
log = staticaddr.GetLogger()
Copy link
Member

Choose a reason for hiding this comment

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

nit: alternatively, you could do this in the package's init() function too, so it's executed only once at the static initialization stage.

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 idea, obvious in hindsight.

d.Lock()
defer d.Unlock()

d.State = state
Copy link
Member

Choose a reason for hiding this comment

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

nit: given we have setter and getter maybe we don't even need to export State.

args.Error(1)
}

func TestManager(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: missing godoc

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, Great Work!!!

@hieblmi hieblmi merged commit 3961d45 into lightninglabs:static-addr-staging Apr 25, 2024
4 checks passed
@hieblmi hieblmi deleted the static-addr-3 branch April 25, 2024 10:39
@hieblmi hieblmi mentioned this pull request Jun 28, 2024
6 tasks
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.

4 participants