From 41506fdcd559b08ddb5980e48043e47e4822619a Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 13 Nov 2022 12:23:30 -0600 Subject: [PATCH] Improved seq diag logging --- forum-network/public/classes/actor.js | 18 +++++++ forum-network/public/classes/bench.js | 30 +++++------- forum-network/public/classes/member.js | 17 +++---- forum-network/public/classes/reputation.js | 3 +- forum-network/public/classes/scene.js | 14 ++++++ .../public/classes/validation-pool.js | 32 ++++++++----- forum-network/public/index.html | 2 +- forum-network/public/mermaid-test.html | 8 ++++ forum-network/public/validation-pool-test.js | 48 +++++++++++++------ 9 files changed, 116 insertions(+), 56 deletions(-) create mode 100644 forum-network/public/mermaid-test.html diff --git a/forum-network/public/classes/actor.js b/forum-network/public/classes/actor.js index c229aaf..97a9b0e 100644 --- a/forum-network/public/classes/actor.js +++ b/forum-network/public/classes/actor.js @@ -7,6 +7,21 @@ export class Actor { this.status.set('New'); this.scene.log(`participant ${this.name}`); this.values = new Map(); + this.active = 0; + this.scene.registerActor(this); + } + + activate() { + this.active += 1; + this.scene.log(`activate ${this.name}`); + } + + deactivate() { + if (!this.active) { + throw new Error(`${this.name} is not active, can not deactivate`); + } + this.active -= 1; + this.scene.log(`deactivate ${this.name}`); } send(dest, action, detail) { @@ -47,6 +62,9 @@ export class Actor { displayValue = this.scene.addDisplayValue(`${this.name} ${label}`); this.values.set(label, displayValue); } + if (value !== displayValue.get()) { + this.scene.log(`note over ${this.name} : ${label} = ${value}`); + } displayValue.set(value); return this; } diff --git a/forum-network/public/classes/bench.js b/forum-network/public/classes/bench.js index 9267456..89883a3 100644 --- a/forum-network/public/classes/bench.js +++ b/forum-network/public/classes/bench.js @@ -1,9 +1,8 @@ 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"; +import { Action } from "./action.js"; export class Bench extends Actor { constructor(name, scene) { @@ -13,7 +12,10 @@ export class Bench extends Actor { this.reputations = new Reputations(); this.actions = { + createValidationPool: new Action('create validation pool', scene), }; + + this.activate(); } listValidationPools() { @@ -51,22 +53,12 @@ export class Bench extends Actor { } 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); + const validationPoolNumber = this.validationPools.size + 1; + const validationPool = new ValidationPool(this, authorId, {fee, duration, tokenLossRatio, contentiousDebate}, + `pool${validationPoolNumber}`, scene); + this.validationPools.set(validationPool.id, validationPool); + this.actions.createValidationPool.log(this, validationPool) + validationPool.activate(); + return validationPool; } } diff --git a/forum-network/public/classes/member.js b/forum-network/public/classes/member.js index 263b6b0..4ec0e07 100644 --- a/forum-network/public/classes/member.js +++ b/forum-network/public/classes/member.js @@ -8,17 +8,18 @@ export class Member extends Actor { super(name, scene); this.actions = { submitPost: new Action('submit post', scene), - initiateValidationPool: new Action('initiate vote', scene), + initiateValidationPool: new Action('initiate validation pool', scene), castVote: new Action('cast vote', scene), revealIdentity: new Action('reveal identity', scene), }; - this.votes = new Map(); + this.validationPools = new Map(); } async initialize() { this.reputationKey = await CryptoUtil.generateAsymmetricKey(); this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey); this.status.set('Initialized'); + this.activate(); return this; } @@ -37,20 +38,20 @@ export class Member extends Actor { return bench.initiateValidationPool(this.reputationPublicKey, options); } - async castVote(validationPool, voteId, position, stake, lockingTime) { + async castVote(validationPool, position, stake, lockingTime) { const signingKey = await CryptoUtil.generateAsymmetricKey(); const signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey) - this.votes.set(voteId, {signingPublicKey}); + this.validationPools.set(validationPool.id, {signingPublicKey}); // TODO: encrypt vote // TODO: sign message this.actions.castVote.log(this, validationPool); - validationPool.castVote(voteId, signingPublicKey, position, stake, lockingTime); + validationPool.castVote(signingPublicKey, position, stake, lockingTime); } - async revealIdentity(validationPool, voteId) { - const {signingPublicKey} = this.votes.get(voteId); + async revealIdentity(validationPool) { + const {signingPublicKey} = this.validationPools.get(validationPool.id); // TODO: sign message this.actions.revealIdentity.log(this, validationPool); - validationPool.revealIdentity(voteId, signingPublicKey, this.reputationPublicKey); + validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey); } } diff --git a/forum-network/public/classes/reputation.js b/forum-network/public/classes/reputation.js index 5c2f1f3..4eb019a 100644 --- a/forum-network/public/classes/reputation.js +++ b/forum-network/public/classes/reputation.js @@ -36,8 +36,7 @@ class Reputation { 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); + .reduce((acc, cur) => acc += cur.tokens, 0); return Math.max(this.tokens - tokensLocked, 0); } } diff --git a/forum-network/public/classes/scene.js b/forum-network/public/classes/scene.js index d90be0e..1c0f33c 100644 --- a/forum-network/public/classes/scene.js +++ b/forum-network/public/classes/scene.js @@ -11,6 +11,7 @@ export class Scene { this.displayValuesBox = this.box.addBox(`${this.name}-values`); this.box.addBox('Spacer').setInnerHTML(' '); this.logBox = this.box.addBox(`${this.name}-log`); + this.actors = new Set(); // this.seqDiagramContainer = this.box.addBox(`${this.name}-seq-diagram-container`); // this.seqDiagramBox = this.box.addBox(`${this.name}-seq-diagram`); // mermaid.mermaidAPI.initialize({ startOnLoad: false }); @@ -21,6 +22,10 @@ export class Scene { return actor; } + registerActor(actor) { + this.actors.add(actor); + } + addAction(name) { const action = new Action(name, this); return action; @@ -36,6 +41,15 @@ export class Scene { return this; } + deactivateAll() { + for (const actor of this.actors.values()) { + console.log(actor); + while (actor.active) { + actor.deactivate(); + } + } + } + // async renderSequenceDiagram() { // await mermaid.mermaidAPI.render( // `${this.name}-seq-diagram-element`, diff --git a/forum-network/public/classes/validation-pool.js b/forum-network/public/classes/validation-pool.js index 58099eb..25534b6 100644 --- a/forum-network/public/classes/validation-pool.js +++ b/forum-network/public/classes/validation-pool.js @@ -1,4 +1,7 @@ import { CryptoUtil } from "./crypto.js"; +import { Vote } from "./vote.js"; +import { Voter } from "./voter.js"; +import { Actor } from "./actor.js"; import params from "./params.js"; const ValidationPoolStates = Object.freeze({ @@ -6,8 +9,9 @@ const ValidationPoolStates = Object.freeze({ CLOSED: "CLOSED", }); -export class ValidationPool { - constructor(validationPool, authorId, {fee, duration, tokenLossRatio, contentiousDebate = false}) { +export class ValidationPool extends Actor { + constructor(bench, authorId, {fee, duration, tokenLossRatio, contentiousDebate = false}, name, scene) { + super(name, scene); // 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}`) @@ -18,7 +22,7 @@ export class ValidationPool { this.state = ValidationPoolStates.OPEN; this.votes = new Map(); this.voters = new Map(); - this.validationPool = validationPool; + this.bench = bench; this.id = CryptoUtil.randomUUID(); this.dateStart = new Date(); this.authorId = authorId; @@ -33,12 +37,13 @@ export class ValidationPool { } } - castVote(signingPublicKey, vote) { + castVote(signingPublicKey, position, stake, lockingTime) { + const vote = new Vote(position, stake, lockingTime); if (this.state === ValidationPoolStates.CLOSED) { - throw new Error(`Vote ${this.id} is closed`); + throw new Error(`Validation pool ${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`); + throw new Error(`Validation pool ${this.id} has expired, no new votes may be cast`); } this.votes.set(signingPublicKey, vote); } @@ -48,10 +53,13 @@ export class ValidationPool { .filter(([_, vote]) => vote.position === position)); } - revealIdentity(signingPublicKey, voter) { + revealIdentity(signingPublicKey, reputationPublicKey) { if (!this.votes.get(signingPublicKey)) { throw new Error("Must vote before revealing identity"); } + const voter = this.bench.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey); + voter.addVoteRecord(this); + this.bench.voters.set(reputationPublicKey, voter); this.voters.set(signingPublicKey, voter); if (this.votes.size === this.voters.size) { // All voters have revealed their reputation public keys @@ -60,6 +68,7 @@ export class ValidationPool { const result = this.evaluateWinningConditions(); this.applyTokenLocking(); this.distributeTokens(result); + this.deactivate(); } } @@ -91,7 +100,7 @@ export class ValidationPool { // 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); + this.bench.reputations.lockTokens(voter.reputationPublicKey, stake, lockingTime); // TODO: If there is an exception here, the voter may have voted incorrectly. Consider penalties. } } @@ -103,12 +112,13 @@ export class ValidationPool { const upvoteValue = getTotalValue(true); const downvoteValue = getTotalValue(false); - const activeAvailableReputation = this.validationPool.getTotalActiveAvailableReputation(); + const activeAvailableReputation = this.bench.getTotalActiveAvailableReputation(); const votePasses = upvoteValue >= params.winningRatio * downvoteValue; const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation; // TODO: If quorum is not met, what should happen? if (!quorumMet) { + this.deactivate(); throw new Error("Quorum is not met"); } return votePasses && quorumMet; @@ -117,7 +127,7 @@ export class ValidationPool { 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); + this.bench.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); @@ -129,7 +139,7 @@ export class ValidationPool { for (const [signingPublicKey, {stake}] of winningVotes.entries()) { const {reputationPublicKey} = this.voters.get(signingPublicKey); const reward = tokensForWinners * stake / totalStakes; - this.validationPool.reputations.addTokens(reputationPublicKey, reward); + this.bench.reputations.addTokens(reputationPublicKey, reward); } } } diff --git a/forum-network/public/index.html b/forum-network/public/index.html index f58a166..12a74be 100644 --- a/forum-network/public/index.html +++ b/forum-network/public/index.html @@ -1,7 +1,6 @@ Forum Network - @@ -10,5 +9,6 @@
  • Forum test
  • Graph test
  • Validation Pool test
  • +
  • Mermaid test
  • diff --git a/forum-network/public/mermaid-test.html b/forum-network/public/mermaid-test.html new file mode 100644 index 0000000..f86538c --- /dev/null +++ b/forum-network/public/mermaid-test.html @@ -0,0 +1,8 @@ + + + Forum Network + + + + + diff --git a/forum-network/public/validation-pool-test.js b/forum-network/public/validation-pool-test.js index 072b71c..2383d7f 100644 --- a/forum-network/public/validation-pool-test.js +++ b/forum-network/public/validation-pool-test.js @@ -14,8 +14,8 @@ const scene = window.scene = new Scene('Validation Pool test', rootBox).log('seq const bench = window.bench = new Bench("Bench", 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(); +const member2 = window.member2 = await new Member("Member2", scene).initialize(); const updateDisplayValues = () => { member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey)); @@ -27,24 +27,42 @@ const updateDisplayValues = () => { }; updateDisplayValues(); - await delay(1000); // First member can self-approve -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 pool = member1.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1}); + await member1.castVote(pool, true, 0, 0); + await member1.revealIdentity(pool); // Vote passes + updateDisplayValues(); + await delay(1000); +} -// 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); +// Failure example: second member can not self-approve +try { + const pool = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1}); + await member2.castVote(pool, true, 0, 0); + await member2.revealIdentity(pool); // Quorum not met! + updateDisplayValues(); + await delay(1000); +} catch(e) { + if (e.message.match(/Quorum is not met/)) { + console.log("Caught expected error: Quorum not met") + } else { + console.error("Unexpected error") + throw e; + } +} // Second member must be approved by first member -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 +{ + const pool = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1}); + await member1.castVote(pool, true, 0.5, 1); + await member1.revealIdentity(pool); // Vote passes + updateDisplayValues(); + await delay(1000); +} updateDisplayValues(); + +scene.deactivateAll();