-
Notifications
You must be signed in to change notification settings - Fork 8
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
Fixes initialisation process on the web frontend #166
Conversation
Pull Request Test Coverage Report for Build 3067527931Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
const promises = Array.from(nodeProxyAddresses.values()).map((proxy) => { | ||
if (proxy !== '') { | ||
const promises = Array.from(nodeProxyAddresses).map(([node, proxy]) => { | ||
if (proxy !== '' && DKGStatuses.get(node) === NodeStatus.NotInitialized) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is enough to prevent the initialization of nodes that were already initialized, because of this cough beautiful Promise.all
(I'm sorry .....) line 221, which either set them all up to Initialized or none...
I think one way to do this would be to use the component DKGStatusRow
, so that each node initializes itself and polls it own status. useChangeAction
could then be notified of the changes via the states in DKGStatuses
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Luckily that works in this case, because "undefined" (and any other non-promise values) count as "resolved". 🙃
I think one way to do this would be to use the component DKGStatusRow, so that each node initializes itself and polls it own status. useChangeAction could then be notified of the changes via the states in DKGStatuses ?
Yes, that would be much better indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but it will return undefined
only if the proxy address of the node could not be found (i.e proxy===''
). Otherwise in pollDKG
:
// Add a timeout
if (attempts === maxAttempts) {
throw new Error('Timeout');
}
...
catch (e) {
return reject(e);
}
So if during the poll one of the node times out for some reason the Promise will be rejected thus the Promise.all
will resolve to rejected for all of them. And the request to initialize will again be sent for all the nodes ...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yes, that's true :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can try to solve this this coming week since it will be the start of term, but after that I probably won't have the time anymore 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the DKG logic to DKGStatusRow, as suggested. That wasn't straightforward as there are probably still too much states spread around between useChangeAction.tsx
, Show.tsx
, and DKGStatusRow.tsx
. Having your input on that would help, if you are willing to :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's already so much better ! But yes there are way too many states to be passed around, it's a bit cumbersome ....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing. However, there is nothing I can add to help here.
🍶 time...
7ef4cd2
to
cda589a
Compare
The setup process is quite shiny now: Screen.Recording.2022-09-15.at.13.28.29.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite a rework ! I love the new states when doing the setup 🚀
But there are still some bugs, and I haven't tested all the cases yet 😅
// legitimate status. | ||
useEffect(() => { | ||
if ( | ||
ongoingAction === OngoingAction.Initializing && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a node is unreachable (either because it could not retrieve its proxy address or because the call to getDKGActor fails), the first intilization works. But then the Status of the election is stuck in Initial, and when clicking again on Initialize node, the process is stuck in this ongoing action (I was looking into how to fix this but haven't found a solution yet)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it's actually the same problem as before, the parents are not notified since there are no changes the second time we click on Initializing (neither the Status nor the DKGLoading states change...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be solved in Show.tsx
and inside the notifyDKGState
function. There we can count the number of Initialized nodes (even if a node is already initialized, it will call this function at least once with its Initialized status), and update the status+ongoingAction once all nodes are initialized. What do you think ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understood exactly what you meant
There's already a useEffect
that checks if all the node were initialized and transitions to the Status Initialized if so (in Show.tsx
line 165). But it's not triggered because none of the entries in DKGStatuses
change.
So yes if a node is already initialized, I think it should still call notifyDKGState
so that it doesn't stay stuck in Initializing.
But I have no idea how it doesn't always remain stuck in Initializing because the setOngoingAction(ongoingAction.None)
used to be handled in useChangeAction
with the onFulfilled/onReject
functions but is now missing for the case of the status Initialized... 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed that with the DKGLoading
. Even if the status doesn't change, the node will always update its DKGLoading
and therefore notify the parent with notifyLoading
. The parent at that time knows that something changed on its children, and can make the necessary checks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cmsigrist what about (added if (proxy === '')
:
useEffect(() => {
if (
ongoingAction === OngoingAction.Initializing &&
(status === NodeStatus.NotInitialized ||
status === NodeStatus.Failed ||
status === NodeStatus.Unreachable)
) {
setDKGLoading(true);
if (proxy === '') {
setStatus(NodeStatus.Unreachable);
setInfo('proxy empty');
setDKGLoading(false);
return;
}
initializeNode()
.then(() => {
setInfo('');
setStatus(NodeStatus.Initialized);
})
.catch((e: Error) => {
setInfo(e.toString());
setStatus(NodeStatus.Failed);
})
.finally(() => {
setDKGLoading(false);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ongoingAction]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works for the initialization (at least it passed all the failure cases I ran) ! 🎉
And then for the setup, I'm not sure if we're supposed to be able to retry setting up a node that has already failed the setup, but if this is the case, then it gets stuck in SettingUp.
It executes the onrejected
part as expected (line 166):
pollDKGStatus(expectedStatus)
.then(
() => {},
(e: Error) => {
setStatus(NodeStatus.Failed);
setInfo(e.toString());
}
)
.finally(() => setDKGLoading(false));
But it doesn't call notifyDKGState
.
And then there's a minor bug where if we set up a first node and it fails but then set up another one which succeeds, the error message remains (the red little (!) next to the node status)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works for the initialization (at least it passed all the failure cases I ran) ! 🎉
Yay 🥳
I'm not sure if we're supposed to be able to retry setting up a node that has already failed the setup, but if this is the case
Yes, we should.
But it doesn't call notifyDKGState.
It should, when doing setStatus(NodeStatus.Failed);
, then the useEffect
in line 68 calls notifyDKGState
, at least if the state was updated. Was it what you meant ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it should, but I don't think it actually does, otherwise it wouldn't be stuck in Setting up ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. I simplified some elements on Show.ts
and it seems to work now ... 🥵
d008f58
to
6c7f680
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm too far from this code base to be of much help, but I had a quick look and it seems a lot of state is just passed around between components. As per our (offline) discussion @nkcr, I'm pretty sure introducing something like Redux or Akita could provide significant benefits.
I say this because then it would allow big chunks of the logic to be moved into dedicated services, dealing only with the network and the state, whereas the components would focus on showing what's in the state and triggering the services.
If the state is external, it can just be queried as needed.
@@ -255,19 +260,23 @@ func (h *Handler) start(start types.Start, deals, resps *list.List, from mino.Ad | |||
// doDKG calls the subsequent DKG steps | |||
func (h *Handler) doDKG(deals, resps *list.List, out mino.Sender, from mino.Address) { | |||
h.log.Info().Str("action", "deal").Msg("new state") | |||
*h.status = dkg.Status{Status: dkg.Dealing} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, this is unsafe, because while we only start Stream
once, we could still receive multiple types.Start
message. If so, this will be called multiple times.
I believe we have the same issue in dela.
Concretely, the impact on this line is minimal, but I'm concerned about the multiple starts in parallel 🤔 am I missing something ? do we have a test that verifies that we'll ignore the second start (on the same stream) if one is already running ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can technically happen if other nodes participating in the setup decide to act badly and send start messages.
if (!Array.from(nodeLoading.values()).includes(true)) { | ||
setDKGLoading(false); | ||
} else { | ||
setDKGLoading(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!Array.from(nodeLoading.values()).includes(true)) { | |
setDKGLoading(false); | |
} else { | |
setDKGLoading(true); | |
const someNodeLoading = Array.from(nodeLoading.values()).includes(true); | |
setDKGLoading(someNodeLoading); |
useEffect(() => { | ||
if (nodeLoading !== null) { | ||
if (!Array.from(nodeLoading.values()).includes(true)) { | ||
setDKGLoading(false); | ||
} else { | ||
setDKGLoading(true); | ||
} | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general exhaustive deps are a very good thing, shouldn't these issues be addressed ?
ignoring them has cost me a lot of time bug fixing, in the past :-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually tried a couple of times to include all the dependencies with @badrlarhdir during the semester. But for some reasons, the useEffects were not triggered at the correct/expected moment when doing so... 🤔
@@ -220,8 +251,6 @@ const ElectionShow: FC = () => { | |||
setOngoingAction={setOngoingAction} | |||
nodeToSetup={nodeToSetup} | |||
setNodeToSetup={setNodeToSetup} | |||
DKGStatuses={DKGStatuses} | |||
setDKGStatuses={setDKGStatuses} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I can't suggest changes on lines that were not modified). But line 225 and 239, it would be great to add:
DKGLoading && ongoingAction === OngoingAction.None &&
And line 230 and 240:
(!DKGLoading || ongoingAction !== OngoingAction.None) &&
So that the status bar still remain visible, same with the action during the initialization and setup (it currently disappears because of the change with the DKGLoading) ?
Instead of pulling out the big boys, an interesting incremental step could be to use the native React context API to wrap all those properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woohoo it works ! 🎉
@@ -206,7 +235,7 @@ const ElectionShow: FC = () => { | |||
<div className="font-bold uppercase text-lg text-gray-700 pb-2">{t('action')}</div> | |||
<div className="px-2"> | |||
{DKGLoading && <LoadingButton>{t('actionLoading')}</LoadingButton>}{' '} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot about this one 😬
DKGLoading && ongoingAction === OngoingAction.None &&
Kudos, SonarCloud Quality Gate passed! |
Yay 🥳 |
Hahaha no problem (we were kind of fixing the mess I left anyway 😂) ! |
Fix #165