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

Add motion staking listeners #36

Merged
merged 11 commits into from
Apr 12, 2023
Merged

Conversation

willm30
Copy link
Contributor

@willm30 willm30 commented Mar 21, 2023

This PR adds motion staking support to the block ingestor. It only handles that the event gets picked up and removed correctly. DB mutations are handled in a subsequent pr.

Test with this CDapp pr: port/270-stake-motion-saga (setup instructions therein)

Also, this PR contains new functionality in the event listener generator and event processor for removing listeners. Please test this out by:

  1. installing and then immediately uninstalling the voting reputation extension
  2. installing the voting rep extension; restarting the block ingestor; and then uninstalling the voting rep extn

You should see the following in the console:

rem-listene

EXTENSION_INITIALISED = 'extensionInitialised',
}

export interface EventProcessorContext {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Allows the listeners to be removed by the extensionUninstalled handler that were generated in extensionInitialised

import { verbose } from '~utils';
import { getMotionSide } from '../helpers';

export default async (event: ContractEvent): Promise<void> => {
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 is extended here and not the focus of this issue. We're just ensuring it picks up the event correctly

@willm30 willm30 force-pushed the add/motion-staking-support branch 3 times, most recently from da697e4 to 74eb9a2 Compare March 23, 2023 12:36
@willm30 willm30 marked this pull request as ready for review March 23, 2023 13:40
@willm30 willm30 changed the title Add motion staking support Add motion staking listeners Mar 23, 2023
@willm30 willm30 requested a review from a team March 23, 2023 13:41
@willm30 willm30 changed the base branch from add/motion-data-to-db to lint/port-motions March 23, 2023 13:45
Copy link
Member

@rdig rdig left a comment

Choose a reason for hiding this comment

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

Seems all good.

Comment on lines 173 to 175
eventProcessContext.removeListeners[colonyAddress]?.[
RemoveListenerKeys.EXTENSION_INITIALISED
];
Copy link
Member

Choose a reason for hiding this comment

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

Uff. While I understand how this came about, it's really a mouth full to read :)

Base automatically changed from lint/port-motions to port/motions March 27, 2023 09:25
src/context.ts Outdated

const context: Context = { listenerRemovers: {} };

export default context;
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 have left off writing a getContext function like I did originally since objects are imported/exported by reference so it's not necessary in this context.

};

/* Generate a unique key for the listenerRemover. */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I say "unique", I'm assuming no client type will have two different events with the exact same signature. Therefore this combination of strings will effectively prevent unintended collisions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If we kept a mapping between contract address and event signature, we could check for existing listeners before adding a new one. I experienced these collisions before in dev, because when restarted, the dev environment replays all the events that happened from block 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When restarting, memory is destroyed and recreated, no? We'd be starting with a fresh context object.

@willm30
Copy link
Contributor Author

willm30 commented Mar 27, 2023

@jakubcolony In this pr, I believe your concern is addressed re handling listeners. Now, the event generator listener can store the listeners in context directly, and I have a written a motion specific function that removes them and calls them (removeMotionListeners), which I call when the extension gets uninstalled.

Pls review when you get a chance.

@willm30 willm30 requested a review from rdig March 27, 2023 17:22
@willm30
Copy link
Contributor Author

willm30 commented Mar 27, 2023

@rdig I pushed some changes to this after you approved to take into account Jakub's suggestion of reducing the points of contact with the context object in the code base. It all stills works but if you'd like to have another glance, that'd be great.

These commits specifically:

51a0acc
519fdd0
1fe0463

Copy link
Member

@rdig rdig left a comment

Choose a reason for hiding this comment

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

All good from my side

Copy link
Collaborator

@jakubcolony jakubcolony left a comment

Choose a reason for hiding this comment

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

Looks much better already but I'm asking for one more round of simplification and a few changes.

One issue is that this is too specific for motions, and without jumping into the future, it doesn't remove the existing ExtensionInitialised listeners when an extension gets removed. Could some changes be made so that it removes all the listeners for a particular contract address when it's no longer relevant?

I think it's important for a new functionality like that to be as isolated as possible and only communicate with the "outside" world by some explicitly exposed methods that can hide all the complexity. Think of it as a class that could do all the work privately and only have a few public methods, such as registerRemover, removeListener, etc. Ideally, all the code related to removing listeners could form a little module within the ingestor.

src/context.ts Outdated
@@ -0,0 +1,11 @@
export type ListenerRemover = () => void;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still can't see the benefit of introducing this concept. It just seems like a place that may grow to contain many unrelated things. Why not just keep it simple and export the functionality directly?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Refactoring this would also allow you to collocate all the code related to removing listeners together, this is definitely improved since the first iteration but still feels like it's in too many places (context, utils, event listeners)

@@ -53,3 +62,34 @@ export const writeMintTokensMotionToDB = async (
},
});
};

