From f475742cbf1ea6b25fefc054d345fabc94f3652d Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 29 Jan 2023 04:38:28 -0600 Subject: [PATCH] Minor refactoring --- forum-network/.eslintrc.js | 14 +++ forum-network/src/classes/exchange.js | 0 forum-network/src/classes/forum-view.js | 8 +- forum-network/src/classes/forum.js | 90 ++++++++------ forum-network/src/classes/list.js | 9 ++ forum-network/src/classes/question.js | 0 forum-network/src/classes/scene.js | 39 +++++-- .../src/classes/{graph.js => wdag.js} | 50 ++++---- forum-network/src/index.css | 6 + forum-network/src/index.html | 16 +-- ...ailability.html => availability.test.html} | 0 .../src/tests/{basic.html => basic.test.html} | 0 .../{debounce.html => debounce.test.html} | 0 .../{flowchart.html => flowchart.test.html} | 0 ...m-network.html => forum-network.test.html} | 0 forum-network/src/tests/forum.html | 9 -- forum-network/src/tests/forum.test.html | 26 +++++ forum-network/src/tests/forum/forum.test.js | 79 ------------- forum-network/src/tests/graph.html | 33 ------ forum-network/src/tests/mocha.test.html | 28 +++++ .../{reputation.html => reputation.test.html} | 10 +- forum-network/src/tests/scripts/forum.test.js | 110 ++++++++++++++++++ forum-network/src/tests/scripts/mocha.test.js | 23 ++++ forum-network/src/tests/scripts/wdag.test.js | 23 ++++ ...on-pool.html => validation-pool.test.html} | 0 forum-network/src/tests/wdag.test.html | 9 ++ 26 files changed, 384 insertions(+), 198 deletions(-) create mode 100644 forum-network/src/classes/exchange.js create mode 100644 forum-network/src/classes/list.js create mode 100644 forum-network/src/classes/question.js rename forum-network/src/classes/{graph.js => wdag.js} (63%) rename forum-network/src/tests/{availability.html => availability.test.html} (100%) rename forum-network/src/tests/{basic.html => basic.test.html} (100%) rename forum-network/src/tests/{debounce.html => debounce.test.html} (100%) rename forum-network/src/tests/{flowchart.html => flowchart.test.html} (100%) rename forum-network/src/tests/{forum-network.html => forum-network.test.html} (100%) delete mode 100644 forum-network/src/tests/forum.html create mode 100644 forum-network/src/tests/forum.test.html delete mode 100644 forum-network/src/tests/forum/forum.test.js delete mode 100644 forum-network/src/tests/graph.html create mode 100644 forum-network/src/tests/mocha.test.html rename forum-network/src/tests/{reputation.html => reputation.test.html} (71%) create mode 100644 forum-network/src/tests/scripts/forum.test.js create mode 100644 forum-network/src/tests/scripts/mocha.test.js create mode 100644 forum-network/src/tests/scripts/wdag.test.js rename forum-network/src/tests/{validation-pool.html => validation-pool.test.html} (100%) create mode 100644 forum-network/src/tests/wdag.test.html diff --git a/forum-network/.eslintrc.js b/forum-network/.eslintrc.js index 01170fe..1d9fdd3 100644 --- a/forum-network/.eslintrc.js +++ b/forum-network/.eslintrc.js @@ -30,4 +30,18 @@ module.exports = { 'no-constant-condition': ['off'], 'no-await-in-loop': ['off'], }, + globals: { + _: 'readonly', + chai: 'readonly', + expect: 'readonly', + mocha: 'readonly', + describe: 'readonly', + context: 'readonly', + it: 'readonly', + specify: 'readonly', + before: 'readonly', + after: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + }, }; diff --git a/forum-network/src/classes/exchange.js b/forum-network/src/classes/exchange.js new file mode 100644 index 0000000..e69de29 diff --git a/forum-network/src/classes/forum-view.js b/forum-network/src/classes/forum-view.js index e6a2d16..3c67def 100644 --- a/forum-network/src/classes/forum-view.js +++ b/forum-network/src/classes/forum-view.js @@ -1,4 +1,4 @@ -import { Graph } from './graph.js'; +import { WDAG } from './wdag.js'; class Author { constructor() { @@ -21,7 +21,7 @@ class PostVertex { export class ForumView { constructor() { this.reputations = new Map(); - this.posts = new Graph(); + this.posts = new WDAG(); this.authors = new Map(); } @@ -53,8 +53,8 @@ export class ForumView { const postVertex = new PostVertex(postId, author, stake, content, citations); console.log('addPost', { id: postId, postContent }); this.posts.addVertex(postId, postVertex); - for (const citation of citations) { - this.posts.addEdge('citation', postId, citation.postId, citation); + for (const { postId: citedPostId, weight } of citations) { + this.posts.addEdge('citation', postId, citedPostId, weight); } this.applyNonbindingReputationEffects(postVertex); } diff --git a/forum-network/src/classes/forum.js b/forum-network/src/classes/forum.js index c67008f..1bcbef3 100644 --- a/forum-network/src/classes/forum.js +++ b/forum-network/src/classes/forum.js @@ -1,17 +1,20 @@ import { Actor } from './actor.js'; -import { Graph } from './graph.js'; +import { WDAG } from './wdag.js'; import { Action } from './action.js'; import { CryptoUtil } from './crypto.js'; import params from '../params.js'; import { ReputationHolder } from './reputation-holder.js'; import { displayNumber } from '../util.js'; +const CITATION = 'citation'; +const BALANCE = 'balance'; + class Post extends Actor { constructor(forum, authorPublicKey, postContent) { const index = forum.posts.countVertices(); const name = `Post${index + 1}`; super(name, forum.scene); - this.id = postContent.id ?? `post_${CryptoUtil.randomUUID()}`; + this.id = postContent.id ?? `post_${CryptoUtil.randomUUID().slice(0, 4)}`; this.authorPublicKey = authorPublicKey; this.value = 0; this.initialValue = 0; @@ -47,7 +50,7 @@ export class Forum extends ReputationHolder { constructor(name, scene) { super(`forum_${CryptoUtil.randomUUID()}`, name, scene); this.id = this.reputationPublicKey; - this.posts = new Graph(scene); + this.posts = new WDAG(scene); this.actions = { addPost: new Action('add post', scene), propagateValue: new Action('propagate value', this.scene), @@ -60,7 +63,7 @@ export class Forum extends ReputationHolder { await this.actions.addPost.log(this, post); this.posts.addVertex(post.id, post, post.getLabel()); for (const { postId: citedPostId, weight } of post.citations) { - this.posts.addEdge('citation', post.id, citedPostId, { weight }); + this.posts.addEdge(CITATION, post.id, citedPostId, weight); } return post.id; } @@ -88,7 +91,8 @@ export class Forum extends ReputationHolder { }) { this.activate(); const initialValue = bench.reputation.valueOf(tokenId); - const post = this.getPost(postId); + const postVertex = this.posts.getVertex(postId); + const post = postVertex.data; post.setStatus('Validated'); post.initialValue = initialValue; this.posts.setVertexLabel(post.id, post.getLabel()); @@ -97,9 +101,13 @@ export class Forum extends ReputationHolder { // so that its value can be updated by future validated posts. post.tokenId = tokenId; - // Compute rewards const rewardsAccumulator = new Map(); - await this.propagateValue(rewardsAccumulator, this, post, initialValue); + + // Compute rewards + await this.propagateValue( + { to: postVertex, from: { data: this } }, + { rewardsAccumulator, increment: initialValue }, + ); // Apply computed rewards to update values of tokens for (const [id, value] of rewardsAccumulator) { @@ -118,46 +126,62 @@ export class Forum extends ReputationHolder { this.deactivate(); } - async propagateValue(rewardsAccumulator, fromActor, post, increment, depth = 0) { - this.actions.propagateValue.log(fromActor, post, `(${increment})`); + /** + * @param {Edge} edge + * @param {Object} opaqueData + */ + async propagateValue(edge, { rewardsAccumulator, increment, depth = 0 }) { + const postVertex = edge.to; + const post = postVertex?.data; + + this.actions.propagateValue.log(edge.from.data, post, `(${increment})`); + + console.log('propagateValue start', { + from: edge.from.id, + to: edge.to.id, + depth, + value: post.value, + increment, + }); - // Recursively distribute reputation to citations, according to weights - let totalOutboundAmount = 0; if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) { - for (const { postId: citedPostId, weight } of post.citations) { - const citedPost = this.getPost(citedPostId); + for (const citationEdge of this.posts.getEdges(CITATION, post)) { + const { to: citedPostVertex, data: { weight } } = citationEdge; + const citedPost = citedPostVertex.data; let outboundAmount = weight * increment; - // If this is a negative citation, it must not bring the target below zero value. + const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.data || 0; + console.log('Citation', { + citationEdge, outboundAmount, balance, citedPostValue: citedPost.value, + }); + // We need to ensure that we propagate no more reputation than we leached if (outboundAmount < 0) { - const citedPostTotalCitationWeight = citedPost.citations.reduce((t, { weight: w }) => t += w, 0); - const citedPostCapacity = citedPost.value / (1 - citedPostTotalCitationWeight); - outboundAmount = Math.max(outboundAmount, -citedPostCapacity); + outboundAmount = Math.max(outboundAmount, -citedPost.value); + if (depth > 0) { + outboundAmount = Math.max(outboundAmount, -balance); + } } - const refundFromOutbound = await this.propagateValue( + increment -= outboundAmount * params.leachingValue; + this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount); + await this.propagateValue(citationEdge, { rewardsAccumulator, - post, - citedPost, - outboundAmount, - depth + 1, - ); - totalOutboundAmount += outboundAmount - refundFromOutbound; + increment: outboundAmount, + depth: depth + 1, + }); } } - // Apply leaching value - const incrementAfterLeaching = increment - totalOutboundAmount * params.leachingValue; + const newValue = post.value + increment; - const rawNewValue = post.value + incrementAfterLeaching; - const newValue = Math.max(0, rawNewValue); - const appliedIncrement = newValue - post.value; - const refundToInbound = increment - appliedIncrement; + console.log('propagateValue end', { + depth, + increment, + newValue, + }); // Award reputation to post author - rewardsAccumulator.set(post.tokenId, appliedIncrement); + rewardsAccumulator.set(post.tokenId, increment); // Increment the value of the post await this.setPostValue(post, newValue); - - return refundToInbound; } } diff --git a/forum-network/src/classes/list.js b/forum-network/src/classes/list.js new file mode 100644 index 0000000..8dc58ac --- /dev/null +++ b/forum-network/src/classes/list.js @@ -0,0 +1,9 @@ +export class List { + constructor() { + this.items = []; + } + + add(item) { + this.items.push(item); + } +} diff --git a/forum-network/src/classes/question.js b/forum-network/src/classes/question.js new file mode 100644 index 0000000..e69de29 diff --git a/forum-network/src/classes/scene.js b/forum-network/src/classes/scene.js index 0d33af9..294fa22 100644 --- a/forum-network/src/classes/scene.js +++ b/forum-network/src/classes/scene.js @@ -2,6 +2,7 @@ import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs'; import { Actor } from './actor.js'; import { Action } from './action.js'; import { debounce, hexToRGB } from '../util.js'; +import { CryptoUtil } from './crypto.js'; class MermaidDiagram { constructor(box, logBox) { @@ -90,10 +91,11 @@ export class Scene { this.box.addBox('Spacer').setInnerHTML(' '); this.topSection = this.box.addBox('Top section').flex(); this.displayValuesBox = this.topSection.addBox('Values'); - this.middleSection = this.box.addBox('Middle section').flex(); + this.middleSection = this.box.addBox('Middle section'); this.box.addBox('Spacer').setInnerHTML(' '); this.actors = new Set(); this.dateStart = new Date(); + this.flowcharts = new Map(); mermaid.mermaidAPI.initialize({ startOnLoad: false, @@ -117,26 +119,49 @@ export class Scene { withSequenceDiagram() { const box = this.box.addBox('Sequence diagram'); this.box.addBox('Spacer').setInnerHTML(' '); - const logBox = this.box.addBox('Sequence diagram text'); + const logBox = this.box.addBox('Sequence diagram text').addClass('dim'); this.sequence = new MermaidDiagram(box, logBox); this.sequence.log('sequenceDiagram', false); return this; } - withFlowchart(direction = 'BT') { - const box = this.topSection.addBox('Flowchart'); - this.box.addBox('Spacer').setInnerHTML(' '); - const logBox = this.box.addBox('Flowchart text'); + withFlowchart({ direction = 'BT' } = {}) { + const box = this.topSection.addBox('Flowchart').addClass('padded'); + const logBox = this.topSection.addBox('Flowchart text').addClass('dim'); this.flowchart = new MermaidDiagram(box, logBox); this.flowchart.log(`graph ${direction}`, false); return this; } + withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) { + const index = this.flowcharts.size; + name = name ?? `Flowchart ${index}`; + id = id ?? `flowchart_${CryptoUtil.randomUUID().slice(0, 4)}`; + const container = this.middleSection.addBox(name).flex(); + const box = container.addBox('Flowchart').addClass('padded'); + const logBox = container.addBox('Flowchart text').addClass('dim'); + const flowchart = new MermaidDiagram(box, logBox); + flowchart.log(`graph ${direction}`, false); + this.flowcharts.set(id, flowchart); + return this; + } + + lastFlowchart() { + if (!this.flowcharts.size) { + if (this.flowchart) { + return this.flowchart; + } + throw new Error('lastFlowchart: No additional flowcharts have been added.'); + } + const flowcharts = Array.from(this.flowcharts.values()); + return flowcharts[flowcharts.length - 1]; + } + withTable() { if (this.table) { return this; } - const box = this.middleSection.addBox('Table'); + const box = this.middleSection.addBox('Table').addClass('padded'); this.box.addBox('Spacer').setInnerHTML(' '); this.table = new Table(box); return this; diff --git a/forum-network/src/classes/graph.js b/forum-network/src/classes/wdag.js similarity index 63% rename from forum-network/src/classes/graph.js rename to forum-network/src/classes/wdag.js index 29a8a7a..58000aa 100644 --- a/forum-network/src/classes/graph.js +++ b/forum-network/src/classes/wdag.js @@ -16,20 +16,27 @@ class Vertex { } class Edge { - constructor(label, from, to, data) { + constructor(label, from, to, weight) { this.from = from; this.to = to; this.label = label; - this.data = data; + this.weight = weight; } } -export class Graph { +export class WDAG { constructor(scene) { this.scene = scene; this.vertices = new Map(); this.edgeLabels = new Map(); this.nextVertexId = 0; + this.flowchart = scene?.flowchart ?? null; + } + + withFlowchart() { + this.scene.withAdditionalFlowchart(); + this.flowchart = this.scene.lastFlowchart(); + return this; } addVertex(id, data, label) { @@ -43,16 +50,12 @@ export class Graph { } const vertex = new Vertex(id, data); this.vertices.set(id, vertex); - if (this.scene && this.scene.flowchart) { - this.scene.flowchart.log(`${id}[${label ?? id}]`); - } + this.flowchart?.log(`${id}[${label ?? id}]`); return this; } setVertexLabel(id, label) { - if (this.scene && this.scene.flowchart) { - this.scene.flowchart.log(`${id}[${label}]`); - } + this.flowchart?.log(`${id}[${label}]`); } getVertex(id) { @@ -68,36 +71,43 @@ export class Graph { } getEdge(label, from, to) { + from = from instanceof Vertex ? from : this.getVertex(from); + to = to instanceof Vertex ? to : this.getVertex(to); const edges = this.edgeLabels.get(label); - return edges?.get(JSON.stringify({ from, to })); + return edges?.get(JSON.stringify([from.id, to.id])); } setEdge(label, from, to, edge) { + from = from instanceof Vertex ? from : this.getVertex(from); + to = to instanceof Vertex ? to : this.getVertex(to); + if (!(edge instanceof Edge)) { + edge = new Edge(edge); + } let edges = this.edgeLabels.get(label); if (!edges) { edges = new Map(); this.edgeLabels.set(label, edges); } - edges.set(JSON.stringify({ from, to }), edge); + edges.set(JSON.stringify([from.id, to.id]), edge); } - addEdge(label, from, to, data) { + addEdge(label, from, to, weight) { + from = from instanceof Vertex ? from : this.getVertex(from); + to = to instanceof Vertex ? to : this.getVertex(to); if (this.getEdge(label, from, to)) { throw new Error(`Edge ${label} from ${from} to ${to} already exists`); } - const edge = new Edge(label, from, to, data); + const edge = new Edge(label, from, to, weight); this.setEdge(label, from, to, edge); - const fromVertex = this.getVertex(from); - fromVertex.edges.from.push(edge); - const toVertex = this.getVertex(to); - toVertex.edges.to.push(edge); - if (this.scene && this.scene.flowchart) { - this.scene.flowchart.log(`${fromVertex.id} -- ${data.weight} --> ${toVertex.id}`); - } + from.edges.from.push(edge); + to.edges.to.push(edge); + this.flowchart?.log(`${from.id} -- ${weight} --> ${to.id}`); return this; } getEdges(label, from, to) { + from = from instanceof Vertex ? from : this.getVertex(from); + to = to instanceof Vertex ? to : this.getVertex(to); const edgeLabels = label ? [label] : Array.from(this.edgeLabels.keys()); return edgeLabels.flatMap((edgeLabel) => { const edges = this.edgeLabels.get(edgeLabel); diff --git a/forum-network/src/index.css b/forum-network/src/index.css index 98c570b..262dc7c 100644 --- a/forum-network/src/index.css +++ b/forum-network/src/index.css @@ -29,6 +29,12 @@ a:visited { font-family: monospace; font-size: 8pt; } +.dim { + opacity: 0.25; +} +.padded { + padding: 20px; +} svg { width: 800px; } diff --git a/forum-network/src/index.html b/forum-network/src/index.html index bb90cf3..a3c3dbe 100644 --- a/forum-network/src/index.html +++ b/forum-network/src/index.html @@ -7,19 +7,19 @@

Tests

Primary

Secondary

Tertiary

diff --git a/forum-network/src/tests/availability.html b/forum-network/src/tests/availability.test.html similarity index 100% rename from forum-network/src/tests/availability.html rename to forum-network/src/tests/availability.test.html diff --git a/forum-network/src/tests/basic.html b/forum-network/src/tests/basic.test.html similarity index 100% rename from forum-network/src/tests/basic.html rename to forum-network/src/tests/basic.test.html diff --git a/forum-network/src/tests/debounce.html b/forum-network/src/tests/debounce.test.html similarity index 100% rename from forum-network/src/tests/debounce.html rename to forum-network/src/tests/debounce.test.html diff --git a/forum-network/src/tests/flowchart.html b/forum-network/src/tests/flowchart.test.html similarity index 100% rename from forum-network/src/tests/flowchart.html rename to forum-network/src/tests/flowchart.test.html diff --git a/forum-network/src/tests/forum-network.html b/forum-network/src/tests/forum-network.test.html similarity index 100% rename from forum-network/src/tests/forum-network.html rename to forum-network/src/tests/forum-network.test.html diff --git a/forum-network/src/tests/forum.html b/forum-network/src/tests/forum.html deleted file mode 100644 index 511012f..0000000 --- a/forum-network/src/tests/forum.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Forum test - - - -
- - diff --git a/forum-network/src/tests/forum.test.html b/forum-network/src/tests/forum.test.html new file mode 100644 index 0000000..7403a5c --- /dev/null +++ b/forum-network/src/tests/forum.test.html @@ -0,0 +1,26 @@ + + + Forum test + + + + + +
+
+ + + + + + + diff --git a/forum-network/src/tests/forum/forum.test.js b/forum-network/src/tests/forum/forum.test.js deleted file mode 100644 index 70acbf3..0000000 --- a/forum-network/src/tests/forum/forum.test.js +++ /dev/null @@ -1,79 +0,0 @@ -import { Box } from '../../classes/box.js'; -import { Scene } from '../../classes/scene.js'; -import { Expert } from '../../classes/expert.js'; -import { Bench } from '../../classes/bench.js'; -import { delay } from '../../util.js'; -import { Forum } from '../../classes/forum.js'; -import { PostContent } from '../../classes/post-content.js'; -import params from '../../params.js'; - -const DEFAULT_DELAY_INTERVAL = 500; - -const rootElement = document.getElementById('forum-test'); -const rootBox = new Box('rootBox', rootElement).flex(); - -const scene = (window.scene = new Scene('Forum test', rootBox)); -scene.withSequenceDiagram(); -scene.withFlowchart(); -scene.withTable(); - -scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor); -scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit); -scene - .addDisplayValue('q3. referenceChainLimit') - .set(params.referenceChainLimit); -scene.addDisplayValue('q4. leachingValue').set(params.leachingValue); -scene.addDisplayValue(' '); - -const experts = (window.experts = []); -const newExpert = async () => { - const index = experts.length; - const name = `Expert${index + 1}`; - const expert = await new Expert(name, scene).initialize(); - experts.push(expert); - return expert; -}; - -const forum = (window.forum = new Forum('Forum', scene)); -const bench = (window.bench = new Bench(forum, 'Bench', scene)); -const expert1 = await newExpert(); -const expert2 = await newExpert(); -const expert3 = await newExpert(); - -bench.addValue('total rep', () => bench.reputation.getTotal()); -forum.addValue('total value', () => forum.getTotalValue()); - -for (const expert of experts) { - expert.addValue('rep', () => bench.reputation.valueOwnedBy(expert.reputationPublicKey)); -} - -const addPost = async (author, title, fee, citations = []) => { - await scene.startSection(); - - const postContent = new PostContent({}).setTitle(title); - for (const { postId, weight } of citations) { - postContent.addCitation(postId, weight); - } - - const { pool, postId } = await author.submitPostWithFee( - bench, - forum, - postContent, - { - fee, - duration: 1000, - tokenLossRatio: 1, - }, - ); - await delay(1000); - await pool.evaluateWinningConditions(); - await scene.endSection(); - await delay(DEFAULT_DELAY_INTERVAL); - return postId; -}; - -const postId1 = await addPost(expert1, 'Post 1', 20); -const postId2 = await addPost(expert2, 'Post 2', 10, [{ postId: postId1, weight: 0.5 }]); -const postId3 = await addPost(expert3, 'Post 3', 10, [{ postId: postId1, weight: -1 }]); -await addPost(expert3, 'Post 4', 100, [{ postId: postId2, weight: -1 }]); -await addPost(expert1, 'Post 5', 100, [{ postId: postId3, weight: -1 }]); diff --git a/forum-network/src/tests/graph.html b/forum-network/src/tests/graph.html deleted file mode 100644 index d3d605f..0000000 --- a/forum-network/src/tests/graph.html +++ /dev/null @@ -1,33 +0,0 @@ - - - Forum Graph - - - -
- - diff --git a/forum-network/src/tests/mocha.test.html b/forum-network/src/tests/mocha.test.html new file mode 100644 index 0000000..b5b9a6a --- /dev/null +++ b/forum-network/src/tests/mocha.test.html @@ -0,0 +1,28 @@ + + + + + Mocha Tests + + + + +
+
+ + + + + + + + + diff --git a/forum-network/src/tests/reputation.html b/forum-network/src/tests/reputation.test.html similarity index 71% rename from forum-network/src/tests/reputation.html rename to forum-network/src/tests/reputation.test.html index 2647d28..e55d4d3 100644 --- a/forum-network/src/tests/reputation.html +++ b/forum-network/src/tests/reputation.test.html @@ -9,9 +9,9 @@