client/web/emberclear/app/services/channels/channel-verifier.ts
import Service, { inject as service } from '@ember/service';
import { identitiesIncludes, identityEquals } from 'emberclear/utils/identity-comparison';
import { VOTE_ACTION } from '@emberclear/local-account/models/vote-chain';
import type { Identity } from '@emberclear/local-account';
import type ChannelContextChain from '@emberclear/local-account/models/channel-context-chain';
import type VoteChain from '@emberclear/local-account/models/vote-chain';
import type VoteVerifier from 'emberclear/services/channels/vote-verifier';
export default class ChannelVerifier extends Service {
@service('channels/vote-verifier') voteVerifier!: VoteVerifier;
async isValidChain(channel: ChannelContextChain): Promise<boolean> {
let isFirstContextChain: boolean = !channel.previousChain && !channel.supportingVote;
if (isFirstContextChain) {
return this.isValidSingleChain(channel);
}
let somethingWrongWithPastOrSupportingVote =
!(await this.isValidChain(channel.previousChain)) ||
!(await this.voteVerifier.isValid(channel.supportingVote)) ||
!this.isVoteCompletedPositive(channel.supportingVote, channel.previousChain.admin);
if (somethingWrongWithPastOrSupportingVote) {
return false;
}
switch (channel.supportingVote.action) {
case VOTE_ACTION.ADD:
return this.isAddValid(channel);
case VOTE_ACTION.PROMOTE:
return this.isPromoteValid(channel);
case VOTE_ACTION.REMOVE:
return this.isRemoveValid(channel);
default:
return false;
}
}
private isValidSingleChain(channel: ChannelContextChain): boolean {
return !(
channel.admin === undefined ||
channel.members.length !== 1 ||
!identityEquals(channel.members.objectAt(0)!, channel.admin)
);
}
private isVoteCompletedPositive(vote: VoteChain, admin: Identity): boolean {
if (
vote.yes.length > vote.no.length + vote.remaining.length ||
(vote.yes.length === vote.no.length + vote.remaining.length &&
identitiesIncludes(vote.yes.toArray(), admin))
) {
return true;
}
return false;
}
private getDiffs(previousMembers: Identity[], currentMembers: Identity[]) {
let currentMembersDiff = currentMembers.filter(
(identity) => !identitiesIncludes(previousMembers, identity)
);
let pastMembersDiff = previousMembers.filter(
(identity) => !identitiesIncludes(currentMembers, identity)
);
return { currentMembersDiff, pastMembersDiff };
}
private isAddValid(channel: ChannelContextChain): boolean {
let previousMembers = channel.previousChain.members.toArray();
let target = channel.supportingVote.target;
let currentMembers = channel.members.toArray();
if (!identityEquals(channel.admin, channel.previousChain.admin)) {
return false;
}
let { currentMembersDiff, pastMembersDiff } = this.getDiffs(previousMembers, currentMembers);
if (pastMembersDiff.length > 0) {
return false;
}
if (currentMembersDiff.length === 1 && currentMembersDiff[0] === target) {
return true;
}
return false;
}
private isPromoteValid(channel: ChannelContextChain): boolean {
let previousMembers = channel.previousChain.members.toArray();
let target = channel.supportingVote.target;
let currentMembers = channel.members.toArray();
let { currentMembersDiff, pastMembersDiff } = this.getDiffs(previousMembers, currentMembers);
if (pastMembersDiff.length > 0 || currentMembersDiff.length > 0) {
return false;
}
if (identityEquals(channel.admin, target)) {
return true;
}
return false;
}
private isRemoveValid(channel: ChannelContextChain): boolean {
let previousMembers = channel.previousChain.members.toArray();
let target = channel.supportingVote.target;
let currentMembers = channel.members.toArray();
if (!identityEquals(channel.admin, channel.previousChain.admin)) {
return false;
}
let { currentMembersDiff, pastMembersDiff } = this.getDiffs(previousMembers, currentMembers);
if (currentMembersDiff.length > 0) {
return false;
}
if (pastMembersDiff.length === 1 && pastMembersDiff[0] === target) {
return true;
}
return false;
}
}
declare module '@ember/service' {
interface Registry {
'channels/channel-verifier': ChannelVerifier;
}
}