Skip to content

Commit 8eb60ba

Browse files
authored
Improve dummy compensation procedure (#205)
* fix: improve dummy compensation procedure * test: fix broken tests * style: fix lint issues
1 parent 93ce719 commit 8eb60ba

File tree

4 files changed

+146
-77
lines changed

4 files changed

+146
-77
lines changed

bulletin_board/server/app/services/voting_scheme/dummy/bulletin_board.rb

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,13 @@ def append_content(content)
3535

3636
def process_create_election_message(_message_identifier, message, _content)
3737
raise RejectedMessage, "There must be at least 2 Trustees" if message.fetch(:trustees, []).count < 2
38+
39+
@state = { quorum: message[:scheme][:quorum] }
3840
end
3941

4042
def process_start_key_ceremony_message(_message_identifier, _message, _content)
41-
@state = { joint_election_key: 1, trustees: [] }
43+
state[:joint_election_key] = 1
44+
state[:trustees] = []
4245
end
4346

4447
def process_key_ceremony_message(message_identifier, _message, content)
@@ -86,76 +89,81 @@ def process_start_tally_message(_message_identifier, _message, _content)
8689
end
8790
end
8891

89-
state[:compensations] = {}
90-
state[:joint_compensations] = {}
91-
state[:compensated] = 0
9292
state[:shares] = []
93+
state[:missing] = []
94+
state[:compensations] = []
9395
state[:joint_shares] = build_questions_struct(1)
96+
state[:joint_compensations] = build_questions_struct(1)
9497

9598
emit_response "tally.cast"
9699
append_content results
97100
end
98101

99102
def process_tally_message(message_identifier, message, content)
100103
if message_identifier.subtype == "missing_trustee"
101-
state[:compensations][message[:trustee_id]] = []
102-
state[:joint_compensations][message[:trustee_id]] = build_questions_struct(1)
104+
state[:missing] << message[:trustee_id] unless state[:shares].include?(message[:trustee_id])
103105

104106
return
105107
end
106108

107109
raise RejectedMessage, "The owner_id doesn't match the sender trustee" if content[:owner_id] != message_identifier.author_id
108110

109-
case message_identifier.subtype
110-
when "share"
111-
process_tally_share_message content
112-
when "compensation"
113-
process_compensation_message content
114-
end
111+
process_tally_message_by_subtype message_identifier.subtype, content
115112

116-
return unless state[:shares].count + state[:compensated] == election.trustees.count
113+
return unless state[:missing].count + state[:shares].count == election.trustees.count &&
114+
state[:shares].count >= state[:quorum]
117115

118-
results = build_questions_struct(0)
119-
state[:joint_shares].each do |question, answers|
120-
answers.each do |answer, joint_share|
121-
results[question][answer] = ((joint_share / state[:joint_election_key])**(1.0 / state[:trustees].count)).round
122-
end
116+
if state[:missing].any?
117+
return unless state[:shares].count == state[:compensations].count
118+
119+
join_compensations
123120
end
124121

125-
emit_response "end_tally", results: results
122+
emit_response "end_tally", results: join_shares
126123
end
127124

128-
def process_tally_share_message(content)
129-
raise RejectedMessage, "The trustee already sent their share" if state[:shares].include?(content[:owner_id])
125+
def process_tally_message_by_subtype(subtype, content)
126+
case subtype
127+
when "share"
128+
raise RejectedMessage, "The trustee already sent their share" if state[:shares].include?(content[:owner_id])
130129

131-
state[:shares] << content[:owner_id]
130+
state[:shares] << content[:owner_id]
131+
state[:missing].delete(content[:owner_id])
132132

133-
content[:contests].each do |question, answers|
134-
answers.each do |answer, share|
135-
state[:joint_shares][question][answer] *= share
133+
content[:contests].each do |question, answers|
134+
answers.each do |answer, share|
135+
state[:joint_shares][question][answer] *= share
136+
end
137+
end
138+
when "compensation"
139+
raise RejectedMessage, "The trustee already sent their compensation" if state[:compensations].include?(content[:owner_id])
140+
141+
state[:compensations] << content[:owner_id]
142+
143+
content[:contests].each do |question, answers|
144+
answers.each do |answer, compensation|
145+
state[:joint_compensations][question][answer] *= compensation
146+
end
136147
end
137148
end
138149
end
139150

140-
def process_compensation_message(content)
141-
compensation = state[:compensations][content[:trustee_id]]
142-
raise RejectedMessage, "The trustee already sent their compensation for #{content[:trustee_id]}" if compensation.include?(content[:owner_id])
143-
144-
compensation << content[:owner_id]
145-
content[:contests].each do |question, answers|
151+
def join_compensations
152+
state[:joint_compensations].each do |question, answers|
146153
answers.each do |answer, value|
147-
state[:joint_compensations][content[:trustee_id]][question][answer] *= value
154+
state[:joint_shares][question][answer] *=
155+
(value**(1.0 / state[:compensations].count) * state[:joint_election_key]).round
148156
end
149157
end
158+
end
150159

151-
return unless state[:compensations].count + state[:compensations][content[:trustee_id]].count == election.trustees.count
152-
153-
state[:joint_compensations][content[:trustee_id]].each do |question, answers|
154-
answers.each do |answer, value|
155-
state[:joint_shares][question][answer] *= (value.round**(1.0 / state[:shares].count)).round
160+
def join_shares
161+
results = build_questions_struct(0)
162+
state[:joint_shares].each do |question, answers|
163+
answers.each do |answer, joint_share|
164+
results[question][answer] = ((joint_share / state[:joint_election_key])**(1.0 / state[:trustees].count)).round
156165
end
157166
end
158-
state[:compensated] += 1
159167
end
160168

161169
def build_questions_struct(initial_value)

bulletin_board/server/spec/factories/models.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
authority { Authority.first }
4040
status { :created }
4141
unique_id { [authority.unique_id, election_id].join(".") }
42-
voting_scheme_state { nil }
42+
voting_scheme_state { Marshal.dump(quorum: 2) }
4343

4444
after(:build) do |election, evaluator|
4545
election.trustees << evaluator.trustees_plus_keys.map(&:first)
@@ -57,12 +57,15 @@
5757
end
5858

5959
status { :key_ceremony }
60-
voting_scheme_state { Marshal.dump(joint_election_key: 1, trustees: trustees_done.map(&:slug)) }
60+
voting_scheme_state { Marshal.dump(quorum: 2, joint_election_key: 1, trustees: trustees_done.map(&:slug)) }
6161
end
6262

6363
trait :key_ceremony_ended do
6464
status { :key_ceremony_ended }
65-
voting_scheme_state { Marshal.dump(joint_election_key: 1, trustees: trustees_plus_keys.map(&:first).map(&:slug)) }
65+
voting_scheme_state do
66+
Marshal.dump(quorum: 2, joint_election_key: Test::Elections.joint_election_key,
67+
trustees: trustees_plus_keys.map(&:first).map(&:slug))
68+
end
6669
end
6770

6871
trait :vote do
@@ -85,12 +88,12 @@
8588

8689
after(:build) do |election, evaluator|
8790
joint_shares = Test::Elections.build_cast(election) { 1 }
88-
election.voting_scheme_state = Marshal.dump(joint_election_key: Test::Elections.joint_election_key,
91+
election.voting_scheme_state = Marshal.dump(quorum: 2,
92+
joint_election_key: Test::Elections.joint_election_key,
8993
trustees: evaluator.trustees_plus_keys.map(&:first).map(&:slug),
9094
joint_shares: joint_shares,
9195
shares: evaluator.trustees_done.map(&:slug),
92-
compensations: {}, joint_compensations: {},
93-
compensated: 0)
96+
compensations: [], joint_compensations: {}, missing: [])
9497
end
9598
end
9699

@@ -103,8 +106,7 @@
103106
trustees: evaluator.trustees_plus_keys.map(&:first).map(&:slug),
104107
joint_shares: joint_shares,
105108
shares: evaluator.trustees_plus_keys.map(&:first).map(&:slug),
106-
compensations: {}, joint_compensations: {},
107-
compensated: 0)
109+
compensations: [], joint_compensations: {}, missing: [])
108110
end
109111
end
110112