const getMotionListenerRemovers = (
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should they be motion specific, or contract address specific? If an extension is uninstalled, a colony deleted (for example, can't happen right now), etc., we probably want to remove all the listeners for that address, not just motions?
This would be another good reason to extract the entire listener removers into their own little module

};

/* Generate a unique key for the listenerRemover. */
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we kept a mapping between contract address and event signature, we could check for existing listeners before adding a new one. I experienced these collisions before in dev, because when restarted, the dev environment replays all the events that happened from block 1.

Comment on lines 53 to 58
const listenerRemoverKey = generateListenerRemoverKey(
contractAddress,
clientType,
eventSignature,
);
ctx.listenerRemovers[listenerRemoverKey] = listenerRemover;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I can imagine there could be a registerListener method that would accept some parameters, then generate the key and add it to the "cache" outside of this function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How could this logic be moved outside of the function? We need a reference to the exact event handler we're removing. Previously I accessed this by returning it from the eventListenerGenerator but based on your feedback have moved it inside.

Copy link
Collaborator

@jakubcolony jakubcolony Apr 12, 2023

Choose a reason for hiding this comment

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

What I imagined was something like this:

// listenerRemover.ts 
const removers = {}; // don't need context, this can be internal to the module

export const saveRemover = (contractAddress, eventSignature, listenerRemover) => {
  const key = generateListenerRemoverKey(...)
  removers[key] = listenerRemover;
}

Then your code in the generator shortens to declaring the remover and calling the saveRemover function. There's no need for the eventListenerGenerator to know about how the removers are stored/accessed.

That's where my analogy to classes came from; you hide as much of the internals as possible, and only expose the public interface (saveRemover and other functions)

@willm30
Copy link
Contributor Author

willm30 commented Apr 7, 2023

@jakubcolony I don't in principle disagree with your feedback, but I would point out that there are many block-ingestor concepts that could have been implemented via classes, and haven't been. Given that, I feel like:

a) my proposed changes are consistent with the style of the repo, and
b) I would benefit from further reading to get myself up to speed with the pattern you're suggesting before implementing it (recommended reading welcome).

I'd also ask you to apply the following test taken from the google engineering practices doc:

In general, reviewers should favor approving a CL once it is in a state where it definitely improves the overall code health of the system being worked on, even if the CL isn't perfect.

I feel like your suggestions could be the subject of a refactoring issue as/when we introduce a new use case for listener removers (currently motions are the only one, right?). Else we risk running the gauntlet of YAGNI / premature abstraction.

I think the listener-remover changes made here are:

  • consistent with the repo style, and
  • improve the health of the repo

They may not be perfect, but I'd prefer we handle changes in a specific refactoring issue if/when it becomes apparent they're needed.

(Btw, I do appreciate your comments! The above is more a discussion of process rather than substance.)

@jakubcolony
Copy link
Collaborator

@willm30 Sorry, my comment might have been written badly. What I meant is collocating the related code together (whatever style you write it; functional as you pointed out is the prevalent one) and gave an OOP class concept as one nicely depicting that - my bad, should've made it clearer. My main point was to keep it together, rather than having the logic spanning across multiple files.

I feel like your suggestions could be the subject of a refactoring issue as/when we introduce a new use case for listener removers (currently motions are the only one, right?). Else we risk running the gauntlet of YAGNI / premature abstraction.

There's the ExtensionInitialised event listener added for each installed extension that should also be removed. There could be a separate issue for that, but I think keeping that in mind might help you refactor this easier (having a separate "module" for removing listeners that would hold all the logic). Tying it closely to motions makes it pretty difficult to refactor down the line.

@willm30
Copy link
Contributor Author

willm30 commented Apr 12, 2023

@jakubcolony Thanks for clarifying. Please review 13f7577 when you get a chance.

Removing the extension initialised listener can be handled here: #49

It should be straightforward enough and has been kept in mind when writing the functionality inside listenerRemover.ts.

const getRemovers = (keys: string[]): ListenerRemover[] => {
const listenerRemovers: ListenerRemover[] = [];

keys.forEach((key) => {
Copy link
Contributor Author

@willm30 willm30 Apr 12, 2023

Choose a reason for hiding this comment

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

I could map here but this is slightly safer because it guards against the scenario where removers[key] returns undefined

(and therefore avoids needing to call removeListeners() as removeListeners?.())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very much a just in case, not expecting it to return undefined

Copy link
Collaborator

@jakubcolony jakubcolony left a comment

Choose a reason for hiding this comment

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

Thanks for addressing the feedback, great work! 👌

@willm30 willm30 merged commit 374bc4a into port/motions Apr 12, 2023
@willm30 willm30 deleted the add/motion-staking-support branch April 12, 2023 14:01
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.

None yet

3 participants