Clarify terminology: Bench, Validation Pool, Vote

This commit is contained in:
Ladd Hoffman 2022-11-13 10:54:07 -06:00
parent c44c70cf03
commit c89ba51dab
11 changed files with 111 additions and 161 deletions

View File

@ -0,0 +1,72 @@
import { Actor } from "./actor.js";
import { Reputations } from "./reputation.js";
import { ValidationPool } from "./validation-pool.js";
import { Vote } from "./vote.js";
import { Voter } from "./voter.js";
import params from "./params.js";
export class Bench extends Actor {
constructor(name, scene) {
super(name, scene);
this.validationPools = new Map();
this.voters = new Map();
this.reputations = new Reputations();
this.actions = {
};
}
listValidationPools() {
Array.from(this.validationPools.values());
}
listActiveVoters() {
const now = new Date();
const thresholdSet = !!params.activeVoterThreshold;
return Array.from(this.voters.values()).filter(voter => {
const hasVoted = !!voter.dateLastVote;
const withinThreshold = now - voter.dateLastVote >= params.activeVoterThreshold;
return hasVoted && (!thresholdSet || withinThreshold);
});
}
getTotalReputation() {
return this.reputations.getTotal();
}
getTotalAvailableReputation() {
return this.reputations.getTotalAvailable();
}
getTotalActiveReputation() {
return this.listActiveVoters()
.map(({reputationPublicKey}) => this.reputations.getTokens(reputationPublicKey))
.reduce((acc, cur) => acc += cur, 0);
}
getTotalActiveAvailableReputation() {
return this.listActiveVoters()
.map(({reputationPublicKey}) => this.reputations.getAvailableTokens(reputationPublicKey))
.reduce((acc, cur) => acc += cur, 0);
}
initiateValidationPool(authorId, {fee, duration, tokenLossRatio, contentiousDebate}) {
const vote = new ValidationPool(this, authorId, {fee, duration, tokenLossRatio, contentiousDebate});
this.validationPools.set(vote.id, vote);
return vote.id;
}
castVote(voteId, signingPublicKey, position, stake, lockingTime) {
const vote = new Vote(position, stake, lockingTime);
const validationPool = this.validationPools.get(voteId);
validationPool.castVote(signingPublicKey, vote);
}
revealIdentity(voteId, signingPublicKey, reputationPublicKey) {
const validationPool = this.validationPools.get(voteId);
const voter = this.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
voter.addVoteRecord(validationPool);
this.voters.set(reputationPublicKey, voter);
validationPool.revealIdentity(signingPublicKey, voter);
}
}

View File

@ -1,72 +0,0 @@
class Blockchain {
constructor() {
this.posts = new CollectionWithReputation();
this.authors = new CollectionWithReputation();
this.nodes = new CollectionWithReputation();
}
vote(voter, batch) {
if (!this.activeVote) {
this.activeVote = new VoteInstance(batch);
this.activeVote.vote(voter, true);
return;
}
if (this.activeVote.matches(batch)) {
this.activeVote.vote(voter, true);
} else {
this.activeVote.vote(voter, false);
}
}
applyBatch(batch) {}
}
class CollectionWithReputation {
constructor() {
this.collection = new Map();
this.reputations = new Map();
}
set(id, value) {
this.collection.set(id, value);
}
get(id) {
this.collection.get(id);
}
setReputation(id, value) {
this.reputations.set(id, value);
}
get(id) {
this.reputations.get(id);
}
}
class VoteInstance {
constructor(content) {
this.content = content;
this.votes = new Map();
}
matches(content) {
return JSON.stringify(content) === JSON.stringify(this.content);
}
vote(voter, opinion) {
this.votes.set(voter.id, { voter, opinion });
}
finalize() {
const count = { for: 0, against: 0 };
for (const vote of this.votes) {
if (vote.opinion === true) {
count.for++;
} else {
count.against++;
}
}
return count.for > count.against;
}
}

View File

