Prepare forum implementation for further development

Includes minor refactoring of existing code / tests
This commit is contained in:
Ladd Hoffman 2023-01-03 01:26:55 -06:00
parent 03af7d4b10
commit 4c53d2a0f7
12 changed files with 173 additions and 38 deletions

View File

@ -0,0 +1,10 @@
export class Token {
constructor(ownerPublicKey) {
this.ownerPublicKey = ownerPublicKey;
}
transfer(newOwnerPublicKey) {
// TODO: Current owner must sign this request
this.ownerPublicKey = newOwnerPublicKey;
}
}

View File

@ -84,6 +84,9 @@ export class ForumNode extends Actor {
// Process an incoming post, received by whatever means // Process an incoming post, received by whatever means
processPost(authorId, post, stake) { processPost(authorId, post, stake) {
if (!post.id) {
post.id = CryptoUtil.randomUUID();
}
this.actions.storePost.log(this, this, null, { authorId, post, stake }); this.actions.storePost.log(this, this, null, { authorId, post, stake });
this.forumView.addPost(authorId, post.id, post, stake); this.forumView.addPost(authorId, post.id, post, stake);
} }

View File

@ -51,6 +51,7 @@ export class ForumView {
const { citations = [], content } = postContent; const { citations = [], content } = postContent;
const author = this.getOrInitializeAuthor(authorId); const author = this.getOrInitializeAuthor(authorId);
const postVertex = new PostVertex(postId, author, stake, content, citations); const postVertex = new PostVertex(postId, author, stake, content, citations);
console.log('addPost', { id: postId, postContent });
this.posts.addVertex(postId, postVertex); this.posts.addVertex(postId, postVertex);
for (const citation of citations) { for (const citation of citations) {
this.posts.addEdge('citation', postId, citation.postId, citation); this.posts.addEdge('citation', postId, citation.postId, citation);

View File

@ -1,21 +1,18 @@
import { Actor } from './actor.js'; import { Actor } from './actor.js';
import { Graph, Vertex } from './graph.js'; import { Graph } from './graph.js';
import { CryptoUtil } from './crypto.js';
import params from './params.js'; import params from './params.js';
import { Action } from './action.js';
class PostVertex extends Vertex { class Post extends Actor {
constructor(forum, authorId, postContent) { constructor(forum, authorId, postContent) {
super(); const index = forum.posts.countVertices();
this.forum = forum; const name = `Post${index + 1}`;
this.id = postContent.id; super(name, forum.scene);
this.id = postContent.id ?? CryptoUtil.randomUUID();
this.authorId = authorId; this.authorId = authorId;
this.citations = postContent.citations;
this.value = 0; this.value = 0;
} }
onValidate({ tokensMinted }) {
this.value = params.initialPostValueFunction({ tokensMinted });
this.forum.distributeReputation(this, this.value);
}
} }
/** /**
@ -26,14 +23,19 @@ export class Forum extends Actor {
super(name, scene); super(name, scene);
this.bench = bench; this.bench = bench;
this.posts = new Graph(); this.posts = new Graph();
this.actions = {
addPost: new Action('add post', scene),
};
} }
async addPost(authorId, postContent) { async addPost(authorId, postContent) {
const post = new PostVertex(this, authorId, postContent); const post = new Post(this, authorId, postContent);
this.actions.addPost.log(this, post);
this.posts.addVertex(post.id, post); this.posts.addVertex(post.id, post);
for (const { postId: citedPostId, weight } of postContent.citations) { for (const { postId: citedPostId, weight } of postContent.citations) {
this.posts.addEdge('citation', post.id, citedPostId, { weight }); this.posts.addEdge('citation', post.id, citedPostId, { weight });
} }
return post.id;
} }
getPost(postId) { getPost(postId) {
@ -44,6 +46,11 @@ export class Forum extends Actor {
return this.posts.getVertices(); return this.posts.getVertices();
} }
onValidate({ tokensMinted }) {
const initialValue = params.initialPostValueFunction({ tokensMinted });
this.distributeReputation(this, initialValue);
}
distributeReputation(post, amount, depth = 0) { distributeReputation(post, amount, depth = 0) {
console.log('distributeReputation', { post, amount, depth }); console.log('distributeReputation', { post, amount, depth });
// Add the given value to the current post // Add the given value to the current post

View File

@ -1,3 +1,6 @@
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
import { debounce } from '../util.js';
export class Vertex { export class Vertex {
constructor(data) { constructor(data) {
this.data = data; this.data = data;
@ -38,7 +41,11 @@ export class Graph {
data = id; data = id;
id = this.nextVertexId++; id = this.nextVertexId++;
} }
if (this.vertices.has(id)) {
throw new Error(`Vertex already exists with id: ${id}`);
}
const vertex = new Vertex(data); const vertex = new Vertex(data);
console.log('addVertex', vertex);
this.vertices.set(id, vertex); this.vertices.set(id, vertex);
return this; return this;
} }
@ -70,6 +77,7 @@ export class Graph {
} }
addEdge(label, from, to, data) { addEdge(label, from, to, data) {
console.log('addEdge', { from, to });
if (this.getEdge(label, from, to)) { if (this.getEdge(label, from, to)) {
throw new Error(`Edge ${label} from ${from} to ${to} already exists`); throw new Error(`Edge ${label} from ${from} to ${to} already exists`);
} }
@ -91,4 +99,24 @@ export class Graph {
}); });
}); });
} }
countVertices() {
return this.vertices.size;
}
async renderGraph() {
const render = async () => {
const dateStart = new Date();
const graph = await mermaid.mermaidAPI.render(
this.seqDiagramElement.getId(),
this.logBox.getInnerText(),
);
this.seqDiagramBox.setInnerHTML(graph);
if (!this.dateLastRender) {
this.dateLastRender = new Date();
}
this.dateLastRender = dateStart;
};
debounce(render, 100);
}
} }

View File

@ -8,6 +8,7 @@ export class Member extends Actor {
constructor(name, scene) { constructor(name, scene) {
super(name, scene); super(name, scene);
this.actions = { this.actions = {
submitPostViaNetwork: new Action('submit post via network', scene),
submitPost: new Action('submit post', scene), submitPost: new Action('submit post', scene),
initiateValidationPool: new Action('initiate validation pool', scene), initiateValidationPool: new Action('initiate validation pool', scene),
castVote: new Action('cast vote', scene), castVote: new Action('cast vote', scene),
@ -28,15 +29,22 @@ export class Member extends Actor {
return this; return this;
} }
async submitPost(forumNode, post, stake) { async submitPostViaNetwork(forumNode, post, stake) {
// TODO: Include fee // TODO: Include fee
const postMessage = new PostMessage({ post, stake }); const postMessage = new PostMessage({ post, stake });
console.log('submitPostViaNetwork', postMessage);
await postMessage.sign(this.reputationKey); await postMessage.sign(this.reputationKey);
this.actions.submitPost.log(this, forumNode, null, { id: post.id }); this.actions.submitPostViaNetwork.log(this, forumNode, null, { id: post.id });
// For now, directly call forumNode.receiveMessage(); // For now, directly call forumNode.receiveMessage();
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON())); await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
} }
async submitPost(forum, postContent) {
// TODO: Include fee
this.actions.submitPost.log(this, forum);
return forum.addPost(this.reputationPublicKey, postContent);
}
async initiateValidationPool(bench, options) { async initiateValidationPool(bench, options) {
// For now, directly call bench.initiateValidationPool(); // For now, directly call bench.initiateValidationPool();
const signingKey = await CryptoUtil.generateAsymmetricKey(); const signingKey = await CryptoUtil.generateAsymmetricKey();

View File

@ -1,5 +1,3 @@
import { CryptoUtil } from './crypto.js';
export class Citation { export class Citation {
constructor(postId, weight) { constructor(postId, weight) {
this.postId = postId; this.postId = postId;
@ -20,7 +18,6 @@ export class Citation {
export class PostContent { export class PostContent {
constructor(content) { constructor(content) {
this.id = CryptoUtil.randomUUID();
this.content = content; this.content = content;
this.citations = []; this.citations = [];
} }
@ -33,16 +30,16 @@ export class PostContent {
toJSON() { toJSON() {
return { return {
id: this.id,
content: this.content, content: this.content,
citations: this.citations.map((citation) => citation.toJSON()), citations: this.citations.map((citation) => citation.toJSON()),
...(this.id ? { id: this.id } : {}),
}; };
} }
static fromJSON({ id, content, citations }) { static fromJSON({ id, content, citations }) {
const post = new PostContent(content); const post = new PostContent(content);
post.id = id;
post.citations = citations.map((citation) => Citation.fromJSON(citation)); post.citations = citations.map((citation) => Citation.fromJSON(citation));
post.id = id;
return post; return post;
} }
} }

View File

@ -69,17 +69,8 @@ export class Scene {
if (!this.dateLastRender) { if (!this.dateLastRender) {
this.dateLastRender = new Date(); this.dateLastRender = new Date();
} }
console.log(
`renderSequenceDiagram time: ${
new Date() - dateStart
} ms, time since last render: ${dateStart - this.dateLastRender}`,
);
this.dateLastRender = dateStart; this.dateLastRender = dateStart;
}; };
debounce(render, 100); debounce(render, 100);
} }
insertSvg(svgCode) {
this.seqDiagramElement.setInnerHTML(svgCode);
}
} }

View File

@ -13,5 +13,6 @@
<li><a href="/tests/mermaid.html">Mermaid</a></li> <li><a href="/tests/mermaid.html">Mermaid</a></li>
<li><a href="/tests/debounce.html">Debounce</a></li> <li><a href="/tests/debounce.html">Debounce</a></li>
<li><a href="/tests/availability.html">Availability</a></li> <li><a href="/tests/availability.html">Availability</a></li>
<li><a href="/tests/forum.html">Forum</a></li>
</ul> </ul>
</body> </body>

View File

@ -55,14 +55,12 @@
const requestor = new Public("Public", scene); const requestor = new Public("Public", scene);
const updateDisplayValues = async () => { const updateDisplayValues = async () => {
member1.setValue( for (const member of members) {
member.setValue(
"rep", "rep",
bench.reputations.getTokens(member1.reputationPublicKey) bench.reputations.getTokens(member.reputationPublicKey)
);
member2.setValue(
"rep",
bench.reputations.getTokens(member2.reputationPublicKey)
); );
}
bench.setValue("total rep", bench.getTotalReputation()); bench.setValue("total rep", bench.getTotalReputation());
await scene.renderSequenceDiagram(); await scene.renderSequenceDiagram();
}; };

View File

@ -13,6 +13,7 @@
import { Member } from "/classes/member.js"; import { Member } from "/classes/member.js";
import { ForumNode } from "/classes/forum-node.js"; import { ForumNode } from "/classes/forum-node.js";
import { ForumNetwork } from "/classes/forum-network.js"; import { ForumNetwork } from "/classes/forum-network.js";
import { CryptoUtil } from "/classes/crypto.js";
import { delay } from "/util.js"; import { delay } from "/util.js";
const rootElement = document.getElementById("forum-network"); const rootElement = document.getElementById("forum-network");
@ -48,15 +49,24 @@
// const blockchain = new Blockchain(); // const blockchain = new Blockchain();
window.post1 = new PostContent({ message: "hi" }); window.post1 = new PostContent({ message: "hi" });
window.post1.id = CryptoUtil.randomUUID();
window.post2 = new PostContent({ message: "hello" }).addCitation( window.post2 = new PostContent({ message: "hello" }).addCitation(
window.post1.id, window.post1.id,
1.0 1.0
); );
await delay(1000); await delay(1000);
await window.author1.submitPost(window.forumNode1, window.post1, 50); await window.author1.submitPostViaNetwork(
window.forumNode1,
window.post1,
50
);
await delay(1000); await delay(1000);
await window.author2.submitPost(window.forumNode2, window.post2, 100); await window.author2.submitPostViaNetwork(
window.forumNode2,
window.post2,
100
);
await delay(1000); await delay(1000);
clearInterval(processInterval); clearInterval(processInterval);

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<head>
<title>Forum test</title>
<link type="text/css" rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="forum-test"></div>
</body>
<script type="module">
import { Box } from "/classes/box.js";
import { Scene } from "/classes/scene.js";
import { Member } from "/classes/member.js";
import { Bench } from "/classes/bench.js";
import { Business } from "/classes/business.js";
import { Availability } from "/classes/availability.js";
import { delay } from "/util.js";
import { Forum } from "/classes/forum.js";
import { Public } from "/classes/public.js";
import { PostContent } from "/classes/post.js";
const 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).log(
"sequenceDiagram"
));
const members = (window.members = []);
const newMember = async () => {
const index = members.length;
const name = `Member${index + 1}`;
const member = await new Member(name, scene).initialize();
members.push(member);
return member;
};
const posts = (window.posts = []);
const newPost = async (author, content, citations) => {
const postContent = new PostContent({ hello: "there" });
const postId = await member1.submitPost(forum, postContent1);
return postId;
};
const member1 = await newMember();
const member2 = await newMember();
await newMember();
const bench = (window.bench = new Bench("Bench", scene));
const forum = (window.forum = new Forum(bench, "Forum", scene));
const updateDisplayValues = async () => {
for (const member of members) {
member.setValue(
"rep",
bench.reputations.getTokens(member.reputationPublicKey)
);
}
bench.setValue("total rep", bench.getTotalReputation());
await scene.renderSequenceDiagram();
};
const updateDisplayValuesAndDelay = async () => {
await updateDisplayValues();
await delay(DELAY_INTERVAL);
};
await updateDisplayValuesAndDelay();
const postId1 = await member1.submitPost(
forum,
new PostContent({ hello: "there" })
);
await updateDisplayValuesAndDelay();
const postId2 = await member1.submitPost(
forum,
new PostContent({ hello: "to you as well" }).addCitation(postId1, 0.5)
);
await updateDisplayValuesAndDelay();
</script>