Improved seq diag logging
This commit is contained in:
parent
c89ba51dab
commit
41506fdcd5
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Forum Network</title>
|
||||
<link type="text/css" rel="stylesheet" href="./index.css" />
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue