Fixup forum logic

This commit is contained in:
Ladd Hoffman 2023-01-08 20:19:33 -06:00
parent 7c7d01aa91
commit 6cc1c25b37
10 changed files with 96 additions and 78 deletions

View File

@ -85,3 +85,15 @@ Token loss ratio
--- ---
parameter q_4 -- what is c_n? 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.

View File

@ -4,7 +4,7 @@ export class Actor {
this.scene = scene; this.scene = scene;
this.callbacks = new Map(); this.callbacks = new Map();
this.status = this.scene.addDisplayValue(`${this.name} status`); this.status = this.scene.addDisplayValue(`${this.name} status`);
this.status.set('New'); this.status.set('Created');
this.values = new Map(); this.values = new Map();
this.active = 0; this.active = 0;
this.scene.registerActor(this); this.scene.registerActor(this);

View File

@ -25,12 +25,11 @@ export class Bench extends Actor {
} }
listActiveVoters() { listActiveVoters() {
const now = new Date();
const thresholdSet = !!params.activeVoterThreshold;
return Array.from(this.voters.values()).filter((voter) => { return Array.from(this.voters.values()).filter((voter) => {
const hasVoted = !!voter.dateLastVote; const hasVoted = !!voter.dateLastVote;
const withinThreshold = now - voter.dateLastVote >= params.activeVoterThreshold; const withinThreshold = !params.activeVoterThreshold
return hasVoted && (!thresholdSet || withinThreshold); || new Date() - voter.dateLastVote >= params.activeVoterThreshold;
return hasVoted && withinThreshold;
}); });
} }

View File

