From 6cc1c25b378fbb905e428a2ae0bd4e936bf37db6 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 8 Jan 2023 20:19:33 -0600 Subject: [PATCH] Fixup forum logic --- forum-network/notes/notes.md | 12 +++ forum-network/src/classes/actor.js | 2 +- forum-network/src/classes/bench.js | 7 +- forum-network/src/classes/expert.js | 2 +- forum-network/src/classes/forum.js | 45 ++++++----- forum-network/src/classes/validation-pool.js | 81 +++++++++----------- forum-network/src/classes/vote.js | 3 +- forum-network/src/params.js | 4 +- forum-network/src/tests/availability.html | 5 +- forum-network/src/tests/forum.html | 13 +++- 10 files changed, 96 insertions(+), 78 deletions(-) diff --git a/forum-network/notes/notes.md b/forum-network/notes/notes.md index b072c8e..e0b99e1 100644 --- a/forum-network/notes/notes.md +++ b/forum-network/notes/notes.md @@ -85,3 +85,15 @@ Token loss ratio --- parameter q_4 -- what is c_n? + +--- + +what is reputation? +valuable evidence that you're going to do what you say you'll do in the future + +--- + +for now, combine c2 and c3 + +validation pool should compute rewards for author, +then send that to the forum to distribute. diff --git a/forum-network/src/classes/actor.js b/forum-network/src/classes/actor.js index 139213b..d632297 100644 --- a/forum-network/src/classes/actor.js +++ b/forum-network/src/classes/actor.js @@ -4,7 +4,7 @@ export class Actor { this.scene = scene; this.callbacks = new Map(); this.status = this.scene.addDisplayValue(`${this.name} status`); - this.status.set('New'); + this.status.set('Created'); this.values = new Map(); this.active = 0; this.scene.registerActor(this); diff --git a/forum-network/src/classes/bench.js b/forum-network/src/classes/bench.js index 3eea51b..4f26d08 100644 --- a/forum-network/src/classes/bench.js +++ b/forum-network/src/classes/bench.js @@ -25,12 +25,11 @@ export class Bench extends Actor { } listActiveVoters() { - const now = new Date(); - const thresholdSet = !!params.activeVoterThreshold; return Array.from(this.voters.values()).filter((voter) => { const hasVoted = !!voter.dateLastVote; - const withinThreshold = now - voter.dateLastVote >= params.activeVoterThreshold; - return hasVoted && (!thresholdSet || withinThreshold); + const withinThreshold = !params.activeVoterThreshold + || new Date() - voter.dateLastVote >= params.activeVoterThreshold; + return hasVoted && withinThreshold; }); } diff --git a/forum-network/src/classes/expert.js b/forum-network/src/classes/expert.js index 39b8483..24437f7 100644 --- a/forum-network/src/classes/expert.js +++ b/forum-network/src/classes/expert.js @@ -60,7 +60,7 @@ export class Expert extends Actor { await this.actions.initiateValidationPool.log( this, bench, - `(fee: ${poolOptions.fee})`, + `(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStake ?? 0})`, ); const pool = await bench.initiateValidationPool(poolOptions); this.validationPools.set(pool.id, poolOptions); diff --git a/forum-network/src/classes/forum.js b/forum-network/src/classes/forum.js index 1c4d263..9b14e3d 100644 --- a/forum-network/src/classes/forum.js +++ b/forum-network/src/classes/forum.js @@ -62,8 +62,7 @@ export class Forum extends Actor { return this.posts.getVerticesData(); } - async setPostValue(postId, value) { - const post = this.getPost(postId); + async setPostValue(post, value) { post.value = value; await post.setValue('value', value); if (this.scene.flowchart) { @@ -71,26 +70,36 @@ export class Forum extends Actor { } } - getPostValue(postId) { - const post = this.getPost(postId); - return post.value; - } - getTotalValue() { return this.getPosts().reduce((total, { value }) => total += value, 0); } - async propagateValue(fromActor, postId, increment, depth = 0) { - if (depth === 0 && this.scene.flowchart) { - const randomId = CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8); - this.scene.flowchart.log(`${postId}_initial_value_${randomId}[${increment}] -- initial value --> ${postId}`); + async onValidate(bench, pool, postId, initialValue) { + initialValue *= params.initialPostValue(); + + if (this.scene.flowchart) { + this.scene.flowchart.log(`${postId}_initial_value[${initialValue}] -- initial value --> ${postId}`); } - if (params.maxPropagationDepth >= 0 && depth > params.maxPropagationDepth) { + + const post = this.getPost(postId); + post.setStatus('Validated'); + const rewards = await this.propagateValue(pool, post, initialValue); + const totalRewards = Array.from(rewards.values()).reduce((total, value) => total += value, 0); + console.log('total awards from forum:', totalRewards); + // Apply computed rewards + for (const [id, value] of rewards) { + bench.reputations.addTokens(id, value); + } + } + + async propagateValue(fromActor, post, increment, depth = 0) { + if (params.referenceChainLimit >= 0 && depth > params.referenceChainLimit) { return []; } - const post = this.getPost(postId); - this.actions.propagateValue.log(fromActor, post, `(increment: ${increment})`); + this.actions.propagateValue.log(fromActor, post, `(${increment})`); + + // Apply leaching value const adjustedIncrement = increment * (1 - params.leachingValue * post.totalCitationWeight); const rewards = new Map(); @@ -102,10 +111,7 @@ export class Forum extends Actor { }; // Increment the value of the post - // Apply leaching value - const currentValue = this.getPostValue(postId); - const newValue = currentValue + adjustedIncrement; - await this.setPostValue(postId, newValue); + await this.setPostValue(post, post.value + adjustedIncrement); // Award reputation to post author console.log('reward for post author', post.authorPublicKey, adjustedIncrement); @@ -113,7 +119,8 @@ export class Forum extends Actor { // Recursively distribute reputation to citations, according to weights for (const { postId: citedPostId, weight } of post.citations) { - addRewards(await this.propagateValue(post, citedPostId, weight * increment, depth + 1)); + const citedPost = this.getPost(citedPostId); + addRewards(await this.propagateValue(post, citedPost, weight * increment, depth + 1)); } return rewards; diff --git a/forum-network/src/classes/validation-pool.js b/forum-network/src/classes/validation-pool.js index 8a79c6b..ecbed93 100644 --- a/forum-network/src/classes/validation-pool.js +++ b/forum-network/src/classes/validation-pool.js @@ -68,11 +68,7 @@ export class ValidationPool extends Actor { this.duration = duration; this.tokenLossRatio = tokenLossRatio; this.contentiousDebate = contentiousDebate; - this.tokensMinted = fee * params.mintingRatio; - this.tokens = { - for: this.tokensMinted * params.stakeForWin, - against: this.tokensMinted * (1 - params.stakeForWin), - }; + this.tokensMinted = fee * params.mintingRatio(); // Tokens minted "for" the post go toward stake of author voting for their own post. // Also, author can provide additional stakes, e.g. availability stakes for work evidence post. this.castVote(signingPublicKey, { @@ -80,12 +76,21 @@ export class ValidationPool extends Actor { stake: this.tokensMinted * params.stakeForAuthor + authorStake, anonymous, }); + this.castVote(undefined, { + position: false, + stake: this.tokensMinted * (1 - params.stakeForAuthor), + isSystemVote: true, + }); } async castVote(signingPublicKey, { - position, stake, lockingTime = 0, anonymous = true, + position, stake, lockingTime = 0, anonymous = true, isSystemVote = false, }) { - const vote = new Vote(position, stake, lockingTime); + if (isSystemVote) { + signingPublicKey = CryptoUtil.randomUUID(); + anonymous = false; + } + const vote = new Vote(position, stake, lockingTime, isSystemVote); if (this.state === ValidationPoolStates.CLOSED) { throw new Error(`Validation pool ${this.id} is closed`); } @@ -100,10 +105,10 @@ export class ValidationPool extends Actor { } } - listVotes(position) { + listVotes(filter) { return new Map( Array.from(this.votes).filter( - ([_, vote]) => vote.position === position, + ([_, vote]) => filter(vote), ), ); } @@ -175,7 +180,9 @@ export class ValidationPool extends Actor { this.setStatus('Closed'); const getVoteValue = ({ stake, lockingTime }) => stake * lockingTime ** params.lockingTimeExponent; - const getTotalValue = (position) => Array.from(this.listVotes(position).values()) + const getTotalValue = (votePosition) => Array.from(this.listVotes( + ({ position }) => position === votePosition, + ).values()) .map(getVoteValue) .reduce((acc, cur) => (acc += cur), 0); @@ -192,8 +199,8 @@ export class ValidationPool extends Actor { }; if (quorumMet) { - this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`); - this.scene.sequence.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`); + this.setStatus(`Resolved - ${votePasses ? 'Won' : 'Lost'}`); + this.scene.sequence.log(`note over ${this.name} : ${votePasses ? 'Win' : 'Lose'}`); this.applyTokenLocking(); await this.distributeReputation(result); // TODO: distribute fees @@ -208,54 +215,40 @@ export class ValidationPool extends Actor { } async distributeReputation({ votePasses }) { - const rewards = new Map(); - const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value); - // TODO: Take tokenLossRatio into account - const getTotalStaked = (position) => Array.from(this.listVotes(position).values()) + const getTotalStaked = (votePosition, excludeSystem = false) => Array.from(this.listVotes( + ({ position, isSystemVote }) => position === votePosition && (!excludeSystem || !isSystemVote), + ).values()) .map(({ stake }) => stake) .reduce((acc, cur) => (acc += cur), 0); - const tokensForWinners = votePasses ? (this.tokens.for + getTotalStaked(false)) - : (this.tokens.against + getTotalStaked(true)); - const winningVotes = this.listVotes(votePasses); + const tokensForWinners = getTotalStaked(!votePasses); + const winningVotes = this.listVotes(({ position, isSystemVote }) => position === votePasses && !isSystemVote); // Reward the winning voters, in proportion to their stakes + const rewards = new Map(); for (const [signingPublicKey, { stake }] of winningVotes) { const { reputationPublicKey } = this.voters.get(signingPublicKey); const reward = (tokensForWinners * stake) / getTotalStaked(votePasses); - addReward(reputationPublicKey, reward); + rewards.set(reputationPublicKey, reward); console.log(`reward for winning voter ${reputationPublicKey}:`, reward); } const awardsFromVoting = Array.from(rewards.values()).reduce((total, value) => total += value, 0); console.log('total awards from voting:', awardsFromVoting); - if (votePasses && !!this.forum) { + if (votePasses) { + const authorReputationPublicKey = this.voters.get(this.authorSigningPublicKey).reputationPublicKey; + const tokensForAuthor = this.tokensMinted * params.stakeForAuthor + rewards.get(authorReputationPublicKey); + + if (votePasses && !!this.forum) { // Recurse through forum to determine reputation effects - const forumReputationEffects = await this.forum.propagateValue(this, this.postId, this.tokensMinted); - for (const [id, value] of forumReputationEffects) { - addReward(id, value); + await this.forum.onValidate( + this.bench, + this, + this.postId, + tokensForAuthor, + ); } - const awardsFromForum = Array.from(forumReputationEffects.values()).reduce((total, value) => total += value, 0); - console.log('total awards from forum:', awardsFromForum); - } - - // Allow for possible attenuation of total value of post, e.g. based on degree of contention - const initialPostValue = this.tokensMinted * params.initialPostValue(); - - // Scale all rewards so that the total is correct - // TODO: Add more precise assertions; otherwise this operation could mask errors. - const currentTotal = Array.from(rewards.values()).reduce((total, value) => total += value, 0); - console.log('total awards before normalization:', currentTotal); - for (const [id, value] of rewards) { - const normalizedValue = (value * initialPostValue) / currentTotal; - console.log(`normalized reward for ${id}: ${value} -> ${normalizedValue}`); - rewards.set(id, normalizedValue); - } - - // Apply computed rewards - for (const [id, value] of rewards) { - this.bench.reputations.addTokens(id, value); } console.log('pool complete'); diff --git a/forum-network/src/classes/vote.js b/forum-network/src/classes/vote.js index 4df9c77..c91fc81 100644 --- a/forum-network/src/classes/vote.js +++ b/forum-network/src/classes/vote.js @@ -1,7 +1,8 @@ export class Vote { - constructor(position, stake, lockingTime) { + constructor(position, stake, lockingTime, isSystemVote = false) { this.position = position; this.stake = stake; this.lockingTime = lockingTime; + this.isSystemVote = isSystemVote; } } diff --git a/forum-network/src/params.js b/forum-network/src/params.js index 4cbb267..9f7b871 100644 --- a/forum-network/src/params.js +++ b/forum-network/src/params.js @@ -1,7 +1,7 @@ const params = { /* Validation Pool parameters */ - mintingRatio: 1, // c1 - stakeForWin: 0.5, // c2 + mintingRatio: () => 1, // c1 + // NOTE: c2 overlaps with c3 and adds excess complexity, so we omit it for now stakeForAuthor: 0.5, // c3 winningRatio: 0.5, // c4 quorum: 0, // c5 diff --git a/forum-network/src/tests/availability.html b/forum-network/src/tests/availability.html index 0ffce02..4e1766a 100644 --- a/forum-network/src/tests/availability.html +++ b/forum-network/src/tests/availability.html @@ -22,9 +22,8 @@ const rootElement = document.getElementById("availability-test"); const rootBox = new Box("rootBox", rootElement).flex(); - const scene = (window.scene = new Scene("Availability test", rootBox).log( - "sequenceDiagram" - )); + const scene = (window.scene = new Scene("Availability test", rootBox)); + scene.withSequenceDiagram(); const experts = (window.experts = []); const newExpert = async () => { diff --git a/forum-network/src/tests/forum.html b/forum-network/src/tests/forum.html index 3d1c7ac..8fa9048 100644 --- a/forum-network/src/tests/forum.html +++ b/forum-network/src/tests/forum.html @@ -28,7 +28,12 @@ scene.withSequenceDiagram(); scene.withFlowchart(); - scene.addDisplayValue("leachingValue").set(params.leachingValue); + 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(" "); const experts = (window.experts = []); @@ -37,14 +42,15 @@ const name = `Expert${index + 1}`; const expert = await new Expert(name, scene).initialize(); experts.push(expert); + // bench.reputations.addTokens(expert.reputationPublicKey, 50); return expert; }; + const forum = (window.forum = new Forum("Forum", scene)); + const bench = (window.bench = new Bench(forum, "Bench", scene)); const expert1 = await newExpert(); const expert2 = await newExpert(); const expert3 = await newExpert(); - const forum = (window.forum = new Forum("Forum", scene)); - const bench = (window.bench = new Bench(forum, "Bench", scene)); const updateDisplayValues = async () => { for (const expert of experts) { @@ -74,6 +80,7 @@ fee: 10, duration: 1000, tokenLossRatio: 1, + // authorStake: 10, } ); await updateDisplayValuesAndDelay(1000);