Prepare forum implementation for further development
Includes minor refactoring of existing code / tests
This commit is contained in:
parent
03af7d4b10
commit
4c53d2a0f7
|
@ -0,0 +1,10 @@
|
||||||
|
export class Token {
|
||||||
|
constructor(ownerPublicKey) {
|
||||||
|
this.ownerPublicKey = ownerPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer(newOwnerPublicKey) {
|
||||||
|
// TODO: Current owner must sign this request
|
||||||
|
this.ownerPublicKey = newOwnerPublicKey;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue