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?
---
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.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);

View File

@ -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;
});
}

View File

@ -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);

View File

@ -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;

View File

@ -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) {
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');

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 () => {

View File

@ -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);