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.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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -11,6 +11,7 @@ export class Scene {
this.displayValuesBox = this.box.addBox(`${this.name}-values`);
this.box.addBox('Spacer').setInnerHTML('&nbsp;');
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`,

View File

@ -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);
}
}
}

View File

@ -1,7 +1,6 @@
<!DOCTYPE html>
<head>
<title>Forum Network</title>
<script type="module" src="./index.js" defer></script>
<link type="text/css" rel="stylesheet" href="./index.css" />
</head>
<body>
@ -10,5 +9,6 @@
<li><a href="./forum-test.html">Forum 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="./mermaid-test.html">Mermaid test</a></li>
</ul>
</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 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
{
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();
// 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();