Fixup forum logic
This commit is contained in:
parent
7c7d01aa91
commit
6cc1c25b37
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue