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

Fixes initialisation process on the web frontend #166

Merged
merged 11 commits into from
Sep 22, 2022
8 changes: 8 additions & 0 deletions services/dkg/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ const (
Setup StatusCode = 1
// Failed is when the actor failed to set up
Failed StatusCode = 2
// Dealing is when the actor is sending its deals
Dealing = 3
// Responding is when the actor sends its responses on the deals
Responding = 4
// Certifying is when the actor is validating its responses
Certifying = 5
// Certified is then the actor is certified
Certified = 6
)

// DKG defines the primitive to start a DKG protocol
Expand Down
11 changes: 10 additions & 1 deletion services/dkg/pedersen/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

etypes "github.com/dedis/d-voting/contracts/evoting/types"
"github.com/dedis/d-voting/internal/testing/fake"
"github.com/dedis/d-voting/services/dkg"
"github.com/dedis/d-voting/services/dkg/pedersen/types"
"go.dedis.ch/dela"
"go.dedis.ch/dela/core/ordering"
Expand Down Expand Up @@ -70,12 +71,14 @@ type Handler struct {

log zerolog.Logger
running bool

status *dkg.Status
}

// NewHandler creates a new handler
func NewHandler(me mino.Address, service ordering.Service, pool pool.Pool,
txnmngr txn.Manager, pubSharesSigner crypto.Signer, handlerData HandlerData,
context serde.Context, electionFac serde.Factory) *Handler {
context serde.Context, electionFac serde.Factory, status *dkg.Status) *Handler {

privKey := handlerData.PrivKey
pubKey := handlerData.PubKey
Expand All @@ -101,6 +104,8 @@ func NewHandler(me mino.Address, service ordering.Service, pool pool.Pool,

log: log,
running: false,

status: status,
}
}

Expand Down Expand Up @@ -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}
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.

h.deal(out)

h.log.Info().Str("action", "respond").Msg("new state")
*h.status = dkg.Status{Status: dkg.Responding}
h.respond(deals, out)

h.log.Info().Str("action", "certify").Msg("new state")
*h.status = dkg.Status{Status: dkg.Certifying}
err := h.certify(resps, out)
if err != nil {
dela.Logger.Error().Msgf("failed to certify: %v", err)
return
}

h.log.Info().Str("action", "finalize").Msg("new state")
*h.status = dkg.Status{Status: dkg.Certified}

// Send back the public DKG key
distKey, err := h.dkg.DistKeyShare()
Expand Down
14 changes: 8 additions & 6 deletions services/dkg/pedersen/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@ func (s *Pedersen) NewActor(electionIDBuf []byte, pool pool.Pool, txmngr txn.Man

ctx := jsonserde.NewContext()

status := &dkg.Status{Status: dkg.Initialized}

// link the actor to an RPC by the election ID
h := NewHandler(s.mino.GetAddress(), s.service, pool, txmngr, s.signer,
handlerData, ctx, s.electionFac)
handlerData, ctx, s.electionFac, status)

no := s.mino.WithSegment(electionID)
rpc := mino.MustCreateRPC(no, RPC, h, s.factory)
Expand All @@ -135,7 +137,7 @@ func (s *Pedersen) NewActor(electionIDBuf []byte, pool pool.Pool, txmngr txn.Man
electionFac: s.electionFac,
handler: h,
electionID: electionID,
status: dkg.Status{Status: dkg.Initialized},
status: status,
log: log,
}

Expand Down Expand Up @@ -167,12 +169,12 @@ type Actor struct {
electionFac serde.Factory
handler *Handler
electionID string
status dkg.Status
status *dkg.Status
log zerolog.Logger
}

func (a *Actor) setErr(err error, args map[string]interface{}) {
a.status = dkg.Status{
*a.status = dkg.Status{
Status: dkg.Failed,
Err: err,
Args: args,
Expand Down Expand Up @@ -313,7 +315,7 @@ func (a *Actor) Setup() (kyber.Point, error) {
a.log.Info().Msgf("ok for %s", addr.String())
}

a.status = dkg.Status{Status: dkg.Setup}
*a.status = dkg.Status{Status: dkg.Setup}
evoting.PromElectionDkgStatus.WithLabelValues(a.electionID).Set(float64(dkg.Setup))

return dkgPubKeys[0], nil
Expand Down Expand Up @@ -397,7 +399,7 @@ func (a *Actor) MarshalJSON() ([]byte, error) {

// Status implements dkg.Actor
func (a *Actor) Status() dkg.Status {
return a.status
return *a.status
}

func electionExists(service ordering.Service, electionIDBuf []byte) (ordering.Proof, bool) {
Expand Down
2 changes: 1 addition & 1 deletion services/dkg/pedersen/mod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func TestPedersen_InitNonEmptyMap(t *testing.T) {

otherActor := Actor{
handler: NewHandler(fake.NewAddress(0), &fake.Service{}, &fake.Pool{},
fake.Manager{}, fake.Signer{}, handlerData, serdecontext, electionFac),
fake.Manager{}, fake.Signer{}, handlerData, serdecontext, electionFac, nil),
}

requireActorsEqual(t, actor, &otherActor)
Expand Down
28 changes: 28 additions & 0 deletions web/frontend/src/components/utils/DKGStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,34 @@ const DKGStatus: FC<DKGStatusProps> = ({ status }) => {
<div>{t('failed')}</div>
</div>
);
case NodeStatus.Dealing:
return (
<div className="flex items-center">
<div className="block h-4 w-4 bg-blue-500 rounded-full mr-2"></div>
<div>{t('dealing')}</div>
</div>
);
case NodeStatus.Responding:
return (
<div className="flex items-center">
<div className="block h-4 w-4 bg-blue-500 rounded-full mr-2"></div>
<div>{t('responding')}</div>
</div>
);
case NodeStatus.Certifying:
return (
<div className="flex items-center">
<div className="block h-4 w-4 bg-blue-500 rounded-full mr-2"></div>
<div>{t('certifying')}</div>
</div>
);
case NodeStatus.Certified:
return (
<div className="flex items-center">
<div className="block h-4 w-4 bg-green-500 rounded-full mr-2"></div>
<div>{t('certified')}</div>
</div>
);
default:
return null;
}
Expand Down
4 changes: 4 additions & 0 deletions web/frontend/src/language/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@
"setupNode": "Setup Node",
"statusOpen": "Open",
"failed": "Failed",
"dealing": "Dealing",
"responding": "Responding",
"certifying": "Certifying",
"certified": "Certified",
"opening": "Opening...",
"statusClose": "Closed",
"closing": "Closing...",
Expand Down
6 changes: 6 additions & 0 deletions web/frontend/src/layout/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const Footer = () => (
<ProxyInput />
</div>
</div>
<div className="text-center">
version:
{process.env.REACT_APP_VERSION || 'unknown'} - build{' '}
{process.env.REACT_APP_BUILD || 'unknown'} - on{' '}
{process.env.REACT_APP_BUILD_TIME || 'unknown'}
</div>
</footer>
</div>
);
Expand Down
53 changes: 48 additions & 5 deletions web/frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ let mockUserDB = setupMockUserDB();
const RESPONSE_TIME = 500;
const CHANGE_STATUS_TIMER = 2000;
const INIT_TIMER = 1000;
const SETUP_TIMER = 2000;
const SHUFFLE_TIMER = 2000;
const DECRYPT_TIMER = 1000;

Expand All @@ -63,7 +62,7 @@ export const handlers = [
? {
lastname: 'Bobster',
firstname: 'Alice',
role: UserRole.Voter,
role: UserRole.Admin,
sciper: userId,
}
: {};
Expand Down Expand Up @@ -263,16 +262,60 @@ export const handlers = [
const newDKGStatus = new Map(mockDKG.get(ElectionID as string));
let node = '';

mockElections.get(ElectionID as string).Roster.forEach((n) => {
const roster = mockElections.get(ElectionID as string).Roster;

const INCREMENT = 1200;

roster.forEach((n) => {
const p = mockNodeProxyAddresses.get(n);
if (p === body.Proxy) {
node = n;
}
});

newDKGStatus.set(node, NodeStatus.Setup);
const setup = () => {
newDKGStatus.set(node, NodeStatus.Setup);
mockDKG.set(ElectionID as string, newDKGStatus);
};

const certified = () => {
roster.forEach((n) => {
newDKGStatus.set(n, NodeStatus.Certified);
});
mockDKG.set(ElectionID as string, newDKGStatus);

setTimeout(setup, INCREMENT);
};

const certifying = () => {
roster.forEach((n) => {
newDKGStatus.set(n, NodeStatus.Certifying);
});
mockDKG.set(ElectionID as string, newDKGStatus);

setTimeout(certified, INCREMENT);
};

const responding = () => {
roster.forEach((n) => {
newDKGStatus.set(n, NodeStatus.Responding);
});
mockDKG.set(ElectionID as string, newDKGStatus);

setTimeout(certifying, INCREMENT);
};

const dealing = () => {
roster.forEach((n) => {
newDKGStatus.set(n, NodeStatus.Dealing);
});
mockDKG.set(ElectionID as string, newDKGStatus);

setTimeout(responding, INCREMENT);
};

setTimeout(dealing, INCREMENT);

setTimeout(() => mockDKG.set(ElectionID as string, newDKGStatus), SETUP_TIMER);
break;
case Action.BeginDecryption:
setTimeout(
Expand Down
63 changes: 46 additions & 17 deletions web/frontend/src/pages/election/Show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Modal from 'components/modal/Modal';
import StatusTimeline from './components/StatusTimeline';
import Loading from 'pages/Loading';
import Action from './components/Action';
import { NodeStatus } from 'types/node';
import { InternalDKGInfo, NodeStatus } from 'types/node';
import useGetResults from './components/utils/useGetResults';
import UserIDTable from './components/UserIDTable';
import DKGStatusTable from './components/DKGStatusTable';
Expand Down Expand Up @@ -42,7 +42,7 @@ const ElectionShow: FC = () => {

const [nodeProxyAddresses, setNodeProxyAddresses] = useState<Map<string, string>>(new Map());
const [nodeToSetup, setNodeToSetup] = useState<[string, string]>(null);
// The status of each node
// The status of each node. Key is the node's address.
const [DKGStatuses, setDKGStatuses] = useState<Map<string, NodeStatus>>(new Map());

const [nodeLoading, setNodeLoading] = useState<Map<string, boolean>>(null);
Expand All @@ -51,6 +51,30 @@ const ElectionShow: FC = () => {
const ongoingItem = 'ongoingAction' + electionID;
const nodeToSetupItem = 'nodeToSetup' + electionID;

// called by a DKG row
const notifyDKGState = (node: string, info: InternalDKGInfo) => {
switch (info.getStatus()) {
case NodeStatus.Failed:
setOngoingAction(OngoingAction.None);
break;
case NodeStatus.Setup:
setOngoingAction(OngoingAction.None);
setStatus(Status.Setup);
break;
}

const newDKGStatuses = new Map(DKGStatuses);
newDKGStatuses.set(node, info.getStatus());
setDKGStatuses(newDKGStatuses);
};

// called by a DKG row
const notifyLoading = (node: string, l: boolean) => {
const newLoading = new Map(nodeLoading);
newLoading.set(node, l);
setNodeLoading(newLoading);
};

// Fetch result when available after a status change
useEffect(() => {
if (status === Status.ResultAvailable && isResultAvailable) {
Expand Down Expand Up @@ -124,11 +148,12 @@ const ElectionShow: FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [roster]);

// Keep the "DKGLoading" state according to "nodeLoading". This state tells if
// one of the element on the map is true.
useEffect(() => {
if (nodeLoading !== null) {
if (!Array.from(nodeLoading.values()).includes(true)) {
setDKGLoading(false);
}
const someNodeLoading = Array.from(nodeLoading.values()).includes(true);
setDKGLoading(someNodeLoading);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Contributor

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 :-/

Copy link
Contributor

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... 🤔

}, [nodeLoading]);
Expand All @@ -139,17 +164,23 @@ const ElectionShow: FC = () => {
if (DKGStatuses !== null && !DKGLoading) {
const statuses = Array.from(DKGStatuses.values());

// We want to update only if all nodes have already set their status
if (statuses.length !== roster.length) {
return;
}

setOngoingAction(OngoingAction.None);

// TODO: can be modified such that if the majority of the node are
// initialized than the election status can still be set to initialized
if (statuses.includes(NodeStatus.NotInitialized)) return;

if (statuses.includes(NodeStatus.Setup)) {
setStatus(Status.Setup);
if (
statuses.includes(NodeStatus.NotInitialized) ||
statuses.includes(NodeStatus.Unreachable) ||
statuses.includes(NodeStatus.Failed)
) {
return;
}

if (statuses.includes(NodeStatus.Unreachable)) return;

setStatus(Status.Initialized);

// Status Failed is handled by useChangeAction
Expand Down Expand Up @@ -220,8 +251,6 @@ const ElectionShow: FC = () => {
setOngoingAction={setOngoingAction}
nodeToSetup={nodeToSetup}
setNodeToSetup={setNodeToSetup}
DKGStatuses={DKGStatuses}
setDKGStatuses={setDKGStatuses}
Copy link
Contributor

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) ?

/>
)}
</div>
Expand All @@ -240,14 +269,14 @@ const ElectionShow: FC = () => {
<DKGStatusTable
roster={roster}
electionId={electionId}
loading={nodeLoading}
setLoading={setNodeLoading}
nodeProxyAddresses={nodeProxyAddresses}
setNodeProxyAddresses={setNodeProxyAddresses}
DKGStatuses={DKGStatuses}
setDKGStatuses={setDKGStatuses}
setTextModalError={setTextModalError}
setShowModalError={setShowModalError}
ongoingAction={ongoingAction}
notifyDKGState={notifyDKGState}
nodeToSetup={nodeToSetup}
notifyLoading={notifyLoading}
/>
</div>
</div>
Expand Down
Loading