Improved seq diag logging

This commit is contained in:
Ladd Hoffman 2022-11-13 12:23:30 -06:00
parent c89ba51dab
commit 41506fdcd5
9 changed files with 116 additions and 56 deletions

View File

@ -7,6 +7,21 @@ export class Actor {
this.status.set('New'); this.status.set('New');
this.scene.log(`participant ${this.name}`); this.scene.log(`participant ${this.name}`);
this.values = new Map(); 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) { send(dest, action, detail) {
@ -47,6 +62,9 @@ export class Actor {
displayValue = this.scene.addDisplayValue(`${this.name} ${label}`); displayValue = this.scene.addDisplayValue(`${this.name} ${label}`);
this.values.set(label, displayValue); this.values.set(label, displayValue);
} }
if (value !== displayValue.get()) {
this.scene.log(`note over ${this.name} : ${label} = ${value}`);
}
displayValue.set(value); displayValue.set(value);
return this; return this;
} }

View File

@ -1,9 +1,8 @@
import { Actor } from "./actor.js"; import { Actor } from "./actor.js";
import { Reputations } from "./reputation.js"; import { Reputations } from "./reputation.js";
import { ValidationPool } from "./validation-pool.js"; import { ValidationPool } from "./validation-pool.js";
import { Vote } from "./vote.js";
import { Voter } from "./voter.js";
import params from "./params.js"; import params from "./params.js";
import { Action } from "./action.js";
export class Bench extends Actor { export class Bench extends Actor {
constructor(name, scene) { constructor(name, scene) {
@ -13,7 +12,10 @@ export class Bench extends Actor {
this.reputations = new Reputations(); this.reputations = new Reputations();
this.actions = { this.actions = {
createValidationPool: new Action('create validation pool', scene),
}; };
this.activate();
} }
listValidationPools() { listValidationPools() {
@ -51,22 +53,12 @@ export class Bench extends Actor {
} }
initiateValidationPool(authorId, {fee, duration, tokenLossRatio, contentiousDebate}) { initiateValidationPool(authorId, {fee, duration, tokenLossRatio, contentiousDebate}) {
const vote = new ValidationPool(this, authorId, {fee, duration, tokenLossRatio, contentiousDebate}); const validationPoolNumber = this.validationPools.size + 1;
this.validationPools.set(vote.id, vote); const validationPool = new ValidationPool(this, authorId, {fee, duration, tokenLossRatio, contentiousDebate},
return vote.id; `pool${validationPoolNumber}`, scene);
} this.validationPools.set(validationPool.id, validationPool);
this.actions.createValidationPool.log(this, validationPool)
castVote(voteId, signingPublicKey, position, stake, lockingTime) { validationPool.activate();
const vote = new Vote(position, stake, lockingTime); return validationPool;
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

@ -8,17 +8,18 @@ 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),
initiateValidationPool: new Action('initiate vote', scene), initiateValidationPool: new Action('initiate validation pool', 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),
}; };
this.votes = new Map(); this.validationPools = new Map();
} }
async initialize() { async initialize() {
this.reputationKey = await CryptoUtil.generateAsymmetricKey(); this.reputationKey = await CryptoUtil.generateAsymmetricKey();
this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey); this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey);
this.status.set('Initialized'); this.status.set('Initialized');
this.activate();
return this; return this;
} }
@ -37,20 +38,20 @@ export class Member extends Actor {
return bench.initiateValidationPool(this.reputationPublicKey, options); 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 signingKey = await CryptoUtil.generateAsymmetricKey();
const signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey) const signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey)
this.votes.set(voteId, {signingPublicKey}); this.validationPools.set(validationPool.id, {signingPublicKey});
// TODO: encrypt vote // TODO: encrypt vote
// TODO: sign message // TODO: sign message
this.actions.castVote.log(this, validationPool); this.actions.castVote.log(this, validationPool);
validationPool.castVote(voteId, signingPublicKey, position, stake, lockingTime); validationPool.castVote(signingPublicKey, position, stake, lockingTime);
} }
async revealIdentity(validationPool, voteId) { async revealIdentity(validationPool) {
const {signingPublicKey} = this.votes.get(voteId); const {signingPublicKey} = this.validationPools.get(validationPool.id);
// TODO: sign message // TODO: sign message
this.actions.revealIdentity.log(this, validationPool); this.actions.revealIdentity.log(this, validationPool);
validationPool.revealIdentity(voteId, signingPublicKey, this.reputationPublicKey); validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey);
} }
} }

