Add support for posts with multiple authors
This commit is contained in:
parent
0a954a01f3
commit
7ae5ff9b03
|
@ -5,18 +5,28 @@ import params from '../../params.js';
|
|||
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
||||
import { displayNumber, EPSILON, INCINERATOR_ADDRESS } from '../../util.js';
|
||||
|
||||
const CITATION = 'citation';
|
||||
const BALANCE = 'balance';
|
||||
const EdgeTypes = {
|
||||
CITATION: 'citation',
|
||||
BALANCE: 'balance',
|
||||
AUTHOR: 'author',
|
||||
};
|
||||
|
||||
const VertexTypes = {
|
||||
POST: 'post',
|
||||
AUTHOR: 'author',
|
||||
};
|
||||
|
||||
class Post extends Actor {
|
||||
constructor(forum, authorPublicKey, postContent) {
|
||||
const index = forum.posts.countVertices();
|
||||
constructor(forum, senderId, postContent) {
|
||||
const index = forum.graph.countVertices(VertexTypes.POST);
|
||||
const name = `Post${index + 1}`;
|
||||
super(name, forum.scene);
|
||||
this.forum = forum;
|
||||
this.id = postContent.id ?? name;
|
||||
this.authorPublicKey = authorPublicKey;
|
||||
this.senderId = senderId;
|
||||
this.value = 0;
|
||||
this.initialValue = 0;
|
||||
this.authors = postContent.authors;
|
||||
this.citations = postContent.citations;
|
||||
this.title = postContent.title;
|
||||
const leachingTotal = this.citations
|
||||
|
@ -25,6 +35,8 @@ class Post extends Actor {
|
|||
const donationTotal = this.citations
|
||||
.filter(({ weight }) => weight > 0)
|
||||
.reduce((total, { weight }) => total += weight, 0);
|
||||
|
||||
// TODO: Move evaluation of these parameters to Validation Pool
|
||||
if (leachingTotal > params.revaluationLimit) {
|
||||
throw new Error('Post leaching total exceeds revaluation limit '
|
||||
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
||||
|
@ -49,6 +61,12 @@ class Post extends Actor {
|
|||
</tr></table>`
|
||||
.replaceAll(/\n\s*/g, '');
|
||||
}
|
||||
|
||||
async setValue(value) {
|
||||
this.value = value;
|
||||
await this.setDisplayValue('value', value);
|
||||
this.forum.graph.setVertexDisplayLabel(this.id, this.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +79,7 @@ export class Forum extends ReputationHolder {
|
|||
super(name, scene);
|
||||
this.dao = dao;
|
||||
this.id = this.reputationPublicKey;
|
||||
this.posts = new WDAG(scene);
|
||||
this.graph = new WDAG(scene);
|
||||
this.actions = {
|
||||
propagate: new Action('propagate', scene),
|
||||
confirm: new Action('confirm', scene),
|
||||
|
@ -69,32 +87,26 @@ export class Forum extends ReputationHolder {
|
|||
};
|
||||
}
|
||||
|
||||
async addPost(authorId, postContent) {
|
||||
console.log('addPost', { authorId, postContent });
|
||||
const post = new Post(this, authorId, postContent);
|
||||
this.posts.addVertex(post.id, post, post.getLabel());
|
||||
async addPost(senderId, postContent) {
|
||||
console.log('addPost', { senderId, postContent });
|
||||
const post = new Post(this, senderId, postContent);
|
||||
this.graph.addVertex(VertexTypes.POST, post.id, post, post.getLabel());
|
||||
for (const { postId: citedPostId, weight } of post.citations) {
|
||||
// Special case: Incinerator
|
||||
if (citedPostId === INCINERATOR_ADDRESS && !this.posts.getVertex(INCINERATOR_ADDRESS)) {
|
||||
this.posts.addVertex(INCINERATOR_ADDRESS, { name: 'Incinerator' }, 'Incinerator');
|
||||
if (citedPostId === INCINERATOR_ADDRESS && !this.graph.getVertex(INCINERATOR_ADDRESS)) {
|
||||
this.graph.addVertex(VertexTypes.POST, INCINERATOR_ADDRESS, { name: 'Incinerator' }, 'Incinerator');
|
||||
}
|
||||
this.posts.addEdge(CITATION, post.id, citedPostId, weight);
|
||||
this.graph.addEdge(EdgeTypes.CITATION, post.id, citedPostId, weight);
|
||||
}
|
||||
return post;
|
||||
}
|
||||
|
||||
getPost(postId) {
|
||||
return this.posts.getVertexData(postId);
|
||||
return this.graph.getVertexData(postId);
|
||||
}
|
||||
|
||||
getPosts() {
|
||||
return this.posts.getVerticesData();
|
||||
}
|
||||
|
||||
async setPostValue(post, value) {
|
||||
post.value = value;
|
||||
await post.setValue('value', value);
|
||||
this.posts.setVertexLabel(post.id, post.getLabel());
|
||||
return this.graph.getVerticesData();
|
||||
}
|
||||
|
||||
getTotalValue() {
|
||||
|
@ -108,38 +120,74 @@ export class Forum extends ReputationHolder {
|
|||
async onValidate({
|
||||
pool, postId, tokenId,
|
||||
}) {
|
||||
console.log('onValidate', { pool, postId, tokenId });
|
||||
const initialValue = this.dao.reputation.valueOf(tokenId);
|
||||
const postVertex = this.posts.getVertex(postId);
|
||||
const postVertex = this.graph.getVertex(postId);
|
||||
const post = postVertex.data;
|
||||
post.setStatus('Validated');
|
||||
post.initialValue = initialValue;
|
||||
this.posts.setVertexLabel(post.id, post.getLabel());
|
||||
this.graph.setVertexDisplayLabel(post.id, post.getLabel());
|
||||
|
||||
// Store a reference to the reputation token associated with this post,
|
||||
// so that its value can be updated by future validated posts.
|
||||
post.tokenId = tokenId;
|
||||
const addAuthorToGraph = (publicKey, weight, authorTokenId) => {
|
||||
const authorVertex = this.graph.getVertex(publicKey)
|
||||
?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, { name: publicKey, publicKey }, publicKey);
|
||||
const authorEdge = this.graph.addEdge(
|
||||
EdgeTypes.AUTHOR,
|
||||
postVertex,
|
||||
authorVertex,
|
||||
weight,
|
||||
{ tokenId: authorTokenId },
|
||||
);
|
||||
console.log('addAuthorToGraph', { authorVertex, authorEdge });
|
||||
};
|
||||
|
||||
// In the case of multiple authors, mint additional (empty) tokens.
|
||||
// If no authors are specified, treat the sender as the sole author.
|
||||
if (!post.authors?.length) {
|
||||
addAuthorToGraph(post.senderId, 1, tokenId);
|
||||
} else {
|
||||
for (const { publicKey, weight } of post.authors) {
|
||||
// If the sender is also listed among the authors, do not mint them an additional token.
|
||||
if (publicKey === post.senderId) {
|
||||
addAuthorToGraph(publicKey, weight, tokenId);
|
||||
} else {
|
||||
addAuthorToGraph(publicKey, weight, this.dao.reputation.mint(this.id, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Verify that cumulative author weight === 1
|
||||
|
||||
const rewardsAccumulator = new Map();
|
||||
|
||||
// Compute rewards
|
||||
// Compute reputation rewards
|
||||
await this.propagateValue(
|
||||
{ to: postVertex, from: { data: pool } },
|
||||
{ rewardsAccumulator, increment: initialValue },
|
||||
);
|
||||
|
||||
// Apply computed rewards to update values of tokens
|
||||
for (const [id, value] of rewardsAccumulator) {
|
||||
if (value < 0) {
|
||||
this.dao.reputation.transferValueFrom(id, post.tokenId, -value);
|
||||
} else {
|
||||
this.dao.reputation.transferValueFrom(post.tokenId, id, value);
|
||||
for (const [authorTokenId, amount] of rewardsAccumulator) {
|
||||
console.log('reward', { authorTokenId, amount });
|
||||
// The primary author gets the validation pool minted token.
|
||||
// So we don't need to transfer any reputation to the primary author.
|
||||
// Their reward will be the remaining balance after all other transfers.
|
||||
if (authorTokenId !== tokenId) {
|
||||
if (amount < 0) {
|
||||
this.dao.reputation.transferValueFrom(authorTokenId, tokenId, -amount);
|
||||
} else {
|
||||
this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer ownership of the minted/staked token, from the posts to the post author
|
||||
this.dao.reputation.transfer(this.id, post.authorPublicKey, post.tokenId);
|
||||
// const toActor = this.scene?.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
||||
// const value = this.dao.reputation.valueOf(post.tokenId);
|
||||
// Transfer ownership of the minted tokens to the authors
|
||||
for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHOR, true)) {
|
||||
const authorVertex = authorEdge.to;
|
||||
const { publicKey } = authorVertex.data;
|
||||
const { tokenId: authorTokenId } = authorEdge.data;
|
||||
this.dao.reputation.transfer(this.id, publicKey, authorTokenId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,7 +198,7 @@ export class Forum extends ReputationHolder {
|
|||
rewardsAccumulator, increment, depth = 0, initialNegative = false,
|
||||
}) {
|
||||
const postVertex = edge.to;
|
||||
const post = postVertex?.data;
|
||||
const post = postVertex.data;
|
||||
this.actions.propagate.log(edge.from.data, post, `(${increment})`);
|
||||
|
||||
if (!!params.referenceChainLimit && depth > params.referenceChainLimit) {
|
||||
|
@ -175,13 +223,14 @@ export class Forum extends ReputationHolder {
|
|||
|
||||
const propagate = async (positive) => {
|
||||
let totalOutboundAmount = 0;
|
||||
const citationEdges = postVertex.getEdges(CITATION, true)
|
||||
const citationEdges = postVertex.getEdges(EdgeTypes.CITATION, true)
|
||||
.filter(({ weight }) => (positive ? weight > 0 : weight < 0));
|
||||
for (const citationEdge of citationEdges) {
|
||||
const { weight } = citationEdge;
|
||||
let outboundAmount = weight * increment;
|
||||
if (Math.abs(outboundAmount) > EPSILON) {
|
||||
const balanceToOutbound = this.posts.getEdgeWeight(BALANCE, citationEdge.from, citationEdge.to) ?? 0;
|
||||
const balanceToOutbound = this.graph.getEdgeWeight(EdgeTypes.BALANCE, citationEdge.from, citationEdge.to)
|
||||
?? 0;
|
||||
let refundFromOutbound = 0;
|
||||
|
||||
// Special case: Incineration.
|
||||
|
@ -225,7 +274,12 @@ export class Forum extends ReputationHolder {
|
|||
|
||||
// Keep a record of the effect of the reputation transferred along this edge in the graph,
|
||||
// so that later, negative citations can be constrained to at most undo these effects.
|
||||
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
|
||||
this.graph.setEdgeWeight(
|
||||
EdgeTypes.BALANCE,
|
||||
citationEdge.from,
|
||||
citationEdge.to,
|
||||
balanceToOutbound + outboundAmount,
|
||||
);
|
||||
totalOutboundAmount += outboundAmount;
|
||||
|
||||
this.actions.confirm.log(
|
||||
|
@ -263,11 +317,15 @@ export class Forum extends ReputationHolder {
|
|||
refundToInbound,
|
||||
});
|
||||
|
||||
// Award reputation to post author
|
||||
rewardsAccumulator.set(post.tokenId, appliedIncrement);
|
||||
// Apply reputation effects to post authors, not to the post directly
|
||||
for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHOR, true)) {
|
||||
const { weight, data: { tokenId } } = authorEdge;
|
||||
const authorIncrement = weight * appliedIncrement;
|
||||
rewardsAccumulator.set(tokenId, authorIncrement);
|
||||
}
|
||||
|
||||
// Increment the value of the post
|
||||
await this.setPostValue(post, newValue);
|
||||
await post.setValue(newValue);
|
||||
|
||||
return refundToInbound;
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ export class ValidationPool extends ReputationHolder {
|
|||
|
||||
// Update computed display values
|
||||
const actor = this.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
||||
await actor.computeValues();
|
||||
await actor.computeDisplayValues();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,9 +235,9 @@ export class ValidationPool extends ReputationHolder {
|
|||
if (!actor) {
|
||||
throw new Error('Actor not found!');
|
||||
}
|
||||
await actor.computeValues();
|
||||
await actor.computeDisplayValues();
|
||||
}
|
||||
await this.dao.computeValues();
|
||||
await this.dao.computeDisplayValues();
|
||||
|
||||
this.scene?.stateToTable(`validation pool ${this.name} complete`);
|
||||
|
||||
|
|
|
@ -58,12 +58,12 @@ export class Actor {
|
|||
this.values.set(label, this.scene?.addDisplayValue(`${this.name} ${label}`));
|
||||
if (fn) {
|
||||
this.valueFunctions.set(label, fn);
|
||||
await this.computeValues();
|
||||
await this.computeDisplayValues();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
async setValue(label, value) {
|
||||
async setDisplayValue(label, value) {
|
||||
if (typeof value === 'function') {
|
||||
return this.addComputedValue(label, value);
|
||||
}
|
||||
|
@ -76,10 +76,10 @@ export class Actor {
|
|||
return this;
|
||||
}
|
||||
|
||||
async computeValues() {
|
||||
async computeDisplayValues() {
|
||||
for (const [label, fn] of this.valueFunctions.entries()) {
|
||||
const value = fn();
|
||||
await this.setValue(label, value);
|
||||
await this.setDisplayValue(label, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,12 @@ import { Action } from '../display/action.js';
|
|||
import {
|
||||
Message, PostMessage, PeerMessage, messageFromJSON,
|
||||
} from './message.js';
|
||||
import { ForumView } from './forum-view.js';
|
||||
import { NetworkNode } from './network-node.js';
|
||||
import { randomID } from '../util/util.js';
|
||||
import { randomID } from '../../util.js';
|
||||
|
||||
export class ForumNode extends NetworkNode {
|
||||
constructor(name, scene) {
|
||||
super(name, scene);
|
||||
this.forumView = new ForumView();
|
||||
this.actions = {
|
||||
...this.actions,
|
||||
storePost: new Action('store post', scene),
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
import { WDAG } from '../supporting/wdag.js';
|
||||
|
||||
class Author {
|
||||
constructor() {
|
||||
this.posts = new Map();
|
||||
this.reputation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class PostVertex {
|
||||
constructor(id, author, stake, content, citations) {
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.content = content;
|
||||
this.stake = stake;
|
||||
this.citations = citations;
|
||||
this.reputation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ForumView {
|
||||
constructor() {
|
||||
this.reputations = new Map();
|
||||
this.posts = new WDAG();
|
||||
this.authors = new Map();
|
||||
}
|
||||
|
||||
getReputation(id) {
|
||||
return this.reputations.get(id);
|
||||
}
|
||||
|
||||
setReputation(id, reputation) {
|
||||
this.reputations.set(id, reputation);
|
||||
}
|
||||
|
||||
incrementReputation(publicKey, increment, _reason) {
|
||||
const reputation = this.getReputation(publicKey) || 0;
|
||||
return this.reputations.set(publicKey, reputation + increment);
|
||||
}
|
||||
|
||||
getOrInitializeAuthor(authorId) {
|
||||
let author = this.authors.get(authorId);
|
||||
if (!author) {
|
||||
author = new Author(authorId);
|
||||
this.authors.set(authorId, author);
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
addPost(authorId, postId, postContent, stake) {
|
||||
const { citations = [], content } = postContent;
|
||||
const author = this.getOrInitializeAuthor(authorId);
|
||||
const postVertex = new PostVertex(postId, author, stake, content, citations);
|
||||
this.posts.addVertex(postId, postVertex);
|
||||
for (const { postId: citedPostId, weight } of citations) {
|
||||
this.posts.addEdge('citation', postId, citedPostId, weight);
|
||||
}
|
||||
}
|
||||
|
||||
getPost(postId) {
|
||||
return this.posts.getVertexData(postId);
|
||||
}
|
||||
|
||||
getPosts() {
|
||||
return this.posts.getVertices();
|
||||
}
|
||||
}
|
|
@ -38,7 +38,8 @@ export class NetworkNode extends Actor {
|
|||
// Enqueue it for further processing.
|
||||
async receiveMessage(messageStr) {
|
||||
const messageJson = JSON.parse(messageStr);
|
||||
const senderReputation = this.forumView.getReputation(messageJson.publicKey) || 0;
|
||||
// const senderReputation = this.forumView.getReputation(messageJson.publicKey) || 0;
|
||||
const senderReputation = 0;
|
||||
this.queue.add(messageJson, senderReputation);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,9 @@ export class ReputationTokenContract extends ERC721 {
|
|||
|
||||
incrementValue(tokenId, increment, context) {
|
||||
const value = this.values.get(tokenId);
|
||||
if (value === undefined) {
|
||||
throw new Error(`Token not found: ${tokenId}`);
|
||||
}
|
||||
const newValue = value + increment;
|
||||
const history = this.histories.get(tokenId) || [];
|
||||
|
||||
|
@ -60,7 +63,7 @@ export class ReputationTokenContract extends ERC721 {
|
|||
const sourceAvailable = this.availableValueOf(fromTokenId);
|
||||
if (sourceAvailable < amount - EPSILON) {
|
||||
throw new Error('Token value transfer: source has insufficient available value. '
|
||||
+ `Needs ${amount}; has ${sourceAvailable}.`);
|
||||
+ `Needs ${amount}; has ${sourceAvailable}.`);
|
||||
}
|
||||
this.incrementValue(fromTokenId, -amount);
|
||||
this.incrementValue(toTokenId, amount);
|
||||
|
|
|
@ -31,6 +31,7 @@ export class ERC721 {
|
|||
}
|
||||
|
||||
mint(to, tokenId) {
|
||||
console.log('ERC721.mint', { to, tokenId });
|
||||
if (this.owners.get(tokenId)) {
|
||||
throw new Error('ERC721: token already minted');
|
||||
}
|
||||
|
@ -60,9 +61,10 @@ export class ERC721 {
|
|||
}
|
||||
|
||||
transfer(from, to, tokenId) {
|
||||
console.log('ERC721.transfer', { from, to, tokenId });
|
||||
const owner = this.owners.get(tokenId);
|
||||
if (owner !== from) {
|
||||
throw new Error('ERC721: transfer from incorrect owner');
|
||||
throw new Error(`ERC721: transfer from incorrect owner ${from}; should be ${owner}`);
|
||||
}
|
||||
this.incrementBalance(from, -1);
|
||||
this.incrementBalance(to, 1);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export class Vertex {
|
||||
constructor(id, data) {
|
||||
constructor(graph, type, id, data) {
|
||||
this.graph = graph;
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
this.edges = {
|
||||
|
@ -8,19 +10,20 @@ export class Vertex {
|
|||
};
|
||||
}
|
||||
|
||||
getEdges(label, away) {
|
||||
getEdges(type, away) {
|
||||
return this.edges[away ? 'from' : 'to'].filter(
|
||||
(edge) => edge.label === label,
|
||||
(edge) => edge.type === type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Edge {
|
||||
constructor(label, from, to, weight) {
|
||||
constructor(type, from, to, weight, data) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
this.weight = weight;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +31,7 @@ export class WDAG {
|
|||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.vertices = new Map();
|
||||
this.edgeLabels = new Map();
|
||||
this.edgeTypes = new Map();
|
||||
this.nextVertexId = 0;
|
||||
this.flowchart = scene?.flowchart;
|
||||
}
|
||||
|
@ -39,7 +42,11 @@ export class WDAG {
|
|||
return this;
|
||||
}
|
||||
|
||||
addVertex(id, data, label) {
|
||||
setVertexDisplayLabel(id, label) {
|
||||
this.flowchart?.log(`${id}[${label}]`);
|
||||
}
|
||||
|
||||
addVertex(type, id, data, label) {
|
||||
// Support simple case of auto-incremented numeric ids
|
||||
if (typeof id === 'object') {
|
||||
data = id;
|
||||
|
@ -48,14 +55,10 @@ export class WDAG {
|
|||
if (this.vertices.has(id)) {
|
||||
throw new Error(`Vertex already exists with id: ${id}`);
|
||||
}
|
||||
const vertex = new Vertex(id, data);
|
||||
const vertex = new Vertex(this, type, id, data);
|
||||
this.vertices.set(id, vertex);
|
||||
this.flowchart?.log(`${id}[${label ?? id}]`);
|
||||
return this;
|
||||
}
|
||||
|
||||
setVertexLabel(id, label) {
|
||||
this.flowchart?.log(`${id}[${label}]`);
|
||||
this.setVertexDisplayLabel(id, label ?? id);
|
||||
return vertex;
|
||||
}
|
||||
|
||||
getVertex(id) {
|
||||
|
@ -74,25 +77,25 @@ export class WDAG {
|
|||
return btoa([from.id, to.id]).replaceAll(/[^A-Za-z0-9]+/g, '');
|
||||
}
|
||||
|
||||
getEdge(label, from, to) {
|
||||
getEdge(type, from, to) {
|
||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||
if (!from || !to) {
|
||||
return undefined;
|
||||
}
|
||||
const edges = this.edgeLabels.get(label);
|
||||
const edges = this.edgeTypes.get(type);
|
||||
const edgeKey = WDAG.getEdgeKey({ from, to });
|
||||
return edges?.get(edgeKey);
|
||||
}
|
||||
|
||||
getEdgeWeight(label, from, to) {
|
||||
return this.getEdge(label, from, to)?.weight;
|
||||
getEdgeWeight(type, from, to) {
|
||||
return this.getEdge(type, from, to)?.weight;
|
||||
}
|
||||
|
||||
getEdgeHtml({ from, to }) {
|
||||
let html = '<table>';
|
||||
for (const { label, weight } of this.getEdges(null, from, to)) {
|
||||
html += `<tr><td>${label}</td><td>${weight}</td></tr>`;
|
||||
for (const { type, weight } of this.getEdges(null, from, to)) {
|
||||
html += `<tr><td>${type}</td><td>${weight}</td></tr>`;
|
||||
}
|
||||
html += '</table>';
|
||||
return html;
|
||||
|
@ -103,14 +106,14 @@ export class WDAG {
|
|||
return `${edgeKey}(${this.getEdgeHtml(edge)})`;
|
||||
}
|
||||
|
||||
setEdgeWeight(label, from, to, weight) {
|
||||
setEdgeWeight(type, from, to, weight, data) {
|
||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||
const edge = new Edge(label, from, to, weight);
|
||||
let edges = this.edgeLabels.get(label);
|
||||
const edge = new Edge(type, from, to, weight, data);
|
||||
let edges = this.edgeTypes.get(type);
|
||||
if (!edges) {
|
||||
edges = new Map();
|
||||
this.edgeLabels.set(label, edges);
|
||||
this.edgeTypes.set(type, edges);
|
||||
}
|
||||
const edgeKey = WDAG.getEdgeKey(edge);
|
||||
edges.set(edgeKey, edge);
|
||||
|
@ -118,26 +121,26 @@ export class WDAG {
|
|||
return edge;
|
||||
}
|
||||
|
||||
addEdge(label, from, to, weight) {
|
||||
addEdge(type, from, to, weight, data) {
|
||||
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.id} to ${to.id} already exists`);
|
||||
if (this.getEdge(type, from, to)) {
|
||||
throw new Error(`Edge ${type} from ${from.id} to ${to.id} already exists`);
|
||||
}
|
||||
const edge = this.setEdgeWeight(label, from, to, weight);
|
||||
const edge = this.setEdgeWeight(type, from, to, weight, data);
|
||||
from.edges.from.push(edge);
|
||||
to.edges.to.push(edge);
|
||||
this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
|
||||
this.flowchart?.log(`class ${WDAG.getEdgeKey(edge)} edge`);
|
||||
return this;
|
||||
return edge;
|
||||
}
|
||||
|
||||
getEdges(label, from, to) {
|
||||
getEdges(type, 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);
|
||||
const edgeTypes = type ? [type] : Array.from(this.edgeTypes.keys());
|
||||
return edgeTypes.flatMap((edgeType) => {
|
||||
const edges = this.edgeTypes.get(edgeType);
|
||||
return Array.from(edges?.values() || []).filter((edge) => {
|
||||
const matchFrom = from === null || from === undefined || from === edge.from;
|
||||
const matchTo = to === null || to === undefined || to === edge.to;
|
||||
|
@ -146,7 +149,10 @@ export class WDAG {
|
|||
});
|
||||
}
|
||||
|
||||
countVertices() {
|
||||
return this.vertices.size;
|
||||
countVertices(type) {
|
||||
if (!type) {
|
||||
return this.vertices.size;
|
||||
}
|
||||
return Array.from(this.vertices.values()).filter((vertex) => vertex.type === type).length;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
export class Citation {
|
||||
class Author {
|
||||
constructor(publicKey, weight) {
|
||||
this.publicKey = publicKey;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
publicKey: this.publicKey,
|
||||
weight: this.weight,
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON({ publicKey, weight }) {
|
||||
return new Author(publicKey, weight);
|
||||
}
|
||||
}
|
||||
|
||||
class Citation {
|
||||
constructor(postId, weight) {
|
||||
this.postId = postId;
|
||||
this.weight = weight;
|
||||
|
@ -17,11 +35,18 @@ export class Citation {
|
|||
}
|
||||
|
||||
export class PostContent {
|
||||
constructor(content) {
|
||||
constructor(content = {}) {
|
||||
this.content = content;
|
||||
this.authors = [];
|
||||
this.citations = [];
|
||||
}
|
||||
|
||||
addAuthor(authorPublicKey, weight) {
|
||||
const author = new Author(authorPublicKey, weight);
|
||||
this.authors.push(author);
|
||||
return this;
|
||||
}
|
||||
|
||||
addCitation(postId, weight) {
|
||||
const citation = new Citation(postId, weight);
|
||||
this.citations.push(citation);
|
||||
|
@ -36,6 +61,7 @@ export class PostContent {
|
|||
toJSON() {
|
||||
return {
|
||||
content: this.content,
|
||||
authors: this.authors.map((author) => author.toJSON()),
|
||||
citations: this.citations.map((citation) => citation.toJSON()),
|
||||
...(this.id ? { id: this.id } : {}),
|
||||
title: this.title,
|
||||
|
@ -43,9 +69,10 @@ export class PostContent {
|
|||
}
|
||||
|
||||
static fromJSON({
|
||||
id, content, citations, title,
|
||||
id, content, authors, citations, title,
|
||||
}) {
|
||||
const post = new PostContent(content);
|
||||
post.authors = authors.map((author) => Author.fromJSON(author));
|
||||
post.citations = citations.map((citation) => Citation.fromJSON(citation));
|
||||
post.id = id;
|
||||
post.title = title;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<li><a href="./tests/forum7.test.html">Negatively cite a zero-valued post</a></li>
|
||||
<li><a href="./tests/forum8.test.html">Incinerate reputation</a></li>
|
||||
<li><a href="./tests/forum9.test.html">Use incineration to achieve more balanced reweighting</a></li>
|
||||
<li><a href="./tests/forum10.test.html">Post with multiple authors</a></li>
|
||||
</ol>
|
||||
</ul>
|
||||
<ul>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<script type="module" src="./scripts/forum/forum7.test.js"></script>
|
||||
<script type="module" src="./scripts/forum/forum8.test.js"></script>
|
||||
<script type="module" src="./scripts/forum/forum9.test.js"></script>
|
||||
<script type="module" src="./scripts/forum/forum10.test.js"></script>
|
||||
<script defer class="mocha-init">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
const actor2 = new Actor('B', scene);
|
||||
const action1 = new Action('Action 1', scene);
|
||||
await action1.log(actor1, actor2);
|
||||
await actor1.setValue('value', 1);
|
||||
await actor1.setDisplayValue('value', 1);
|
||||
|
||||
await scene.withFlowchart();
|
||||
await scene.flowchart.log('A --> B');
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<title>Forum test 10</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2><a href="../">DGF Tests</a></h2>
|
||||
<div id="mocha"></div>
|
||||
<div id="scene"></div>
|
||||
</body>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
|
||||
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://unpkg.com/mocha/mocha.js"></script>
|
||||
<script src="https://unpkg.com/chai/chai.js"></script>
|
||||
<script type="module" src="./scripts/forum/forum10.test.js"></script>
|
||||
<script defer class="mocha-init">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
});
|
||||
chai.should();
|
||||
</script>
|
|
@ -18,7 +18,7 @@ const newExpert = async () => {
|
|||
const index = experts.length;
|
||||
const name = `Expert${index + 1}`;
|
||||
const expert = await new Expert(dao, name, scene).initialize();
|
||||
expert.setValue(
|
||||
expert.setDisplayValue(
|
||||
'rep',
|
||||
() => dao.reputation.valueOwnedBy(expert.reputationPublicKey),
|
||||
);
|
||||
|
@ -36,7 +36,7 @@ const setup = async () => {
|
|||
scene.withTable();
|
||||
|
||||
dao = new DAO('DGF', scene);
|
||||
await dao.setValue('total rep', () => dao.reputation.getTotal());
|
||||
await dao.setDisplayValue('total rep', () => dao.reputation.getTotal());
|
||||
|
||||
experts = [];
|
||||
|
||||
|
|
|
@ -19,17 +19,27 @@ export class ForumTest {
|
|||
};
|
||||
}
|
||||
|
||||
async addPost(author, fee, citations = []) {
|
||||
async addPost(authors, fee, citations = []) {
|
||||
const postIndex = this.posts.length;
|
||||
const title = `posts[${postIndex}]`;
|
||||
await this.scene.sequence.startSection();
|
||||
|
||||
const postContent = new PostContent({}).setTitle(title);
|
||||
const postContent = new PostContent().setTitle(title);
|
||||
|
||||
const submitter = Array.isArray(authors) ? authors[0].author : authors;
|
||||
|
||||
if (Array.isArray(authors)) {
|
||||
for (const { author, weight } of authors) {
|
||||
console.log('author', { author, weight });
|
||||
postContent.addAuthor(author.reputationPublicKey, weight);
|
||||
}
|
||||
}
|
||||
|
||||
for (const { postId, weight } of citations) {
|
||||
postContent.addCitation(postId, weight);
|
||||
}
|
||||
|
||||
const { pool, postId } = await author.submitPostWithFee(
|
||||
const { pool, postId } = await submitter.submitPostWithFee(
|
||||
postContent,
|
||||
{
|
||||
fee,
|
||||
|
@ -77,11 +87,9 @@ export class ForumTest {
|
|||
this.posts = [];
|
||||
|
||||
await this.newExpert();
|
||||
// await newExpert();
|
||||
// await newExpert();
|
||||
|
||||
await this.dao.addComputedValue('total value', () => this.dao.reputation.getTotal());
|
||||
// await this.dao.addComputedValue('total reputation', () => this.dao.forum.getTotalValue());
|
||||
this.dao.computeValues();
|
||||
this.dao.computeDisplayValues();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { mochaRun } from '../../../util.js';
|
||||
import { ForumTest } from './forum.test-util.js';
|
||||
|
||||
describe('Forum', function tests() {
|
||||
this.timeout(0);
|
||||
|
||||
const forumTest = new ForumTest();
|
||||
|
||||
before(async () => {
|
||||
await forumTest.setup();
|
||||
});
|
||||
|
||||
context('Post with multiple authors', async () => {
|
||||
let forum;
|
||||
let experts;
|
||||
let posts;
|
||||
|
||||
before(async () => {
|
||||
forum = forumTest.forum;
|
||||
experts = forumTest.experts;
|
||||
posts = forumTest.posts;
|
||||
|
||||
await forumTest.newExpert();
|
||||
await forumTest.newExpert();
|
||||
});
|
||||
|
||||
it('Post1', async () => {
|
||||
const authors = [
|
||||
{ author: experts[0], weight: 0.5 },
|
||||
{ author: experts[1], weight: 0.25 },
|
||||
{ author: experts[2], weight: 0.25 },
|
||||
];
|
||||
await forumTest.addPost(authors, 10);
|
||||
forum.getPost(posts[0]).value.should.equal(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
mochaRun();
|
|
@ -15,11 +15,11 @@ describe('Query the graph', function tests() {
|
|||
before(() => {
|
||||
graph = (window.graph = new WDAG()).withFlowchart();
|
||||
|
||||
graph.addVertex({});
|
||||
graph.addVertex({});
|
||||
graph.addVertex({});
|
||||
graph.addVertex({});
|
||||
graph.addVertex({});
|
||||
graph.addVertex('v1', {});
|
||||
graph.addVertex('v1', {});
|
||||
graph.addVertex('v1', {});
|
||||
graph.addVertex('v1', {});
|
||||
graph.addVertex('v1', {});
|
||||
|
||||
graph.addEdge('e1', 0, 1, 1);
|
||||
graph.addEdge('e1', 2, 1, 0.5);
|
||||
|
|
Loading…
Reference in New Issue