parent
721718ac13
commit
3cf24c6fa1
|
@ -50,3 +50,9 @@ At a minimum this can be a list where each item includes the identifier of the c
|
||||||
Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post.
|
Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
So: If some incoming reputation can’t be propagated due to a constraint like this, the question is what should happen with that unspent power. I think right now, I have it so when that limit is reached on leaching from PostA, the extra reputation accrues evenly (because of +0.5 citation) equally to PostX and PostZ.
|
||||||
|
|
||||||
|
Intuitively, we might want less value accruing to PostX, as it seems intended as a small utility post to transfer power.
|
||||||
|
|
||||||
|
However, we can combine these mechanisms. PostX
|
||||||
|
|
|
@ -53,7 +53,7 @@ export class Forum extends ReputationHolder {
|
||||||
this.posts = new WDAG(scene);
|
this.posts = new WDAG(scene);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
addPost: new Action('add post', scene),
|
addPost: new Action('add post', scene),
|
||||||
propagateValue: new Action('propagate value', this.scene),
|
propagateValue: new Action('propagate', this.scene),
|
||||||
transfer: new Action('transfer', this.scene),
|
transfer: new Action('transfer', this.scene),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ export class Forum extends ReputationHolder {
|
||||||
bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
|
bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
|
||||||
const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
||||||
const value = bench.reputation.valueOf(post.tokenId);
|
const value = bench.reputation.valueOf(post.tokenId);
|
||||||
this.actions.transfer.log(this, toActor, `(value: ${value})`);
|
this.actions.transfer.log(this, toActor, `(${value})`);
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,13 +146,12 @@ export class Forum extends ReputationHolder {
|
||||||
|
|
||||||
if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) {
|
if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) {
|
||||||
let totalOutboundAmount = 0;
|
let totalOutboundAmount = 0;
|
||||||
|
let totalRefundFromOutbound = 0;
|
||||||
for (const citationEdge of postVertex.getEdges(CITATION, true)) {
|
for (const citationEdge of postVertex.getEdges(CITATION, true)) {
|
||||||
const { to: citedPostVertex, weight } = citationEdge;
|
const { to: citedPostVertex, weight } = citationEdge;
|
||||||
let outboundAmount = weight * increment;
|
let outboundAmount = weight * increment;
|
||||||
const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.weight || 0;
|
const balanceEdge = this.posts.getEdge(BALANCE, postVertex, citedPostVertex);
|
||||||
console.log('Citation', {
|
const balance = balanceEdge?.weight ?? 0;
|
||||||
citationEdge, outboundAmount, balance,
|
|
||||||
});
|
|
||||||
// We need to ensure that we propagate no more reputation than we leached
|
// We need to ensure that we propagate no more reputation than we leached
|
||||||
if (depth > 0 && weight < 0) {
|
if (depth > 0 && weight < 0) {
|
||||||
outboundAmount = outboundAmount < 0
|
outboundAmount = outboundAmount < 0
|
||||||
|
@ -164,10 +163,34 @@ export class Forum extends ReputationHolder {
|
||||||
increment: outboundAmount,
|
increment: outboundAmount,
|
||||||
depth: depth + 1,
|
depth: depth + 1,
|
||||||
});
|
});
|
||||||
|
totalRefundFromOutbound += Math.abs(refundFromOutbound);
|
||||||
outboundAmount -= refundFromOutbound;
|
outboundAmount -= refundFromOutbound;
|
||||||
this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
|
this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
|
||||||
|
|
||||||
totalOutboundAmount += outboundAmount;
|
totalOutboundAmount += outboundAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that we know what value could not be propagated as negative citations,
|
||||||
|
// Let's see what happens if we redistribute it among the positive citations :)
|
||||||
|
|
||||||
|
const positiveCitations = postVertex.getEdges(CITATION, true).filter(({ weight }) => weight > 0);
|
||||||
|
const totalPositiveCitationWeight = positiveCitations.reduce((total, { weight }) => total += weight, 0);
|
||||||
|
|
||||||
|
for (const citationEdge of positiveCitations) {
|
||||||
|
const { to: citedPostVertex, weight } = citationEdge;
|
||||||
|
const outboundAmount = totalRefundFromOutbound * (weight / totalPositiveCitationWeight);
|
||||||
|
const balance = this.posts.getEdgeWeight(BALANCE, postVertex, citedPostVertex) ?? 0;
|
||||||
|
await this.propagateValue(citationEdge, {
|
||||||
|
rewardsAccumulator,
|
||||||
|
increment: outboundAmount,
|
||||||
|
depth: depth + 1,
|
||||||
|
});
|
||||||
|
this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
|
||||||
|
totalOutboundAmount += outboundAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And what if they don't have any positive citations? The value should just accrue to this post.
|
||||||
|
|
||||||
increment -= totalOutboundAmount * params.leachingValue;
|
increment -= totalOutboundAmount * params.leachingValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,10 @@ export class Scene {
|
||||||
activationBorderColor: '#569595',
|
activationBorderColor: '#569595',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
edgeNodeColor: '#4d585c',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
withSequenceDiagram() {
|
withSequenceDiagram() {
|
||||||
|
@ -131,6 +135,7 @@ export class Scene {
|
||||||
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
||||||
this.flowchart = new MermaidDiagram(box, logBox);
|
this.flowchart = new MermaidDiagram(box, logBox);
|
||||||
this.flowchart.log(`graph ${direction}`, false);
|
this.flowchart.log(`graph ${direction}`, false);
|
||||||
|
this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +148,7 @@ export class Scene {
|
||||||
const logBox = container.addBox('Flowchart text').addClass('dim');
|
const logBox = container.addBox('Flowchart text').addClass('dim');
|
||||||
const flowchart = new MermaidDiagram(box, logBox);
|
const flowchart = new MermaidDiagram(box, logBox);
|
||||||
flowchart.log(`graph ${direction}`, false);
|
flowchart.log(`graph ${direction}`, false);
|
||||||
|
this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false);
|
||||||
this.flowcharts.set(id, flowchart);
|
this.flowcharts.set(id, flowchart);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
// Transfer ownership of the minted token, from the pool to the forum
|
// Transfer ownership of the minted token, from the pool to the forum
|
||||||
this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId);
|
this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId);
|
||||||
const value = this.bench.reputation.valueOf(this.tokenId);
|
const value = this.bench.reputation.valueOf(this.tokenId);
|
||||||
this.actions.transfer.log(this, this.forum, `(value: ${value})`);
|
this.actions.transfer.log(this, this.forum, `(${value})`);
|
||||||
|
|
||||||
// Recurse through forum to determine reputation effects
|
// Recurse through forum to determine reputation effects
|
||||||
await this.forum.onValidate({
|
await this.forum.onValidate({
|
||||||
|
|
|
@ -70,6 +70,19 @@ export class WDAG {
|
||||||
return Array.from(this.vertices.values()).map(({ data }) => data);
|
return Array.from(this.vertices.values()).map(({ data }) => data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEdgeLabel(label) {
|
||||||
|
return this.edgeLabels.get(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrCreateEdgeLabel(label) {
|
||||||
|
let edges = this.getEdgeLabel(label);
|
||||||
|
if (!edges) {
|
||||||
|
edges = new Map();
|
||||||
|
this.edgeLabels.set(label, edges);
|
||||||
|
}
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
getEdge(label, from, to) {
|
getEdge(label, from, to) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
|
@ -77,16 +90,41 @@ export class WDAG {
|
||||||
return edges?.get(JSON.stringify([from.id, to.id]));
|
return edges?.get(JSON.stringify([from.id, to.id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
setEdge(label, from, to, edge) {
|
getEdgeWeight(label, from, to) {
|
||||||
|
return this.getEdge(label, from, to)?.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getEdgeKey({ from, to }) {
|
||||||
|
return btoa([from.id, to.id]).replaceAll(/[^A-Z]+/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
getEdgeHtml({ from, to }) {
|
||||||
|
let html = '<table>';
|
||||||
|
for (const { label, weight } of this.getEdges(null, from, to)) {
|
||||||
|
html += `<tr><td>${label}</td><td>${weight}</td></tr>`;
|
||||||
|
}
|
||||||
|
html += '</table>';
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEdgeFlowchartNode(edge) {
|
||||||
|
const edgeKey = WDAG.getEdgeKey(edge);
|
||||||
|
return `${edgeKey}(${this.getEdgeHtml(edge)}):::edge`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEdgeWeight(label, from, to, weight) {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
edge = typeof edge === 'number' ? new Edge(label, from, to, edge) : edge;
|
const edge = new Edge(label, from, to, weight);
|
||||||
let edges = this.edgeLabels.get(label);
|
let edges = this.edgeLabels.get(label);
|
||||||
if (!edges) {
|
if (!edges) {
|
||||||
edges = new Map();
|
edges = new Map();
|
||||||
this.edgeLabels.set(label, edges);
|
this.edgeLabels.set(label, edges);
|
||||||
}
|
}
|
||||||
edges.set(JSON.stringify([from.id, to.id]), edge);
|
const edgeKey = WDAG.getEdgeKey(edge);
|
||||||
|
edges.set(edgeKey, edge);
|
||||||
|
this.flowchart?.log(this.getEdgeFlowchartNode(edge));
|
||||||
|
return edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEdge(label, from, to, weight) {
|
addEdge(label, from, to, weight) {
|
||||||
|
@ -95,11 +133,10 @@ export class WDAG {
|
||||||
if (this.getEdge(label, from, to)) {
|
if (this.getEdge(label, from, to)) {
|
||||||
throw new Error(`Edge ${label} from ${from} to ${to} already exists`);
|
throw new Error(`Edge ${label} from ${from} to ${to} already exists`);
|
||||||
}
|
}
|
||||||
const edge = new Edge(label, from, to, weight);
|
const edge = this.setEdgeWeight(label, from, to, weight);
|
||||||
this.setEdge(label, from, to, edge);
|
|
||||||
from.edges.from.push(edge);
|
from.edges.from.push(edge);
|
||||||
to.edges.to.push(edge);
|
to.edges.to.push(edge);
|
||||||
this.flowchart?.log(`${from.id} -- ${weight} --> ${to.id}`);
|
this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { ForumTest } from './forum.test-util.js';
|
||||||
|
|
||||||
|
describe('Forum', () => {
|
||||||
|
const forumTest = new ForumTest();
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await forumTest.setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Redistribute power', async () => {
|
||||||
|
it('Post1', async () => {
|
||||||
|
const { forum, experts, posts } = forumTest;
|
||||||
|
await forumTest.addPost(experts[0], 20);
|
||||||
|
forum.getPost(posts[0]).value.should.equal(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Post2', async () => {
|
||||||
|
const { forum, experts, posts } = forumTest;
|
||||||
|
await forumTest.addPost(experts[0], 10);
|
||||||
|
forum.getPost(posts[0]).value.should.equal(20);
|
||||||
|
forum.getPost(posts[1]).value.should.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
||||||
|
const { forum, experts, posts } = forumTest;
|
||||||
|
await forumTest.addPost(experts[0], 10, [
|
||||||
|
{ postId: posts[0], weight: -0.5 },
|
||||||
|
{ postId: posts[1], weight: 0.5 },
|
||||||
|
]);
|
||||||
|
forum.getPost(posts[0]).value.should.equal(15);
|
||||||
|
forum.getPost(posts[1]).value.should.equal(15);
|
||||||
|
forum.getPost(posts[2]).value.should.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Post4 copies Post3 to increase its power', async () => {
|
||||||
|
const { forum, experts, posts } = forumTest;
|
||||||
|
await forumTest.addPost(experts[0], 10, [
|
||||||
|
{ postId: posts[0], weight: -0.5 },
|
||||||
|
{ postId: posts[1], weight: 0.5 },
|
||||||
|
]);
|
||||||
|
forum.getPost(posts[0]).value.should.equal(10);
|
||||||
|
forum.getPost(posts[1]).value.should.equal(20);
|
||||||
|
forum.getPost(posts[2]).value.should.equal(10);
|
||||||
|
forum.getPost(posts[3]).value.should.equal(10);
|
||||||
|
});
|
||||||
|
it('Post5 cites Post3 and Post4 to increase their power', async () => {
|
||||||
|
const { forum, experts, posts } = forumTest;
|
||||||
|
await forumTest.addPost(experts[0], 20, [
|
||||||
|
{ postId: posts[2], weight: 0.5 },
|
||||||
|
{ postId: posts[3], weight: 0.5 },
|
||||||
|
]);
|
||||||
|
forum.getPost(posts[0]).value.should.equal(0);
|
||||||
|
forum.getPost(posts[1]).value.should.equal(40);
|
||||||
|
forum.getPost(posts[2]).value.should.equal(40);
|
||||||
|
forum.getPost(posts[3]).value.should.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 }]);
|
|
@ -8,21 +8,28 @@ describe('Forum', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Redistribute power through subsequent support', async () => {
|
context('Redistribute power through subsequent support', async () => {
|
||||||
|
let forum;
|
||||||
|
let experts;
|
||||||
|
let posts;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
forum = forumTest.forum;
|
||||||
|
experts = forumTest.experts;
|
||||||
|
posts = forumTest.posts;
|
||||||
|
});
|
||||||
|
|
||||||
it('Post1', async () => {
|
it('Post1', async () => {
|
||||||
const { forum, experts, posts } = forumTest;
|
|
||||||
await forumTest.addPost(experts[0], 10);
|
await forumTest.addPost(experts[0], 10);
|
||||||
forum.getPost(posts[0]).value.should.equal(10);
|
forum.getPost(posts[0]).value.should.equal(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Post2', async () => {
|
it('Post2', async () => {
|
||||||
const { forum, experts, posts } = forumTest;
|
|
||||||
await forumTest.addPost(experts[0], 10);
|
await forumTest.addPost(experts[0], 10);
|
||||||
forum.getPost(posts[0]).value.should.equal(10);
|
forum.getPost(posts[0]).value.should.equal(10);
|
||||||
forum.getPost(posts[1]).value.should.equal(10);
|
forum.getPost(posts[1]).value.should.equal(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
it('Post3 cites Post2 and negatively cites Post1', async () => {
|
||||||
const { forum, experts, posts } = forumTest;
|
|
||||||
await forumTest.addPost(experts[0], 0, [
|
await forumTest.addPost(experts[0], 0, [
|
||||||
{ postId: posts[0], weight: -0.5 },
|
{ postId: posts[0], weight: -0.5 },
|
||||||
{ postId: posts[1], weight: 0.5 },
|
{ postId: posts[1], weight: 0.5 },
|
||||||
|
@ -33,7 +40,6 @@ describe('Forum', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Post4 cites Post3 to strengthen its effect', async () => {
|
it('Post4 cites Post3 to strengthen its effect', async () => {
|
||||||
const { forum, experts, posts } = forumTest;
|
|
||||||
await forumTest.addPost(experts[0], 20, [
|
await forumTest.addPost(experts[0], 20, [
|
||||||
{ postId: posts[2], weight: 1 },
|
{ postId: posts[2], weight: 1 },
|
||||||
]);
|
]);
|
||||||
|
@ -42,6 +48,18 @@ describe('Forum', () => {
|
||||||
forum.getPost(posts[2]).value.should.equal(20);
|
forum.getPost(posts[2]).value.should.equal(20);
|
||||||
forum.getPost(posts[3]).value.should.equal(0);
|
forum.getPost(posts[3]).value.should.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Post5 cites Post3 to strengthen its effect', async () => {
|
||||||
|
await forumTest.addPost(experts[0], 20, [
|
||||||
|
{ postId: posts[2], weight: 1 },
|
||||||
|
]);
|
||||||
|
console.log('test5', { posts });
|
||||||
|
forum.getPost(posts[0]).value.should.equal(0);
|
||||||
|
forum.getPost(posts[1]).value.should.equal(20);
|
||||||
|
forum.getPost(posts[2]).value.should.equal(20);
|
||||||
|
forum.getPost(posts[3]).value.should.equal(0);
|
||||||
|
forum.getPost(posts[4]).value.should.equal(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue