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

Implement local watcher #182

Conversation

manoranjith
Copy link
Contributor

@manoranjith manoranjith commented Sep 9, 2021

Overview

  • This PR implements the server side component local watcher as per the design described in proposal#4.
  • This component should be initialized by the user of the framework and pass it as an argument to the client.New API.

Implementation details

  • The interface definitions are located in a package: go-perun/watcher.
  • The implementation of local watcher is located in a go-perun/watcher/local.

Data structures

  1. Watcher:

    • This represents the watcher instance, initializes using the NewWatcher function.
    • It contains
      • channel.RegistererSubscriber instance for interacting with the blockchain.
      • registry that holds a map of all the channels currently registered with the Watcher.
    • Both the above data structures are safe for concurrent use.
  2. ch:

    • Represents the data corresponding to a channel.
    • It contains
      • id of the channel.

      • params of the channel. Required while registering dispute for the channel. This will be passed when registering the channel with the Watcher via StartWatching function.

      • parent of this channel. For ledger channels, this is set to channel.Zero.

      • subChsAccess, (relevant only for ledger channel) a mutex used for concurrency safe access of a family of channels. When a dispute registration is required for a channel, we traverse back to the parent and lock this mutex. It is unlocked only after the dispute registration is completed. This also ensures, we lock only the relevant channels and not the entire registry when registering disputes.

      • registeredVersion: when a dispute is registered for a ledger, sub or virtual channel, this field is updated. This is used to ensure, we do not register the same version more than once.

      • requestLatestTx and latestTx are a set of channels used for retrieving the latest state of this channel without use of mutex locks.

  3. registry: Used to store and retrieve channels registered with the watcher. It is protected by a mutex.

  4. StatesPubSub and AdjudicatorPubSub implements the functionalities exactly as described in the proposal.