View File

@ -36,8 +36,7 @@ class Reputation {
const now = new Date(); const now = new Date();
const tokensLocked = Array.from(this.locks.values()) const tokensLocked = Array.from(this.locks.values())
.filter(({dateCreated, duration}) => now - dateCreated < duration) .filter(({dateCreated, duration}) => now - dateCreated < duration)
.map(({tokens}) => tokens) .reduce((acc, cur) => acc += cur.tokens, 0);
.reduce((acc, cur) => acc += cur, 0);
return Math.max(this.tokens - tokensLocked, 0); return Math.max(this.tokens - tokensLocked, 0);
} }
} }

View File

@ -11,6 +11,7 @@ export class Scene {
this.displayValuesBox = this.box.addBox(`${this.name}-values`); this.displayValuesBox = this.box.addBox(`${this.name}-values`);
this.box.addBox('Spacer').setInnerHTML('&nbsp;'); this.box.addBox('Spacer').setInnerHTML('&nbsp;');
this.logBox = this.box.addBox(`${this.name}-log`); this.logBox = this.box.addBox(`${this.name}-log`);
this.actors = new Set();
// this.seqDiagramContainer = this.box.addBox(`${this.name}-seq-diagram-container`); // this.seqDiagramContainer = this.box.addBox(`${this.name}-seq-diagram-container`);
// this.seqDiagramBox = this.box.addBox(`${this.name}-seq-diagram`); // this.seqDiagramBox = this.box.addBox(`${this.name}-seq-diagram`);
// mermaid.mermaidAPI.initialize({ startOnLoad: false }); // mermaid.mermaidAPI.initialize({ startOnLoad: false });
@ -21,6 +22,10 @@ export class Scene {
return actor; return actor;
} }
registerActor(actor) {
this.actors.add(actor);
}
addAction(name) { addAction(name) {
const action = new Action(name, this); const action = new Action(name, this);
return action; return action;
@ -36,6 +41,15 @@ export class Scene {
return this; return this;
} }
deactivateAll() {
for (const actor of this.actors.values()) {
console.log(actor);
while (actor.active) {
actor.deactivate();
}
}
}
// async renderSequenceDiagram() { // async renderSequenceDiagram() {
// await mermaid.mermaidAPI.render( // await mermaid.mermaidAPI.render(
// `${this.name}-seq-diagram-element`, // `${this.name}-seq-diagram-element`,

View File

@ -1,4 +1,7 @@
import { CryptoUtil } from "./crypto.js"; 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"; import params from "./params.js";
const ValidationPoolStates = Object.freeze({ const ValidationPoolStates = Object.freeze({
@ -6,8 +9,9 @@ const ValidationPoolStates = Object.freeze({
CLOSED: "CLOSED", CLOSED: "CLOSED",
}); });
export class ValidationPool { export class ValidationPool extends Actor {
constructor(validationPool, authorId, {fee, duration, tokenLossRatio, contentiousDebate = false}) { 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 = 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))) {
throw new Error(`Token loss ratio must be in the range [0, 1]; got ${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.state = ValidationPoolStates.OPEN;
this.votes = new Map(); this.votes = new Map();
this.voters = new Map(); this.voters = new Map();
this.validationPool = validationPool; this.bench = bench;
this.id = CryptoUtil.randomUUID(); this.id = CryptoUtil.randomUUID();
this.dateStart = new Date(); this.dateStart = new Date();
this.authorId = authorId; 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) { 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) { 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); this.votes.set(signingPublicKey, vote);
} }
@ -48,10 +53,13 @@ export class ValidationPool {
.filter(([_, vote]) => vote.position === position)); .filter(([_, vote]) => vote.position === position));
} }
revealIdentity(signingPublicKey, voter) { revealIdentity(signingPublicKey, reputationPublicKey) {
if (!this.votes.get(signingPublicKey)) { if (!this.votes.get(signingPublicKey)) {
throw new Error("Must vote before revealing identity"); 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); this.voters.set(signingPublicKey, voter);
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
@ -60,6 +68,7 @@ export class ValidationPool {
const result = this.evaluateWinningConditions(); const result = this.evaluateWinningConditions();
this.applyTokenLocking(); this.applyTokenLocking();
this.distributeTokens(result); this.distributeTokens(result);
this.deactivate();
} }
} }
@ -91,7 +100,7 @@ export class ValidationPool {
// specified amounts of time. // specified amounts of time.
for (const [signingPublicKey, {stake, lockingTime}] of this.votes.entries()) { for (const [signingPublicKey, {stake, lockingTime}] of this.votes.entries()) {
const voter = this.voters.get(signingPublicKey); 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. // 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 upvoteValue = getTotalValue(true);
const downvoteValue = getTotalValue(false); const downvoteValue = getTotalValue(false);
const activeAvailableReputation = this.validationPool.getTotalActiveAvailableReputation(); const activeAvailableReputation = this.bench.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;
// TODO: If quorum is not met, what should happen? // TODO: If quorum is not met, what should happen?
if (!quorumMet) { if (!quorumMet) {
this.deactivate();
throw new Error("Quorum is not met"); throw new Error("Quorum is not met");
} }
return votePasses && quorumMet; return votePasses && quorumMet;
@ -117,7 +127,7 @@ export class ValidationPool {
distributeTokens(result) { distributeTokens(result) {
// Reward the author // Reward the author
// TODO: Penalty to the author if the vote does not pass? // 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 // Reward the vote winners, in proportion to their stakes
const tokensForWinners = result ? this.tokens.for : this.tokens.against; const tokensForWinners = result ? this.tokens.for : this.tokens.against;
const winningVotes = this.listVotes(result); const winningVotes = this.listVotes(result);
@ -129,7 +139,7 @@ export class ValidationPool {
for (const [signingPublicKey, {stake}] of winningVotes.entries()) { for (const [signingPublicKey, {stake}] of winningVotes.entries()) {
const {reputationPublicKey} = this.voters.get(signingPublicKey); const {reputationPublicKey} = this.voters.get(signingPublicKey);
const reward = tokensForWinners * stake / totalStakes; const reward = tokensForWinners * stake / totalStakes;
this.validationPool.reputations.addTokens(reputationPublicKey, reward); this.bench.reputations.addTokens(reputationPublicKey, reward);
} }
} }
} }

View File

@ -1,7 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum Network</title> <title>Forum Network</title>
<script type="module" src="./index.js" defer></script>
<link type="text/css" rel="stylesheet" href="./index.css" /> <link type="text/css" rel="stylesheet" href="./index.css" />
</head> </head>
<body> <body>
@ -10,5 +9,6 @@
<li><a href="./forum-test.html">Forum test</a></li> <li><a href="./forum-test.html">Forum test</a></li>
<li><a href="./graph-test.html">Graph test</a></li> <li><a href="./graph-test.html">Graph test</a></li>
<li><a href="./validation-pool-test.html">Validation Pool test</a></li> <li><a href="./validation-pool-test.html">Validation Pool test</a></li>
<li><a href="./mermaid-test.html">Mermaid test</a></li>
</ul> </ul>
</body> </body>

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<head>
<title>Forum Network</title>
<link type="text/css" rel="stylesheet" href="./index.css" />
</head>
<body>
</body>

View File

@ -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 bench = window.bench = new Bench("Bench", scene);
const member1 = window.member1 = await new Member("member1", scene).initialize(); const member1 = window.member1 = await new Member("Member1", scene).initialize();
const member2 = window.member2 = await new Member("member2", scene).initialize(); const member2 = window.member2 = await new Member("Member2", scene).initialize();
const updateDisplayValues = () => { const updateDisplayValues = () => {
member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey)); member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey));
@ -27,24 +27,42 @@ const updateDisplayValues = () => {
}; };
updateDisplayValues(); updateDisplayValues();
await delay(1000); await delay(1000);
// First member can self-approve // First member can self-approve
const vote1 = member1.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1}); {
await member1.castVote(bench, vote1, true, 0, 0); const pool = member1.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member1.revealIdentity(bench, vote1); // Vote passes await member1.castVote(pool, true, 0, 0);
updateDisplayValues(); await member1.revealIdentity(pool); // Vote passes
updateDisplayValues();
await delay(1000);
}
// const vote2 = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1}); // Failure example: second member can not self-approve
// await member2.castVote(bench, vote2, true, 0); try {
// await member2.revealIdentity(bench, vote2); // Quorum is not met! const pool = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member2.castVote(pool, true, 0, 0);
await delay(1000); 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 // 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); const pool = member2.initiateValidationPool(bench, {fee: 1, duration: 1000, tokenLossRatio: 1});
await member1.revealIdentity(bench, vote2); // Vote passes await member1.castVote(pool, true, 0.5, 1);
await member1.revealIdentity(pool); // Vote passes
updateDisplayValues();
await delay(1000);
}
updateDisplayValues(); updateDisplayValues();
scene.deactivateAll();