@ -8,7 +8,7 @@ export class Member extends Actor {
super(name, scene);
this.actions = {
submitPost: new Action('submit post', scene),
initiateVote: new Action('initiate vote', scene),
initiateValidationPool: new Action('initiate vote', scene),
castVote: new Action('cast vote', scene),
revealIdentity: new Action('reveal identity', scene),
};
@ -31,10 +31,10 @@ export class Member extends Actor {
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
}
initiateVote(validationPool, options) {
// For now, directly call validationPool.initiateVote();
this.actions.initiateVote.log(this, validationPool);
return validationPool.initiateVote(this.reputationPublicKey, options);
initiateValidationPool(bench, options) {
// For now, directly call bench.initiateValidationPool();
this.actions.initiateValidationPool.log(this, bench);
return bench.initiateValidationPool(this.reputationPublicKey, options);
}
async castVote(validationPool, voteId, position, stake, lockingTime) {

View File

@ -1,12 +1,12 @@
import { CryptoUtil } from "../crypto.js";
import { CryptoUtil } from "./crypto.js";
import params from "./params.js";
const VoteInstanceStates = Object.freeze({
const ValidationPoolStates = Object.freeze({
OPEN: "OPEN",
CLOSED: "CLOSED",
});
export class VoteInstance {
export class ValidationPool {
constructor(validationPool, authorId, {fee, duration, tokenLossRatio, contentiousDebate = false}) {
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
if (!contentiousDebate && (tokenLossRatio < 0 || tokenLossRatio > 1 || [null, undefined].includes(tokenLossRatio))) {
@ -15,7 +15,7 @@ export class VoteInstance {
if (duration < params.voteDuration.min || (params.voteDuration.max && duration > params.voteDuration.max) || [null, undefined].includes(duration)) {
throw new Error(`Duration must be in the range [${params.voteDuration.min}, ${params.voteDuration.max ?? 'Inf'}]; got ${duration}`);
}
this.state = VoteInstanceStates.OPEN;
this.state = ValidationPoolStates.OPEN;
this.votes = new Map();
this.voters = new Map();
this.validationPool = validationPool;
@ -34,7 +34,7 @@ export class VoteInstance {
}
castVote(signingPublicKey, vote) {
if (this.state === VoteInstanceStates.CLOSED) {
if (this.state === ValidationPoolStates.CLOSED) {
throw new Error(`Vote ${this.id} is closed`);
}
if (this.duration && new Date() - this.dateStart > this.duration) {
@ -56,7 +56,7 @@ export class VoteInstance {
if (this.votes.size === this.voters.size) {
// All voters have revealed their reputation public keys
// Now we can evaluate winning conditions
this.state = VoteInstanceStates.CLOSED;
this.state = ValidationPoolStates.CLOSED;
const result = this.evaluateWinningConditions();
this.applyTokenLocking();
this.distributeTokens(result);
@ -103,7 +103,7 @@ export class VoteInstance {
const upvoteValue = getTotalValue(true);
const downvoteValue = getTotalValue(false);
const activeAvailableReputation = this.validationPool.activeAvailableReputation();
const activeAvailableReputation = this.validationPool.getTotalActiveAvailableReputation();
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;

View File

@ -1 +0,0 @@
export { ValidationPool } from "./validation-pool.js";

View File

@ -1,59 +0,0 @@
import { Actor } from "../actor.js";
import { Action } from "../action.js";
import { Reputations } from "../reputation.js";
import { VoteInstance } from "./vote-instance.js";
import { Vote } from "./vote.js";
import { Voter } from "./voter.js";
import params from "./params.js";
export class ValidationPool extends Actor {
constructor(name, scene) {
super(name, scene);
this.voteInstances = new Map();
this.voters = new Map();
this.reputations = new Reputations();
this.actions = {
};
}
listVoteInstances() {
Array.from(this.voteInstances.values());
}
listActiveVoters() {
const now = new Date();
const thresholdSet = !!params.activeVoterThreshold;
return Array.from(this.voters.values()).filter(voter => {
const hasVoted = !!voter.dateLastVote;
const withinThreshold = now - voter.dateLastVote >= params.activeVoterThreshold;
return hasVoted && (!thresholdSet || withinThreshold);
});
}
activeAvailableReputation() {
return this.listActiveVoters()
.map(({reputationPublicKey}) => this.reputations.getAvailableTokens(reputationPublicKey))
.reduce((acc, cur) => acc += cur, 0);
}
initiateVote(authorId, {fee, duration, tokenLossRatio, contentiousDebate}) {
const vote = new VoteInstance(this, authorId, {fee, duration, tokenLossRatio, contentiousDebate});
this.voteInstances.set(vote.id, vote);
return vote.id;
}
castVote(voteId, signingPublicKey, position, stake, lockingTime) {
const vote = new Vote(position, stake, lockingTime);
const voteInstance = this.voteInstances.get(voteId);
voteInstance.castVote(signingPublicKey, vote);
}
revealIdentity(voteId, signingPublicKey, reputationPublicKey) {
const voteInstance = this.voteInstances.get(voteId);
const voter = this.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
voter.addVoteRecord(voteInstance);
this.voters.set(reputationPublicKey, voter);
voteInstance.revealIdentity(signingPublicKey, voter);
}
}

View File

@ -5,7 +5,7 @@
font-size: 12pt;
}
.box .name {
width: 12em;
width: 15em;
font-weight: bold;
text-align: right;
margin-right: 6pt;

View File

@ -1,7 +1,7 @@
import { Box } from './classes/box.js';
import { Scene } from './classes/scene.js';
import { Member } from './classes/member.js';
import { ValidationPool } from './classes/validation-pool/index.js';
import { Bench } from './classes/bench.js';
const delay = async (ms) => {
await new Promise((resolve) => setTimeout(resolve, ms));
@ -12,29 +12,39 @@ const rootBox = new Box('rootBox', rootElement).flex();
const scene = window.scene = new Scene('Validation Pool test', rootBox).log('sequenceDiagram');
const pool = window.validationPool = new ValidationPool("validationPool", scene);
const bench = window.bench = new Bench("Bench", scene);
const member1 = window.member1 = (await new Member("member1", scene).initialize()).setValue('rep', 0);
const member2 = window.member2 = (await new Member("member2", scene).initialize()).setValue('rep', 0);
const member1 = window.member1 = await new Member("member1", scene).initialize();
const member2 = window.member2 = await new Member("member2", scene).initialize();
const updateDisplayValues = () => {
member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey));
member2.setValue('rep', bench.reputations.getTokens(member2.reputationPublicKey));
bench.setValue('total rep', bench.getTotalReputation());
bench.setValue('available rep', bench.getTotalAvailableReputation());
bench.setValue('active rep', bench.getTotalActiveReputation());
bench.setValue('active available rep', bench.getTotalActiveAvailableReputation());
};
updateDisplayValues();
await delay(1000);
// First member can self-approve
const vote1 = member1.initiateVote(pool, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member1.castVote(pool, vote1, true, 0, 0);
await member1.revealIdentity(pool, vote1); // Vote passes
member1.setValue('rep', pool.reputations.getTokens(member1.reputationPublicKey));
const vote1 = member1.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member1.castVote(bench, vote1, true, 0, 0);
await member1.revealIdentity(bench, vote1); // Vote passes
updateDisplayValues();
// const vote2 = member2.initiateVote(pool, {fee: 1, duration: 1000, tokenLossRatio: 1});
// await member2.castVote(pool, vote2, true, 0);
// await member2.revealIdentity(pool, vote2); // Quorum is not met!
// const vote2 = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
// await member2.castVote(bench, vote2, true, 0);
// await member2.revealIdentity(bench, vote2); // Quorum is not met!
await delay(1000);
// Second member must be approved by first member
const vote2 = member2.initiateVote(pool, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member1.castVote(pool, vote2, true, 0.5, 1);
await member1.revealIdentity(pool, vote2); // Vote passes
const vote2 = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member1.castVote(bench, vote2, true, 0.5, 1);
await member1.revealIdentity(bench, vote2); // Vote passes
member1.setValue('rep', pool.reputations.getTokens(member1.reputationPublicKey));
member2.setValue('rep', pool.reputations.getTokens(member2.reputationPublicKey));
updateDisplayValues();