@ -60,7 +60,7 @@ export class Expert extends Actor {
await this.actions.initiateValidationPool.log( await this.actions.initiateValidationPool.log(
this, this,
bench, bench,
`(fee: ${poolOptions.fee})`, `(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStake ?? 0})`,
); );
const pool = await bench.initiateValidationPool(poolOptions); const pool = await bench.initiateValidationPool(poolOptions);
this.validationPools.set(pool.id, poolOptions); this.validationPools.set(pool.id, poolOptions);

View File

@ -62,8 +62,7 @@ export class Forum extends Actor {
return this.posts.getVerticesData(); return this.posts.getVerticesData();
} }
async setPostValue(postId, value) { async setPostValue(post, value) {
const post = this.getPost(postId);
post.value = value; post.value = value;
await post.setValue('value', value); await post.setValue('value', value);
if (this.scene.flowchart) { if (this.scene.flowchart) {
@ -71,26 +70,36 @@ export class Forum extends Actor {
} }
} }
getPostValue(postId) {
const post = this.getPost(postId);
return post.value;
}
getTotalValue() { getTotalValue() {
return this.getPosts().reduce((total, { value }) => total += value, 0); return this.getPosts().reduce((total, { value }) => total += value, 0);
} }
async propagateValue(fromActor, postId, increment, depth = 0) { async onValidate(bench, pool, postId, initialValue) {
if (depth === 0 && this.scene.flowchart) { initialValue *= params.initialPostValue();
const randomId = CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8);
this.scene.flowchart.log(`${postId}_initial_value_${randomId}[${increment}] -- initial value --> ${postId}`); 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 []; 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 adjustedIncrement = increment * (1 - params.leachingValue * post.totalCitationWeight);
const rewards = new Map(); const rewards = new Map();
@ -102,10 +111,7 @@ export class Forum extends Actor {
}; };
// Increment the value of the post // Increment the value of the post
// Apply leaching value await this.setPostValue(post, post.value + adjustedIncrement);
const currentValue = this.getPostValue(postId);
const newValue = currentValue + adjustedIncrement;
await this.setPostValue(postId, newValue);
// Award reputation to post author // Award reputation to post author
console.log('reward for post author', post.authorPublicKey, adjustedIncrement); 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 // Recursively distribute reputation to citations, according to weights
for (const { postId: citedPostId, weight } of post.citations) { 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; return rewards;

View File

@ -68,11 +68,7 @@ export class ValidationPool extends Actor {
this.duration = duration; this.duration = duration;
this.tokenLossRatio = tokenLossRatio; this.tokenLossRatio = tokenLossRatio;
this.contentiousDebate = contentiousDebate; this.contentiousDebate = contentiousDebate;
this.tokensMinted = fee * params.mintingRatio; this.tokensMinted = fee * params.mintingRatio();
this.tokens = {
for: this.tokensMinted * params.stakeForWin,
against: this.tokensMinted * (1 - params.stakeForWin),
};
// Tokens minted "for" the post go toward stake of author voting for their own post. // 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. // Also, author can provide additional stakes, e.g. availability stakes for work evidence post.
this.castVote(signingPublicKey, { this.castVote(signingPublicKey, {
@ -80,12 +76,21 @@ export class ValidationPool extends Actor {
stake: this.tokensMinted * params.stakeForAuthor + authorStake, stake: this.tokensMinted * params.stakeForAuthor + authorStake,
anonymous, anonymous,
}); });
this.castVote(undefined, {
position: false,
stake: this.tokensMinted * (1 - params.stakeForAuthor),
isSystemVote: true,
});
} }
async castVote(signingPublicKey, { 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) { if (this.state === ValidationPoolStates.CLOSED) {
throw new Error(`Validation pool ${this.id} is 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( return new Map(
Array.from(this.votes).filter( Array.from(this.votes).filter(
([_, vote]) => vote.position === position, ([_, vote]) => filter(vote),
), ),
); );
} }
@ -175,7 +180,9 @@ export class ValidationPool extends Actor {
this.setStatus('Closed'); this.setStatus('Closed');
const getVoteValue = ({ stake, lockingTime }) => stake * lockingTime ** params.lockingTimeExponent; 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) .map(getVoteValue)
.reduce((acc, cur) => (acc += cur), 0); .reduce((acc, cur) => (acc += cur), 0);
@ -192,8 +199,8 @@ export class ValidationPool extends Actor {
}; };
if (quorumMet) { if (quorumMet) {
this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`); this.setStatus(`Resolved - ${votePasses ? 'Won' : 'Lost'}`);
this.scene.sequence.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`); this.scene.sequence.log(`note over ${this.name} : ${votePasses ? 'Win' : 'Lose'}`);
this.applyTokenLocking(); this.applyTokenLocking();
await this.distributeReputation(result); await this.distributeReputation(result);
// TODO: distribute fees // TODO: distribute fees
@ -208,54 +215,40 @@ export class ValidationPool extends Actor {
} }
async distributeReputation({ votePasses }) { async distributeReputation({ votePasses }) {
const rewards = new Map();
const addReward = (id, value) => rewards.set(id, (rewards.get(id) ?? 0) + value);
// TODO: Take tokenLossRatio into account // 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) .map(({ stake }) => stake)
.reduce((acc, cur) => (acc += cur), 0); .reduce((acc, cur) => (acc += cur), 0);
const tokensForWinners = votePasses ? (this.tokens.for + getTotalStaked(false)) const tokensForWinners = getTotalStaked(!votePasses);
: (this.tokens.against + getTotalStaked(true)); const winningVotes = this.listVotes(({ position, isSystemVote }) => position === votePasses && !isSystemVote);
const winningVotes = this.listVotes(votePasses);
// Reward the winning voters, in proportion to their stakes // Reward the winning voters, in proportion to their stakes
const rewards = new Map();
for (const [signingPublicKey, { stake }] of winningVotes) { for (const [signingPublicKey, { stake }] of winningVotes) {
const { reputationPublicKey } = this.voters.get(signingPublicKey); const { reputationPublicKey } = this.voters.get(signingPublicKey);
const reward = (tokensForWinners * stake) / getTotalStaked(votePasses); const reward = (tokensForWinners * stake) / getTotalStaked(votePasses);
addReward(reputationPublicKey, reward); rewards.set(reputationPublicKey, reward);
console.log(`reward for winning voter ${reputationPublicKey}:`, reward); console.log(`reward for winning voter ${reputationPublicKey}:`, reward);
} }
const awardsFromVoting = Array.from(rewards.values()).reduce((total, value) => total += value, 0); const awardsFromVoting = Array.from(rewards.values()).reduce((total, value) => total += value, 0);
console.log('total awards from voting:', awardsFromVoting); 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 // Recurse through forum to determine reputation effects
const forumReputationEffects = await this.forum.propagateValue(this, this.postId, this.tokensMinted); await this.forum.onValidate(
for (const [id, value] of forumReputationEffects) { this.bench,
addReward(id, value); 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'); console.log('pool complete');

View File

@ -1,7 +1,8 @@
export class Vote { export class Vote {
constructor(position, stake, lockingTime) { constructor(position, stake, lockingTime, isSystemVote = false) {
this.position = position; this.position = position;
this.stake = stake; this.stake = stake;
this.lockingTime = lockingTime; this.lockingTime = lockingTime;
this.isSystemVote = isSystemVote;
} }
} }

View File

@ -1,7 +1,7 @@
const params = { const params = {
/* Validation Pool parameters */ /* Validation Pool parameters */
mintingRatio: 1, // c1 mintingRatio: () => 1, // c1
stakeForWin: 0.5, // c2 // NOTE: c2 overlaps with c3 and adds excess complexity, so we omit it for now
stakeForAuthor: 0.5, // c3 stakeForAuthor: 0.5, // c3
winningRatio: 0.5, // c4 winningRatio: 0.5, // c4
quorum: 0, // c5 quorum: 0, // c5

View File

@ -22,9 +22,8 @@
const rootElement = document.getElementById("availability-test"); const rootElement = document.getElementById("availability-test");
const rootBox = new Box("rootBox", rootElement).flex(); const rootBox = new Box("rootBox", rootElement).flex();
const scene = (window.scene = new Scene("Availability test", rootBox).log( const scene = (window.scene = new Scene("Availability test", rootBox));
"sequenceDiagram" scene.withSequenceDiagram();
));
const experts = (window.experts = []); const experts = (window.experts = []);
const newExpert = async () => { const newExpert = async () => {

View File

@ -28,7 +28,12 @@
scene.withSequenceDiagram(); scene.withSequenceDiagram();
scene.withFlowchart(); 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(" "); scene.addDisplayValue(" ");
const experts = (window.experts = []); const experts = (window.experts = []);
@ -37,14 +42,15 @@
const name = `Expert${index + 1}`; const name = `Expert${index + 1}`;
const expert = await new Expert(name, scene).initialize(); const expert = await new Expert(name, scene).initialize();
experts.push(expert); experts.push(expert);
// bench.reputations.addTokens(expert.reputationPublicKey, 50);
return expert; return expert;
}; };
const forum = (window.forum = new Forum("Forum", scene));
const bench = (window.bench = new Bench(forum, "Bench", scene));
const expert1 = await newExpert(); const expert1 = await newExpert();
const expert2 = await newExpert(); const expert2 = await newExpert();
const expert3 = 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 () => { const updateDisplayValues = async () => {
for (const expert of experts) { for (const expert of experts) {
@ -74,6 +80,7 @@
fee: 10, fee: 10,
duration: 1000, duration: 1000,
tokenLossRatio: 1, tokenLossRatio: 1,
// authorStake: 10,
} }
); );
await updateDisplayValuesAndDelay(1000); await updateDisplayValuesAndDelay(1000);