From 23a95ed11320078c60d2c2ebf1ec477e68f41c9c Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Tue, 14 Mar 2023 21:32:38 -0500 Subject: [PATCH 01/11] Add seq diagram line for rejected citation of incinerator --- forum-network/src/classes/dao/forum.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index 7826cca..ae65ed5 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -185,6 +185,13 @@ export class Forum extends ReputationHolder { // Only a positive amount may be incinerated! Otherwise the sink could be used as a source. if (outboundAmount < 0) { this.scene?.flowchart?.log(`style ${citationEdge.from.id} fill:#620000`); + this.actions.propagate.log( + citationEdge.from.data, + { name: 'Incinerator' }, + `(${increment})`, + undefined, + '-x', + ); throw new Error('Incinerator can only receive positive citations!'); } // Reputation sent to the incinerator is burned! This means it is deducted from the sender, From 0a954a01f368f284398d2617137524e8215e61b4 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Thu, 13 Apr 2023 08:21:06 -0500 Subject: [PATCH 02/11] Notes --- forum-network/notes/chain.md | 16 +++++++++++++++ forum-network/notes/ecosystem.md | 27 ++++++++++++++++++++++++++ forum-network/notes/infrastructure.md | 5 +++++ forum-network/notes/matrix.md | 25 ++++++++++++++++++++++++ forum-network/notes/todo.md | 17 ++++++++++++++++ forum-network/src/classes/dao/forum.js | 4 ++++ 6 files changed, 94 insertions(+) create mode 100644 forum-network/notes/ecosystem.md create mode 100644 forum-network/notes/infrastructure.md create mode 100644 forum-network/notes/todo.md diff --git a/forum-network/notes/chain.md b/forum-network/notes/chain.md index 6d2ea62..b8bd81b 100644 --- a/forum-network/notes/chain.md +++ b/forum-network/notes/chain.md @@ -28,3 +28,19 @@ cardano -- "dynamic availability"? staking pools -- does it make sense with reputation? what about for governance voting -- do we want a representative republic or a democracy? + +--- + +# Protocol brainstorming + +Each node must build/maintain a view of the history and/or state of the "smart contract" operations. + +Nodes must sign messages to each other with asymmetric keys. + +This is intended to be an open network that anyone can join. + +Each node must verify the results reported by other nodes, and themselves report results to other nodes. + +In order to receive payments, the network must solve the same problems that (other) block chains have solved, i.e. must prevent double-spend; must prevent tampering with the ledger. + +Storage may be ranked into tiers, where there is core data essential to the integrity of the ledger; ancillary data that is important or desirable for review of the ledger; and supplementary data that is of variable importants for particular use cases, but does not compose the core fabric of the system. diff --git a/forum-network/notes/ecosystem.md b/forum-network/notes/ecosystem.md new file mode 100644 index 0000000..f514f5c --- /dev/null +++ b/forum-network/notes/ecosystem.md @@ -0,0 +1,27 @@ +# Ecosystem Health + +How do we incentivize and reward the producers and maintainers of infrastructure? Of essential goods and services? + +How do we reward pro-social behavior? + +How do we encourage creativity? + +Vision/Mission + +Craig: Give people tools to enable them to better express their values by collaborating + +truth +good +beauty + +thought +action +perception + +ideas +knowledge +beliefs + +utility + +evolution in the true sense -- most directions it can change will be detrimental diff --git a/forum-network/notes/infrastructure.md b/forum-network/notes/infrastructure.md new file mode 100644 index 0000000..221b493 --- /dev/null +++ b/forum-network/notes/infrastructure.md @@ -0,0 +1,5 @@ +At the base layer, we need a peer to peer protocol that allows new nodes to join the network and send and receive messages. It must protect against denial of service attacks. It must support the establishment of consensus, to varying strengths. + +We need a lightweight messaging solution to facilitate gathering information from the edges of the network, but we also need to protect against denial of service by malicious actors. + +[gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) looks like a good protocol for this. diff --git a/forum-network/notes/matrix.md b/forum-network/notes/matrix.md index f5a0f5b..31898fe 100644 --- a/forum-network/notes/matrix.md +++ b/forum-network/notes/matrix.md @@ -7,3 +7,28 @@ Matrix supports "Application Services", which are limited to funcion in a passiv Synapse, a Matrix server implementation, supports "Modules" The Matrix devs recognize the need for a robust reputation system and are in pursuit of funding and development for that purpose. + +```mermaid +graph LR + +subgraph Matrix +homeserver[Homeserver] +chainClient[Blockchain
connected
client] +publicClient[Public
Matrix-only
client] +end + +blockchain[Blockchain] +%% subgraph Blockchain +%% forum[Forum] +%% post[Post] +%% availability[Availability] +%% wsc[WSC] +%% pool[Validation
Pool] +%% end + +publicClient --> homeserver +chainClient --> homeserver +chainClient --> blockchain +homeserver --> blockchain + +``` diff --git a/forum-network/notes/todo.md b/forum-network/notes/todo.md new file mode 100644 index 0000000..222b715 --- /dev/null +++ b/forum-network/notes/todo.md @@ -0,0 +1,17 @@ +# signature verification + +# voting topologies + +# client implementations + +## example workflows + +- retroactive sign flip + +# storage scaling + +# blockchain underpinnings + +# reputation token lifetime / decay + +- active/passive diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index ae65ed5..4a87a26 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -101,6 +101,10 @@ export class Forum extends ReputationHolder { return this.getPosts().reduce((total, { value }) => total += value, 0); } + // getLatestContract(type) { } + + // getContract(type) { } + async onValidate({ pool, postId, tokenId, }) { From 7ae5ff9b0379c153e5a6143debf2cd1ef0bdf6b5 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sat, 15 Apr 2023 11:50:57 -0500 Subject: [PATCH 03/11] Add support for posts with multiple authors --- forum-network/src/classes/dao/forum.js | 144 ++++++++++++------ .../src/classes/dao/validation-pool.js | 6 +- forum-network/src/classes/display/actor.js | 8 +- .../src/classes/forum-network/forum-node.js | 4 +- .../src/classes/forum-network/forum-view.js | 67 -------- .../src/classes/forum-network/network-node.js | 3 +- .../classes/reputation/reputation-token.js | 5 +- .../src/classes/supporting/erc721.js | 4 +- forum-network/src/classes/supporting/wdag.js | 76 ++++----- .../src/classes/util/post-content.js | 33 +++- forum-network/src/index.html | 1 + forum-network/src/tests/all.test.html | 1 + forum-network/src/tests/flowchart.test.html | 2 +- forum-network/src/tests/forum10.test.html | 26 ++++ .../src/tests/scripts/availability.test.js | 4 +- .../tests/scripts/forum/forum.test-util.js | 20 ++- .../src/tests/scripts/forum/forum10.test.js | 39 +++++ forum-network/src/tests/scripts/wdag.test.js | 10 +- 18 files changed, 278 insertions(+), 175 deletions(-) delete mode 100644 forum-network/src/classes/forum-network/forum-view.js create mode 100644 forum-network/src/tests/forum10.test.html create mode 100644 forum-network/src/tests/scripts/forum/forum10.test.js diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index 4a87a26..cec6fdb 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -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 { ` .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; } diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index d288b29..6348cfa 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -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`); diff --git a/forum-network/src/classes/display/actor.js b/forum-network/src/classes/display/actor.js index efdd08c..046f923 100644 --- a/forum-network/src/classes/display/actor.js +++ b/forum-network/src/classes/display/actor.js @@ -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); } } diff --git a/forum-network/src/classes/forum-network/forum-node.js b/forum-network/src/classes/forum-network/forum-node.js index 49ae7c5..b262357 100644 --- a/forum-network/src/classes/forum-network/forum-node.js +++ b/forum-network/src/classes/forum-network/forum-node.js @@ -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), diff --git a/forum-network/src/classes/forum-network/forum-view.js b/forum-network/src/classes/forum-network/forum-view.js deleted file mode 100644 index a7e19a0..0000000 --- a/forum-network/src/classes/forum-network/forum-view.js +++ /dev/null @@ -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(); - } -} diff --git a/forum-network/src/classes/forum-network/network-node.js b/forum-network/src/classes/forum-network/network-node.js index 72072b9..345e3ad 100644 --- a/forum-network/src/classes/forum-network/network-node.js +++ b/forum-network/src/classes/forum-network/network-node.js @@ -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); } diff --git a/forum-network/src/classes/reputation/reputation-token.js b/forum-network/src/classes/reputation/reputation-token.js index 6a479e1..a48b00e 100644 --- a/forum-network/src/classes/reputation/reputation-token.js +++ b/forum-network/src/classes/reputation/reputation-token.js @@ -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); diff --git a/forum-network/src/classes/supporting/erc721.js b/forum-network/src/classes/supporting/erc721.js index 5b2b215..9a68166 100644 --- a/forum-network/src/classes/supporting/erc721.js +++ b/forum-network/src/classes/supporting/erc721.js @@ -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); diff --git a/forum-network/src/classes/supporting/wdag.js b/forum-network/src/classes/supporting/wdag.js index bec7a34..1b96124 100644 --- a/forum-network/src/classes/supporting/wdag.js +++ b/forum-network/src/classes/supporting/wdag.js @@ -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 = ''; - for (const { label, weight } of this.getEdges(null, from, to)) { - html += ``; + for (const { type, weight } of this.getEdges(null, from, to)) { + html += ``; } html += '
${label}${weight}
${type}${weight}
'; 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; } } diff --git a/forum-network/src/classes/util/post-content.js b/forum-network/src/classes/util/post-content.js index 8512f3f..5308d36 100644 --- a/forum-network/src/classes/util/post-content.js +++ b/forum-network/src/classes/util/post-content.js @@ -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; diff --git a/forum-network/src/index.html b/forum-network/src/index.html index 03916ad..c9b568c 100644 --- a/forum-network/src/index.html +++ b/forum-network/src/index.html @@ -24,6 +24,7 @@
  • Negatively cite a zero-valued post
  • Incinerate reputation
  • Use incineration to achieve more balanced reweighting
  • +
  • Post with multiple authors
    • diff --git a/forum-network/src/tests/all.test.html b/forum-network/src/tests/all.test.html index ef3ab5e..2b0da79 100644 --- a/forum-network/src/tests/all.test.html +++ b/forum-network/src/tests/all.test.html @@ -36,6 +36,7 @@ + + + + + diff --git a/forum-network/src/tests/scripts/availability.test.js b/forum-network/src/tests/scripts/availability.test.js index 556da23..b4e1d37 100644 --- a/forum-network/src/tests/scripts/availability.test.js +++ b/forum-network/src/tests/scripts/availability.test.js @@ -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 = []; diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum/forum.test-util.js index fea2e8a..c90f9ab 100644 --- a/forum-network/src/tests/scripts/forum/forum.test-util.js +++ b/forum-network/src/tests/scripts/forum/forum.test-util.js @@ -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(); } } diff --git a/forum-network/src/tests/scripts/forum/forum10.test.js b/forum-network/src/tests/scripts/forum/forum10.test.js new file mode 100644 index 0000000..693ed8b --- /dev/null +++ b/forum-network/src/tests/scripts/forum/forum10.test.js @@ -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(); diff --git a/forum-network/src/tests/scripts/wdag.test.js b/forum-network/src/tests/scripts/wdag.test.js index 4fd1243..439dd17 100644 --- a/forum-network/src/tests/scripts/wdag.test.js +++ b/forum-network/src/tests/scripts/wdag.test.js @@ -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); From 77643acf6f29495afbe728b7d050b9d7c68655b4 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 07:59:12 -0500 Subject: [PATCH 04/11] Display author reputations --- forum-network/src/classes/dao/forum.js | 36 +++++++++++++++++-- .../classes/reputation/reputation-token.js | 6 +++- forum-network/src/classes/supporting/wdag.js | 4 +++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index cec6fdb..5d2a758 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -69,6 +69,34 @@ class Post extends Actor { } } +class Author { + constructor(forum, publicKey) { + this.forum = forum; + this.publicKey = publicKey; + this.name = publicKey; + } + + getReputation() { + const authorVertex = this.forum.graph.getVertex(this.publicKey); + if (!authorVertex) { + return 0; + } + const authorEdges = authorVertex.getEdges(EdgeTypes.AUTHOR, false); + const tokenValues = authorEdges.map(({ data: { tokenId } }) => this.forum.dao.reputation.valueOf(tokenId)); + console.log('getReputation', { authorEdges, tokenValues }); + return tokenValues.reduce((value, total) => total += value, 0); + } + + getLabel() { + return `${this.name} + + + +
      reputation${displayNumber(this.getReputation())}
      ` + .replaceAll(/\n\s*/g, ''); + } +} + /** * Purpose: * - Forum: Maintain a directed, acyclic, graph of positively and negatively weighted citations. @@ -129,8 +157,9 @@ export class Forum extends ReputationHolder { this.graph.setVertexDisplayLabel(post.id, post.getLabel()); const addAuthorToGraph = (publicKey, weight, authorTokenId) => { + const author = new Author(this, publicKey); const authorVertex = this.graph.getVertex(publicKey) - ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, { name: publicKey, publicKey }, publicKey); + ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.getLabel()); const authorEdge = this.graph.addEdge( EdgeTypes.AUTHOR, postVertex, @@ -184,9 +213,10 @@ export class Forum extends ReputationHolder { // 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 author = authorVertex.data; const { tokenId: authorTokenId } = authorEdge.data; - this.dao.reputation.transfer(this.id, publicKey, authorTokenId); + this.dao.reputation.transfer(this.id, author.publicKey, authorTokenId); + authorVertex.setDisplayLabel(author.getLabel()); } } diff --git a/forum-network/src/classes/reputation/reputation-token.js b/forum-network/src/classes/reputation/reputation-token.js index a48b00e..65847d6 100644 --- a/forum-network/src/classes/reputation/reputation-token.js +++ b/forum-network/src/classes/reputation/reputation-token.js @@ -79,7 +79,11 @@ export class ReputationTokenContract extends ERC721 { } valueOf(tokenId) { - return this.values.get(tokenId); + const value = this.values.get(tokenId); + if (value === undefined) { + throw new Error(`Token not found: ${tokenId}`); + } + return value; } availableValueOf(tokenId) { diff --git a/forum-network/src/classes/supporting/wdag.js b/forum-network/src/classes/supporting/wdag.js index 1b96124..ac35558 100644 --- a/forum-network/src/classes/supporting/wdag.js +++ b/forum-network/src/classes/supporting/wdag.js @@ -15,6 +15,10 @@ export class Vertex { (edge) => edge.type === type, ); } + + setDisplayLabel(label) { + this.graph.setVertexDisplayLabel(this.id, label); + } } export class Edge { From 5b0b87d2d3aa324c599e4d0c4ee9e1933f95d468 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 08:21:09 -0500 Subject: [PATCH 05/11] Show author reputations on sequence diagram --- forum-network/src/classes/actors/expert.js | 4 +-- forum-network/src/classes/dao/dao.js | 6 ++--- forum-network/src/classes/dao/forum.js | 26 +++++++++++-------- forum-network/src/classes/display/actor.js | 4 +-- forum-network/src/classes/display/scene.js | 6 +++-- .../classes/reputation/reputation-holder.js | 4 +-- .../tests/scripts/forum/forum.test-util.js | 10 +++---- .../src/tests/scripts/forum/forum10.test.js | 4 +-- 8 files changed, 34 insertions(+), 30 deletions(-) diff --git a/forum-network/src/classes/actors/expert.js b/forum-network/src/classes/actors/expert.js index 69a1d7c..99f98bb 100644 --- a/forum-network/src/classes/actors/expert.js +++ b/forum-network/src/classes/actors/expert.js @@ -4,8 +4,8 @@ import { CryptoUtil } from '../util/crypto.js'; import { ReputationHolder } from '../reputation/reputation-holder.js'; export class Expert extends ReputationHolder { - constructor(dao, name, scene) { - super(name, scene); + constructor(dao, name, scene, options) { + super(name, scene, options); this.dao = dao; this.actions = { submitPostViaNetwork: new Action('submit post via network', scene), diff --git a/forum-network/src/classes/dao/dao.js b/forum-network/src/classes/dao/dao.js index 938d019..bdf4ec0 100644 --- a/forum-network/src/classes/dao/dao.js +++ b/forum-network/src/classes/dao/dao.js @@ -13,8 +13,8 @@ import { Actor } from '../display/actor.js'; * - Reputation: Keep track of reputation accrued to each expert */ export class DAO extends Actor { - constructor(name, scene) { - super(name, scene); + constructor(name, scene, options) { + super(name, scene, options); /* Contracts */ this.forum = new Forum(this, 'Forum', scene); @@ -38,7 +38,7 @@ export class DAO extends Actor { return Array.from(this.experts.values()).filter((voter) => { const hasVoted = !!voter.dateLastVote; const withinThreshold = !params.activeVoterThreshold - || new Date() - voter.dateLastVote >= params.activeVoterThreshold; + || new Date() - voter.dateLastVote >= params.activeVoterThreshold; return hasVoted && withinThreshold; }); } diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index 5d2a758..01175ca 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -69,8 +69,9 @@ class Post extends Actor { } } -class Author { +class Author extends Actor { constructor(forum, publicKey) { + super(publicKey, forum.scene); this.forum = forum; this.publicKey = publicKey; this.name = publicKey; @@ -158,6 +159,7 @@ export class Forum extends ReputationHolder { const addAuthorToGraph = (publicKey, weight, authorTokenId) => { const author = new Author(this, publicKey); + author.setDisplayValue('reputation', () => author.getReputation()); const authorVertex = this.graph.getVertex(publicKey) ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.getLabel()); const authorEdge = this.graph.addEdge( @@ -217,6 +219,7 @@ export class Forum extends ReputationHolder { const { tokenId: authorTokenId } = authorEdge.data; this.dao.reputation.transfer(this.id, author.publicKey, authorTokenId); authorVertex.setDisplayLabel(author.getLabel()); + await author.computeDisplayValues(); } } @@ -338,6 +341,17 @@ export class Forum extends ReputationHolder { const appliedIncrement = newValue - post.value; const refundToInbound = increment - 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 }, to: { data: author } } = authorEdge; + const authorIncrement = weight * appliedIncrement; + rewardsAccumulator.set(tokenId, authorIncrement); + this.actions.propagate.log(post, author, `(${authorIncrement})`); + } + + // Increment the value of the post + await post.setValue(newValue); + console.log('propagateValue end', { depth, increment, @@ -347,16 +361,6 @@ export class Forum extends ReputationHolder { refundToInbound, }); - // 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 post.setValue(newValue); - return refundToInbound; } } diff --git a/forum-network/src/classes/display/actor.js b/forum-network/src/classes/display/actor.js index 046f923..f489d4d 100644 --- a/forum-network/src/classes/display/actor.js +++ b/forum-network/src/classes/display/actor.js @@ -1,7 +1,7 @@ import { displayNumber } from '../../util.js'; export class Actor { - constructor(name, scene) { + constructor(name, scene, { announce = false } = {}) { if (!scene) throw new Error('An actor without a scene!'); this.name = name; this.scene = scene; @@ -11,7 +11,7 @@ export class Actor { this.values = new Map(); this.valueFunctions = new Map(); this.active = 0; - scene?.registerActor(this); + scene?.registerActor(this, announce); } activate() { diff --git a/forum-network/src/classes/display/scene.js b/forum-network/src/classes/display/scene.js index f0e76b3..344109c 100644 --- a/forum-network/src/classes/display/scene.js +++ b/forum-network/src/classes/display/scene.js @@ -86,9 +86,11 @@ export class Scene { return this; } - registerActor(actor) { + registerActor(actor, announce) { this.actors.add(actor); - // this.sequence?.log(`participant ${actor.name}`); + if (announce) { + this.sequence?.log(`participant ${actor.name}`); + } } findActor(fn) { diff --git a/forum-network/src/classes/reputation/reputation-holder.js b/forum-network/src/classes/reputation/reputation-holder.js index b2aaf0e..29696ae 100644 --- a/forum-network/src/classes/reputation/reputation-holder.js +++ b/forum-network/src/classes/reputation/reputation-holder.js @@ -2,8 +2,8 @@ import { randomID } from '../../util.js'; import { Actor } from '../display/actor.js'; export class ReputationHolder extends Actor { - constructor(name, scene) { - super(name, scene); + constructor(name, scene, options) { + super(name, scene, options); this.reputationPublicKey = `${name}_${randomID()}`; } } diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum/forum.test-util.js index c90f9ab..aabcd46 100644 --- a/forum-network/src/tests/scripts/forum/forum.test-util.js +++ b/forum-network/src/tests/scripts/forum/forum.test-util.js @@ -55,10 +55,10 @@ export class ForumTest { return postId; } - async newExpert() { + async newExpert(options) { const index = this.experts.length; const name = `Expert${index + 1}`; - const expert = await new Expert(this.dao, name, this.scene).initialize(); + const expert = await new Expert(this.dao, name, this.scene, options).initialize(); this.experts.push(expert); // await expert.addComputedValue('rep', () => this.dao.reputation.valueOwnedBy(expert.reputationPublicKey)); return expert; @@ -81,15 +81,13 @@ export class ForumTest { scene.addDisplayValue('q4. leachingValue').set(params.leachingValue); scene.addDisplayValue(' '); - this.dao = new DAO('DAO', scene); + this.dao = new DAO('DAO', scene, { announce: true }); this.forum = this.dao.forum; this.experts = []; this.posts = []; - await this.newExpert(); + await this.newExpert({ announce: true }); await this.dao.addComputedValue('total value', () => this.dao.reputation.getTotal()); - // await this.dao.addComputedValue('total reputation', () => this.dao.forum.getTotalValue()); - this.dao.computeDisplayValues(); } } diff --git a/forum-network/src/tests/scripts/forum/forum10.test.js b/forum-network/src/tests/scripts/forum/forum10.test.js index 693ed8b..fa3fb02 100644 --- a/forum-network/src/tests/scripts/forum/forum10.test.js +++ b/forum-network/src/tests/scripts/forum/forum10.test.js @@ -20,8 +20,8 @@ describe('Forum', function tests() { experts = forumTest.experts; posts = forumTest.posts; - await forumTest.newExpert(); - await forumTest.newExpert(); + await forumTest.newExpert({ announce: true }); + await forumTest.newExpert({ announce: true }); }); it('Post1', async () => { From e6969fde81064384f07000006d8d7b5abb7625b4 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 08:31:09 -0500 Subject: [PATCH 06/11] Add example: posts with overlapping authors --- forum-network/src/index.html | 1 + forum-network/src/tests/all.test.html | 1 + forum-network/src/tests/forum11.test.html | 26 ++++++++++ .../src/tests/scripts/forum/forum11.test.js | 47 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 forum-network/src/tests/forum11.test.html create mode 100644 forum-network/src/tests/scripts/forum/forum11.test.js diff --git a/forum-network/src/index.html b/forum-network/src/index.html index c9b568c..c0d4a65 100644 --- a/forum-network/src/index.html +++ b/forum-network/src/index.html @@ -25,6 +25,7 @@
    • Incinerate reputation
    • Use incineration to achieve more balanced reweighting
    • Post with multiple authors
    • +
    • Multiple posts with overlapping authors
      diff --git a/forum-network/src/tests/all.test.html b/forum-network/src/tests/all.test.html index 2b0da79..2729287 100644 --- a/forum-network/src/tests/all.test.html +++ b/forum-network/src/tests/all.test.html @@ -37,6 +37,7 @@ + + + + + diff --git a/forum-network/src/tests/scripts/forum/forum11.test.js b/forum-network/src/tests/scripts/forum/forum11.test.js new file mode 100644 index 0000000..2d6e983 --- /dev/null +++ b/forum-network/src/tests/scripts/forum/forum11.test.js @@ -0,0 +1,47 @@ +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('Multiple posts with overlapping authors', async () => { + let forum; + let experts; + let posts; + + before(async () => { + forum = forumTest.forum; + experts = forumTest.experts; + posts = forumTest.posts; + + await forumTest.newExpert({ announce: true }); + await forumTest.newExpert({ announce: true }); + }); + + it('Post1 with two authors', async () => { + const authors = [ + { author: experts[0], weight: 0.5 }, + { author: experts[1], weight: 0.5 }, + ]; + await forumTest.addPost(authors, 10); + forum.getPost(posts[0]).value.should.equal(10); + }); + + it('Post2 with two authors, one shared with Post1', async () => { + const authors = [ + { author: experts[1], weight: 0.5 }, + { author: experts[2], weight: 0.5 }, + ]; + await forumTest.addPost(authors, 10); + forum.getPost(posts[0]).value.should.equal(10); + }); + }); +}); + +mochaRun(); From 3141d07a41b7ab9125e7a54f655153aea65656f5 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 13:11:14 -0500 Subject: [PATCH 07/11] Enable easy show/hide experts when coding forum tests --- forum-network/src/classes/actors/expert.js | 23 ++++- forum-network/src/classes/dao/availability.js | 2 +- forum-network/src/classes/dao/business.js | 4 +- forum-network/src/classes/dao/forum.js | 78 ++++++----------- .../src/classes/dao/validation-pool.js | 2 +- forum-network/src/classes/display/actor.js | 13 ++- forum-network/src/classes/display/box.js | 2 +- .../src/classes/display/display-value.js | 2 +- forum-network/src/classes/display/mermaid.js | 2 +- forum-network/src/classes/display/scene.js | 16 ++-- forum-network/src/classes/display/sequence.js | 2 +- forum-network/src/classes/display/table.js | 2 +- .../src/classes/forum-network/forum-node.js | 2 +- .../src/classes/forum-network/message.js | 4 +- forum-network/src/classes/ideas/storage.js | 2 +- .../classes/reputation/reputation-holder.js | 2 +- .../classes/reputation/reputation-token.js | 4 +- .../classes/{util => supporting}/crypto.js | 0 .../{util => supporting}/post-content.js | 0 forum-network/src/classes/supporting/wdag.js | 87 +++++++++++-------- .../src/classes/util/prioritized-queue.js | 24 ----- forum-network/src/tests/flowchart.test.html | 2 +- forum-network/src/tests/reputation.test.html | 2 +- .../src/tests/scripts/availability.test.js | 4 +- .../src/tests/scripts/business.test.js | 2 +- .../src/tests/scripts/debounce.test.js | 2 +- .../src/tests/scripts/forum-network.test.js | 4 +- .../tests/scripts/forum/forum.test-util.js | 15 ++-- .../src/tests/scripts/forum/forum1.test.js | 17 ++-- .../src/tests/scripts/forum/forum10.test.js | 9 +- .../src/tests/scripts/forum/forum11.test.js | 9 +- .../src/tests/scripts/forum/forum2.test.js | 2 +- .../src/tests/scripts/forum/forum3.test.js | 2 +- .../src/tests/scripts/forum/forum4.test.js | 2 +- .../src/tests/scripts/forum/forum5.test.js | 2 +- .../src/tests/scripts/forum/forum6.test.js | 3 +- .../src/tests/scripts/forum/forum7.test.js | 2 +- .../src/tests/scripts/forum/forum8.test.js | 2 +- .../src/tests/scripts/forum/forum9.test.js | 2 +- .../src/tests/scripts/validation-pool.test.js | 4 +- forum-network/src/tests/scripts/vm.test.js | 2 +- forum-network/src/tests/scripts/wdag.test.js | 2 +- forum-network/src/util/constants.js | 14 +++ .../src/{util.js => util/helpers.js} | 8 +- 44 files changed, 192 insertions(+), 194 deletions(-) rename forum-network/src/classes/{util => supporting}/crypto.js (100%) rename forum-network/src/classes/{util => supporting}/post-content.js (100%) delete mode 100644 forum-network/src/classes/util/prioritized-queue.js create mode 100644 forum-network/src/util/constants.js rename forum-network/src/{util.js => util/helpers.js} (81%) diff --git a/forum-network/src/classes/actors/expert.js b/forum-network/src/classes/actors/expert.js index 99f98bb..3a6f9e8 100644 --- a/forum-network/src/classes/actors/expert.js +++ b/forum-network/src/classes/actors/expert.js @@ -1,7 +1,9 @@ import { Action } from '../display/action.js'; import { PostMessage } from '../forum-network/message.js'; -import { CryptoUtil } from '../util/crypto.js'; +import { CryptoUtil } from '../supporting/crypto.js'; import { ReputationHolder } from '../reputation/reputation-holder.js'; +import { EdgeTypes } from '../../util/constants.js'; +import { displayNumber } from '../../util/helpers.js'; export class Expert extends ReputationHolder { constructor(dao, name, scene, options) { @@ -20,6 +22,25 @@ export class Expert extends ReputationHolder { this.tokens = []; } + getReputation() { + const authorVertex = this.dao.forum.graph.getVertex(this.reputationPublicKey); + if (!authorVertex) { + return 0; + } + const authorEdges = authorVertex.getEdges(EdgeTypes.AUTHOR, false); + const tokenValues = authorEdges.map(({ data: { tokenId } }) => this.dao.reputation.valueOf(tokenId)); + return tokenValues.reduce((value, total) => total += value, 0); + } + + getLabel() { + return `${this.name} + + + +
      reputation${displayNumber(this.getReputation())}
      ` + .replaceAll(/\n\s*/g, ''); + } + async initialize() { this.reputationKey = await CryptoUtil.generateAsymmetricKey(); // this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey); diff --git a/forum-network/src/classes/dao/availability.js b/forum-network/src/classes/dao/availability.js index 47d936d..8cdb638 100644 --- a/forum-network/src/classes/dao/availability.js +++ b/forum-network/src/classes/dao/availability.js @@ -1,6 +1,6 @@ import { Action } from '../display/action.js'; import { Actor } from '../display/actor.js'; -import { CryptoUtil } from '../util/crypto.js'; +import { CryptoUtil } from '../supporting/crypto.js'; class Worker { constructor(reputationPublicKey, tokenId, stakeAmount, duration) { diff --git a/forum-network/src/classes/dao/business.js b/forum-network/src/classes/dao/business.js index 67a2705..8c266ee 100644 --- a/forum-network/src/classes/dao/business.js +++ b/forum-network/src/classes/dao/business.js @@ -1,7 +1,7 @@ -import { randomID } from '../../util.js'; +import { randomID } from '../../util/helpers.js'; import { Action } from '../display/action.js'; import { Actor } from '../display/actor.js'; -import { PostContent } from '../util/post-content.js'; +import { PostContent } from '../supporting/post-content.js'; class Request { static nextSeq = 0; diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index 01175ca..cbfac86 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -3,18 +3,10 @@ import { Action } from '../display/action.js'; import { Actor } from '../display/actor.js'; import params from '../../params.js'; import { ReputationHolder } from '../reputation/reputation-holder.js'; -import { displayNumber, EPSILON, INCINERATOR_ADDRESS } from '../../util.js'; - -const EdgeTypes = { - CITATION: 'citation', - BALANCE: 'balance', - AUTHOR: 'author', -}; - -const VertexTypes = { - POST: 'post', - AUTHOR: 'author', -}; +import { displayNumber } from '../../util/helpers.js'; +import { + EPSILON, INCINERATOR_ADDRESS, EdgeTypes, VertexTypes, +} from '../../util/constants.js'; class Post extends Actor { constructor(forum, senderId, postContent) { @@ -65,36 +57,7 @@ class Post extends Actor { async setValue(value) { this.value = value; await this.setDisplayValue('value', value); - this.forum.graph.setVertexDisplayLabel(this.id, this.getLabel()); - } -} - -class Author extends Actor { - constructor(forum, publicKey) { - super(publicKey, forum.scene); - this.forum = forum; - this.publicKey = publicKey; - this.name = publicKey; - } - - getReputation() { - const authorVertex = this.forum.graph.getVertex(this.publicKey); - if (!authorVertex) { - return 0; - } - const authorEdges = authorVertex.getEdges(EdgeTypes.AUTHOR, false); - const tokenValues = authorEdges.map(({ data: { tokenId } }) => this.forum.dao.reputation.valueOf(tokenId)); - console.log('getReputation', { authorEdges, tokenValues }); - return tokenValues.reduce((value, total) => total += value, 0); - } - - getLabel() { - return `${this.name} - - - -
      reputation${displayNumber(this.getReputation())}
      ` - .replaceAll(/\n\s*/g, ''); + this.forum.graph.getVertex(this.id).setDisplayLabel(this.getLabel()); } } @@ -155,21 +118,29 @@ export class Forum extends ReputationHolder { const post = postVertex.data; post.setStatus('Validated'); post.initialValue = initialValue; - this.graph.setVertexDisplayLabel(post.id, post.getLabel()); + postVertex.setDisplayLabel(post.getLabel()); const addAuthorToGraph = (publicKey, weight, authorTokenId) => { - const author = new Author(this, publicKey); + // For graph display purposes, we want to use the existing Expert actors from the current scene. + const author = this.scene.findActor(({ reputationPublicKey }) => reputationPublicKey === publicKey); + if (author.options.hide) { + return; + } author.setDisplayValue('reputation', () => author.getReputation()); const authorVertex = this.graph.getVertex(publicKey) - ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.getLabel()); - const authorEdge = this.graph.addEdge( + ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.getLabel(), { + hide: author.options.hide, + }); + this.graph.addEdge( EdgeTypes.AUTHOR, postVertex, authorVertex, weight, { tokenId: authorTokenId }, + { + hide: author.options.hide, + }, ); - console.log('addAuthorToGraph', { authorVertex, authorEdge }); }; // In the case of multiple authors, mint additional (empty) tokens. @@ -198,8 +169,9 @@ export class Forum extends ReputationHolder { ); // Apply computed rewards to update values of tokens - for (const [authorTokenId, amount] of rewardsAccumulator) { - console.log('reward', { authorTokenId, amount }); + for (const [authorEdge, amount] of rewardsAccumulator) { + const { to: authorVertex, data: { tokenId: authorTokenId } } = authorEdge; + const { data: author } = authorVertex; // 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. @@ -210,6 +182,8 @@ export class Forum extends ReputationHolder { this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount); } } + await author.computeDisplayValues(); + authorVertex.setDisplayLabel(author.getLabel()); } // Transfer ownership of the minted tokens to the authors @@ -218,8 +192,6 @@ export class Forum extends ReputationHolder { const author = authorVertex.data; const { tokenId: authorTokenId } = authorEdge.data; this.dao.reputation.transfer(this.id, author.publicKey, authorTokenId); - authorVertex.setDisplayLabel(author.getLabel()); - await author.computeDisplayValues(); } } @@ -343,9 +315,9 @@ export class Forum extends ReputationHolder { // Apply reputation effects to post authors, not to the post directly for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHOR, true)) { - const { weight, data: { tokenId }, to: { data: author } } = authorEdge; + const { weight, to: { data: author } } = authorEdge; const authorIncrement = weight * appliedIncrement; - rewardsAccumulator.set(tokenId, authorIncrement); + rewardsAccumulator.set(authorEdge, authorIncrement); this.actions.propagate.log(post, author, `(${authorIncrement})`); } diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index 6348cfa..24dcc4d 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -3,7 +3,7 @@ import { Stake } from '../supporting/stake.js'; import { Voter } from '../supporting/voter.js'; import params from '../../params.js'; import { Action } from '../display/action.js'; -import { displayNumber } from '../../util.js'; +import { displayNumber } from '../../util/helpers.js'; const ValidationPoolStates = Object.freeze({ OPEN: 'OPEN', diff --git a/forum-network/src/classes/display/actor.js b/forum-network/src/classes/display/actor.js index f489d4d..0f1b011 100644 --- a/forum-network/src/classes/display/actor.js +++ b/forum-network/src/classes/display/actor.js @@ -1,7 +1,13 @@ -import { displayNumber } from '../../util.js'; +import { displayNumber } from '../../util/helpers.js'; export class Actor { - constructor(name, scene, { announce = false } = {}) { + /** + * @param {string} name + * @param {Scene} scene + * @param {boolean} options.announce + * @param {boolean} options.hide + */ + constructor(name, scene, options = {}) { if (!scene) throw new Error('An actor without a scene!'); this.name = name; this.scene = scene; @@ -11,7 +17,8 @@ export class Actor { this.values = new Map(); this.valueFunctions = new Map(); this.active = 0; - scene?.registerActor(this, announce); + this.options = options; + scene?.registerActor(this); } activate() { diff --git a/forum-network/src/classes/display/box.js b/forum-network/src/classes/display/box.js index 46f49f6..bee1f88 100644 --- a/forum-network/src/classes/display/box.js +++ b/forum-network/src/classes/display/box.js @@ -1,5 +1,5 @@ import { DisplayValue } from './display-value.js'; -import { randomID } from '../../util.js'; +import { randomID } from '../../util/helpers.js'; export class Box { constructor(name, parentEl, options = {}) { diff --git a/forum-network/src/classes/display/display-value.js b/forum-network/src/classes/display/display-value.js index e92de70..70a2279 100644 --- a/forum-network/src/classes/display/display-value.js +++ b/forum-network/src/classes/display/display-value.js @@ -1,4 +1,4 @@ -import { displayNumber } from '../../util.js'; +import { displayNumber } from '../../util/helpers.js'; export class DisplayValue { constructor(name, box) { diff --git a/forum-network/src/classes/display/mermaid.js b/forum-network/src/classes/display/mermaid.js index 9d42760..72f0171 100644 --- a/forum-network/src/classes/display/mermaid.js +++ b/forum-network/src/classes/display/mermaid.js @@ -1,5 +1,5 @@ import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs'; -import { debounce } from '../../util.js'; +import { debounce } from '../../util/helpers.js'; export class MermaidDiagram { constructor(box, logBox) { diff --git a/forum-network/src/classes/display/scene.js b/forum-network/src/classes/display/scene.js index 344109c..8f62ad1 100644 --- a/forum-network/src/classes/display/scene.js +++ b/forum-network/src/classes/display/scene.js @@ -1,5 +1,5 @@ import { Action } from './action.js'; -import { CryptoUtil } from '../util/crypto.js'; +import { CryptoUtil } from '../supporting/crypto.js'; import { MermaidDiagram } from './mermaid.js'; import { SequenceDiagram } from './sequence.js'; import { Table } from './table.js'; @@ -86,9 +86,9 @@ export class Scene { return this; } - registerActor(actor, announce) { + registerActor(actor) { this.actors.add(actor); - if (announce) { + if (actor.options.announce) { this.sequence?.log(`participant ${actor.name}`); } } @@ -116,10 +116,12 @@ export class Scene { row.set('elapsedMs', new Date() - this.dateStart); row.set('label', label); for (const actor of this.actors) { - for (const [aKey, { name, value }] of actor.getValuesMap()) { - const key = `${actor.name}:${aKey}`; - columns.push({ key, title: name }); - row.set(key, value); + if (!actor.options.hide) { + for (const [aKey, { name, value }] of actor.getValuesMap()) { + const key = `${actor.name}:${aKey}`; + columns.push({ key, title: name }); + row.set(key, value); + } } } columns.push({ key: 'label', title: '' }); diff --git a/forum-network/src/classes/display/sequence.js b/forum-network/src/classes/display/sequence.js index 39fbe2f..2a3b530 100644 --- a/forum-network/src/classes/display/sequence.js +++ b/forum-network/src/classes/display/sequence.js @@ -1,4 +1,4 @@ -import { hexToRGB } from '../../util.js'; +import { hexToRGB } from '../../util/helpers.js'; import { MermaidDiagram } from './mermaid.js'; export class SequenceDiagram extends MermaidDiagram { diff --git a/forum-network/src/classes/display/table.js b/forum-network/src/classes/display/table.js index ee579d4..670acde 100644 --- a/forum-network/src/classes/display/table.js +++ b/forum-network/src/classes/display/table.js @@ -1,4 +1,4 @@ -import { displayNumber } from '../../util.js'; +import { displayNumber } from '../../util/helpers.js'; export class Table { constructor(box) { diff --git a/forum-network/src/classes/forum-network/forum-node.js b/forum-network/src/classes/forum-network/forum-node.js index b262357..ff76ed7 100644 --- a/forum-network/src/classes/forum-network/forum-node.js +++ b/forum-network/src/classes/forum-network/forum-node.js @@ -3,7 +3,7 @@ import { Message, PostMessage, PeerMessage, messageFromJSON, } from './message.js'; import { NetworkNode } from './network-node.js'; -import { randomID } from '../../util.js'; +import { randomID } from '../../util/helpers.js'; export class ForumNode extends NetworkNode { constructor(name, scene) { diff --git a/forum-network/src/classes/forum-network/message.js b/forum-network/src/classes/forum-network/message.js index 2e0ab81..c0cb1be 100644 --- a/forum-network/src/classes/forum-network/message.js +++ b/forum-network/src/classes/forum-network/message.js @@ -1,5 +1,5 @@ -import { CryptoUtil } from '../util/crypto.js'; -import { PostContent } from '../util/post-content.js'; +import { CryptoUtil } from '../supporting/crypto.js'; +import { PostContent } from '../supporting/post-content.js'; export class Message { constructor(content) { diff --git a/forum-network/src/classes/ideas/storage.js b/forum-network/src/classes/ideas/storage.js index e317264..a2d6b1a 100644 --- a/forum-network/src/classes/ideas/storage.js +++ b/forum-network/src/classes/ideas/storage.js @@ -1,4 +1,4 @@ -import { randomID } from '../util/util.js'; +import { randomID } from '../util/util/helpers.js'; class Pledge { constructor({ stake, duration }) { diff --git a/forum-network/src/classes/reputation/reputation-holder.js b/forum-network/src/classes/reputation/reputation-holder.js index 29696ae..5d89e61 100644 --- a/forum-network/src/classes/reputation/reputation-holder.js +++ b/forum-network/src/classes/reputation/reputation-holder.js @@ -1,4 +1,4 @@ -import { randomID } from '../../util.js'; +import { randomID } from '../../util/helpers.js'; import { Actor } from '../display/actor.js'; export class ReputationHolder extends Actor { diff --git a/forum-network/src/classes/reputation/reputation-token.js b/forum-network/src/classes/reputation/reputation-token.js index 65847d6..73ea289 100644 --- a/forum-network/src/classes/reputation/reputation-token.js +++ b/forum-network/src/classes/reputation/reputation-token.js @@ -1,6 +1,6 @@ import { ERC721 } from '../supporting/erc721.js'; - -import { EPSILON, randomID } from '../../util.js'; +import { randomID } from '../../util/helpers.js'; +import { EPSILON } from '../../util/constants.js'; class Lock { constructor(tokenId, amount, duration) { diff --git a/forum-network/src/classes/util/crypto.js b/forum-network/src/classes/supporting/crypto.js similarity index 100% rename from forum-network/src/classes/util/crypto.js rename to forum-network/src/classes/supporting/crypto.js diff --git a/forum-network/src/classes/util/post-content.js b/forum-network/src/classes/supporting/post-content.js similarity index 100% rename from forum-network/src/classes/util/post-content.js rename to forum-network/src/classes/supporting/post-content.js diff --git a/forum-network/src/classes/supporting/wdag.js b/forum-network/src/classes/supporting/wdag.js index ac35558..e3a71bf 100644 --- a/forum-network/src/classes/supporting/wdag.js +++ b/forum-network/src/classes/supporting/wdag.js @@ -1,9 +1,12 @@ +const getEdgeKey = ({ from, to }) => btoa([from.id, to.id]).replaceAll(/[^A-Za-z0-9]+/g, ''); + export class Vertex { - constructor(graph, type, id, data) { + constructor(graph, type, id, data, options = {}) { this.graph = graph; this.type = type; this.id = id; this.data = data; + this.options = options; this.edges = { from: [], to: [], @@ -17,17 +20,50 @@ export class Vertex { } setDisplayLabel(label) { - this.graph.setVertexDisplayLabel(this.id, label); + if (this.options.hide) { + return; + } + this.graph.flowchart?.log(`${this.id}[${label}]`); } } export class Edge { - constructor(type, from, to, weight, data) { + constructor(graph, type, from, to, weight, data, options = {}) { + this.graph = graph; this.from = from; this.to = to; this.type = type; this.weight = weight; this.data = data; + this.options = options; + } + + getHtml() { + let html = ''; + for (const { type, weight } of this.graph.getEdges(null, this.from, this.to)) { + html += ``; + } + html += '
      ${type}${weight}
      '; + return html; + } + + getFlowchartNode() { + return `${getEdgeKey(this)}(${this.getHtml()})`; + } + + displayEdgeNode() { + if (this.options.hide) { + return; + } + this.graph.flowchart?.log(this.getFlowchartNode()); + } + + displayEdge() { + if (this.options.hide) { + return; + } + this.graph.flowchart?.log(`${this.from.id} --- ${this.getFlowchartNode()} --> ${this.to.id}`); + this.graph.flowchart?.log(`class ${getEdgeKey(this)} edge`); } } @@ -46,11 +82,7 @@ export class WDAG { return this; } - setVertexDisplayLabel(id, label) { - this.flowchart?.log(`${id}[${label}]`); - } - - addVertex(type, id, data, label) { + addVertex(type, id, data, label, options) { // Support simple case of auto-incremented numeric ids if (typeof id === 'object') { data = id; @@ -59,9 +91,9 @@ export class WDAG { if (this.vertices.has(id)) { throw new Error(`Vertex already exists with id: ${id}`); } - const vertex = new Vertex(this, type, id, data); + const vertex = new Vertex(this, type, id, data, options); this.vertices.set(id, vertex); - this.setVertexDisplayLabel(id, label ?? id); + vertex.setDisplayLabel(id, label ?? id); return vertex; } @@ -77,10 +109,6 @@ export class WDAG { return Array.from(this.vertices.values()).map(({ data }) => data); } - static getEdgeKey({ from, to }) { - return btoa([from.id, to.id]).replaceAll(/[^A-Za-z0-9]+/g, ''); - } - getEdge(type, from, to) { from = from instanceof Vertex ? from : this.getVertex(from); to = to instanceof Vertex ? to : this.getVertex(to); @@ -88,7 +116,7 @@ export class WDAG { return undefined; } const edges = this.edgeTypes.get(type); - const edgeKey = WDAG.getEdgeKey({ from, to }); + const edgeKey = getEdgeKey({ from, to }); return edges?.get(edgeKey); } @@ -96,46 +124,31 @@ export class WDAG { return this.getEdge(type, from, to)?.weight; } - getEdgeHtml({ from, to }) { - let html = ''; - for (const { type, weight } of this.getEdges(null, from, to)) { - html += ``; - } - html += '
      ${type}${weight}
      '; - return html; - } - - getEdgeFlowchartNode(edge) { - const edgeKey = WDAG.getEdgeKey(edge); - return `${edgeKey}(${this.getEdgeHtml(edge)})`; - } - - setEdgeWeight(type, from, to, weight, data) { + setEdgeWeight(type, from, to, weight, data, options) { from = from instanceof Vertex ? from : this.getVertex(from); to = to instanceof Vertex ? to : this.getVertex(to); - const edge = new Edge(type, from, to, weight, data); + const edge = new Edge(this, type, from, to, weight, data, options); let edges = this.edgeTypes.get(type); if (!edges) { edges = new Map(); this.edgeTypes.set(type, edges); } - const edgeKey = WDAG.getEdgeKey(edge); + const edgeKey = getEdgeKey(edge); edges.set(edgeKey, edge); - this.flowchart?.log(this.getEdgeFlowchartNode(edge)); + edge.displayEdgeNode(); return edge; } - addEdge(type, from, to, weight, data) { + addEdge(type, from, to, weight, data, options) { from = from instanceof Vertex ? from : this.getVertex(from); to = to instanceof Vertex ? to : this.getVertex(to); if (this.getEdge(type, from, to)) { throw new Error(`Edge ${type} from ${from.id} to ${to.id} already exists`); } - const edge = this.setEdgeWeight(type, from, to, weight, data); + const edge = this.setEdgeWeight(type, from, to, weight, data, options); 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`); + edge.displayEdge(); return edge; } diff --git a/forum-network/src/classes/util/prioritized-queue.js b/forum-network/src/classes/util/prioritized-queue.js deleted file mode 100644 index 79b5851..0000000 --- a/forum-network/src/classes/util/prioritized-queue.js +++ /dev/null @@ -1,24 +0,0 @@ -export class PrioritizedQueue { - constructor() { - this.buffer = []; - } - - // Add an item to the buffer, ahead of the next lowest priority item - add(message, priority) { - const idx = this.buffer.findIndex((item) => item.priority < priority); - if (idx < 0) { - this.buffer.push({ message, priority }); - } else { - this.buffer.splice(idx, 0, { message, priority }); - } - } - - // Return the highest priority item in the buffer - pop() { - if (!this.buffer.length) { - return null; - } - const item = this.buffer.shift(); - return item.message; - } -} diff --git a/forum-network/src/tests/flowchart.test.html b/forum-network/src/tests/flowchart.test.html index 97f9021..ff07fe1 100644 --- a/forum-network/src/tests/flowchart.test.html +++ b/forum-network/src/tests/flowchart.test.html @@ -14,7 +14,7 @@ import { Scene } from '../classes/display/scene.js'; import { Actor } from '../classes/display/actor.js'; import { Action } from '../classes/display/action.js'; - import { delay } from '../util.js'; + import { delay } from '../util/helpers.js'; const DEFAULT_DELAY_INTERVAL = 500; diff --git a/forum-network/src/tests/reputation.test.html b/forum-network/src/tests/reputation.test.html index 6c2bad7..d91e105 100644 --- a/forum-network/src/tests/reputation.test.html +++ b/forum-network/src/tests/reputation.test.html @@ -15,7 +15,7 @@ // import { ValidationPool } from '../classes/validation-pool.js'; // import { TokenHolder } from '../classes/token-holder.js'; // import { ReputationToken } from '../classes/reputation-token.js'; - import { delay } from '../util.js'; + import { delay } from '../util/helpers.js'; const DEFAULT_DELAY_INTERVAL = 500; diff --git a/forum-network/src/tests/scripts/availability.test.js b/forum-network/src/tests/scripts/availability.test.js index b4e1d37..418335c 100644 --- a/forum-network/src/tests/scripts/availability.test.js +++ b/forum-network/src/tests/scripts/availability.test.js @@ -3,9 +3,9 @@ import { Scene } from '../../classes/display/scene.js'; import { Expert } from '../../classes/actors/expert.js'; import { DAO } from '../../classes/dao/dao.js'; import { Public } from '../../classes/actors/public.js'; -import { PostContent } from '../../classes/util/post-content.js'; +import { PostContent } from '../../classes/supporting/post-content.js'; import { delayOrWait } from '../../classes/display/controls.js'; -import { mochaRun } from '../../util.js'; +import { mochaRun } from '../../util/helpers.js'; const DELAY_INTERVAL = 100; const POOL_DURATION = 200; diff --git a/forum-network/src/tests/scripts/business.test.js b/forum-network/src/tests/scripts/business.test.js index dd3a524..97bb8b2 100644 --- a/forum-network/src/tests/scripts/business.test.js +++ b/forum-network/src/tests/scripts/business.test.js @@ -1,7 +1,7 @@ import { Business } from '../../classes/dao/business.js'; import { Scene } from '../../classes/display/scene.js'; import { Box } from '../../classes/display/box.js'; -import { mochaRun } from '../../util.js'; +import { mochaRun } from '../../util/helpers.js'; describe('Business', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/debounce.test.js b/forum-network/src/tests/scripts/debounce.test.js index b0de18e..2945e18 100644 --- a/forum-network/src/tests/scripts/debounce.test.js +++ b/forum-network/src/tests/scripts/debounce.test.js @@ -2,7 +2,7 @@ import { Box } from '../../classes/display/box.js'; import { Scene } from '../../classes/display/scene.js'; import { Action } from '../../classes/display/action.js'; import { Actor } from '../../classes/display/actor.js'; -import { debounce, delay, mochaRun } from '../../util.js'; +import { debounce, delay, mochaRun } from '../../util/helpers.js'; describe('Debounce', () => { let scene; diff --git a/forum-network/src/tests/scripts/forum-network.test.js b/forum-network/src/tests/scripts/forum-network.test.js index 5cbe692..c40a394 100644 --- a/forum-network/src/tests/scripts/forum-network.test.js +++ b/forum-network/src/tests/scripts/forum-network.test.js @@ -1,10 +1,10 @@ import { Box } from '../../classes/display/box.js'; import { Scene } from '../../classes/display/scene.js'; -import { PostContent } from '../../classes/util/post-content.js'; +import { PostContent } from '../../classes/supporting/post-content.js'; import { Expert } from '../../classes/actors/expert.js'; import { ForumNode } from '../../classes/forum-network/forum-node.js'; import { Network } from '../../classes/forum-network/network.js'; -import { mochaRun, randomID } from '../../util.js'; +import { mochaRun, randomID } from '../../util/helpers.js'; import { delayOrWait } from '../../classes/display/controls.js'; describe('Forum Network', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum/forum.test-util.js index aabcd46..2409d0c 100644 --- a/forum-network/src/tests/scripts/forum/forum.test-util.js +++ b/forum-network/src/tests/scripts/forum/forum.test-util.js @@ -1,7 +1,7 @@ import { Box } from '../../../classes/display/box.js'; import { Scene } from '../../../classes/display/scene.js'; import { Expert } from '../../../classes/actors/expert.js'; -import { PostContent } from '../../../classes/util/post-content.js'; +import { PostContent } from '../../../classes/supporting/post-content.js'; import params from '../../../params.js'; import { DAO } from '../../../classes/dao/dao.js'; import { delayOrWait } from '../../../classes/display/controls.js'; @@ -55,12 +55,16 @@ export class ForumTest { return postId; } - async newExpert(options) { + async newExpert() { + // Hide by default, for simplicity of rendering first 9 forum tests + const options = { + hide: !this.options.displayAuthors, + announce: this.options.displayAuthors, + }; const index = this.experts.length; const name = `Expert${index + 1}`; const expert = await new Expert(this.dao, name, this.scene, options).initialize(); this.experts.push(expert); - // await expert.addComputedValue('rep', () => this.dao.reputation.valueOwnedBy(expert.reputationPublicKey)); return expert; } @@ -81,12 +85,13 @@ export class ForumTest { scene.addDisplayValue('q4. leachingValue').set(params.leachingValue); scene.addDisplayValue(' '); - this.dao = new DAO('DAO', scene, { announce: true }); + // If we're going to announce experts, announce the DAO so it appears first. + this.dao = new DAO('DAO', scene, { announce: this.options.displayAuthors }); this.forum = this.dao.forum; this.experts = []; this.posts = []; - await this.newExpert({ announce: true }); + await this.newExpert(); await this.dao.addComputedValue('total value', () => this.dao.reputation.getTotal()); } diff --git a/forum-network/src/tests/scripts/forum/forum1.test.js b/forum-network/src/tests/scripts/forum/forum1.test.js index 2800972..b2e8895 100644 --- a/forum-network/src/tests/scripts/forum/forum1.test.js +++ b/forum-network/src/tests/scripts/forum/forum1.test.js @@ -1,13 +1,15 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); - const forumTest = new ForumTest(); + const forumTest = new ForumTest({ displayAuthors: false }); before(async () => { await forumTest.setup(); + await forumTest.newExpert(); + await forumTest.newExpert(); }); context('Negative citation of a negative citation', async () => { @@ -19,14 +21,14 @@ describe('Forum', function tests() { it('Post2 negatively cites Post1', async () => { const { forum, experts, posts } = forumTest; - await forumTest.addPost(experts[0], 10, [{ postId: posts[0], weight: -1 }]); + await forumTest.addPost(experts[1], 10, [{ postId: posts[0], weight: -1 }]); forum.getPost(posts[0]).value.should.equal(0); forum.getPost(posts[1]).value.should.equal(20); }); it('Post3 negatively cites Post2, restoring Post1 post to its initial value', async () => { const { forum, experts, posts } = forumTest; - await forumTest.addPost(experts[0], 10, [{ postId: posts[1], weight: -1 }]); + await forumTest.addPost(experts[2], 10, [{ postId: posts[1], weight: -1 }]); forum.getPost(posts[0]).value.should.equal(10); forum.getPost(posts[1]).value.should.equal(0); forum.getPost(posts[2]).value.should.equal(20); @@ -34,11 +36,4 @@ describe('Forum', function tests() { }); }); -// await addPost(experts[0], 10); -// await addPost(experts[0], 10, [{ postId: posts[3], weight: -1 }]); -// await addPost(experts[0], 10, [{ postId: posts[4], weight: -1 }]); - -// await addPost(expert3, 'Post 4', 100, [{ postId: postId2, weight: -1 }]); -// await addPost(expert1, 'Post 5', 100, [{ postId: postId3, weight: -1 }]); - mochaRun(); diff --git a/forum-network/src/tests/scripts/forum/forum10.test.js b/forum-network/src/tests/scripts/forum/forum10.test.js index fa3fb02..fc21536 100644 --- a/forum-network/src/tests/scripts/forum/forum10.test.js +++ b/forum-network/src/tests/scripts/forum/forum10.test.js @@ -1,13 +1,15 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); - const forumTest = new ForumTest(); + const forumTest = new ForumTest({ displayAuthors: true }); before(async () => { await forumTest.setup(); + await forumTest.newExpert(); + await forumTest.newExpert(); }); context('Post with multiple authors', async () => { @@ -19,9 +21,6 @@ describe('Forum', function tests() { forum = forumTest.forum; experts = forumTest.experts; posts = forumTest.posts; - - await forumTest.newExpert({ announce: true }); - await forumTest.newExpert({ announce: true }); }); it('Post1', async () => { diff --git a/forum-network/src/tests/scripts/forum/forum11.test.js b/forum-network/src/tests/scripts/forum/forum11.test.js index 2d6e983..18553e6 100644 --- a/forum-network/src/tests/scripts/forum/forum11.test.js +++ b/forum-network/src/tests/scripts/forum/forum11.test.js @@ -1,13 +1,15 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); - const forumTest = new ForumTest(); + const forumTest = new ForumTest({ displayAuthors: true }); before(async () => { await forumTest.setup(); + await forumTest.newExpert(); + await forumTest.newExpert(); }); context('Multiple posts with overlapping authors', async () => { @@ -19,9 +21,6 @@ describe('Forum', function tests() { forum = forumTest.forum; experts = forumTest.experts; posts = forumTest.posts; - - await forumTest.newExpert({ announce: true }); - await forumTest.newExpert({ announce: true }); }); it('Post1 with two authors', async () => { diff --git a/forum-network/src/tests/scripts/forum/forum2.test.js b/forum-network/src/tests/scripts/forum/forum2.test.js index 6c76676..71b1e76 100644 --- a/forum-network/src/tests/scripts/forum/forum2.test.js +++ b/forum-network/src/tests/scripts/forum/forum2.test.js @@ -1,4 +1,4 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum3.test.js b/forum-network/src/tests/scripts/forum/forum3.test.js index f8d65ce..1fd49e1 100644 --- a/forum-network/src/tests/scripts/forum/forum3.test.js +++ b/forum-network/src/tests/scripts/forum/forum3.test.js @@ -1,4 +1,4 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum4.test.js b/forum-network/src/tests/scripts/forum/forum4.test.js index 2349110..1a35b13 100644 --- a/forum-network/src/tests/scripts/forum/forum4.test.js +++ b/forum-network/src/tests/scripts/forum/forum4.test.js @@ -1,4 +1,4 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum5.test.js b/forum-network/src/tests/scripts/forum/forum5.test.js index 52e819b..b6c0a34 100644 --- a/forum-network/src/tests/scripts/forum/forum5.test.js +++ b/forum-network/src/tests/scripts/forum/forum5.test.js @@ -1,4 +1,4 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum6.test.js b/forum-network/src/tests/scripts/forum/forum6.test.js index 8cb920e..69ddd39 100644 --- a/forum-network/src/tests/scripts/forum/forum6.test.js +++ b/forum-network/src/tests/scripts/forum/forum6.test.js @@ -1,4 +1,5 @@ -import { EPSILON, mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; +import { EPSILON } from '../../../util/constants.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum7.test.js b/forum-network/src/tests/scripts/forum/forum7.test.js index 670e634..83cbf59 100644 --- a/forum-network/src/tests/scripts/forum/forum7.test.js +++ b/forum-network/src/tests/scripts/forum/forum7.test.js @@ -1,4 +1,4 @@ -import { mochaRun } from '../../../util.js'; +import { mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum8.test.js b/forum-network/src/tests/scripts/forum/forum8.test.js index 513cc91..a27f0e2 100644 --- a/forum-network/src/tests/scripts/forum/forum8.test.js +++ b/forum-network/src/tests/scripts/forum/forum8.test.js @@ -1,4 +1,4 @@ -import { INCINERATOR_ADDRESS, mochaRun } from '../../../util.js'; +import { INCINERATOR_ADDRESS, mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum9.test.js b/forum-network/src/tests/scripts/forum/forum9.test.js index 5bc484a..321d182 100644 --- a/forum-network/src/tests/scripts/forum/forum9.test.js +++ b/forum-network/src/tests/scripts/forum/forum9.test.js @@ -1,4 +1,4 @@ -import { INCINERATOR_ADDRESS, mochaRun } from '../../../util.js'; +import { INCINERATOR_ADDRESS, mochaRun } from '../../../util/helpers.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/validation-pool.test.js b/forum-network/src/tests/scripts/validation-pool.test.js index 3d1e8c4..337ff74 100644 --- a/forum-network/src/tests/scripts/validation-pool.test.js +++ b/forum-network/src/tests/scripts/validation-pool.test.js @@ -1,10 +1,10 @@ import { Box } from '../../classes/display/box.js'; import { Scene } from '../../classes/display/scene.js'; import { Expert } from '../../classes/actors/expert.js'; -import { PostContent } from '../../classes/util/post-content.js'; +import { PostContent } from '../../classes/supporting/post-content.js'; import { DAO } from '../../classes/dao/dao.js'; import { delayOrWait } from '../../classes/display/controls.js'; -import { mochaRun } from '../../util.js'; +import { mochaRun } from '../../util/helpers.js'; const POOL_DURATION_MS = 100; const DEFAULT_DELAY_MS = 100; diff --git a/forum-network/src/tests/scripts/vm.test.js b/forum-network/src/tests/scripts/vm.test.js index ffd1967..fd2b1df 100644 --- a/forum-network/src/tests/scripts/vm.test.js +++ b/forum-network/src/tests/scripts/vm.test.js @@ -2,7 +2,7 @@ import { Actor } from '../../classes/display/actor.js'; import { Box } from '../../classes/display/box.js'; import { Scene } from '../../classes/display/scene.js'; import { VM } from '../../classes/supporting/vm.js'; -import { mochaRun } from '../../util.js'; +import { mochaRun } from '../../util/helpers.js'; const contractIds = ['contract-id-1', 'contract-id-2']; diff --git a/forum-network/src/tests/scripts/wdag.test.js b/forum-network/src/tests/scripts/wdag.test.js index 439dd17..dfc15a3 100644 --- a/forum-network/src/tests/scripts/wdag.test.js +++ b/forum-network/src/tests/scripts/wdag.test.js @@ -1,7 +1,7 @@ import { Box } from '../../classes/display/box.js'; import { Scene } from '../../classes/display/scene.js'; import { WDAG } from '../../classes/supporting/wdag.js'; -import { mochaRun } from '../../util.js'; +import { mochaRun } from '../../util/helpers.js'; const rootElement = document.getElementById('scene'); const rootBox = new Box('rootBox', rootElement).flex(); diff --git a/forum-network/src/util/constants.js b/forum-network/src/util/constants.js new file mode 100644 index 0000000..65b28c9 --- /dev/null +++ b/forum-network/src/util/constants.js @@ -0,0 +1,14 @@ +export const EPSILON = 2.23e-16; + +export const INCINERATOR_ADDRESS = 0; + +export const EdgeTypes = { + CITATION: 'citation', + BALANCE: 'balance', + AUTHOR: 'author', +}; + +export const VertexTypes = { + POST: 'post', + AUTHOR: 'author', +}; diff --git a/forum-network/src/util.js b/forum-network/src/util/helpers.js similarity index 81% rename from forum-network/src/util.js rename to forum-network/src/util/helpers.js index 427bfb1..53ad6ee 100644 --- a/forum-network/src/util.js +++ b/forum-network/src/util/helpers.js @@ -1,11 +1,5 @@ -import { CryptoUtil } from './classes/util/crypto.js'; - const timers = new Map(); -export const EPSILON = 2.23e-16; - -export const INCINERATOR_ADDRESS = 0; - export const debounce = async (fn, delayMs) => { const timer = timers.get(fn); if (timer) { @@ -39,7 +33,7 @@ export const displayNumber = (value, decimals = 2) => (value.toString().length > ? value.toFixed(decimals) : value); -export const randomID = () => CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8); +export const randomID = () => window.crypto.randomUUID().replaceAll('-', '').slice(0, 8); export const mochaRun = () => { if (mocha._state !== 'running') { From dad2d66fc812bd7302116b1399611181645cbc56 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 18:03:01 -0500 Subject: [PATCH 08/11] Minor fixups --- forum-network/src/classes/actors/expert.js | 9 --------- forum-network/src/classes/dao/forum.js | 2 +- forum-network/src/tests/scripts/forum/forum8.test.js | 3 ++- forum-network/src/tests/scripts/forum/forum9.test.js | 3 ++- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/forum-network/src/classes/actors/expert.js b/forum-network/src/classes/actors/expert.js index 3a6f9e8..7f04f2e 100644 --- a/forum-network/src/classes/actors/expert.js +++ b/forum-network/src/classes/actors/expert.js @@ -49,15 +49,6 @@ export class Expert extends ReputationHolder { return this; } - async submitPostViaNetwork(forumNode, post, stake) { - // TODO: Include fee - const postMessage = new PostMessage({ post, stake }); - await postMessage.sign(this.reputationKey); - await this.actions.submitPostViaNetwork.log(this, forumNode); - // For now, directly call forumNode.receiveMessage(); - await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON())); - } - async submitPostWithFee(postContent, poolOptions) { const post = await this.dao.forum.addPost(this.reputationPublicKey, postContent); await this.actions.submitPost.log(this, post); diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index cbfac86..d8bce96 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -191,7 +191,7 @@ export class Forum extends ReputationHolder { const authorVertex = authorEdge.to; const author = authorVertex.data; const { tokenId: authorTokenId } = authorEdge.data; - this.dao.reputation.transfer(this.id, author.publicKey, authorTokenId); + this.dao.reputation.transfer(this.id, author.reputationPublicKey, authorTokenId); } } diff --git a/forum-network/src/tests/scripts/forum/forum8.test.js b/forum-network/src/tests/scripts/forum/forum8.test.js index a27f0e2..d695782 100644 --- a/forum-network/src/tests/scripts/forum/forum8.test.js +++ b/forum-network/src/tests/scripts/forum/forum8.test.js @@ -1,4 +1,5 @@ -import { INCINERATOR_ADDRESS, mochaRun } from '../../../util/helpers.js'; +import { mochaRun } from '../../../util/helpers.js'; +import { INCINERATOR_ADDRESS } from '../../../util/constants.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { diff --git a/forum-network/src/tests/scripts/forum/forum9.test.js b/forum-network/src/tests/scripts/forum/forum9.test.js index 321d182..8fbbf02 100644 --- a/forum-network/src/tests/scripts/forum/forum9.test.js +++ b/forum-network/src/tests/scripts/forum/forum9.test.js @@ -1,4 +1,5 @@ -import { INCINERATOR_ADDRESS, mochaRun } from '../../../util/helpers.js'; +import { mochaRun } from '../../../util/helpers.js'; +import { INCINERATOR_ADDRESS } from '../../../util/constants.js'; import { ForumTest } from './forum.test-util.js'; describe('Forum', function tests() { From b71649df972d264504d9631fbd79db882c3e1abf Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 18:30:44 -0500 Subject: [PATCH 09/11] Add verification of resulting author reputations --- forum-network/src/classes/actors/expert.js | 7 +------ forum-network/src/classes/dao/business.js | 2 +- forum-network/src/classes/dao/dao.js | 4 ++-- forum-network/src/classes/dao/forum.js | 17 ++++++++++------- .../src/classes/dao/validation-pool.js | 7 +++++-- .../src/tests/scripts/forum/forum10.test.js | 8 +++++++- .../src/tests/scripts/forum/forum11.test.js | 10 ++++++++++ 7 files changed, 36 insertions(+), 19 deletions(-) diff --git a/forum-network/src/classes/actors/expert.js b/forum-network/src/classes/actors/expert.js index 7f04f2e..6928e45 100644 --- a/forum-network/src/classes/actors/expert.js +++ b/forum-network/src/classes/actors/expert.js @@ -61,14 +61,9 @@ export class Expert extends ReputationHolder { async initiateValidationPool(poolOptions) { // For now, make direct call rather than network poolOptions.reputationPublicKey = this.reputationPublicKey; - const pool = await this.dao.initiateValidationPool(poolOptions); + const pool = await this.dao.initiateValidationPool(this, poolOptions); this.tokens.push(pool.tokenId); this.validationPools.set(pool.id, poolOptions); - await this.actions.initiateValidationPool.log( - this, - pool, - `(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStakeAmount ?? 0})`, - ); return pool; } diff --git a/forum-network/src/classes/dao/business.js b/forum-network/src/classes/dao/business.js index 8c266ee..da603ed 100644 --- a/forum-network/src/classes/dao/business.js +++ b/forum-network/src/classes/dao/business.js @@ -77,7 +77,7 @@ export class Business extends Actor { // Initiate a validation pool for this work evidence. await this.actions.initiateValidationPool.log(this, this.dao); - const pool = await this.dao.initiateValidationPool({ + const pool = await this.dao.initiateValidationPool(this, { postId, fee: request.fee, duration, diff --git a/forum-network/src/classes/dao/dao.js b/forum-network/src/classes/dao/dao.js index bdf4ec0..95929b2 100644 --- a/forum-network/src/classes/dao/dao.js +++ b/forum-network/src/classes/dao/dao.js @@ -55,10 +55,10 @@ export class DAO extends Actor { .reduce((acc, cur) => (acc += cur), 0); } - async initiateValidationPool(poolOptions, stakeOptions) { + async initiateValidationPool(fromActor, poolOptions, stakeOptions) { const validationPoolNumber = this.validationPools.size + 1; const name = `Pool${validationPoolNumber}`; - const pool = new ValidationPool(this, poolOptions, name, this.scene); + const pool = new ValidationPool(this, poolOptions, name, this.scene, fromActor); this.validationPools.set(pool.id, pool); if (stakeOptions) { diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index d8bce96..b7c76a7 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -137,17 +137,17 @@ export class Forum extends ReputationHolder { authorVertex, weight, { tokenId: authorTokenId }, - { - hide: author.options.hide, - }, + { hide: author.options.hide }, ); }; // In the case of multiple authors, mint additional (empty) tokens. // If no authors are specified, treat the sender as the sole author. + // TODO: Verify that cumulative author weight == 1 if (!post.authors?.length) { addAuthorToGraph(post.senderId, 1, tokenId); } else { + // TODO: Verify that author list includes the sender 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) { @@ -158,8 +158,6 @@ export class Forum extends ReputationHolder { } } - // TODO: Verify that cumulative author weight === 1 - const rewardsAccumulator = new Map(); // Compute reputation rewards @@ -181,11 +179,16 @@ export class Forum extends ReputationHolder { } else { this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount); } + await author.computeDisplayValues(); + authorVertex.setDisplayLabel(author.getLabel()); } - await author.computeDisplayValues(); - authorVertex.setDisplayLabel(author.getLabel()); } + const senderVertex = this.graph.getVertex(post.senderId); + const { data: sender } = senderVertex; + await sender.computeDisplayValues(); + senderVertex.setDisplayLabel(sender.getLabel()); + // Transfer ownership of the minted tokens to the authors for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHOR, true)) { const authorVertex = authorEdge.to; diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index 24dcc4d..7be2cff 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -27,16 +27,21 @@ export class ValidationPool extends ReputationHolder { }, name, scene, + fromActor, ) { super(name, scene); this.id = this.reputationPublicKey; this.actions = { + initiate: new Action('initiate validation pool', scene), reward: new Action('reward', scene), transfer: new Action('transfer', scene), mint: new Action('mint', scene), }; + this.actions.initiate.log(fromActor, this, `(fee: ${fee})`); + this.activate(); + // If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio() if ( !contentiousDebate @@ -90,8 +95,6 @@ export class ValidationPool extends ReputationHolder { const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey); voter.addVoteRecord(this); this.dao.experts.set(reputationPublicKey, voter); - - this.activate(); } getTokenLossRatio() { diff --git a/forum-network/src/tests/scripts/forum/forum10.test.js b/forum-network/src/tests/scripts/forum/forum10.test.js index fc21536..6327ae0 100644 --- a/forum-network/src/tests/scripts/forum/forum10.test.js +++ b/forum-network/src/tests/scripts/forum/forum10.test.js @@ -16,14 +16,16 @@ describe('Forum', function tests() { let forum; let experts; let posts; + let dao; before(async () => { forum = forumTest.forum; experts = forumTest.experts; posts = forumTest.posts; + dao = forumTest.dao; }); - it('Post1', async () => { + it('Post1 has three authors and reputation is distributed among them', async () => { const authors = [ { author: experts[0], weight: 0.5 }, { author: experts[1], weight: 0.25 }, @@ -31,6 +33,10 @@ describe('Forum', function tests() { ]; await forumTest.addPost(authors, 10); forum.getPost(posts[0]).value.should.equal(10); + + dao.reputation.valueOwnedBy(experts[0].reputationPublicKey).should.equal(5); + dao.reputation.valueOwnedBy(experts[1].reputationPublicKey).should.equal(2.5); + dao.reputation.valueOwnedBy(experts[2].reputationPublicKey).should.equal(2.5); }); }); }); diff --git a/forum-network/src/tests/scripts/forum/forum11.test.js b/forum-network/src/tests/scripts/forum/forum11.test.js index 18553e6..0b144cc 100644 --- a/forum-network/src/tests/scripts/forum/forum11.test.js +++ b/forum-network/src/tests/scripts/forum/forum11.test.js @@ -16,11 +16,13 @@ describe('Forum', function tests() { let forum; let experts; let posts; + let dao; before(async () => { forum = forumTest.forum; experts = forumTest.experts; posts = forumTest.posts; + dao = forumTest.dao; }); it('Post1 with two authors', async () => { @@ -30,6 +32,10 @@ describe('Forum', function tests() { ]; await forumTest.addPost(authors, 10); forum.getPost(posts[0]).value.should.equal(10); + + dao.reputation.valueOwnedBy(experts[0].reputationPublicKey).should.equal(5); + dao.reputation.valueOwnedBy(experts[1].reputationPublicKey).should.equal(5); + dao.reputation.valueOwnedBy(experts[2].reputationPublicKey).should.equal(0); }); it('Post2 with two authors, one shared with Post1', async () => { @@ -39,6 +45,10 @@ describe('Forum', function tests() { ]; await forumTest.addPost(authors, 10); forum.getPost(posts[0]).value.should.equal(10); + + dao.reputation.valueOwnedBy(experts[0].reputationPublicKey).should.equal(5); + dao.reputation.valueOwnedBy(experts[1].reputationPublicKey).should.equal(10); + dao.reputation.valueOwnedBy(experts[2].reputationPublicKey).should.equal(5); }); }); }); From 226e95cfba72913da2f720afc1b9d3613a621252 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 20:17:25 -0500 Subject: [PATCH 10/11] Fixup, add authors to graph even if hidden --- forum-network/src/classes/dao/forum.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index b7c76a7..1a861a6 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -123,9 +123,6 @@ export class Forum extends ReputationHolder { const addAuthorToGraph = (publicKey, weight, authorTokenId) => { // For graph display purposes, we want to use the existing Expert actors from the current scene. const author = this.scene.findActor(({ reputationPublicKey }) => reputationPublicKey === publicKey); - if (author.options.hide) { - return; - } author.setDisplayValue('reputation', () => author.getReputation()); const authorVertex = this.graph.getVertex(publicKey) ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.getLabel(), { @@ -143,18 +140,18 @@ export class Forum extends ReputationHolder { // In the case of multiple authors, mint additional (empty) tokens. // If no authors are specified, treat the sender as the sole author. - // TODO: Verify that cumulative author weight == 1 + // TODO: Verify that cumulative author weight == 1. if (!post.authors?.length) { addAuthorToGraph(post.senderId, 1, tokenId); } else { - // TODO: Verify that author list includes the sender 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)); - } + const authorTokenId = (publicKey === post.senderId) ? tokenId : this.dao.reputation.mint(this.id, 0); + addAuthorToGraph(publicKey, weight, authorTokenId); + } + // If the sender is not an author, they will end up with the minted token but with zero value. + if (!post.authors.find(({ publicKey }) => publicKey === post.senderId)) { + addAuthorToGraph(post.senderId, 0, tokenId); } } From 335c89b6d4808c914b455ddc7931a7242a9190ee Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 16 Apr 2023 20:40:27 -0500 Subject: [PATCH 11/11] Fixup vertex label --- forum-network/src/classes/supporting/wdag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forum-network/src/classes/supporting/wdag.js b/forum-network/src/classes/supporting/wdag.js index e3a71bf..c28d314 100644 --- a/forum-network/src/classes/supporting/wdag.js +++ b/forum-network/src/classes/supporting/wdag.js @@ -93,7 +93,7 @@ export class WDAG { } const vertex = new Vertex(this, type, id, data, options); this.vertices.set(id, vertex); - vertex.setDisplayLabel(id, label ?? id); + vertex.setDisplayLabel(label ?? id); return vertex; }