Reputation tokens
This commit is contained in:
parent
cd5fce820c
commit
8982ac610f
|
@ -2,14 +2,11 @@ import { Action } from './action.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from './actor.js';
|
||||||
|
|
||||||
class Worker {
|
class Worker {
|
||||||
stake = 0;
|
constructor(tokenId) {
|
||||||
|
this.tokenId = tokenId;
|
||||||
available = true;
|
this.stakeAmount = 0;
|
||||||
|
this.available = true;
|
||||||
assignedRequestId = null;
|
this.assignedRequestId = null;
|
||||||
|
|
||||||
constructor(reputationPublicKey) {
|
|
||||||
this.reputationPublicKey = reputationPublicKey;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,16 +25,16 @@ export class Availability extends Actor {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
register(reputationPublicKey, stake, __duration) {
|
register({ stakeAmount, tokenId }) {
|
||||||
// TODO: expire after duration
|
// TODO: expire after duration
|
||||||
// ? Is a particular stake amount required?
|
// ? Is a particular stake amount required?
|
||||||
const worker = this.workers.get(reputationPublicKey) ?? new Worker(reputationPublicKey);
|
const worker = this.workers.get(tokenId) ?? new Worker(tokenId);
|
||||||
if (!worker.available) {
|
if (!worker.available) {
|
||||||
throw new Error('Worker is already registered and busy. Can not increase stake.');
|
throw new Error('Worker is already registered and busy. Can not increase stake.');
|
||||||
}
|
}
|
||||||
worker.stake += stake;
|
worker.stakeAmount += stakeAmount;
|
||||||
// TODO: Interact with Bench contract to encumber reputation?
|
// TODO: Interact with Bench contract to encumber reputation?
|
||||||
this.workers.set(reputationPublicKey, worker);
|
this.workers.set(tokenId, worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// unregister() { }
|
// unregister() { }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from './actor.js';
|
||||||
import { Reputations } from './reputation.js';
|
|
||||||
import { ValidationPool } from './validation-pool.js';
|
import { ValidationPool } from './validation-pool.js';
|
||||||
import params from '../params.js';
|
import params from '../params.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from './action.js';
|
||||||
|
import { ReputationTokenContract } from './reputation-token.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purpose: Keep track of reputation holders
|
* Purpose: Keep track of reputation holders
|
||||||
|
@ -13,7 +13,7 @@ export class Bench extends Actor {
|
||||||
this.forum = forum;
|
this.forum = forum;
|
||||||
this.validationPools = new Map();
|
this.validationPools = new Map();
|
||||||
this.voters = new Map();
|
this.voters = new Map();
|
||||||
this.reputations = new Reputations();
|
this.reputation = new ReputationTokenContract();
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
createValidationPool: new Action('create validation pool', scene),
|
createValidationPool: new Action('create validation pool', scene),
|
||||||
|
@ -33,53 +33,22 @@ export class Bench extends Actor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTotalReputation() {
|
getActiveReputation() {
|
||||||
return this.reputations.getTotal();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalAvailableReputation() {
|
|
||||||
return this.reputations.getTotalAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalActiveReputation() {
|
|
||||||
return this.listActiveVoters()
|
return this.listActiveVoters()
|
||||||
.map(({ reputationPublicKey }) => this.reputations.getTokens(reputationPublicKey))
|
.map(({ reputationPublicKey }) => this.reputation.valueOwnedBy(reputationPublicKey))
|
||||||
.reduce((acc, cur) => (acc += cur), 0);
|
.reduce((acc, cur) => (acc += cur), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTotalActiveAvailableReputation() {
|
getActiveAvailableReputation() {
|
||||||
return this.listActiveVoters()
|
return this.listActiveVoters()
|
||||||
.map(({ reputationPublicKey }) => this.reputations.getAvailableTokens(reputationPublicKey))
|
.map(({ reputationPublicKey }) => this.reputation.availableValueOwnedBy(reputationPublicKey))
|
||||||
.reduce((acc, cur) => (acc += cur), 0);
|
.reduce((acc, cur) => (acc += cur), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initiateValidationPool({
|
async initiateValidationPool(poolOptions) {
|
||||||
postId,
|
|
||||||
fee,
|
|
||||||
duration,
|
|
||||||
tokenLossRatio,
|
|
||||||
contentiousDebate,
|
|
||||||
signingPublicKey,
|
|
||||||
authorStake,
|
|
||||||
anonymous,
|
|
||||||
}) {
|
|
||||||
const validationPoolNumber = this.validationPools.size + 1;
|
const validationPoolNumber = this.validationPools.size + 1;
|
||||||
const validationPool = new ValidationPool(
|
const name = `Pool${validationPoolNumber}`;
|
||||||
this,
|
const validationPool = new ValidationPool(this, this.forum, poolOptions, name, this.scene);
|
||||||
this.forum,
|
|
||||||
{
|
|
||||||
postId,
|
|
||||||
fee,
|
|
||||||
duration,
|
|
||||||
tokenLossRatio,
|
|
||||||
contentiousDebate,
|
|
||||||
signingPublicKey,
|
|
||||||
authorStake,
|
|
||||||
anonymous,
|
|
||||||
},
|
|
||||||
`Pool${validationPoolNumber}`,
|
|
||||||
this.scene,
|
|
||||||
);
|
|
||||||
this.validationPools.set(validationPool.id, validationPool);
|
this.validationPools.set(validationPool.id, validationPool);
|
||||||
await this.actions.createValidationPool.log(this, validationPool);
|
await this.actions.createValidationPool.log(this, validationPool);
|
||||||
validationPool.activate();
|
validationPool.activate();
|
||||||
|
|
|
@ -66,9 +66,9 @@ export class Business extends Actor {
|
||||||
fee: request.fee,
|
fee: request.fee,
|
||||||
duration,
|
duration,
|
||||||
tokenLossRatio,
|
tokenLossRatio,
|
||||||
signingPublicKey: reputationPublicKey,
|
reputationPublicKey,
|
||||||
anonymous: false,
|
authorStakeAmount: this.worker.stakeAmount,
|
||||||
authorStake: this.worker.stake,
|
tokenId: this.worker.tokenId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// When the validation pool concludes,
|
// When the validation pool concludes,
|
||||||
|
|
|
@ -56,6 +56,7 @@ export class ERC721 /* is ERC165 */ {
|
||||||
if (!owner) {
|
if (!owner) {
|
||||||
throw new Error('ERC721: invalid token ID');
|
throw new Error('ERC721: invalid token ID');
|
||||||
}
|
}
|
||||||
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
transferFrom(from, to, tokenId) {
|
transferFrom(from, to, tokenId) {
|
||||||
|
@ -65,6 +66,7 @@ export class ERC721 /* is ERC165 */ {
|
||||||
}
|
}
|
||||||
this.incrementBalance(from, -1);
|
this.incrementBalance(from, -1);
|
||||||
this.incrementBalance(to, 1);
|
this.incrementBalance(to, 1);
|
||||||
|
this.owners.set(tokenId, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Enable or disable approval for a third party ("operator") to manage
|
/// @notice Enable or disable approval for a third party ("operator") to manage
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import { Actor } from './actor.js';
|
|
||||||
import { Action } from './action.js';
|
import { Action } from './action.js';
|
||||||
import { PostMessage } from './message.js';
|
import { PostMessage } from './message.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from './crypto.js';
|
||||||
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
|
|
||||||
export class Expert extends Actor {
|
export class Expert extends ReputationHolder {
|
||||||
constructor(name, scene) {
|
constructor(name, scene) {
|
||||||
super(name, scene);
|
super(undefined, name, scene);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
submitPostViaNetwork: new Action('submit post via network', scene),
|
submitPostViaNetwork: new Action('submit post via network', scene),
|
||||||
submitPost: new Action('submit post', scene),
|
submitPost: new Action('submit post', scene),
|
||||||
initiateValidationPool: new Action('initiate validation pool', scene),
|
initiateValidationPool: new Action('initiate validation pool', scene),
|
||||||
stake: new Action('stake on post', scene),
|
stake: new Action('stake on post', scene),
|
||||||
revealIdentity: new Action('reveal identity', scene),
|
|
||||||
registerAvailability: new Action('register availability', scene),
|
registerAvailability: new Action('register availability', scene),
|
||||||
getAssignedWork: new Action('get assigned work', scene),
|
getAssignedWork: new Action('get assigned work', scene),
|
||||||
submitWork: new Action('submit work evidence', scene),
|
submitWork: new Action('submit work evidence', scene),
|
||||||
};
|
};
|
||||||
this.validationPools = new Map();
|
this.validationPools = new Map();
|
||||||
|
this.tokens = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
@ -46,60 +46,42 @@ export class Expert extends Actor {
|
||||||
await this.actions.submitPost.log(this, forum);
|
await this.actions.submitPost.log(this, forum);
|
||||||
const postId = await forum.addPost(this.reputationPublicKey, postContent);
|
const postId = await forum.addPost(this.reputationPublicKey, postContent);
|
||||||
const pool = await this.initiateValidationPool(bench, { ...poolOptions, postId, anonymous: false });
|
const pool = await this.initiateValidationPool(bench, { ...poolOptions, postId, anonymous: false });
|
||||||
|
this.tokens.push(pool.tokenId);
|
||||||
return { postId, pool };
|
return { postId, pool };
|
||||||
}
|
}
|
||||||
|
|
||||||
async initiateValidationPool(bench, poolOptions) {
|
async initiateValidationPool(bench, poolOptions) {
|
||||||
// For now, directly call bench.initiateValidationPool();
|
// For now, directly call bench.initiateValidationPool();
|
||||||
if (poolOptions.anonymous) {
|
poolOptions.reputationPublicKey = this.reputationPublicKey;
|
||||||
const signingKey = await CryptoUtil.generateAsymmetricKey();
|
|
||||||
poolOptions.signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey);
|
|
||||||
} else {
|
|
||||||
poolOptions.signingPublicKey = this.reputationPublicKey;
|
|
||||||
}
|
|
||||||
await this.actions.initiateValidationPool.log(
|
await this.actions.initiateValidationPool.log(
|
||||||
this,
|
this,
|
||||||
bench,
|
bench,
|
||||||
`(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStake ?? 0})`,
|
`(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStakeAmount ?? 0})`,
|
||||||
);
|
);
|
||||||
const pool = await bench.initiateValidationPool(poolOptions);
|
const pool = await bench.initiateValidationPool(poolOptions);
|
||||||
|
this.tokens.push(pool.tokenId);
|
||||||
this.validationPools.set(pool.id, poolOptions);
|
this.validationPools.set(pool.id, poolOptions);
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
async stake(validationPool, {
|
async stake(validationPool, {
|
||||||
position, amount, lockingTime, anonymous = false,
|
position, amount, lockingTime,
|
||||||
}) {
|
}) {
|
||||||
let signingPublicKey;
|
|
||||||
if (anonymous) {
|
|
||||||
const signingKey = await CryptoUtil.generateAsymmetricKey();
|
|
||||||
signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey);
|
|
||||||
this.validationPools.set(validationPool.id, { signingPublicKey });
|
|
||||||
} else {
|
|
||||||
signingPublicKey = this.reputationPublicKey;
|
|
||||||
}
|
|
||||||
// TODO: encrypt stake
|
// TODO: encrypt stake
|
||||||
// TODO: sign message
|
// TODO: sign message
|
||||||
await this.actions.stake.log(
|
await this.actions.stake.log(
|
||||||
this,
|
this,
|
||||||
validationPool,
|
validationPool,
|
||||||
`(${position ? 'for' : 'against'}, stake: ${amount}, anonymous: ${anonymous})`,
|
`(${position ? 'for' : 'against'}, stake: ${amount})`,
|
||||||
);
|
);
|
||||||
return validationPool.stake(signingPublicKey, {
|
return validationPool.stake(this, {
|
||||||
position, amount, lockingTime, anonymous,
|
position, amount, lockingTime, tokenId: this.tokens[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async revealIdentity(validationPool) {
|
async registerAvailability(availability, stakeAmount) {
|
||||||
const { signingPublicKey } = this.validationPools.get(validationPool.id);
|
await this.actions.registerAvailability.log(this, availability, `(stake: ${stakeAmount})`);
|
||||||
// TODO: sign message
|
await availability.register({ stakeAmount, tokenId: this.tokens[0].id });
|
||||||
await this.actions.revealIdentity.log(this, validationPool);
|
|
||||||
validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerAvailability(availability, stake) {
|
|
||||||
await this.actions.registerAvailability.log(this, availability, `(stake: ${stake})`);
|
|
||||||
await availability.register(this.reputationPublicKey, stake);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAssignedWork(availability, business) {
|
async getAssignedWork(availability, business) {
|
||||||
|
|
|
@ -3,13 +3,14 @@ import { Graph } from './graph.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from './action.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from './crypto.js';
|
||||||
import params from '../params.js';
|
import params from '../params.js';
|
||||||
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
|
|
||||||
class Post extends Actor {
|
class Post extends Actor {
|
||||||
constructor(forum, authorPublicKey, postContent) {
|
constructor(forum, authorPublicKey, postContent) {
|
||||||
const index = forum.posts.countVertices();
|
const index = forum.posts.countVertices();
|
||||||
const name = `Post${index + 1}`;
|
const name = `Post${index + 1}`;
|
||||||
super(name, forum.scene);
|
super(name, forum.scene);
|
||||||
this.id = postContent.id ?? CryptoUtil.randomUUID();
|
this.id = postContent.id ?? `post_${CryptoUtil.randomUUID()}`;
|
||||||
this.authorPublicKey = authorPublicKey;
|
this.authorPublicKey = authorPublicKey;
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
this.citations = postContent.citations;
|
this.citations = postContent.citations;
|
||||||
|
@ -28,9 +29,10 @@ class Post extends Actor {
|
||||||
/**
|
/**
|
||||||
* Purpose: Maintain a directed, acyclic, weighted graph of posts referencing other posts
|
* Purpose: Maintain a directed, acyclic, weighted graph of posts referencing other posts
|
||||||
*/
|
*/
|
||||||
export class Forum extends Actor {
|
export class Forum extends ReputationHolder {
|
||||||
constructor(name, scene) {
|
constructor(name, scene) {
|
||||||
super(name, scene);
|
super(`forum_${CryptoUtil.randomUUID()}`, name, scene);
|
||||||
|
this.id = this.reputationPublicKey;
|
||||||
this.posts = new Graph(scene);
|
this.posts = new Graph(scene);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
addPost: new Action('add post', scene),
|
addPost: new Action('add post', scene),
|
||||||
|
@ -74,8 +76,10 @@ export class Forum extends Actor {
|
||||||
return this.getPosts().reduce((total, { value }) => total += value, 0);
|
return this.getPosts().reduce((total, { value }) => total += value, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onValidate(bench, pool, postId, initialValue) {
|
async onValidate({
|
||||||
initialValue *= params.initialPostValue();
|
bench, pool, postId, tokenId,
|
||||||
|
}) {
|
||||||
|
const initialValue = bench.reputation.valueOf(tokenId);
|
||||||
|
|
||||||
if (this.scene.flowchart) {
|
if (this.scene.flowchart) {
|
||||||
this.scene.flowchart.log(`${postId}_initial_value[${initialValue}] -- initial value --> ${postId}`);
|
this.scene.flowchart.log(`${postId}_initial_value[${initialValue}] -- initial value --> ${postId}`);
|
||||||
|
@ -84,14 +88,21 @@ export class Forum extends Actor {
|
||||||
const post = this.getPost(postId);
|
const post = this.getPost(postId);
|
||||||
post.setStatus('Validated');
|
post.setStatus('Validated');
|
||||||
|
|
||||||
|
// Store a reference to the reputation token associated with this post,
|
||||||
|
// so that its value can be updated by future validated posts.
|
||||||
|
post.tokenId = tokenId;
|
||||||
|
|
||||||
// Compute rewards
|
// Compute rewards
|
||||||
const rewardsAccumulator = new Map();
|
const rewardsAccumulator = new Map();
|
||||||
await this.propagateValue(rewardsAccumulator, pool, post, initialValue);
|
await this.propagateValue(rewardsAccumulator, pool, post, initialValue);
|
||||||
|
|
||||||
// Apply computed rewards
|
// Apply computed rewards to update values of tokens
|
||||||
for (const [id, value] of rewardsAccumulator) {
|
for (const [id, value] of rewardsAccumulator) {
|
||||||
bench.reputations.addTokens(id, value);
|
bench.reputation.transferValueFrom(post.tokenId, id, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transfer ownership of the minted/staked token, from the forum to the post author
|
||||||
|
bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async propagateValue(rewardsAccumulator, fromActor, post, increment, depth = 0) {
|
async propagateValue(rewardsAccumulator, fromActor, post, increment, depth = 0) {
|
||||||
|
@ -123,7 +134,7 @@ export class Forum extends Actor {
|
||||||
const appliedIncrement = newValue - post.value;
|
const appliedIncrement = newValue - post.value;
|
||||||
|
|
||||||
// Award reputation to post author
|
// Award reputation to post author
|
||||||
rewardsAccumulator.set(post.authorPublicKey, appliedIncrement);
|
rewardsAccumulator.set(post.tokenId, appliedIncrement);
|
||||||
|
|
||||||
// Increment the value of the post
|
// Increment the value of the post
|
||||||
await this.setPostValue(post, newValue);
|
await this.setPostValue(post, newValue);
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Actor } from './actor.js';
|
||||||
|
|
||||||
|
export class ReputationHolder extends Actor {
|
||||||
|
constructor(reputationPublicKey, name, scene) {
|
||||||
|
super(name, scene);
|
||||||
|
this.reputationPublicKey = reputationPublicKey;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,106 @@
|
||||||
import { ERC721 } from './erc721.js';
|
import { ERC721 } from './erc721.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from './crypto.js';
|
||||||
|
|
||||||
export class ReputationToken extends ERC721 {
|
const EPSILON = 2.23e-16;
|
||||||
|
|
||||||
|
class Lock {
|
||||||
|
constructor(tokenId, amount, duration) {
|
||||||
|
this.dateCreated = new Date();
|
||||||
|
this.tokenId = tokenId;
|
||||||
|
this.amount = amount;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReputationTokenContract extends ERC721 {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Reputation', 'REP');
|
super('Reputation', 'REP');
|
||||||
this.histories = new Map(); // token id --> {increment, context (i.e. validation pool id)}
|
this.histories = new Map(); // token id --> {increment, context (i.e. validation pool id)}
|
||||||
this.values = new Map(); // token id --> current value
|
this.values = new Map(); // token id --> current value
|
||||||
|
this.locks = new Set(); // {tokenId, amount, start, duration}
|
||||||
}
|
}
|
||||||
|
|
||||||
mint(to, value, context) {
|
mint(to, value, context) {
|
||||||
const tokenId = CryptoUtil.randomUUID();
|
const tokenId = `token_${CryptoUtil.randomUUID()}`;
|
||||||
super.mint(to, tokenId);
|
super.mint(to, tokenId);
|
||||||
this.values.set(tokenId, value);
|
this.values.set(tokenId, value);
|
||||||
this.histories.set(tokenId, [{ increment: value, context }]);
|
this.histories.set(tokenId, [{ increment: value, context }]);
|
||||||
|
return tokenId;
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementValue(tokenId, increment, context) {
|
incrementValue(tokenId, increment, context) {
|
||||||
const value = this.values.get(tokenId);
|
const value = this.values.get(tokenId);
|
||||||
const newValue = value + increment;
|
const newValue = value + increment;
|
||||||
const history = this.histories.get(tokenId);
|
const history = this.histories.get(tokenId) || [];
|
||||||
if (newValue < 0) {
|
|
||||||
throw new Error('Token value can not become negative');
|
if (newValue < -EPSILON) {
|
||||||
|
throw new Error(`Token value can not become negative. Attempted to set value = ${newValue}`);
|
||||||
}
|
}
|
||||||
this.values.set(tokenId, newValue);
|
this.values.set(tokenId, newValue);
|
||||||
history.push({ increment, context });
|
history.push({ increment, context });
|
||||||
|
this.histories.set(tokenId, history);
|
||||||
|
}
|
||||||
|
|
||||||
|
transferValueFrom(fromTokenId, toTokenId, amount) {
|
||||||
|
const sourceAvailable = this.availableValueOf(fromTokenId);
|
||||||
|
const targetAvailable = this.availableValueOf(toTokenId);
|
||||||
|
if (sourceAvailable < amount - EPSILON) {
|
||||||
|
throw new Error('Token value transfer: source has insufficient available value. '
|
||||||
|
+ `Needs ${amount}; has ${sourceAvailable}.`);
|
||||||
|
}
|
||||||
|
if (targetAvailable < -amount + EPSILON) {
|
||||||
|
throw new Error('Token value transfer: target has insufficient available value. '
|
||||||
|
+ `Needs ${-amount}; has ${targetAvailable}.`);
|
||||||
|
}
|
||||||
|
this.incrementValue(fromTokenId, -amount);
|
||||||
|
this.incrementValue(toTokenId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock(tokenId, amount, duration) {
|
||||||
|
const lock = new Lock(tokenId, amount, duration);
|
||||||
|
this.locks.add(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
historyOf(tokenId) {
|
||||||
|
return this.histories.get(tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
valueOf(tokenId) {
|
||||||
|
return this.values.get(tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
availableValueOf(tokenId) {
|
||||||
|
const amountLocked = Array.from(this.locks.values())
|
||||||
|
.filter(({ tokenId: lockTokenId }) => lockTokenId === tokenId)
|
||||||
|
.filter(({ dateCreated, duration }) => new Date() - dateCreated < duration)
|
||||||
|
.reduce((total, { amount }) => total += amount, 0);
|
||||||
|
|
||||||
|
return this.valueOf(tokenId) - amountLocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueOwnedBy(ownerId) {
|
||||||
|
return Array.from(this.owners.entries())
|
||||||
|
.filter(([__, owner]) => owner === ownerId)
|
||||||
|
.map(([tokenId, __]) => this.valueOf(tokenId))
|
||||||
|
.reduce((total, value) => total += value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
availableValueOwnedBy(ownerId) {
|
||||||
|
return Array.from(this.owners.entries())
|
||||||
|
.filter(([__, owner]) => owner === ownerId)
|
||||||
|
.map(([tokenId, __]) => this.availableValueOf(tokenId))
|
||||||
|
.reduce((total, value) => total += value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotal() {
|
||||||
|
return Array.from(this.values.values()).reduce((total, value) => total += value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalAvailable() {
|
||||||
|
const amountLocked = Array.from(this.locks.values())
|
||||||
|
.filter(({ dateCreated, duration }) => new Date() - dateCreated < duration)
|
||||||
|
.reduce((total, { amount }) => total += amount, 0);
|
||||||
|
|
||||||
|
return this.getTotal() - amountLocked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import params from '../params.js';
|
import params from '../params.js';
|
||||||
|
|
||||||
export class Stake {
|
export class Stake {
|
||||||
constructor(position, amount, lockingTime) {
|
constructor({
|
||||||
|
tokenId, position, amount, lockingTime,
|
||||||
|
}) {
|
||||||
|
this.tokenId = tokenId;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.lockingTime = lockingTime;
|
this.lockingTime = lockingTime;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from './crypto.js';
|
||||||
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
import { Stake } from './stake.js';
|
import { Stake } from './stake.js';
|
||||||
import { Voter } from './voter.js';
|
import { Voter } from './voter.js';
|
||||||
import { Actor } from './actor.js';
|
|
||||||
import params from '../params.js';
|
import params from '../params.js';
|
||||||
|
|
||||||
const ValidationPoolStates = Object.freeze({
|
const ValidationPoolStates = Object.freeze({
|
||||||
|
@ -13,24 +13,24 @@ const ValidationPoolStates = Object.freeze({
|
||||||
/**
|
/**
|
||||||
* Purpose: Enable voting
|
* Purpose: Enable voting
|
||||||
*/
|
*/
|
||||||
export class ValidationPool extends Actor {
|
export class ValidationPool extends ReputationHolder {
|
||||||
constructor(
|
constructor(
|
||||||
bench,
|
bench,
|
||||||
forum,
|
forum,
|
||||||
{
|
{
|
||||||
postId,
|
postId,
|
||||||
signingPublicKey,
|
reputationPublicKey,
|
||||||
fee,
|
fee,
|
||||||
duration,
|
duration,
|
||||||
tokenLossRatio,
|
tokenLossRatio,
|
||||||
contentiousDebate = false,
|
contentiousDebate = false,
|
||||||
authorStake = 0,
|
authorStakeAmount = 0,
|
||||||
anonymous = true,
|
|
||||||
},
|
},
|
||||||
name,
|
name,
|
||||||
scene,
|
scene,
|
||||||
) {
|
) {
|
||||||
super(name, scene);
|
super(`pool_${CryptoUtil.randomUUID()}`, name, scene);
|
||||||
|
this.id = this.reputationPublicKey;
|
||||||
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
||||||
if (
|
if (
|
||||||
!contentiousDebate
|
!contentiousDebate
|
||||||
|
@ -58,60 +58,29 @@ export class ValidationPool extends Actor {
|
||||||
this.postId = postId;
|
this.postId = postId;
|
||||||
this.state = ValidationPoolStates.OPEN;
|
this.state = ValidationPoolStates.OPEN;
|
||||||
this.setStatus('Open');
|
this.setStatus('Open');
|
||||||
this.stakes = new Map();
|
this.stakes = new Set();
|
||||||
this.voters = new Map();
|
|
||||||
this.id = CryptoUtil.randomUUID();
|
|
||||||
this.dateStart = new Date();
|
this.dateStart = new Date();
|
||||||
this.authorSigningPublicKey = signingPublicKey;
|
this.authorReputationPublicKey = reputationPublicKey;
|
||||||
this.anonymous = anonymous;
|
|
||||||
this.fee = fee;
|
this.fee = fee;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
this.tokenLossRatio = tokenLossRatio;
|
this.tokenLossRatio = tokenLossRatio;
|
||||||
this.contentiousDebate = contentiousDebate;
|
this.contentiousDebate = contentiousDebate;
|
||||||
this.tokensMinted = fee * params.mintingRatio();
|
this.mintedValue = fee * params.mintingRatio();
|
||||||
|
this.tokenId = this.bench.reputation.mint(this.id, this.mintedValue);
|
||||||
// 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.stake(signingPublicKey, {
|
this.stake(this, {
|
||||||
position: true,
|
position: true,
|
||||||
amount: this.tokensMinted * params.stakeForAuthor + authorStake,
|
amount: this.mintedValue * params.stakeForAuthor + authorStakeAmount,
|
||||||
anonymous,
|
tokenId: this.tokenId,
|
||||||
});
|
});
|
||||||
this.stake(this.id, {
|
this.stake(this, {
|
||||||
position: false,
|
position: false,
|
||||||
amount: this.tokensMinted * (1 - params.stakeForAuthor),
|
amount: this.mintedValue * (1 - params.stakeForAuthor),
|
||||||
|
tokenId: this.tokenId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async stake(signingPublicKey, {
|
|
||||||
position, amount, lockingTime = 0, anonymous = false,
|
|
||||||
}) {
|
|
||||||
if (this.state === ValidationPoolStates.CLOSED) {
|
|
||||||
throw new Error(`Validation pool ${this.id} is closed`);
|
|
||||||
}
|
|
||||||
if (this.duration && new Date() - this.dateStart > this.duration) {
|
|
||||||
throw new Error(
|
|
||||||
`Validation pool ${this.id} has expired, no new votes may be cast`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const stake = new Stake(position, amount, lockingTime);
|
|
||||||
this.stakes.set(signingPublicKey, stake);
|
|
||||||
console.log('new stake', stake);
|
|
||||||
if (!anonymous) {
|
|
||||||
await this.revealIdentity(signingPublicKey, signingPublicKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async revealIdentity(signingPublicKey, reputationPublicKey) {
|
|
||||||
if (!this.stakes.get(signingPublicKey)) {
|
|
||||||
throw new Error('Must stake before revealing identity');
|
|
||||||
}
|
|
||||||
const voter = this.bench.voters.get(reputationPublicKey)
|
|
||||||
?? new Voter(reputationPublicKey);
|
|
||||||
voter.addVoteRecord(this);
|
|
||||||
this.bench.voters.set(reputationPublicKey, voter);
|
|
||||||
this.voters.set(signingPublicKey, voter);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTokenLossRatio() {
|
getTokenLossRatio() {
|
||||||
if (!this.contentiousDebate) {
|
if (!this.contentiousDebate) {
|
||||||
return this.tokenLossRatio;
|
return this.tokenLossRatio;
|
||||||
|
@ -134,58 +103,79 @@ export class ValidationPool extends Actor {
|
||||||
return stage / (params.contentiousDebate.stages - 1);
|
return stage / (params.contentiousDebate.stages - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyTokenLocking() {
|
|
||||||
// Before evaluating the winning conditions,
|
|
||||||
// we need to make sure any staked tokens are locked for the
|
|
||||||
// specified amounts of time.
|
|
||||||
for (const [
|
|
||||||
signingPublicKey,
|
|
||||||
{ stake, lockingTime },
|
|
||||||
] of this.stakes) {
|
|
||||||
const voter = this.voters.get(signingPublicKey);
|
|
||||||
this.bench.reputations.lockTokens(
|
|
||||||
voter.reputationPublicKey,
|
|
||||||
stake,
|
|
||||||
lockingTime,
|
|
||||||
);
|
|
||||||
// TODO: If there is an exception here, the voter may have voted incorrectly. Consider penalties.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} outcome: null --> all entries. Otherwise filters to position === outcome.
|
* @param {boolean} outcome: null --> all entries. Otherwise filters to position === outcome.
|
||||||
* @param {object} getStakeEntries options
|
|
||||||
* @param {boolean} options.excludeSystem: Whether to exclude votes cast during pool initialization
|
* @param {boolean} options.excludeSystem: Whether to exclude votes cast during pool initialization
|
||||||
* @returns [signingPublicKey, stake][]
|
* @returns stake[]
|
||||||
*/
|
*/
|
||||||
getStakeEntries(outcome, options = {}) {
|
getStakes(outcome, { excludeSystem }) {
|
||||||
const { excludeSystem = false } = options;
|
return Array.from(this.stakes.values())
|
||||||
const entries = Array.from(this.stakes.entries());
|
.filter(({ tokenId }) => !excludeSystem || tokenId !== this.tokenId)
|
||||||
// console.log('entries', entries);
|
.filter(({ position }) => outcome === null || position === outcome);
|
||||||
return entries
|
|
||||||
.filter(([signingPublicKey, __]) => !excludeSystem || signingPublicKey !== this.id)
|
|
||||||
.filter(([__, { position }]) => outcome === null || position === outcome);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} outcome: null --> all entries. Otherwise filters to position === outcome.
|
* @param {boolean} outcome: null --> all entries. Otherwise filters to position === outcome.
|
||||||
* @param {object} getStakeEntries options
|
|
||||||
* @returns number
|
* @returns number
|
||||||
*/
|
*/
|
||||||
getTotalStakedOnPost(outcome, options) {
|
getTotalStakedOnPost(outcome) {
|
||||||
return this.getStakeEntries(outcome, options)
|
return this.getStakes(outcome, { excludeSystem: false })
|
||||||
.map(([__, stake]) => stake.getStakeValue())
|
.map((stake) => stake.getStakeValue())
|
||||||
.reduce((acc, cur) => (acc += cur), 0);
|
.reduce((acc, cur) => (acc += cur), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} outcome: null --> all entries. Otherwise filters to position === outcome.
|
* @param {boolean} outcome: null --> all entries. Otherwise filters to position === outcome.
|
||||||
* @param {object} getStakeEntries options
|
|
||||||
* @returns number
|
* @returns number
|
||||||
*/
|
*/
|
||||||
getTotalValueOfStakesForOutcome(outcome, options) {
|
getTotalValueOfStakesForOutcome(outcome) {
|
||||||
return this.getStakeEntries(outcome, options)
|
return this.getStakes(outcome, { excludeSystem: false })
|
||||||
.reduce((total, [__, { amount }]) => (total += amount), 0);
|
.reduce((total, { amount }) => (total += amount), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This can be handled as a hook on receipt of reputation token transfer
|
||||||
|
async stake(reputationHolder, {
|
||||||
|
tokenId, position, amount, lockingTime = 0,
|
||||||
|
}) {
|
||||||
|
if (this.state === ValidationPoolStates.CLOSED) {
|
||||||
|
throw new Error(`Validation pool ${this.id} is closed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.duration && new Date() - this.dateStart > this.duration) {
|
||||||
|
throw new Error(
|
||||||
|
`Validation pool ${this.id} has expired, no new votes may be cast.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { reputationPublicKey } = reputationHolder;
|
||||||
|
if (reputationPublicKey !== this.bench.reputation.ownerOf(tokenId)) {
|
||||||
|
throw new Error('Reputation may only be staked by its owner!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const stake = new Stake({
|
||||||
|
tokenId, position, amount, lockingTime,
|
||||||
|
});
|
||||||
|
this.stakes.add(stake);
|
||||||
|
|
||||||
|
// Transfer staked amount from the sender to the validation pool
|
||||||
|
this.bench.reputation.transferValueFrom(tokenId, this.tokenId, amount);
|
||||||
|
|
||||||
|
// Keep a record of voters and their votes
|
||||||
|
if (tokenId !== this.tokenId) {
|
||||||
|
const voter = this.bench.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
||||||
|
voter.addVoteRecord(this);
|
||||||
|
this.bench.voters.set(reputationPublicKey, voter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTokenLocking() {
|
||||||
|
// Before evaluating the winning conditions,
|
||||||
|
// we need to make sure any staked tokens are locked for the
|
||||||
|
// specified amounts of time.
|
||||||
|
for (const { tokenId, amount, lockingTime } of this.stakes.values()) {
|
||||||
|
this.bench.reputation.lock(tokenId, amount, lockingTime);
|
||||||
|
// TODO: If there is an exception here, the voter may have voted incorrectly. Consider penalties.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateWinningConditions() {
|
async evaluateWinningConditions() {
|
||||||
|
@ -196,16 +186,13 @@ export class ValidationPool extends Actor {
|
||||||
if (elapsed < this.duration) {
|
if (elapsed < this.duration) {
|
||||||
throw new Error(`Validation pool duration has not yet elapsed! ${this.duration - elapsed} ms remaining.`);
|
throw new Error(`Validation pool duration has not yet elapsed! ${this.duration - elapsed} ms remaining.`);
|
||||||
}
|
}
|
||||||
if (this.voters.size < this.stakes.size) {
|
|
||||||
throw new Error('Not all voters have revealed their reputation public keys!');
|
|
||||||
}
|
|
||||||
// Now we can evaluate winning conditions
|
// Now we can evaluate winning conditions
|
||||||
this.state = ValidationPoolStates.CLOSED;
|
this.state = ValidationPoolStates.CLOSED;
|
||||||
this.setStatus('Closed');
|
this.setStatus('Closed');
|
||||||
|
|
||||||
const upvoteValue = this.getTotalValueOfStakesForOutcome(true);
|
const upvoteValue = this.getTotalValueOfStakesForOutcome(true);
|
||||||
const downvoteValue = this.getTotalValueOfStakesForOutcome(false);
|
const downvoteValue = this.getTotalValueOfStakesForOutcome(false);
|
||||||
const activeAvailableReputation = this.bench.getTotalActiveAvailableReputation();
|
const activeAvailableReputation = this.bench.getActiveAvailableReputation();
|
||||||
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
|
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
|
||||||
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
|
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
|
||||||
|
|
||||||
|
@ -238,44 +225,35 @@ export class ValidationPool extends Actor {
|
||||||
|
|
||||||
// In a tightly binding validation pool, losing voter stakes are transferred to winning voters.
|
// In a tightly binding validation pool, losing voter stakes are transferred to winning voters.
|
||||||
const tokensForWinners = this.getTotalStakedOnPost(!votePasses);
|
const tokensForWinners = this.getTotalStakedOnPost(!votePasses);
|
||||||
const winningVotes = this.getStakeEntries(votePasses, { excludeSystem: true });
|
const winningEntries = this.getStakes(votePasses, { excludeSystem: true });
|
||||||
const totalValueOfStakesForWin = this.getTotalValueOfStakesForOutcome(votePasses);
|
const totalValueOfStakesForWin = this.getTotalValueOfStakesForOutcome(votePasses);
|
||||||
|
|
||||||
// Compute rewards for the winning voters, in proportion to the value of their stakes.
|
// Compute rewards for the winning voters, in proportion to the value of their stakes.
|
||||||
const rewards = new Map();
|
for (const stake of winningEntries) {
|
||||||
for (const [signingPublicKey, stake] of winningVotes) {
|
const { tokenId, amount } = stake;
|
||||||
const { reputationPublicKey } = this.voters.get(signingPublicKey);
|
|
||||||
const value = stake.getStakeValue();
|
const value = stake.getStakeValue();
|
||||||
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
|
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
|
||||||
rewards.set(reputationPublicKey, reward);
|
// Also return each winning voter their staked amount
|
||||||
|
const reputationPublicKey = this.bench.reputation.ownerOf(tokenId);
|
||||||
|
console.log(`reward for winning stake by ${reputationPublicKey}: ${reward}`);
|
||||||
|
this.bench.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('rewards for stakes', rewards);
|
|
||||||
|
|
||||||
const authorReputationPublicKey = this.voters.get(this.authorSigningPublicKey).reputationPublicKey;
|
|
||||||
|
|
||||||
// Distribute awards to voters other than the author
|
|
||||||
for (const [reputationPublicKey, amount] of rewards) {
|
|
||||||
if (reputationPublicKey !== authorReputationPublicKey) {
|
|
||||||
this.bench.reputations.addTokens(reputationPublicKey, amount);
|
|
||||||
console.log(`reward for stake by ${reputationPublicKey}:`, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (votePasses) {
|
|
||||||
// Distribute awards to author via the forum
|
|
||||||
const tokensForAuthor = this.tokensMinted * params.stakeForAuthor + rewards.get(authorReputationPublicKey);
|
|
||||||
console.log('sending reward for author stake to forum', { tokensForAuthor });
|
|
||||||
|
|
||||||
if (votePasses && !!this.forum) {
|
if (votePasses && !!this.forum) {
|
||||||
|
// Distribute awards to author via the forum
|
||||||
|
// const tokensForAuthor = this.mintedValue * params.stakeForAuthor + rewards.get(this.tokenId);
|
||||||
|
console.log(`sending reward for author stake to forum: ${this.bench.reputation.valueOf(this.tokenId)}`);
|
||||||
|
|
||||||
|
// Transfer ownership of the minted token, from the pool to the forum
|
||||||
|
this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId);
|
||||||
|
|
||||||
// Recurse through forum to determine reputation effects
|
// Recurse through forum to determine reputation effects
|
||||||
await this.forum.onValidate(
|
await this.forum.onValidate({
|
||||||
this.bench,
|
bench: this.bench,
|
||||||
this,
|
pool: this,
|
||||||
this.postId,
|
postId: this.postId,
|
||||||
tokensForAuthor,
|
tokenId: this.tokenId,
|
||||||
);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('pool complete');
|
console.log('pool complete');
|
||||||
|
|
|
@ -7,22 +7,22 @@
|
||||||
<div id="availability-test"></div>
|
<div id="availability-test"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
import { Expert } from "/../classes/expert.js";
|
import { Expert } from '../classes/expert.js';
|
||||||
import { Bench } from "/../classes/bench.js";
|
import { Bench } from '../classes/bench.js';
|
||||||
import { Business } from "/../classes/business.js";
|
import { Business } from '../classes/business.js';
|
||||||
import { Availability } from "/../classes/availability.js";
|
import { Availability } from '../classes/availability.js';
|
||||||
import { delay } from "/util.js";
|
import { delay } from '../util.js';
|
||||||
import { Forum } from "/../classes/forum.js";
|
import { Forum } from '../classes/forum.js';
|
||||||
import { Public } from "/../classes/public.js";
|
import { Public } from '../classes/public.js';
|
||||||
|
|
||||||
const DELAY_INTERVAL = 500;
|
const DELAY_INTERVAL = 500;
|
||||||
|
|
||||||
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));
|
const scene = (window.scene = new Scene('Availability test', rootBox));
|
||||||
scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
|
|
||||||
const experts = (window.experts = []);
|
const experts = (window.experts = []);
|
||||||
|
@ -37,30 +37,30 @@
|
||||||
const expert1 = await newExpert();
|
const expert1 = await newExpert();
|
||||||
const expert2 = await newExpert();
|
const expert2 = await newExpert();
|
||||||
await newExpert();
|
await newExpert();
|
||||||
const forum = (window.forum = new Forum("Forum", scene));
|
const forum = (window.forum = new Forum('Forum', scene));
|
||||||
const bench = (window.bench = new Bench(forum, "Bench", scene));
|
const bench = (window.bench = new Bench(forum, 'Bench', scene));
|
||||||
const availability = (window.bench = new Availability(
|
const availability = (window.bench = new Availability(
|
||||||
bench,
|
bench,
|
||||||
"Availability",
|
'Availability',
|
||||||
scene
|
scene,
|
||||||
));
|
));
|
||||||
const business = (window.business = new Business(
|
const business = (window.business = new Business(
|
||||||
bench,
|
bench,
|
||||||
forum,
|
forum,
|
||||||
availability,
|
availability,
|
||||||
"Business",
|
'Business',
|
||||||
scene
|
scene,
|
||||||
));
|
));
|
||||||
const requestor = new Public("Public", scene);
|
const requestor = new Public('Public', scene);
|
||||||
|
|
||||||
const updateDisplayValues = async () => {
|
const updateDisplayValues = async () => {
|
||||||
for (const expert of experts) {
|
for (const expert of experts) {
|
||||||
await expert.setValue(
|
await expert.setValue(
|
||||||
"rep",
|
'rep',
|
||||||
bench.reputations.getTokens(expert.reputationPublicKey)
|
bench.reputation.valueOwnedBy(expert.reputationPublicKey),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await bench.setValue("total rep", bench.getTotalReputation());
|
await bench.setValue('total rep', bench.reputation.getTotal());
|
||||||
await scene.sequence.render();
|
await scene.sequence.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
await requestor.submitRequest(
|
await requestor.submitRequest(
|
||||||
business,
|
business,
|
||||||
{ fee: 100 },
|
{ fee: 100 },
|
||||||
{ please: "do some work" }
|
{ please: 'do some work' },
|
||||||
);
|
);
|
||||||
await updateDisplayValuesAndDelay();
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
@ -119,12 +119,12 @@
|
||||||
business,
|
business,
|
||||||
request.id,
|
request.id,
|
||||||
{
|
{
|
||||||
here: "is some evidence of work product",
|
here: 'is some evidence of work product',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
worker.deactivate();
|
worker.deactivate();
|
||||||
await updateDisplayValuesAndDelay();
|
await updateDisplayValuesAndDelay();
|
||||||
|
@ -146,10 +146,10 @@
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.match(/Validation pool has already been resolved/)) {
|
if (e.message.match(/Validation pool has already been resolved/)) {
|
||||||
console.log(
|
console.log(
|
||||||
"Caught expected error: Validation pool has already been resolved"
|
'Caught expected error: Validation pool has already been resolved',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error("Unexpected error");
|
console.error('Unexpected error');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
<div id="basic"></div>
|
<div id="basic"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById("basic");
|
const rootElement = document.getElementById('basic');
|
||||||
const rootBox = new Box("rootBox", rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
function randomDelay(min, max) {
|
function randomDelay(min, max) {
|
||||||
const delayMs = min + Math.random() * max;
|
const delayMs = min + Math.random() * max;
|
||||||
|
@ -26,30 +26,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
const scene = new Scene("Scene 1", rootBox);
|
const scene = new Scene('Scene 1', rootBox);
|
||||||
const webClientStatus = scene.addDisplayValue("WebClient Status");
|
const webClientStatus = scene.addDisplayValue('WebClient Status');
|
||||||
const node1Status = scene.addDisplayValue("Node 1 Status");
|
const node1Status = scene.addDisplayValue('Node 1 Status');
|
||||||
const blockchainStatus = scene.addDisplayValue("Blockchain Status");
|
const blockchainStatus = scene.addDisplayValue('Blockchain Status');
|
||||||
|
|
||||||
const webClient = scene.addActor("web client");
|
const webClient = scene.addActor('web client');
|
||||||
const node1 = scene.addActor("node 1");
|
const node1 = scene.addActor('node 1');
|
||||||
const blockchain = scene.addActor("blockchain");
|
const blockchain = scene.addActor('blockchain');
|
||||||
const requestForumPage = scene.addAction("requestForumPage");
|
const requestForumPage = scene.addAction('requestForumPage');
|
||||||
const readBlockchainData = scene.addAction("readBlockchainData");
|
const readBlockchainData = scene.addAction('readBlockchainData');
|
||||||
const blockchainData = scene.addAction("blockchainData");
|
const blockchainData = scene.addAction('blockchainData');
|
||||||
const forumPage = scene.addAction("forumPage");
|
const forumPage = scene.addAction('forumPage');
|
||||||
|
|
||||||
webClientStatus.set("Initialized");
|
webClientStatus.set('Initialized');
|
||||||
node1Status.set("Idle");
|
node1Status.set('Idle');
|
||||||
blockchainStatus.set("Idle");
|
blockchainStatus.set('Idle');
|
||||||
|
|
||||||
node1.on(requestForumPage, (src, detail) => {
|
node1.on(requestForumPage, (src, detail) => {
|
||||||
node1Status.set("Processing request");
|
node1Status.set('Processing request');
|
||||||
node1.on(blockchainData, (_src, data) => {
|
node1.on(blockchainData, (_src, data) => {
|
||||||
node1Status.set("Processing response");
|
node1Status.set('Processing response');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
node1.send(src, forumPage, data);
|
node1.send(src, forumPage, data);
|
||||||
node1Status.set("Idle");
|
node1Status.set('Idle');
|
||||||
}, randomDelay(500, 1000));
|
}, randomDelay(500, 1000));
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -58,27 +58,27 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
blockchain.on(readBlockchainData, (src, _detail) => {
|
blockchain.on(readBlockchainData, (src, _detail) => {
|
||||||
blockchainStatus.set("Processing request");
|
blockchainStatus.set('Processing request');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
blockchain.send(src, blockchainData, {});
|
blockchain.send(src, blockchainData, {});
|
||||||
blockchainStatus.set("Idle");
|
blockchainStatus.set('Idle');
|
||||||
}, randomDelay(500, 1500));
|
}, randomDelay(500, 1500));
|
||||||
});
|
});
|
||||||
|
|
||||||
webClient.on(forumPage, (_src, _detail) => {
|
webClient.on(forumPage, (_src, _detail) => {
|
||||||
webClientStatus.set("Received forum page");
|
webClientStatus.set('Received forum page');
|
||||||
});
|
});
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
webClient.send(node1, requestForumPage);
|
webClient.send(node1, requestForumPage);
|
||||||
webClientStatus.set("Requested forum page");
|
webClientStatus.set('Requested forum page');
|
||||||
}, randomDelay(6000, 12000));
|
}, randomDelay(6000, 12000));
|
||||||
}
|
}
|
||||||
|
|
||||||
(async function run() {
|
(async function run() {
|
||||||
const scene = new Scene("Scene 2", rootBox);
|
const scene = new Scene('Scene 2', rootBox);
|
||||||
|
|
||||||
const webClient = scene.addActor("webClient");
|
const webClient = scene.addActor('webClient');
|
||||||
|
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
const memories = [];
|
const memories = [];
|
||||||
|
@ -117,71 +117,71 @@
|
||||||
requestStorageData,
|
requestStorageData,
|
||||||
storageData,
|
storageData,
|
||||||
] = [
|
] = [
|
||||||
"seek truth",
|
'seek truth',
|
||||||
"consider available information",
|
'consider available information',
|
||||||
"evaluate confidence",
|
'evaluate confidence',
|
||||||
"choose response",
|
'choose response',
|
||||||
"qualified opinions",
|
'qualified opinions',
|
||||||
"request in-memory data",
|
'request in-memory data',
|
||||||
"in-memory data",
|
'in-memory data',
|
||||||
"request storage data",
|
'request storage data',
|
||||||
"storage data",
|
'storage data',
|
||||||
].map((name) => scene.addAction(name));
|
].map((name) => scene.addAction(name));
|
||||||
|
|
||||||
memories.forEach((memory) => {
|
memories.forEach((memory) => {
|
||||||
memory.setStatus("Idle");
|
memory.setStatus('Idle');
|
||||||
memory.on(requestMemoryData, async (src, _detail) => {
|
memory.on(requestMemoryData, async (src, _detail) => {
|
||||||
memory.setStatus("Retrieving data");
|
memory.setStatus('Retrieving data');
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
memory.send(src, memoryData, {});
|
memory.send(src, memoryData, {});
|
||||||
memory.setStatus("Idle");
|
memory.setStatus('Idle');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
storages.forEach((storage) => {
|
storages.forEach((storage) => {
|
||||||
storage.setStatus("Idle");
|
storage.setStatus('Idle');
|
||||||
storage.on(requestStorageData, async (src, _detail) => {
|
storage.on(requestStorageData, async (src, _detail) => {
|
||||||
storage.setStatus("Retrieving data");
|
storage.setStatus('Retrieving data');
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
storage.send(src, storageData, {});
|
storage.send(src, storageData, {});
|
||||||
storage.setStatus("Idle");
|
storage.setStatus('Idle');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
node.setStatus("Idle");
|
node.setStatus('Idle');
|
||||||
node.on(seekTruth, async (seeker, detail) => {
|
node.on(seekTruth, async (seeker, detail) => {
|
||||||
node.setStatus("Processing request");
|
node.setStatus('Processing request');
|
||||||
|
|
||||||
node.on(chooseResponse, async (_src, _info) => {
|
node.on(chooseResponse, async (_src, _info) => {
|
||||||
node.setStatus("Choosing response");
|
node.setStatus('Choosing response');
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
node.send(seeker, qualifiedOpinions, {});
|
node.send(seeker, qualifiedOpinions, {});
|
||||||
node.setStatus("Idle");
|
node.setStatus('Idle');
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on(evaluateConfidence, async (_src, _info) => {
|
node.on(evaluateConfidence, async (_src, _info) => {
|
||||||
node.setStatus("Evaluating confidence");
|
node.setStatus('Evaluating confidence');
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
node.send(node, chooseResponse);
|
node.send(node, chooseResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on(considerInfo, async (_src, _info) => {
|
node.on(considerInfo, async (_src, _info) => {
|
||||||
node.setStatus("Considering info");
|
node.setStatus('Considering info');
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
node.send(node, evaluateConfidence);
|
node.send(node, evaluateConfidence);
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on(memoryData, (_src, _data) => {
|
node.on(memoryData, (_src, _data) => {
|
||||||
node.on(storageData, (__src, __data) => {
|
node.on(storageData, (__src, __data) => {
|
||||||
if (detail?.readConcern === "single") {
|
if (detail?.readConcern === 'single') {
|
||||||
node.send(node, considerInfo, {});
|
node.send(node, considerInfo, {});
|
||||||
} else {
|
} else {
|
||||||
const peer = getPeer(node);
|
const peer = getPeer(node);
|
||||||
node.on(qualifiedOpinions, (___src, info) => {
|
node.on(qualifiedOpinions, (___src, info) => {
|
||||||
node.send(node, considerInfo, info);
|
node.send(node, considerInfo, info);
|
||||||
});
|
});
|
||||||
node.send(peer, seekTruth, { readConcern: "single" });
|
node.send(peer, seekTruth, { readConcern: 'single' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
node.send(node.storage, requestStorageData);
|
node.send(node.storage, requestStorageData);
|
||||||
|
@ -193,11 +193,11 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
webClient.on(qualifiedOpinions, (_src, _detail) => {
|
webClient.on(qualifiedOpinions, (_src, _detail) => {
|
||||||
webClient.setStatus("Received opinions and qualifications");
|
webClient.setStatus('Received opinions and qualifications');
|
||||||
});
|
});
|
||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
webClient.setStatus("Seek truth");
|
webClient.setStatus('Seek truth');
|
||||||
webClient.send(nodes[0], seekTruth);
|
webClient.send(nodes[0], seekTruth);
|
||||||
})();
|
}());
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
<div id="debounce-test"></div>
|
<div id="debounce-test"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
import { debounce, delay } from "/util.js";
|
import { debounce, delay } from '../util.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById("debounce-test");
|
const rootElement = document.getElementById('debounce-test');
|
||||||
const rootBox = new Box("rootBox", rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
const scene = (window.scene = new Scene("Debounce test", rootBox));
|
window.scene = new Scene('Debounce test', rootBox);
|
||||||
|
|
||||||
let eventCount = 0;
|
let eventCount = 0;
|
||||||
const event = () => {
|
const event = () => {
|
||||||
|
|
|
@ -7,33 +7,32 @@
|
||||||
<div id="flowchart-test"></div>
|
<div id="flowchart-test"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
import { Actor } from "/../classes/actor.js";
|
import { Actor } from '../classes/actor.js';
|
||||||
import { Action } from "/../classes/action.js";
|
import { Action } from '../classes/action.js';
|
||||||
import { Expert } from "/../classes/expert.js";
|
import { delay } from '../util.js';
|
||||||
import { delay } from "/util.js";
|
|
||||||
|
|
||||||
const DEFAULT_DELAY_INTERVAL = 500;
|
const DEFAULT_DELAY_INTERVAL = 500;
|
||||||
|
|
||||||
const rootElement = document.getElementById("flowchart-test");
|
const rootElement = document.getElementById('flowchart-test');
|
||||||
const rootBox = new Box("rootBox", rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
const scene = (window.scene = new Scene("Flowchart test", rootBox));
|
const scene = (window.scene = new Scene('Flowchart test', rootBox));
|
||||||
scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
|
|
||||||
const actor1 = new Actor("A", scene);
|
const actor1 = new Actor('A', scene);
|
||||||
const actor2 = new Actor("B", scene);
|
const actor2 = new Actor('B', scene);
|
||||||
const action1 = new Action("Action 1", scene);
|
const action1 = new Action('Action 1', scene);
|
||||||
await action1.log(actor1, actor2);
|
await action1.log(actor1, actor2);
|
||||||
await actor1.setValue("value", 1);
|
await actor1.setValue('value', 1);
|
||||||
|
|
||||||
await scene.withFlowchart();
|
await scene.withFlowchart();
|
||||||
await scene.flowchart.log("A --> B");
|
await scene.flowchart.log('A --> B');
|
||||||
|
|
||||||
await delay(DEFAULT_DELAY_INTERVAL);
|
await delay(DEFAULT_DELAY_INTERVAL);
|
||||||
action1.log(actor1, actor2);
|
action1.log(actor1, actor2);
|
||||||
|
|
||||||
await delay(DEFAULT_DELAY_INTERVAL);
|
await delay(DEFAULT_DELAY_INTERVAL);
|
||||||
await scene.flowchart.log("A --> C");
|
await scene.flowchart.log('A --> C');
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,35 +7,35 @@
|
||||||
<div id="forum-network"></div>
|
<div id="forum-network"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
import { PostContent } from "/../classes/post-content.js";
|
import { PostContent } from '../classes/post-content.js';
|
||||||
import { Expert } from "/../classes/expert.js";
|
import { Expert } from '../classes/expert.js';
|
||||||
import { ForumNode } from "/../classes/forum-node.js";
|
import { ForumNode } from '../classes/forum-node.js';
|
||||||
import { ForumNetwork } from "/../classes/forum-network.js";
|
import { ForumNetwork } from '../classes/forum-network.js';
|
||||||
import { CryptoUtil } from "/../classes/crypto.js";
|
import { CryptoUtil } from '../classes/crypto.js';
|
||||||
import { delay } from "/util.js";
|
import { delay } from '../util.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById("forum-network");
|
const rootElement = document.getElementById('forum-network');
|
||||||
const rootBox = new Box("rootBox", rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
window.scene = new Scene("Forum Network test", rootBox).log(
|
window.scene = new Scene('Forum Network test', rootBox).log(
|
||||||
"sequenceDiagram"
|
'sequenceDiagram',
|
||||||
);
|
);
|
||||||
|
|
||||||
window.author1 = await new Expert("author1", window.scene).initialize();
|
window.author1 = await new Expert('author1', window.scene).initialize();
|
||||||
window.author2 = await new Expert("author2", window.scene).initialize();
|
window.author2 = await new Expert('author2', window.scene).initialize();
|
||||||
|
|
||||||
window.forumNetwork = new ForumNetwork();
|
window.forumNetwork = new ForumNetwork();
|
||||||
|
|
||||||
window.forumNode1 = await new ForumNode("node1", window.scene).initialize(
|
window.forumNode1 = await new ForumNode('node1', window.scene).initialize(
|
||||||
window.forumNetwork
|
window.forumNetwork,
|
||||||
);
|
);
|
||||||
window.forumNode2 = await new ForumNode("node2", window.scene).initialize(
|
window.forumNode2 = await new ForumNode('node2', window.scene).initialize(
|
||||||
window.forumNetwork
|
window.forumNetwork,
|
||||||
);
|
);
|
||||||
window.forumNode3 = await new ForumNode("node3", window.scene).initialize(
|
window.forumNode3 = await new ForumNode('node3', window.scene).initialize(
|
||||||
window.forumNetwork
|
window.forumNetwork,
|
||||||
);
|
);
|
||||||
|
|
||||||
const processInterval = setInterval(async () => {
|
const processInterval = setInterval(async () => {
|
||||||
|
@ -48,24 +48,24 @@
|
||||||
|
|
||||||
// const blockchain = new Blockchain();
|
// const blockchain = new Blockchain();
|
||||||
|
|
||||||
window.post1 = new PostContent({ message: "hi" });
|
window.post1 = new PostContent({ message: 'hi' });
|
||||||
window.post1.id = CryptoUtil.randomUUID();
|
window.post1.id = CryptoUtil.randomUUID();
|
||||||
window.post2 = new PostContent({ message: "hello" }).addCitation(
|
window.post2 = new PostContent({ message: 'hello' }).addCitation(
|
||||||
window.post1.id,
|
window.post1.id,
|
||||||
1.0
|
1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await window.author1.submitPostViaNetwork(
|
await window.author1.submitPostViaNetwork(
|
||||||
window.forumNode1,
|
window.forumNode1,
|
||||||
window.post1,
|
window.post1,
|
||||||
50
|
50,
|
||||||
);
|
);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await window.author2.submitPostViaNetwork(
|
await window.author2.submitPostViaNetwork(
|
||||||
window.forumNode2,
|
window.forumNode2,
|
||||||
window.post2,
|
window.post2,
|
||||||
100
|
100,
|
||||||
);
|
);
|
||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,10 +52,10 @@
|
||||||
for (const expert of experts) {
|
for (const expert of experts) {
|
||||||
await expert.setValue(
|
await expert.setValue(
|
||||||
'rep',
|
'rep',
|
||||||
bench.reputations.getTokens(expert.reputationPublicKey),
|
bench.reputation.valueOwnedBy(expert.reputationPublicKey),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await bench.setValue('total rep', bench.getTotalReputation());
|
await bench.setValue('total rep', bench.reputation.getTotal());
|
||||||
await forum.setValue('total value', forum.getTotalValue());
|
await forum.setValue('total value', forum.getTotalValue());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@
|
||||||
fee: 10,
|
fee: 10,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
// authorStake: 10,
|
// authorStakeAmount: 10,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await updateDisplayValuesAndDelay(1000);
|
await updateDisplayValuesAndDelay(1000);
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
<div id="graph-test"></div>
|
<div id="graph-test"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
import { Graph } from "/../classes/graph.js";
|
import { Graph } from '../classes/graph.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById("graph-test");
|
const rootElement = document.getElementById('graph-test');
|
||||||
const rootBox = new Box("rootBox", rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
window.scene = new Scene("Graph test", rootBox);
|
window.scene = new Scene('Graph test', rootBox);
|
||||||
|
|
||||||
window.graph = new Graph();
|
window.graph = new Graph();
|
||||||
|
|
||||||
|
@ -29,5 +29,5 @@
|
||||||
addVertex();
|
addVertex();
|
||||||
addVertex();
|
addVertex();
|
||||||
|
|
||||||
window.graph.addEdge("e1", 0, 1);
|
window.graph.addEdge('e1', 0, 1);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>Reputation test</title>
|
||||||
|
<link type="text/css" rel="stylesheet" href="/index.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="test"></div>
|
||||||
|
</body>
|
||||||
|
<script type="module">
|
||||||
|
import { Box } from '../classes/box.js';
|
||||||
|
import { Scene } from '../classes/scene.js';
|
||||||
|
import { ValidationPool } from '../classes/validation-pool.js';
|
||||||
|
import { TokenHolder } from '../classes/token-holder.js';
|
||||||
|
import { ReputationToken } from '../classes/reputation-token.js';
|
||||||
|
import { delay } from '../util.js';
|
||||||
|
|
||||||
|
const DEFAULT_DELAY_INTERVAL = 500;
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('forum-test');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
|
const scene = (window.scene = new Scene('Forum test', rootBox));
|
||||||
|
scene.withSequenceDiagram();
|
||||||
|
scene.withFlowchart();
|
||||||
|
|
||||||
|
const pool = new ValidationPool();
|
||||||
|
const repToken = new ReputationToken();
|
||||||
|
|
||||||
|
// const tokenMinter = new TokenHolder('TokenMinter', scene);
|
||||||
|
|
||||||
|
await delay(DEFAULT_DELAY_INTERVAL);
|
||||||
|
</script>
|
|
@ -7,43 +7,45 @@
|
||||||
<div id="validation-pool"></div>
|
<div id="validation-pool"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Box } from "/../classes/box.js";
|
import { Box } from '../classes/box.js';
|
||||||
import { Scene } from "/../classes/scene.js";
|
import { Scene } from '../classes/scene.js';
|
||||||
import { Expert } from "/../classes/expert.js";
|
import { Expert } from '../classes/expert.js';
|
||||||
import { Bench } from "/../classes/bench.js";
|
import { Bench } from '../classes/bench.js';
|
||||||
import { Forum } from "/../classes/forum.js";
|
import { Forum } from '../classes/forum.js';
|
||||||
import { delay } from "/util.js";
|
import { PostContent } from '../classes/post-content.js';
|
||||||
|
import { delay } from '../util.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById("validation-pool");
|
const rootElement = document.getElementById('validation-pool');
|
||||||
const rootBox = new Box("rootBox", rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
const scene = (window.scene = new Scene("Validation Pool test", rootBox));
|
const scene = (window.scene = new Scene('Validation Pool test', rootBox));
|
||||||
await scene.withSequenceDiagram();
|
await scene.withSequenceDiagram();
|
||||||
const expert1 = (window.expert1 = await new Expert(
|
const expert1 = (window.expert1 = await new Expert(
|
||||||
"Expert1",
|
'Expert1',
|
||||||
scene
|
scene,
|
||||||
).initialize());
|
).initialize());
|
||||||
const expert2 = (window.expert2 = await new Expert(
|
const expert2 = (window.expert2 = await new Expert(
|
||||||
"Expert2",
|
'Expert2',
|
||||||
scene
|
scene,
|
||||||
).initialize());
|
).initialize());
|
||||||
const bench = (window.bench = new Bench(undefined, "Bench", scene));
|
const forum = (window.forum = new Forum('Forum', scene));
|
||||||
|
const bench = (window.bench = new Bench(forum, 'Bench', scene));
|
||||||
|
|
||||||
const updateDisplayValues = async () => {
|
const updateDisplayValues = async () => {
|
||||||
await expert1.setValue(
|
await expert1.setValue(
|
||||||
"rep",
|
'rep',
|
||||||
bench.reputations.getTokens(expert1.reputationPublicKey)
|
bench.reputation.valueOwnedBy(expert1.reputationPublicKey),
|
||||||
);
|
);
|
||||||
await expert2.setValue(
|
await expert2.setValue(
|
||||||
"rep",
|
'rep',
|
||||||
bench.reputations.getTokens(expert2.reputationPublicKey)
|
bench.reputation.valueOwnedBy(expert2.reputationPublicKey),
|
||||||
);
|
);
|
||||||
await bench.setValue("total rep", bench.getTotalReputation());
|
await bench.setValue('total rep', bench.reputation.getTotal());
|
||||||
// With params.lockingTimeExponent = 0 and params.activeVoterThreshold = null,
|
// With params.lockingTimeExponent = 0 and params.activeVoterThreshold = null,
|
||||||
// these next 3 propetries are all equal to total rep
|
// these next 3 propetries are all equal to total rep
|
||||||
// await bench.setValue('available rep', bench.getTotalAvailableReputation());
|
// await bench.setValue('available rep', bench.reputation.getTotalAvailable());
|
||||||
// await bench.setValue('active rep', bench.getTotalActiveReputation());
|
// await bench.setValue('active rep', bench.getActiveReputation());
|
||||||
// await bench.setValue('active available rep', bench.getTotalActiveAvailableReputation());
|
// await bench.setValue('active available rep', bench.getActiveAvailableReputation());
|
||||||
await scene.sequence.render();
|
await scene.sequence.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -52,12 +54,11 @@
|
||||||
|
|
||||||
// First expert can self-approve
|
// First expert can self-approve
|
||||||
{
|
{
|
||||||
const pool = await expert1.initiateValidationPool(bench, {
|
const { pool } = await expert1.submitPostWithFee(bench, forum, new PostContent(), {
|
||||||
fee: 7,
|
fee: 7,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
});
|
});
|
||||||
await expert1.revealIdentity(pool);
|
|
||||||
// Attempting to evaluate winning conditions before the duration has expired
|
// Attempting to evaluate winning conditions before the duration has expired
|
||||||
// should result in an exception
|
// should result in an exception
|
||||||
try {
|
try {
|
||||||
|
@ -65,56 +66,51 @@
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.match(/Validation pool duration has not yet elapsed/)) {
|
if (e.message.match(/Validation pool duration has not yet elapsed/)) {
|
||||||
console.log(
|
console.log(
|
||||||
"Caught expected error: Validation pool duration has not yet elapsed"
|
'Caught expected error: Validation pool duration has not yet elapsed',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error("Unexpected error");
|
console.error('Unexpected error');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await pool.evaluateWinningConditions(); // Stake passes
|
await pool.evaluateWinningConditions(); // Vote passes
|
||||||
await updateDisplayValues();
|
await updateDisplayValues();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failure example: second expert can not self-approve
|
// Failure example: second expert can not self-approve
|
||||||
try {
|
try {
|
||||||
const pool = await expert2.initiateValidationPool(bench, {
|
const { pool } = await expert2.submitPostWithFee(bench, forum, new PostContent(), {
|
||||||
fee: 1,
|
fee: 1,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
});
|
});
|
||||||
await expert2.revealIdentity(pool);
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await pool.evaluateWinningConditions(); // Quorum not met!
|
await pool.evaluateWinningConditions(); // Quorum not met!
|
||||||
await updateDisplayValues();
|
await updateDisplayValues();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.match(/Quorum is not met/)) {
|
if (e.message.match(/Quorum is not met/)) {
|
||||||
console.log("Caught expected error: Quorum not met");
|
console.log('Caught expected error: Quorum not met');
|
||||||
} else {
|
} else {
|
||||||
console.error("Unexpected error");
|
console.error('Unexpected error');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second expert must be approved by first expert
|
// Second expert must be approved by first expert
|
||||||
{
|
{
|
||||||
const pool = await expert2.initiateValidationPool(bench, {
|
const { pool } = await expert2.submitPostWithFee(bench, forum, new PostContent(), {
|
||||||
fee: 1,
|
fee: 1,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
anonymous: true,
|
|
||||||
});
|
});
|
||||||
await expert1.stake(pool, {
|
await expert1.stake(pool, {
|
||||||
position: true,
|
position: true,
|
||||||
amount: 4,
|
amount: 4,
|
||||||
lockingTime: 0,
|
lockingTime: 0,
|
||||||
anonymous: true,
|
|
||||||
});
|
});
|
||||||
await expert1.revealIdentity(pool);
|
|
||||||
await expert2.revealIdentity(pool);
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await pool.evaluateWinningConditions(); // Stake passes
|
await pool.evaluateWinningConditions(); // Stake passes
|
||||||
await updateDisplayValues();
|
await updateDisplayValues();
|
||||||
|
|
Loading…
Reference in New Issue