Clarify terminology: Bench, Validation Pool, Vote
This commit is contained in:
parent
c44c70cf03
commit
c89ba51dab
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ export class Member extends Actor {
|
||||||
super(name, scene);
|
super(name, scene);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
submitPost: new Action('submit post', scene),
|
submitPost: new Action('submit post', scene),
|
||||||
initiateVote: new Action('initiate vote', scene),
|
initiateValidationPool: new Action('initiate vote', scene),
|
||||||
castVote: new Action('cast vote', scene),
|
castVote: new Action('cast vote', scene),
|
||||||
revealIdentity: new Action('reveal identity', scene),
|
revealIdentity: new Action('reveal identity', scene),
|
||||||
};
|
};
|
||||||
|
@ -31,10 +31,10 @@ export class Member extends Actor {
|
||||||
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
|
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
|
||||||
}
|
}
|
||||||
|
|
||||||
initiateVote(validationPool, options) {
|
initiateValidationPool(bench, options) {
|
||||||
// For now, directly call validationPool.initiateVote();
|
// For now, directly call bench.initiateValidationPool();
|
||||||
this.actions.initiateVote.log(this, validationPool);
|
this.actions.initiateValidationPool.log(this, bench);
|
||||||
return validationPool.initiateVote(this.reputationPublicKey, options);
|
return bench.initiateValidationPool(this.reputationPublicKey, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async castVote(validationPool, voteId, position, stake, lockingTime) {
|
async castVote(validationPool, voteId, position, stake, lockingTime) {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { CryptoUtil } from "../crypto.js";
|
import { CryptoUtil } from "./crypto.js";
|
||||||
import params from "./params.js";
|
import params from "./params.js";
|
||||||
|
|
||||||
const VoteInstanceStates = Object.freeze({
|
const ValidationPoolStates = Object.freeze({
|
||||||
OPEN: "OPEN",
|
OPEN: "OPEN",
|
||||||
CLOSED: "CLOSED",
|
CLOSED: "CLOSED",
|
||||||
});
|
});
|
||||||
|
|
||||||
export class VoteInstance {
|
export class ValidationPool {
|
||||||
constructor(validationPool, authorId, {fee, duration, tokenLossRatio, contentiousDebate = false}) {
|
constructor(validationPool, authorId, {fee, duration, tokenLossRatio, contentiousDebate = false}) {
|
||||||
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
||||||
if (!contentiousDebate && (tokenLossRatio < 0 || tokenLossRatio > 1 || [null, undefined].includes(tokenLossRatio))) {
|
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)) {
|
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}`);
|
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.votes = new Map();
|
||||||
this.voters = new Map();
|
this.voters = new Map();
|
||||||
this.validationPool = validationPool;
|
this.validationPool = validationPool;
|
||||||
|
@ -34,7 +34,7 @@ export class VoteInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
castVote(signingPublicKey, vote) {
|
castVote(signingPublicKey, vote) {
|
||||||
if (this.state === VoteInstanceStates.CLOSED) {
|
if (this.state === ValidationPoolStates.CLOSED) {
|
||||||
throw new Error(`Vote ${this.id} is closed`);
|
throw new Error(`Vote ${this.id} is closed`);
|
||||||
}
|
}
|
||||||
if (this.duration && new Date() - this.dateStart > this.duration) {
|
if (this.duration && new Date() - this.dateStart > this.duration) {
|
||||||
|
@ -56,7 +56,7 @@ export class VoteInstance {
|
||||||
if (this.votes.size === this.voters.size) {
|
if (this.votes.size === this.voters.size) {
|
||||||
// All voters have revealed their reputation public keys
|
// All voters have revealed their reputation public keys
|
||||||
// Now we can evaluate winning conditions
|
// Now we can evaluate winning conditions
|
||||||
this.state = VoteInstanceStates.CLOSED;
|
this.state = ValidationPoolStates.CLOSED;
|
||||||
const result = this.evaluateWinningConditions();
|
const result = this.evaluateWinningConditions();
|
||||||
this.applyTokenLocking();
|
this.applyTokenLocking();
|
||||||
this.distributeTokens(result);
|
this.distributeTokens(result);
|
||||||
|
@ -103,7 +103,7 @@ export class VoteInstance {
|
||||||
|
|
||||||
const upvoteValue = getTotalValue(true);
|
const upvoteValue = getTotalValue(true);
|
||||||
const downvoteValue = getTotalValue(false);
|
const downvoteValue = getTotalValue(false);
|
||||||
const activeAvailableReputation = this.validationPool.activeAvailableReputation();
|
const activeAvailableReputation = this.validationPool.getTotalActiveAvailableReputation();
|
||||||
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
|
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
|
||||||
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
|
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export { ValidationPool } from "./validation-pool.js";
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
.box .name {
|
.box .name {
|
||||||
width: 12em;
|
width: 15em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-right: 6pt;
|
margin-right: 6pt;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box } from './classes/box.js';
|
import { Box } from './classes/box.js';
|
||||||
import { Scene } from './classes/scene.js';
|
import { Scene } from './classes/scene.js';
|
||||||
import { Member } from './classes/member.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) => {
|
const delay = async (ms) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 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 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 member1 = window.member1 = await new Member("member1", scene).initialize();
|
||||||
const member2 = window.member2 = (await new Member("member2", scene).initialize()).setValue('rep', 0);
|
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);
|
await delay(1000);
|
||||||
|
|
||||||
// First member can self-approve
|
// First member can self-approve
|
||||||
const vote1 = member1.initiateVote(pool, {fee: 1, duration: 1000, tokenLossRatio: 1});
|
const vote1 = member1.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
|
||||||
await member1.castVote(pool, vote1, true, 0, 0);
|
await member1.castVote(bench, vote1, true, 0, 0);
|
||||||
await member1.revealIdentity(pool, vote1); // Vote passes
|
await member1.revealIdentity(bench, vote1); // Vote passes
|
||||||
member1.setValue('rep', pool.reputations.getTokens(member1.reputationPublicKey));
|
updateDisplayValues();
|
||||||
|
|
||||||
// const vote2 = member2.initiateVote(pool, {fee: 1, duration: 1000, tokenLossRatio: 1});
|
// const vote2 = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
|
||||||
// await member2.castVote(pool, vote2, true, 0);
|
// await member2.castVote(bench, vote2, true, 0);
|
||||||
// await member2.revealIdentity(pool, vote2); // Quorum is not met!
|
// await member2.revealIdentity(bench, vote2); // Quorum is not met!
|
||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|
||||||
// Second member must be approved by first member
|
// Second member must be approved by first member
|
||||||
const vote2 = member2.initiateVote(pool, {fee: 1, duration: 1000, tokenLossRatio: 1});
|
const vote2 = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
|
||||||
await member1.castVote(pool, vote2, true, 0.5, 1);
|
await member1.castVote(bench, vote2, true, 0.5, 1);
|
||||||
await member1.revealIdentity(pool, vote2); // Vote passes
|
await member1.revealIdentity(bench, vote2); // Vote passes
|
||||||
|
|
||||||
member1.setValue('rep', pool.reputations.getTokens(member1.reputationPublicKey));
|
updateDisplayValues();
|
||||||
member2.setValue('rep', pool.reputations.getTokens(member2.reputationPublicKey));
|
|
||||||
|
|
Loading…
Reference in New Issue