voting_schemes/dummy/js-adapter/src/trustee_wrapper.js

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
export const CREATED = 0;
2-
export const KEY_CEREMONY = 1;
3-
export const KEY_CEREMONY_ENDED = 2;
4-
export const TALLY = 3;
5-
export const TALLY_ENDED = 4;
1+
export const NONE = 0;
2+
export const CREATED = 1;
3+
export const KEY_CEREMONY = 2;
4+
export const KEY_CEREMONY_ENDED = 3;
5+
export const TALLY = 4;
6+
export const TALLY_ENDED = 5;
67

78
export const CREATE_ELECTION = "create_election";
89
export const START_KEY_CEREMONY = "start_key_ceremony";
@@ -29,10 +30,13 @@ export class TrusteeWrapper {
2930
*/
3031
constructor({ trusteeId }) {
3132
this.trusteeId = trusteeId;
32-
this.status = CREATED;
33+
this.status = NONE;
3334
this.electionPublicKey = 0;
3435
this.jointElectionKey = 0;
3536
this.tallyCastMessage = null;
37+
this.quorum = 0;
38+
this.trusteesKeys = {};
39+
this.trusteesShares = {};
3640
}
3741

3842
/**
@@ -45,6 +49,13 @@ export class TrusteeWrapper {
4549
*/
4650
processMessage(messageType, decodedData) {
4751
switch (this.status) {
52+
case NONE: {
53+
if (messageType === CREATE_ELECTION) {
54+
this.quorum = decodedData.scheme.quorum;
55+
this.status = CREATED;
56+
}
57+
break;
58+
}
4859
case CREATED: {
4960
if (messageType === START_KEY_CEREMONY) {
5061
this.status = KEY_CEREMONY;
@@ -61,7 +72,10 @@ export class TrusteeWrapper {
6172
break;
6273
}
6374
case KEY_CEREMONY: {
64-
if (messageType === END_KEY_CEREMONY) {
75+
if (messageType === KEY_CEREMONY_STEP_1) {
76+
const content = JSON.parse(decodedData.content);
77+
this.trusteesKeys[content.owner_id] = content.election_public_key;
78+
} else if (messageType === END_KEY_CEREMONY) {
6579
const content = JSON.parse(decodedData.content);
6680
this.jointElectionKey = content.joint_election_key;
6781
this.status = KEY_CEREMONY_ENDED;
@@ -92,30 +106,20 @@ export class TrusteeWrapper {
92106
contests,
93107
}),
94108
};
109+
} else if (messageType === TALLY_SHARE) {
110+
const content = JSON.parse(decodedData.content);
111+
this.trusteesShares[content.owner_id] = true;
112+
113+
return this._compensate();
95114
} else if (messageType === TALLY_MISSING_TRUSTEE) {
96-
const contests = JSON.parse(this.tallyCastMessage);
97-
for (const [question, answers] of Object.entries(contests)) {
98-
for (const [answer, value] of Object.entries(answers)) {
99-
contests[question][answer] =
100-
(1.0 *
101-
(value % this.electionPublicKey) *
102-
this.jointElectionKey) /
103-
this.electionPublicKey /
104-
this.electionPublicKey;
105-
}
115+
if (!(decodedData.truestee_id in this.trusteesShares)) {
116+
this.trusteesShares[decodedData.truestee_id] = false;
117+
return this._compensate();
106118
}
107-
108-
return {
109-
messageType: TALLY_COMPENSATION,
110-
content: JSON.stringify({
111-
owner_id: this.trusteeId,
112-
trustee_id: decodedData.trustee_id,
113-
contests,
114-
}),
115-
};
116119
} else if (messageType === END_TALLY) {
117120
this.status = TALLY_ENDED;
118121
}
122+
119123
break;
120124
}
121125
}
@@ -127,7 +131,7 @@ export class TrusteeWrapper {
127131
* @returns {boolean}
128132
*/
129133
isFresh() {
130-
return this.status === CREATED;
134+
return this.status === NONE;
131135
}
132136

133137
/**
@@ -187,4 +191,33 @@ export class TrusteeWrapper {
187191
isTallyDone() {
188192
return this.status >= TALLY_ENDED;
189193
}
194+
195+
_compensate() {
196+
const trusteesCount = Object.keys(this.trusteesKeys).length;
197+
const missingTrustees = Object.values(this.trusteesShares).filter(
198+
(val) => !val
199+
).length;
200+
201+
if (
202+
missingTrustees > 0 &&
203+
Object.keys(this.trusteesShares).length === trusteesCount
204+
) {
205+
const contests = JSON.parse(this.tallyCastMessage);
206+
for (const [question, answers] of Object.entries(contests)) {
207+
for (const [answer, value] of Object.entries(answers)) {
208+
contests[question][answer] =
209+
Math.pow(value % this.electionPublicKey, missingTrustees) /
210+
Math.pow(this.electionPublicKey, trusteesCount - missingTrustees);
211+
}
212+
}
213+
214+
return {
215+
messageType: TALLY_COMPENSATION,
216+
content: JSON.stringify({
217+
owner_id: this.trusteeId,
218+
contests,
219+
}),
220+
};
221+
}
222+
}
190223
}

0 commit comments

Comments
 (0)