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

refactor account/watcher and add mocks/tests #316

Merged
merged 6 commits into from
Dec 8, 2021
Merged

Conversation

positiveblue
Copy link
Contributor

  • Make account/manager take an interface for the watcher instead of a explicit type.
  • Split the watcher in two components:
    • Controller: initialize/delete logic + dispatch handlers
    • Watcher: watcher state + handle logic
  • Add tests for controller + watcher

@positiveblue positiveblue force-pushed the watcher-interface-v3 branch 2 times, most recently from 5c32556 to c17d06b Compare November 21, 2021 01:21
@guggero guggero self-requested a review November 22, 2021 16:09
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.

Very nice to see the new mocks in action. The refactor looks pretty good to me, just a few suggestions for the naming and how to simplify it even more.

account/watcher/watcher.go Outdated Show resolved Hide resolved
account/watcher/controller.go Outdated Show resolved Hide resolved
account/watcher/controller.go Outdated Show resolved Hide resolved
account/watcher/watcher.go Outdated Show resolved Hide resolved
account/watcher/controller_test.go Outdated Show resolved Hide resolved
account/watcher/controller_test.go Outdated Show resolved Hide resolved
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.

Looking good! Way better with the EventHandler interface IMO. See my comment about getting rid of the Handlers struct as well.
After that we're good to go 🎉

account/watcher/controller.go Outdated Show resolved Hide resolved

// Controller is the interface used by other components to communicate with the
// watcher.

Copy link
Member

Choose a reason for hiding this comment

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

nit: extra newline.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, it's removed in a later commit. Can move it here.

@@ -153,13 +153,17 @@ func NewManager(cfg *ManagerConfig) *Manager {
quit: make(chan struct{}),
}

m.watcherCtrl = watcher.NewController(&watcher.Config{
ChainNotifier: cfg.ChainNotifier,
eventHandler := watcher.NewEventHandler(&watcher.Handlers{
Copy link
Member

Choose a reason for hiding this comment

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

I think we should just export those handle* methods on the manager and just pass m here instead of adding this wrapper struct.

account/watcher/watcher.go Show resolved Hide resolved
@positiveblue
Copy link
Contributor Author

positiveblue commented Dec 2, 2021

@guggero I don't know why I cannot reply under every comment so I will reply to all of them here:

one downside of having a non-exported implementation

yes, I would avoid both: return a non exported struct type and return an interface. However, given that this is only used in account/manager having it non exported is not a big problem.

export those handle* methods on the manager and just pass m here instead of adding this wrapper struct.

(Y)

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.

Great initiative on this 💯 Some final nits, otherwise LGTM 🎉

account/watcher/controller.go Show resolved Hide resolved
account/manager.go Show resolved Hide resolved
account/watcher/controller.go 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.

Nice refactors, and tests! My only major comment is about the expiry watcher, other than that looks good!

}

// OverdueExpirations handles the expirations for the given block.
func (w *expiryWatcher) OverdueExpirations(blockHeight uint32) {
Copy link
Member

Choose a reason for hiding this comment

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

I think a better design could be to have a priority queue of accounts where the priority is defined by the block height. This would allow us to expire everything before and at the specified block height.

Copy link
Member

Choose a reason for hiding this comment

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

Or alternatively make newblock/expiry atomic.

continue
}

err := w.handlers.HandleAccountExpiry(
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to lock for HandleAccountExpiry? An alternative could be to collect expiring accounts in the lock then expire them outside the lock.


// NewBlock updates the current bestHeight.
func (w *expiryWatcher) NewBlock(bestHeight uint32) {
w.bestHeight = bestHeight
Copy link
Member

Choose a reason for hiding this comment

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

Since expiry happens when the height is changed, it'd be possible to simplify the interface a bit and do the height change and the expiry handling atomically under the same lock. By only expiring the current block if two blocks come right after each other in succession we may miss an expiration (almost no chance in practice but still).

account/watcher/controller_test.go Outdated Show resolved Hide resolved
account/watcher/controller_test.go Outdated Show resolved Hide resolved
account/watcher/controller_test.go Outdated Show resolved Hide resolved
account/watcher/controller_test.go Outdated Show resolved Hide resolved
account/watcher/controller_test.go Outdated Show resolved Hide resolved
account/watcher/watcher_test.go Outdated Show resolved Hide resolved
account/watcher/watcher_test.go Outdated Show resolved Hide resolved
@positiveblue
Copy link
Contributor Author

Feedback updated 👍

For some of the question:

better design could be to have a priority queue of accounts where the priority is defined by the block height

Totally agree. I think it is something we can add later though. Do you know any good implementation that allows update the priority of the elements in it?

By only expiring the current block if two blocks come right after each other in succession we may miss an expiration (almost no chance in practice but still).

Yes I thought about this but it cannot happen because the controller was calling the two funcs (new block + overdue expirations) before being able to receive another block (iterate the select). I made them atomic, better put it together so we do not get confused and use the API wrong

Copy link
Contributor

@carlaKC carlaKC left a comment

Choose a reason for hiding this comment

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

Just did a high level pass since there are already 2 reviewers. Great work on interface refactors + mock :)

// that is confirmation or spend.
type Watcher struct {
// controller implements the Controller interface
type controller struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add compile time assertion that we satisfy the controller interface?
var _ Controller = (*controller)(nil)

account/watcher/controller.go Show resolved Hide resolved
HandleAccountExpiry(*btcec.PublicKey, uint32) error
}

// ExpiryWatcher is the interface for the component in charge of the accounts' expiration.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: wrap at 80?
These look a bit over

expectedErr string
}{{
name: "Watch account spend happy path",
// TODO (positiveblue): add tests for `cancel` logic
Copy link
Contributor

Choose a reason for hiding this comment

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

happening in this PR or a follow up?

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.

LGTM 💯

}

delete(expirationsPerHeight, bestHeight)
c.watcher.NewBlock(uint32(newBlock))
Copy link
Member

Choose a reason for hiding this comment

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

One thing to keep in mind is that if HandleAccountExpiry blocks for some time, then the blockChan might get clogged.

Use the new watcher interface in the account manager instead of of a specific
struct type.
Split watcher logic in three pices:
    - Controller: API + message dispatching
    - ExpiryWatcher: handle account expirations
    - EventHandler: implementation for each handler
@guggero guggero merged commit 6884748 into master Dec 8, 2021
@guggero guggero deleted the watcher-interface-v3 branch December 8, 2021 15:13
@guggero guggero mentioned this pull request May 5, 2022
1 task
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.

4 participants