Methods

  1. NewWatcher initializes the watcher.
  2. Watcher.StartWatchingForLedgerChannel and Watcher.StartWatchingForNonLedgerChannel` APIs are used to register a channel with the watcher. Though the implementations of both the APIs are same, two APIs are provided because
    • LedgerChannel does not require parent while,
    • Sub-Channel requires a parent.
    • Regarding the names, I also think we need a better name, used this for time being.
  3. AdjudicatorSub.EventStream can be used by the client for receiving adjudicator events from the watcher.
  4. StatesPub.Publish can be used by the client to send newer transactions to the watcher.
  5. Watcher.StopWatching and Watcher.Shutdown are yet to implemented.

Tests

It was difficult for me to figure out how to add tests using the simulated backend. Particularly, I couldn't figure out how to make the client send events to the watcher, before integrating the watcher API into client.

However, I implemented some tests based on generated mocks , which I used these to guide my development. I have added them in the PR (in separate commits) for time being. After figuring out how to test simulated backed, we could also replace these tests.

Open points

  1. What do we do when registration fails ? Should we load the error information into the adjudicator events pub-sub to close it ?
  2. What do we do in the below scenario ?
    1. Ledger channel (LC1) is registered with the watcher.
    2. Client opens a sub-channel (SC1).
    3. Funding transaction is completed on LC1 and updated to the watcher.
    4. Before SC1 registers itself with the watcher, an event is RegisteredEvent is received for LC1.
    5. Now, when we want to collect the latest state for all sub-channels of LC1, we will not be able to fund SC1.

Resolves #174

@manoranjith manoranjith force-pushed the 174-implement-local-watcher branch 3 times, most recently from 8d2d2a9 to b36713f Compare September 9, 2021 18:40
@manoranjith manoranjith force-pushed the 174-implement-local-watcher branch 2 times, most recently from 659c280 to 096646e Compare September 13, 2021 14:10
- Previously, the adjudicator interface consisted of four methods.

- But, only some of the methods were required in any particular place.

- Hence, define an interface for each of the methods and compose the
  adjudicator interfaces from these smaller interfaces.

Signed-off-by: Manoranjith <[email protected]>
- Add an interface "RegisterSubscriber" that combines the Register and
  Subscribe methods. This will be used by the watcher.

Signed-off-by: Manoranjith <[email protected]>
@manoranjith manoranjith marked this pull request as ready for review September 16, 2021 00:19
@manoranjith manoranjith requested review from matthiasgeihs and removed request for matthiasgeihs September 16, 2021 00:20
- Implementation is same for ledger and non-ledger channels
  (sub/virtual).

- However, since non-ledger channel require an additional parameter
  (parent), seaprate API interfaces are provided.

- Mocks (placed in watcher/internal/mocks) were generated using testify
  framework.

Signed-off-by: Manoranjith <[email protected]>
- Use a channel based mechanism to retrieve the latest transactions
  while handling dispute in order to avoid mutex locks.

Signed-off-by: Manoranjith <[email protected]>
- Usage of the API is described in doc comments and all the scenarios
  are covered in tests.

Signed-off-by: Manoranjith <[email protected]>
@manoranjith manoranjith changed the title 174 Implement local watcher Implement local watcher Sep 16, 2021
Copy link
Contributor

@matthiasgeihs matthiasgeihs 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 few high level comments. Could you give me and @ggwpez a code intro in our next meeting?

//
// This channel will be closed when client requests the watcher to stop
// watching or when there is an error. After the channel is closed, error
// message can be read using the Err method.
AdjudicatorSub interface {
Next() <-chan channel.AdjudicatorEvent
EventStream() <-chan channel.AdjudicatorEvent
Copy link
Contributor

Choose a reason for hiding this comment

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

Wha happens if EventStream() is called twice? Should the implementation support multiplexing?

Copy link
Contributor Author

@manoranjith manoranjith Sep 21, 2021

Choose a reason for hiding this comment

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

Updated documentation that Repeated calls to the EventStream method returns the same channel instance. in PR #212.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think multiplexing is not necessary in our use case, because the only consumer of the events is the watch function.

If at a later point in time, we have the need for consuming the events in multiple places, then we could implement multiplexing.

}

func (w *Watcher) startWatching(ctx context.Context, parent channel.ID, signedState channel.SignedState) (
watcher.StatesPub, watcher.AdjudicatorSub, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The format looks odd. It looks like this is the first line of the function while in fact it is the beginning of the return parameters. If the line gets too long, you can place each parameter on a separate line, like

func (w *Watcher) StartWatchingSubChannel(
	ctx context.Context,
	parent channel.ID,
	signedState channel.SignedState,
) (watcher.StatesPub, watcher.AdjudicatorSub, error) {

I think this is more intuitive to read.

The same comment applies to all functions using that format.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have updated in PR #213.

// StartWatchingLedgerChannel starts watching for a ledger channel.
func (w *Watcher) StartWatchingLedgerChannel(ctx context.Context, signedState channel.SignedState) (
watcher.StatesPub, watcher.AdjudicatorSub, error) {
return w.startWatching(ctx, channel.Zero, signedState)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like the usage channel.Zero. Even though it seems very unlikely that a channel will have ID zero, it would still be better to not make any assumptions about certain ID values.

It would be better to explicitly mark the parent parameter as optional. You could use a pointer for that and check for nil, which is also not super clean but at least it does not require any assumptions about the ID value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have updated the implementation to use nil instead of Channel.Zero See PRs #213 and #214.

thisCh *ch) {
for e := eventsFromChainSub.Next(); e != nil; e = eventsFromChainSub.Next() {
switch e.(type) {
case *channel.RegisteredEvent:
Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed in #207 , you may need to check also for the Progressed and Concluded events to be sure that you notice channel registration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in #215.

Copy link
Contributor

@ggwpez ggwpez left a comment

Choose a reason for hiding this comment

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

Comments from the meeting.

watcher/watcher.go Show resolved Hide resolved
watcher/watcher.go Outdated Show resolved Hide resolved
watcher/watcher.go Show resolved Hide resolved
watcher/local/statespubsub.go Show resolved Hide resolved
watcher/local/registry.go Show resolved Hide resolved
watcher/local/watcher_test.go Show resolved Hide resolved
watcher/local/watcher.go Show resolved Hide resolved
watcher/local/watcher.go Show resolved Hide resolved
watcher/local/watcher.go Show resolved Hide resolved
watcher/local/watcher.go Show resolved Hide resolved
@manoranjith manoranjith marked this pull request as draft September 21, 2021 05:36
@manoranjith manoranjith marked this pull request as ready for review September 21, 2021 05:40
Registerer
Withdrawer
Progresser
EventSubscriber
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This can be removed. As adjudicator does not make subscriptions.

Copy link
Contributor Author

@manoranjith manoranjith Sep 21, 2021

Choose a reason for hiding this comment

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

EventSubscriber removed from Adjudicator, as it not used by the client anymore. (It is used only by the Watcher and Watcher uses the RegisterSubscriber method. See PR #212.

@matthiasgeihs
Copy link
Contributor

Closed in favor #248

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement the local watcher
3 participants