From c44c70cf03b7f8cb3539d1f2e1753a8427676cf4 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sat, 12 Nov 2022 16:20:42 -0600 Subject: [PATCH] Basic validation pool is working --- forum-network/public/classes/actor.js | 16 ++ forum-network/public/classes/crypto.js | 6 +- forum-network/public/classes/forum-node.js | 2 +- forum-network/public/classes/member.js | 30 ++- forum-network/public/classes/post.js | 4 +- forum-network/public/classes/reputation.js | 87 ++++++- .../public/classes/validation-pool.js | 214 ------------------ .../public/classes/validation-pool/index.js | 1 + .../public/classes/validation-pool/params.js | 20 ++ .../validation-pool/validation-pool.js | 59 +++++ .../classes/validation-pool/vote-instance.js | 135 +++++++++++ .../public/classes/validation-pool/vote.js | 7 + .../public/classes/validation-pool/voter.js | 14 ++ forum-network/public/validation-pool-test.js | 34 ++- 14 files changed, 391 insertions(+), 238 deletions(-) delete mode 100644 forum-network/public/classes/validation-pool.js create mode 100644 forum-network/public/classes/validation-pool/index.js create mode 100644 forum-network/public/classes/validation-pool/params.js create mode 100644 forum-network/public/classes/validation-pool/validation-pool.js create mode 100644 forum-network/public/classes/validation-pool/vote-instance.js create mode 100644 forum-network/public/classes/validation-pool/vote.js create mode 100644 forum-network/public/classes/validation-pool/voter.js diff --git a/forum-network/public/classes/actor.js b/forum-network/public/classes/actor.js index 86f3eb2..c229aaf 100644 --- a/forum-network/public/classes/actor.js +++ b/forum-network/public/classes/actor.js @@ -6,6 +6,7 @@ export class Actor { this.status = this.scene.addDisplayValue(`${this.name} status`); this.status.set('New'); this.scene.log(`participant ${this.name}`); + this.values = new Map(); } send(dest, action, detail) { @@ -34,4 +35,19 @@ export class Actor { this.status.set(status); return this; } + + addValue(label) { + this.values.set(label, this.scene.addDisplayValue(`${this.name} ${label}`)); + return this; + } + + setValue(label, value) { + let displayValue = this.values.get(label); + if (!displayValue) { + displayValue = this.scene.addDisplayValue(`${this.name} ${label}`); + this.values.set(label, displayValue); + } + displayValue.set(value); + return this; + } } diff --git a/forum-network/public/classes/crypto.js b/forum-network/public/classes/crypto.js index 431a60c..4d37394 100644 --- a/forum-network/public/classes/crypto.js +++ b/forum-network/public/classes/crypto.js @@ -3,7 +3,7 @@ export class CryptoUtil { static hash = 'SHA-256'; - static async generateSigningKey() { + static async generateAsymmetricKey() { return await window.crypto.subtle.generateKey( { name: CryptoUtil.algorithm, @@ -55,4 +55,8 @@ export class CryptoUtil { ['verify'], ); } + + static randomUUID() { + return window.crypto.randomUUID(); + } } diff --git a/forum-network/public/classes/forum-node.js b/forum-network/public/classes/forum-node.js index 0c7154a..a4a82d4 100644 --- a/forum-network/public/classes/forum-node.js +++ b/forum-network/public/classes/forum-node.js @@ -18,7 +18,7 @@ export class ForumNode extends Actor { // Generate a signing key pair and connect to the network async initialize(forumNetwork) { - this.keyPair = await CryptoUtil.generateSigningKey(); + this.keyPair = await CryptoUtil.generateAsymmetricKey(); this.forumNetwork = forumNetwork.addNode(this); this.status.set('Initialized'); return this; diff --git a/forum-network/public/classes/member.js b/forum-network/public/classes/member.js index e6bd126..ef2350d 100644 --- a/forum-network/public/classes/member.js +++ b/forum-network/public/classes/member.js @@ -16,31 +16,41 @@ export class Member extends Actor { } async initialize() { - this.keyPair = await CryptoUtil.generateSigningKey(); + this.reputationKey = await CryptoUtil.generateAsymmetricKey(); + this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey); this.status.set('Initialized'); return this; } async submitPost(forumNode, post, stake) { + // TODO: Include fee const postMessage = new PostMessage({ post, stake }); - await postMessage.sign(this.keyPair); + await postMessage.sign(this.reputationKey); this.actions.submitPost.log(this, forumNode, null, { id: post.id }); // For now, directly call forumNode.receiveMessage(); await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON())); } - async castVote(validationPool, voteId, position, stake) { - const signingKey = await CryptoUtil.generateSigningKey(); - this.votes.set(voteId, {signingKey}); - // TODO: signed CastVoteMessage + initiateVote(validationPool, options) { + // For now, directly call validationPool.initiateVote(); + this.actions.initiateVote.log(this, validationPool); + return validationPool.initiateVote(this.reputationPublicKey, options); + } + + async castVote(validationPool, voteId, position, stake, lockingTime) { + const signingKey = await CryptoUtil.generateAsymmetricKey(); + const signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey) + this.votes.set(voteId, {signingPublicKey}); + // TODO: encrypt vote + // TODO: sign message this.actions.castVote.log(this, validationPool); - validationPool.castVote(voteId, signingKey.publicKey, position, stake); + validationPool.castVote(voteId, signingPublicKey, position, stake, lockingTime); } async revealIdentity(validationPool, voteId) { - const {signingKey} = this.votes.get(voteId); - // TODO: signed RevealIdentityMessage + const {signingPublicKey} = this.votes.get(voteId); + // TODO: sign message this.actions.revealIdentity.log(this, validationPool); - validationPool.revealIdentity(voteId, signingKey.publicKey, this.keyPair.publicKey); + validationPool.revealIdentity(voteId, signingPublicKey, this.reputationPublicKey); } } diff --git a/forum-network/public/classes/post.js b/forum-network/public/classes/post.js index 7768d68..cd7d230 100644 --- a/forum-network/public/classes/post.js +++ b/forum-network/public/classes/post.js @@ -1,6 +1,8 @@ +import { CryptoUtil } from "./crypto.js"; + export class Post { constructor(content) { - this.id = crypto.randomUUID(); + this.id = CryptoUtil.randomUUID(); this.content = content; this.citations = []; } diff --git a/forum-network/public/classes/reputation.js b/forum-network/public/classes/reputation.js index e81dd24..5c2f1f3 100644 --- a/forum-network/public/classes/reputation.js +++ b/forum-network/public/classes/reputation.js @@ -1,3 +1,86 @@ -class Reputation { - constructor() {} +class Lock { + constructor(tokens, duration) { + this.dateCreated = new Date(); + this.tokens = tokens; + this.duration = duration; + } +} + +class Reputation { + constructor() { + this.tokens = 0; + this.locks = new Set(); + } + + addTokens(tokens) { + if (this.tokens + tokens < 0) { + throw new Error(`Token balance can not become negative`); + } + this.tokens += tokens; + } + + lockTokens(tokens, duration) { + if (tokens > this.getAvailableTokens()) { + throw new Error("Can not lock more tokens than are available") + } + const lock = new Lock(tokens, duration); + this.locks.add(lock); + // TODO: Prune locks once expired + } + + getTokens() { + return this.tokens; + } + + getAvailableTokens() { + const now = new Date(); + const tokensLocked = Array.from(this.locks.values()) + .filter(({dateCreated, duration}) => now - dateCreated < duration) + .map(({tokens}) => tokens) + .reduce((acc, cur) => acc += cur, 0); + return Math.max(this.tokens - tokensLocked, 0); + } +} + +export class Reputations extends Map { + getTokens(reputationPublicKey) { + const reputation = this.get(reputationPublicKey); + if (!reputation) { + return 0; + } + return reputation.getTokens(); + } + + getAvailableTokens(reputationPublicKey) { + const reputation = this.get(reputationPublicKey); + if (!reputation) { + return 0; + } + return reputation.getAvailableTokens(); + } + + addTokens(reputationPublicKey, tokens) { + const reputation = this.get(reputationPublicKey) ?? new Reputation(); + reputation.addTokens(tokens); + this.set(reputationPublicKey, reputation); + } + + lockTokens(reputationPublicKey, tokens, duration) { + if (!tokens || !duration) { + return; + } + const reputation = this.get(reputationPublicKey); + if (!reputation) { + throw new Error(`${reputationPublicKey} has no tokens to lock`); + } + reputation.lockTokens(tokens, duration); + } + + getTotal() { + return Array.from(this.values()).reduce((acc, cur) => acc += cur.getTokens(), 0); + } + + getTotalAvailable() { + return Array.from(this.values()).reduce((acc, cur) => acc += cur.getAvailableTokens(), 0); + } } diff --git a/forum-network/public/classes/validation-pool.js b/forum-network/public/classes/validation-pool.js deleted file mode 100644 index e9565ad..0000000 --- a/forum-network/public/classes/validation-pool.js +++ /dev/null @@ -1,214 +0,0 @@ -import { Actor } from "./actor.js"; -import { Action } from "./action.js"; - -const params = { - mintingRatio: 1, // c1 - stakeForWin: 0.5, // c2 - stakeForAuthor: 0.5, // c3 - winningRatio: 0.5, // c4 - quorum: 1, // c5 - activeVoterThreshold: null, // c6 - voteDuration: { // c7 - min: 0, - max: null, - }, - // NOTE: c8 is the token loss ratio, which is specified as a runtime argument - contentiousDebate: { - period: 5000, // c9 - stages: 3, // c10 - }, - lockingTimeExponent: 0, // c11 -}; - -function getTokenLossRatio(elapsed) { - let stageDuration = params.contentiousDebate.period / 2; - let stage = 0; - let t = 0; - while (true) { - t += stageDuration; - stageDuration /= 2; - if (t > elapsed) { - break; - } - stage += 1; - if (stage >= params.contentiousDebate.stages - 1) { - break; - } - } - return stage / (params.contentiousDebate.stages - 1); -} - -class Voter { - constructor(reputationPublicKey) { - this.reputationPublicKey = reputationPublicKey; - this.voteHistory = []; - this.reputation = 0; - this.dateLastVote = null; - } - - addVoteRecord(vote) { - this.voteHistory.push(vote); - if (!this.dateLastVote || vote.dateStart > this.dateLastVote) { - this.dateLastVote = vote.dateStart; - } - } - - getReputation() { - return this.reputation; - } -} - -class Vote { - constructor(validationPool, {fee, duration, tokenLossRatio, contentiousDebate = false}) { - if (tokenLossRatio < 0 || tokenLossRatio > 1) { - throw new Error(`Token loss ratio must be in the range [0, 1]; got ${tokenLossRatio}`) - } - if (duration < params.voteDuration.min || duration > params.voteDuration.max) { - throw new Error(`Duration must be in the range [${params.voteDuration.min}, ${params.voteDuration.max ?? 'Inf'}]; got ${duration}`); - } - this.votes = new Map(); - this.voters = new Map(); - this.validationPool = validationPool; - this.id = window.crypto.randomUUID(); - this.dateStart = new Date(); - this.fee = fee; - this.duration = duration; - this.tokenLossRatio = tokenLossRatio; - this.contentiousDebate = contentiousDebate; - - this.tokens = { - win: fee * params.mintingRatio * params.stakeForWin, - lose: fee * params.mintingRatio * (1 - params.stakeForWin), - author: fee * params.mintingRatio * params.stakeForAuthor, - } - } - - castVote(signingPublicKey, position, stake, lockingTime) { - if (this.duration && new Date() - this.dateStart > this.duration) { - throw new Error(`Vote ${this.id} has expired, no new votes may be cast`); - } - this.votes.set(signingPublicKey, { position, stake, lockingTime }); - } - - revealIdentity(signingPublicKey, voter) { - if (!this.votes.get(signingPublicKey)) { - throw new Error("Must vote before revealing identity"); - } - this.voters.set(signingPublicKey, voter); - if (this.votes.size === this.voters.size) { - // All voters have revealed their reputation public keys - // Now we can evaluate winning conditions - this.applyTokenLocking(); - this.evaluateWinningConditions(); - } - } - - getTokenLossRatio() { - if (!this.contentiousDebate) { - return this.tokenLossRatio; - } - const elapsed = new Date() - this.dateStart; - let stageDuration = params.contentiousDebate.period / 2; - let stage = 0; - let t = 0; - while (true) { - t += stageDuration; - stageDuration /= 2; - if (t > elapsed) { - break; - } - stage += 1; - if (stage >= params.contentiousDebate.stages - 1) { - break; - } - } - return stage / (params.contentiousDebate.stages - 1); - } - - applyTokenLocking() { - // Before evaluating the winning conditions, - // we need to make sure any staked tokens are locked for the - // specified amounts of time. - // TODO: Implement token locking - } - - evaluateWinningConditions() { - let upvotes = 0; - let downvotes = 0; - - for (const {position, stake, lockingTime} of this.votes.values()) { - const value = stake * Math.pow(lockingTime, params.lockingTimeExponent); - if (position === true) { - upvotes += value; - } else { - downvotes += value; - } - } - - const activeVoterCount = this.validationPool.countActiveVoters(); - const votePasses = upvotes >= params.winningRatio * downvotes; - const quorumMet = upvotes + downvotes >= params.quorum * activeVoterCount; - - if (votePasses && quorumMet) { - - } - } - - listVoters() { - return Array.from(this.voters.values()); - } -} - -export class ValidationPool extends Actor { - constructor(name, scene) { - super(name, scene); - this.votes = []; - this.voters = new Map(); - - this.actions = { - initializeVote: new Action('initialize vote', scene), - }; - } - - listVotes() { - Array.from(this.votes.values()); - } - - listActiveVoters() { - const now = new Date(); - return Array.from(this.voters.values()).filter(voter => { - if (!params.activeVoterThreshold) { - return true; - } - if (!voter.dateLastVote) { - return false; - } - return now - voter.dateLastVote >= params.activeVoterThreshold; - }); - } - - countActiveVoters() { - return this.listActiveVoters().length; - } - - initiateVote({fee, duration, tokenLossRatio, contentiousDebate}) { - const vote = new Vote(this, {fee, duration, tokenLossRatio, contentiousDebate}); - this.actions.initializeVote.log(this, this); - this.votes.set(vote.id, vote); - return vote.id; - } - - castVote(voteId, signingPublicKey, position, stake, lockingTime) { - // TODO: Implement vote encryption - const vote = this.votes.get(voteId); - vote.castVote(signingPublicKey, position, stake, lockingTime); - } - - revealIdentity(voteId, signingPublicKey, reputationPublicKey) { - const vote = this.votes.get(voteId); - const voter = this.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey); - voter.addVoteRecord(vote); - this.voters.set(reputationPublicKey, voter); - vote.revealIdentity(signingPublicKey, voter); - } -} diff --git a/forum-network/public/classes/validation-pool/index.js b/forum-network/public/classes/validation-pool/index.js new file mode 100644 index 0000000..181664c --- /dev/null +++ b/forum-network/public/classes/validation-pool/index.js @@ -0,0 +1 @@ +export { ValidationPool } from "./validation-pool.js"; diff --git a/forum-network/public/classes/validation-pool/params.js b/forum-network/public/classes/validation-pool/params.js new file mode 100644 index 0000000..bf8264d --- /dev/null +++ b/forum-network/public/classes/validation-pool/params.js @@ -0,0 +1,20 @@ +const params = { + mintingRatio: 1, // c1 + stakeForWin: 0.5, // c2 + stakeForAuthor: 0.5, // c3 + winningRatio: 0.5, // c4 + quorum: 1, // c5 + activeVoterThreshold: null, // c6 + voteDuration: { // c7 + min: 0, + max: null, + }, + // NOTE: c8 is the token loss ratio, which is specified as a runtime argument + contentiousDebate: { + period: 5000, // c9 + stages: 3, // c10 + }, + lockingTimeExponent: 0, // c11 +}; + +export default params; diff --git a/forum-network/public/classes/validation-pool/validation-pool.js b/forum-network/public/classes/validation-pool/validation-pool.js new file mode 100644 index 0000000..9310952 --- /dev/null +++ b/forum-network/public/classes/validation-pool/validation-pool.js @@ -0,0 +1,59 @@ +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); + } +} diff --git a/forum-network/public/classes/validation-pool/vote-instance.js b/forum-network/public/classes/validation-pool/vote-instance.js new file mode 100644 index 0000000..f21f56d --- /dev/null +++ b/forum-network/public/classes/validation-pool/vote-instance.js @@ -0,0 +1,135 @@ +import { CryptoUtil } from "../crypto.js"; +import params from "./params.js"; + +const VoteInstanceStates = Object.freeze({ + OPEN: "OPEN", + CLOSED: "CLOSED", +}); + +export class VoteInstance { + 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))) { + throw new Error(`Token loss ratio must be in the range [0, 1]; got ${tokenLossRatio}`) + } + 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.votes = new Map(); + this.voters = new Map(); + this.validationPool = validationPool; + this.id = CryptoUtil.randomUUID(); + this.dateStart = new Date(); + this.authorId = authorId; + this.fee = fee; + this.duration = duration; + this.tokenLossRatio = tokenLossRatio; + this.contentiousDebate = contentiousDebate; + this.tokens = { + for: fee * params.mintingRatio * params.stakeForWin, + against: fee * params.mintingRatio * (1 - params.stakeForWin), + author: fee * params.mintingRatio * params.stakeForAuthor, + } + } + + castVote(signingPublicKey, vote) { + if (this.state === VoteInstanceStates.CLOSED) { + throw new Error(`Vote ${this.id} is closed`); + } + if (this.duration && new Date() - this.dateStart > this.duration) { + throw new Error(`Vote ${this.id} has expired, no new votes may be cast`); + } + this.votes.set(signingPublicKey, vote); + } + + listVotes(position) { + return new Map(Array.from(this.votes.entries()) + .filter(([_, vote]) => vote.position === position)); + } + + revealIdentity(signingPublicKey, voter) { + if (!this.votes.get(signingPublicKey)) { + throw new Error("Must vote before revealing identity"); + } + this.voters.set(signingPublicKey, voter); + 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; + const result = this.evaluateWinningConditions(); + this.applyTokenLocking(); + this.distributeTokens(result); + } + } + + getTokenLossRatio() { + if (!this.contentiousDebate) { + return this.tokenLossRatio; + } + const elapsed = new Date() - this.dateStart; + let stageDuration = params.contentiousDebate.period / 2; + let stage = 0; + let t = 0; + while (true) { + t += stageDuration; + stageDuration /= 2; + if (t > elapsed) { + break; + } + stage += 1; + if (stage >= params.contentiousDebate.stages - 1) { + break; + } + } + return stage / (params.contentiousDebate.stages - 1); + } + + applyTokenLocking() { + // Before evaluating the winning conditions, + // we need to make sure any staked tokens are locked for the + // specified amounts of time. + for (const [signingPublicKey, {stake, lockingTime}] of this.votes.entries()) { + const voter = this.voters.get(signingPublicKey); + this.validationPool.reputations.lockTokens(voter.reputationPublicKey, stake, lockingTime); + // TODO: If there is an exception here, the voter may have voted incorrectly. Consider penalties. + } + } + + evaluateWinningConditions() { + const getVoteValue = ({stake, lockingTime}) => stake * Math.pow(lockingTime, params.lockingTimeExponent); + const getTotalValue = (position) => Array.from(this.listVotes(position).values()) + .map(getVoteValue).reduce((acc, cur) => acc += cur, 0); + + const upvoteValue = getTotalValue(true); + const downvoteValue = getTotalValue(false); + const activeAvailableReputation = this.validationPool.activeAvailableReputation(); + const votePasses = upvoteValue >= params.winningRatio * downvoteValue; + const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation; + + // TODO: If quorum is not met, what should happen? + if (!quorumMet) { + throw new Error("Quorum is not met"); + } + return votePasses && quorumMet; + } + + distributeTokens(result) { + // Reward the author + // TODO: Penalty to the author if the vote does not pass? + this.validationPool.reputations.addTokens(this.authorId, this.tokens.author); + // Reward the vote winners, in proportion to their stakes + const tokensForWinners = result ? this.tokens.for : this.tokens.against; + const winningVotes = this.listVotes(result); + const totalStakes = Array.from(winningVotes.values()) + .map(({stake}) => stake).reduce((acc, cur) => acc += cur, 0); + if (!totalStakes) { + return; + } + for (const [signingPublicKey, {stake}] of winningVotes.entries()) { + const {reputationPublicKey} = this.voters.get(signingPublicKey); + const reward = tokensForWinners * stake / totalStakes; + this.validationPool.reputations.addTokens(reputationPublicKey, reward); + } + } +} diff --git a/forum-network/public/classes/validation-pool/vote.js b/forum-network/public/classes/validation-pool/vote.js new file mode 100644 index 0000000..4df9c77 --- /dev/null +++ b/forum-network/public/classes/validation-pool/vote.js @@ -0,0 +1,7 @@ +export class Vote { + constructor(position, stake, lockingTime) { + this.position = position; + this.stake = stake; + this.lockingTime = lockingTime; + } +} diff --git a/forum-network/public/classes/validation-pool/voter.js b/forum-network/public/classes/validation-pool/voter.js new file mode 100644 index 0000000..046fc80 --- /dev/null +++ b/forum-network/public/classes/validation-pool/voter.js @@ -0,0 +1,14 @@ +export class Voter { + constructor(reputationPublicKey) { + this.reputationPublicKey = reputationPublicKey; + this.voteHistory = []; + this.dateLastVote = null; + } + + addVoteRecord(vote) { + this.voteHistory.push(vote); + if (!this.dateLastVote || vote.dateStart > this.dateLastVote) { + this.dateLastVote = vote.dateStart; + } + } +} diff --git a/forum-network/public/validation-pool-test.js b/forum-network/public/validation-pool-test.js index 1a16dc7..7d4d20f 100644 --- a/forum-network/public/validation-pool-test.js +++ b/forum-network/public/validation-pool-test.js @@ -1,7 +1,11 @@ import { Box } from './classes/box.js'; import { Scene } from './classes/scene.js'; import { Member } from './classes/member.js'; -import { ValidationPool } from './classes/validation-pool.js'; +import { ValidationPool } from './classes/validation-pool/index.js'; + +const delay = async (ms) => { + await new Promise((resolve) => setTimeout(resolve, ms)); +}; const rootElement = document.getElementById('validation-pool'); const rootBox = new Box('rootBox', rootElement).flex(); @@ -10,15 +14,27 @@ const scene = window.scene = new Scene('Validation Pool test', rootBox).log('seq const pool = window.validationPool = new ValidationPool("validationPool", scene); -const member1 = window.member1 = await new Member("member1", scene).initialize(); -const member2 = window.member2 = await new Member("member2", scene).initialize(); +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 voteId = pool.initiateVote({fee: 1, duration: 1, isBinding: false}); +await delay(1000); -await member1.castVote(pool, voteId, true, 50); -await member2.castVote(pool, voteId, true, 50); +// 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)); -await member1.revealIdentity(pool, voteId); -await member2.revealIdentity(pool, voteId); +// 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! -// await scene.renderSequenceDiagram(); +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 + +member1.setValue('rep', pool.reputations.getTokens(member1.reputationPublicKey)); +member2.setValue('rep', pool.reputations.getTokens(member2.reputationPublicKey));