dao-governance-framework/forum-network/src/classes/forum.js

122 lines
4.0 KiB
JavaScript

import { Actor } from './actor.js';
import { Graph } from './graph.js';
import { Action } from './action.js';
import { CryptoUtil } from './crypto.js';
import params from '../params.js';
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 ?? CryptoUtil.randomUUID();
this.authorPublicKey = authorPublicKey;
this.value = 0;
this.citations = postContent.citations;
this.title = postContent.title;
this.totalCitationWeight = this.citations.reduce((total, { weight }) => total += weight, 0);
if (this.totalCitationWeight > params.revaluationLimit) {
throw new Error('Post total citation weight exceeds revaluation limit '
+ `(${this.totalCitationWeight} > ${params.revaluationLimit})`);
}
if (this.citations.some(({ weight }) => Math.abs(weight) > 1)) {
throw new Error('Each citation weight must be in the range [-1, 1]');
}
}
}
/**
* Purpose: Maintain a directed, acyclic, weighted graph of posts referencing other posts
*/
export class Forum extends Actor {
constructor(name, scene) {
super(name, scene);
this.posts = new Graph(scene);
this.actions = {
addPost: new Action('add post', scene),
propagateValue: new Action('propagate value', this.scene),
};
}
async addPost(authorId, postContent) {
const post = new Post(this, authorId, postContent);
await this.actions.addPost.log(this, post);
this.posts.addVertex(post.id, post, post.title);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${post.id} -- value --> ${post.id}_value[0]`);
}
for (const { postId: citedPostId, weight } of post.citations) {
this.posts.addEdge('citation', post.id, citedPostId, { weight });
if (this.scene.flowchart) {
this.scene.flowchart.log(`${post.id} -- ${weight} --> ${citedPostId}`);
}
}
return post.id;
}
getPost(postId) {
return this.posts.getVertexData(postId);
}
getPosts() {
return this.posts.getVerticesData();
}
async setPostValue(postId, value) {
const post = this.getPost(postId);
post.value = value;
await post.setValue('value', value);
if (this.scene.flowchart) {
this.scene.flowchart.log(`${post.id}_value[${value}]`);
}
}
getPostValue(postId) {
const post = this.getPost(postId);
return post.value;
}
getTotalValue() {
return this.getPosts().reduce((total, { value }) => total += value, 0);
}
async propagateValue(fromActor, postId, increment, depth = 0) {
if (depth === 0 && this.scene.flowchart) {
const randomId = CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8);
this.scene.flowchart.log(`${postId}_initial_value_${randomId}[${increment}] -- initial value --> ${postId}`);
}
if (params.maxPropagationDepth >= 0 && depth > params.maxPropagationDepth) {
return [];
}
const post = this.getPost(postId);
this.actions.propagateValue.log(fromActor, post, `(increment: ${increment})`);
const adjustedIncrement = increment * (1 - params.leachingValue * post.totalCitationWeight);
const rewards = new Map();
const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value);
const addRewards = (r) => {
for (const [id, value] of r) {
addReward(id, value);
}
};
// Increment the value of the post
// Apply leaching value
const currentValue = this.getPostValue(postId);
const newValue = currentValue + adjustedIncrement;
await this.setPostValue(postId, newValue);
// Award reputation to post author
console.log('reward for post author', post.authorPublicKey, adjustedIncrement);
addReward(post.authorPublicKey, adjustedIncrement);
// Recursively distribute reputation to citations, according to weights
for (const { postId: citedPostId, weight } of post.citations) {
addRewards(await this.propagateValue(post, citedPostId, weight * increment, depth + 1));
}
return rewards;
}
}