Fix forum logic for negative citations

This commit is contained in:
Ladd Hoffman 2023-01-29 18:28:27 -06:00
parent 0c98ae2505
commit 7cda474d20
9 changed files with 228 additions and 159 deletions

View File

@ -1,5 +1,5 @@
import { Actor } from './actor.js';
import { WDAG } from './wdag.js';
import { WDAG, Vertex } from './wdag.js';
import { Action } from './action.js';
import { CryptoUtil } from './crypto.js';
import params from '../params.js';
@ -14,7 +14,7 @@ class Post extends Actor {
const index = forum.posts.countVertices();
const name = `Post${index + 1}`;
super(name, forum.scene);
this.id = postContent.id ?? `post_${CryptoUtil.randomUUID().slice(0, 4)}`;
this.id = postContent.id ?? name;
this.authorPublicKey = authorPublicKey;
this.value = 0;
this.initialValue = 0;
@ -147,41 +147,48 @@ export class Forum extends ReputationHolder {
if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) {
for (const citationEdge of postVertex.getEdges(CITATION, true)) {
const { to: citedPostVertex, weight } = citationEdge;
const citedPost = citedPostVertex.data;
let outboundAmount = weight * increment;
const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.data || 0;
const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.weight || 0;
console.log('Citation', {
citationEdge, outboundAmount, balance, citedPostValue: citedPost.value,
citationEdge, outboundAmount, balance,
});
// We need to ensure that we propagate no more reputation than we leached
if (outboundAmount < 0) {
outboundAmount = Math.max(outboundAmount, -citedPost.value);
if (depth > 0) {
outboundAmount = Math.max(outboundAmount, -balance);
}
if (depth > 0 && weight < 0) {
outboundAmount = outboundAmount < 0
? Math.max(outboundAmount, -balance)
: Math.min(outboundAmount, -balance);
}
increment -= outboundAmount * params.leachingValue;
this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
await this.propagateValue(citationEdge, {
const refundFromOutbound = await this.propagateValue(citationEdge, {
rewardsAccumulator,
increment: outboundAmount,
depth: depth + 1,
});
outboundAmount -= refundFromOutbound;
this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
increment -= outboundAmount * params.leachingValue;
}
}
const newValue = post.value + increment;
const rawNewValue = post.value + increment;
const newValue = Math.max(0, rawNewValue);
const appliedIncrement = newValue - post.value;
const refundToInbound = increment - appliedIncrement;
console.log('propagateValue end', {
depth,
increment,
rawNewValue,
newValue,
appliedIncrement,
refundToInbound,
});
// Award reputation to post author
rewardsAccumulator.set(post.tokenId, increment);
rewardsAccumulator.set(post.tokenId, appliedIncrement);
// Increment the value of the post
await this.setPostValue(post, newValue);
return refundToInbound;
}
}

View File

@ -1,4 +1,4 @@
class Vertex {
export class Vertex {
constructor(id, data) {
this.id = id;
this.data = data;
@ -15,7 +15,7 @@ class Vertex {
}
}
class Edge {
export class Edge {
constructor(label, from, to, weight) {
this.from = from;
this.to = to;
@ -80,9 +80,7 @@ export class WDAG {
setEdge(label, from, to, edge) {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to);
if (!(edge instanceof Edge)) {
edge = new Edge(edge);
}
edge = typeof edge === 'number' ? new Edge(label, from, to, edge) : edge;
let edges = this.edgeLabels.get(label);
if (!edges) {
edges = new Map();

View File

@ -9,7 +9,13 @@
<ul>
<li><a href="./tests/validation-pool.test.html">Validation Pool</a></li>
<li><a href="./tests/availability.test.html">Availability + Business</a></li>
<li><a href="./tests/forum.test.html">Forum</a></li>
<li>
Forum
<ul>
<li><a href="./tests/forum1.test.html">1</a></li>
<li><a href="./tests/forum2.test.html">2</a></li>
</ul>
</li>
</ul>
<h3>Secondary</h3>
<ul>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<head>
<title>Forum test</title>
<title>Forum test 1</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" />
@ -10,9 +10,9 @@
<div id="scene"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js" integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script type="module" src="./scripts/forum.test.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/forum1.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<head>
<title>Forum test 2</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" />
</head>
<body>
<div id="mocha"></div>
<div id="scene"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js" integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/forum2.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',
globals: ['scene', 'bench', 'forum', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
});
mocha.checkLeaks();
chai.should();
</script>
<script defer class="mocha-exec">
// TODO: Weird race condition -- resolve this in a better way
setTimeout(() => mocha.run(), 1000);
</script>

View File

@ -0,0 +1,88 @@
import { Box } from '../../classes/box.js';
import { Scene } from '../../classes/scene.js';
import { Expert } from '../../classes/expert.js';
import { Bench } from '../../classes/bench.js';
import { delay } from '../../util.js';
import { Forum } from '../../classes/forum.js';
import { PostContent } from '../../classes/post-content.js';
import params from '../../params.js';
const DEFAULT_DELAY_MS = 1;
const POOL_DURATION_MS = 50;
export class ForumTest {
constructor() {
this.scene = null;
this.forum = null;
this.bench = null;
this.experts = null;
this.posts = null;
}
async newExpert() {
const index = this.experts.length;
const name = `Expert${index + 1}`;
const expert = await new Expert(name, this.scene).initialize();
this.experts.push(expert);
// expert.addValue('rep', () => bench.reputation.valueOwnedBy(expert.reputationPublicKey))
return expert;
}
async addPost(author, fee, citations = []) {
const postIndex = this.posts.length;
const title = `posts[${postIndex}]`;
await this.scene.startSection();
const postContent = new PostContent({}).setTitle(title);
for (const { postId, weight } of citations) {
postContent.addCitation(postId, weight);
}
const { pool, postId } = await author.submitPostWithFee(
this.bench,
this.forum,
postContent,
{
fee,
duration: POOL_DURATION_MS,
tokenLossRatio: 1,
},
);
this.posts.push(postId);
await delay(POOL_DURATION_MS);
await pool.evaluateWinningConditions();
await this.scene.endSection();
await delay(DEFAULT_DELAY_MS);
return postId;
}
async setup() {
const rootElement = document.getElementById('scene');
const rootBox = new Box('rootBox', rootElement).flex();
this.scene = (window.scene = new Scene('Forum test', rootBox));
this.scene.withSequenceDiagram();
this.scene.withFlowchart();
this.scene.withTable();
this.scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor);
this.scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit);
this.scene
.addDisplayValue('q3. referenceChainLimit')
.set(params.referenceChainLimit);
this.scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
this.scene.addDisplayValue('&nbsp;');
this.forum = (window.forum = new Forum('Forum', this.scene));
this.bench = (window.bench = new Bench(this.forum, 'Bench', this.scene));
this.experts = (window.experts = []);
this.posts = (window.posts = []);
await this.newExpert();
// await newExpert();
// await newExpert();
// bench.addValue('total rep', () => bench.reputation.getTotal());
this.forum.addValue('total value', () => this.forum.getTotalValue());
}
}

View File

@ -1,135 +0,0 @@
import { Box } from '../../classes/box.js';
import { Scene } from '../../classes/scene.js';
import { Expert } from '../../classes/expert.js';
import { Bench } from '../../classes/bench.js';
import { delay } from '../../util.js';
import { Forum } from '../../classes/forum.js';
import { PostContent } from '../../classes/post-content.js';
import params from '../../params.js';
const DEFAULT_DELAY_MS = 1;
const POOL_DURATION_MS = 50;
let scene;
let forum;
let bench;
let experts;
let posts;
async function newExpert() {
const index = experts.length;
const name = `Expert${index + 1}`;
const expert = await new Expert(name, scene).initialize();
experts.push(expert);
return expert;
}
async function addPost(author, fee, citations = []) {
const postIndex = posts.length;
const title = `posts[${postIndex}]`;
await scene.startSection();
const postContent = new PostContent({}).setTitle(title);
for (const { postId, weight } of citations) {
postContent.addCitation(postId, weight);
}
const { pool, postId } = await author.submitPostWithFee(
bench,
forum,
postContent,
{
fee,
duration: POOL_DURATION_MS,
tokenLossRatio: 1,
},
);
posts.push(postId);
await delay(POOL_DURATION_MS);
await pool.evaluateWinningConditions();
await scene.endSection();
await delay(DEFAULT_DELAY_MS);
return postId;
}
async function setup() {
const rootElement = document.getElementById('scene');
const rootBox = new Box('rootBox', rootElement).flex();
scene = (window.scene = new Scene('Forum test', rootBox));
scene.withSequenceDiagram();
scene.withFlowchart();
scene.withTable();
scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor);
scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit);
scene
.addDisplayValue('q3. referenceChainLimit')
.set(params.referenceChainLimit);
scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
scene.addDisplayValue('&nbsp;');
forum = (window.forum = new Forum('Forum', scene));
bench = (window.bench = new Bench(forum, 'Bench', scene));
experts = (window.experts = []);
posts = (window.posts = []);
await newExpert();
// await newExpert();
// await newExpert();
// bench.addValue('total rep', () => bench.reputation.getTotal());
forum.addValue('total value', () => forum.getTotalValue());
// for (const expert of experts) {
// expert.addValue('rep', () => bench.reputation.valueOwnedBy(expert.reputationPublicKey));
// }
}
describe('Forum', () => {
before(async () => {
await setup();
});
context('Negative citation of a negative citation with max strength', async () => {
it('Post1', async () => {
await addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(10);
});
it('Post2 negatively cites Post1', async () => {
await addPost(experts[0], 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 () => {
await addPost(experts[0], 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);
});
});
context('Negative citation of a weaker negative citation', async () => {
it('Post4', async () => {
await addPost(experts[0], 10);
forum.getPost(posts[3]).value.should.equal(10);
});
it('Post5 negatively cites Post4', async () => {
await addPost(experts[0], 10, [{ postId: posts[3], weight: -0.5 }]);
forum.getPost(posts[3]).value.should.equal(5);
forum.getPost(posts[4]).value.should.equal(15);
});
it('Post6 negatively cites Post5, restoring Post4 post to its initial value', async () => {
await addPost(experts[0], 20, [{ postId: posts[4], weight: -1 }]);
forum.getPost(posts[3]).value.should.equal(10);
forum.getPost(posts[4]).value.should.equal(0);
forum.getPost(posts[5]).value.should.equal(30);
});
});
});
// 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 }]);

View File

@ -0,0 +1,39 @@
import { ForumTest } from './forum.test-util.js';
describe('Forum', () => {
const forumTest = new ForumTest();
before(async () => {
await forumTest.setup();
});
context('Negative citation of a negative citation with max strength', async () => {
it('Post1', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(10);
});
it('Post2 negatively cites Post1', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 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 }]);
forum.getPost(posts[0]).value.should.equal(10);
forum.getPost(posts[1]).value.should.equal(0);
forum.getPost(posts[2]).value.should.equal(20);
});
});
});
// 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 }]);

View File

@ -0,0 +1,39 @@
import { ForumTest } from './forum.test-util.js';
describe('Forum', () => {
const forumTest = new ForumTest();
before(async () => {
await forumTest.setup();
});
context('Negative citation of a weaker negative citation', async () => {
it('Post4', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(10);
});
it('Post5 negatively cites Post4', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10, [{ postId: posts[0], weight: -0.5 }]);
forum.getPost(posts[0]).value.should.equal(5);
forum.getPost(posts[1]).value.should.equal(15);
});
it('Post6 negatively cites Post5, restoring Post4 post to its initial value', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 20, [{ 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(30);
});
});
});
// 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 }]);