Refactor to simplify
- Add DAO class to contain the various contracts and data - Use window?.scene? instead of passing as arg to every constructor
This commit is contained in:
parent
c8ce74cf13
commit
3541802d93
|
@ -39,6 +39,7 @@ module.exports = {
|
||||||
context: 'readonly',
|
context: 'readonly',
|
||||||
it: 'readonly',
|
it: 'readonly',
|
||||||
specify: 'readonly',
|
specify: 'readonly',
|
||||||
|
should: 'readonly',
|
||||||
before: 'readonly',
|
before: 'readonly',
|
||||||
after: 'readonly',
|
after: 'readonly',
|
||||||
beforeEach: 'readonly',
|
beforeEach: 'readonly',
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
expert Expert1
|
||||||
|
expert Expert2
|
||||||
|
forum Forum
|
||||||
|
|
||||||
|
source -- action --> destination
|
|
@ -59,14 +59,8 @@ In a messaging system, the work is
|
||||||
- Receiving messages
|
- Receiving messages
|
||||||
- Processing messages
|
- Processing messages
|
||||||
- Sending messages
|
- Sending messages
|
||||||
|
|
||||||
and may include
|
|
||||||
|
|
||||||
- Maintaining context related to incoming messages for the duration of some operation
|
- Maintaining context related to incoming messages for the duration of some operation
|
||||||
- Performing computations
|
- Performing computations
|
||||||
- Storing data
|
|
||||||
- Retrieving data
|
|
||||||
- Performing other actions
|
|
||||||
|
|
||||||
The work of verifying peers in a messaging system is
|
The work of verifying peers in a messaging system is
|
||||||
|
|
||||||
|
@ -76,6 +70,8 @@ The work of verifying peers in a messaging system is
|
||||||
- Voting in validation pools?
|
- Voting in validation pools?
|
||||||
|
|
||||||
The work of providing a storage service extends that of participating in a messaging system.
|
The work of providing a storage service extends that of participating in a messaging system.
|
||||||
|
- Storing data
|
||||||
|
- Retrieving data
|
||||||
|
|
||||||
The work of verifying peers work products in a storage network is
|
The work of verifying peers work products in a storage network is
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
export class Action {
|
export class Action {
|
||||||
constructor(name, scene) {
|
constructor(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.scene = scene;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async log(src, dest, msg, obj, symbol = '->>') {
|
async log(src, dest, msg, obj, symbol = '->>') {
|
||||||
if (this.scene.sequence) {
|
await window?.scene?.sequence?.log(
|
||||||
await this.scene.sequence.log(
|
|
||||||
`${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${
|
`${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${
|
||||||
JSON.stringify(obj) ?? ''
|
JSON.stringify(obj) ?? ''
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
import { displayNumber } from '../util.js';
|
import { displayNumber } from '../util.js';
|
||||||
|
|
||||||
export class Actor {
|
export class Actor {
|
||||||
constructor(name, scene) {
|
constructor(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.scene = scene;
|
|
||||||
this.callbacks = new Map();
|
this.callbacks = new Map();
|
||||||
this.status = this.scene.addDisplayValue(`${this.name} status`);
|
this.status = window?.scene?.addDisplayValue(`${this.name} status`);
|
||||||
this.status.set('Created');
|
this.status.set('Created');
|
||||||
this.values = new Map();
|
this.values = new Map();
|
||||||
this.valueFunctions = new Map();
|
this.valueFunctions = new Map();
|
||||||
this.active = 0;
|
this.active = 0;
|
||||||
this.scene.registerActor(this);
|
window?.scene?.registerActor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
this.active += 1;
|
this.active += 1;
|
||||||
this.scene.sequence.log(`activate ${this.name}`, false);
|
window?.scene?.sequence.log(`activate ${this.name}`, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deactivate() {
|
async deactivate() {
|
||||||
|
@ -23,7 +22,7 @@ export class Actor {
|
||||||
throw new Error(`${this.name} is not active, can not deactivate`);
|
throw new Error(`${this.name} is not active, can not deactivate`);
|
||||||
}
|
}
|
||||||
this.active -= 1;
|
this.active -= 1;
|
||||||
await this.scene.sequence.log(`deactivate ${this.name}`);
|
await window?.scene?.sequence.log(`deactivate ${this.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(dest, action, detail) {
|
async send(dest, action, detail) {
|
||||||
|
@ -36,7 +35,7 @@ export class Actor {
|
||||||
const cb = this.callbacks.get(action.name);
|
const cb = this.callbacks.get(action.name);
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[${this.scene.name} actor ${this.name} does not have a callback registered for ${action.name}`,
|
`[${window?.scene?.name} actor ${this.name} does not have a callback registered for ${action.name}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await cb(src, detail);
|
await cb(src, detail);
|
||||||
|
@ -54,7 +53,7 @@ export class Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
addValue(label, fn) {
|
addValue(label, fn) {
|
||||||
this.values.set(label, this.scene.addDisplayValue(`${this.name} ${label}`));
|
this.values.set(label, window?.scene?.addDisplayValue(`${this.name} ${label}`));
|
||||||
if (fn) {
|
if (fn) {
|
||||||
this.valueFunctions.set(label, fn);
|
this.valueFunctions.set(label, fn);
|
||||||
}
|
}
|
||||||
|
@ -67,11 +66,11 @@ export class Actor {
|
||||||
}
|
}
|
||||||
let displayValue = this.values.get(label);
|
let displayValue = this.values.get(label);
|
||||||
if (!displayValue) {
|
if (!displayValue) {
|
||||||
displayValue = this.scene.addDisplayValue(`${this.name} ${label}`);
|
displayValue = window?.scene?.addDisplayValue(`${this.name} ${label}`);
|
||||||
this.values.set(label, displayValue);
|
this.values.set(label, displayValue);
|
||||||
}
|
}
|
||||||
if (value !== displayValue.get()) {
|
if (value !== displayValue.get()) {
|
||||||
await this.scene.sequence.log(`note over ${this.name} : ${label} = ${value}`);
|
await window?.scene?.sequence.log(`note over ${this.name} : ${label} = ${value}`);
|
||||||
}
|
}
|
||||||
displayValue.set(value);
|
displayValue.set(value);
|
||||||
return this;
|
return this;
|
||||||
|
@ -85,7 +84,7 @@ export class Actor {
|
||||||
value = displayNumber(value);
|
value = displayNumber(value);
|
||||||
}
|
}
|
||||||
if (value !== displayValue.get()) {
|
if (value !== displayValue.get()) {
|
||||||
await this.scene.sequence.log(`note over ${this.name} : ${label} = ${value}`);
|
await window?.scene?.sequence.log(`note over ${this.name} : ${label} = ${value}`);
|
||||||
}
|
}
|
||||||
displayValue.set(value);
|
displayValue.set(value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,12 @@ class Worker {
|
||||||
* Purpose: Enable staking reputation to enter the pool of workers
|
* Purpose: Enable staking reputation to enter the pool of workers
|
||||||
*/
|
*/
|
||||||
export class Availability extends Actor {
|
export class Availability extends Actor {
|
||||||
constructor(bench, name, scene) {
|
constructor(dao, name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.bench = bench;
|
this.dao = dao;
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
assignWork: new Action('assign work', scene),
|
assignWork: new Action('assign work'),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.workers = new Map();
|
this.workers = new Map();
|
||||||
|
@ -30,7 +30,7 @@ export class Availability extends Actor {
|
||||||
|
|
||||||
register(reputationPublicKey, { stakeAmount, tokenId, duration }) {
|
register(reputationPublicKey, { stakeAmount, tokenId, duration }) {
|
||||||
// TODO: Should be signed by token owner
|
// TODO: Should be signed by token owner
|
||||||
this.bench.reputation.lock(tokenId, stakeAmount, duration);
|
this.dao.reputation.lock(tokenId, stakeAmount, duration);
|
||||||
const workerId = CryptoUtil.randomUUID();
|
const workerId = CryptoUtil.randomUUID();
|
||||||
this.workers.set(workerId, new Worker(reputationPublicKey, tokenId, stakeAmount, duration));
|
this.workers.set(workerId, new Worker(reputationPublicKey, tokenId, stakeAmount, duration));
|
||||||
return workerId;
|
return workerId;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { DisplayValue } from './display-value.js';
|
import { DisplayValue } from './display-value.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { randomID } from '../util.js';
|
||||||
|
|
||||||
export class Box {
|
export class Box {
|
||||||
constructor(name, parentEl, elementType = 'div') {
|
constructor(name, parentEl, elementType = 'div') {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.el = document.createElement(elementType);
|
this.el = document.createElement(elementType);
|
||||||
this.el.id = `box_${CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8)}`;
|
this.el.id = `box_${randomID()}`;
|
||||||
this.el.classList.add('box');
|
this.el.classList.add('box');
|
||||||
if (name) {
|
if (name) {
|
||||||
this.el.setAttribute('box-name', name);
|
this.el.setAttribute('box-name', name);
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
|
import { randomID } from '../util.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from './action.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from './actor.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
|
||||||
import { PostContent } from './post-content.js';
|
import { PostContent } from './post-content.js';
|
||||||
|
|
||||||
class Request {
|
class Request {
|
||||||
|
static nextSeq = 0;
|
||||||
|
|
||||||
constructor(fee, content) {
|
constructor(fee, content) {
|
||||||
this.id = CryptoUtil.randomUUID();
|
this.seq = this.nextSeq;
|
||||||
|
this.nextSeq += 1;
|
||||||
|
this.id = `req_${randomID()}`;
|
||||||
this.fee = fee;
|
this.fee = fee;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.worker = null;
|
this.worker = null;
|
||||||
|
@ -16,16 +20,14 @@ class Request {
|
||||||
* Purpose: Enable fee-driven work requests, to be completed by workers from the availability pool
|
* Purpose: Enable fee-driven work requests, to be completed by workers from the availability pool
|
||||||
*/
|
*/
|
||||||
export class Business extends Actor {
|
export class Business extends Actor {
|
||||||
constructor(bench, forum, availability, name, scene) {
|
constructor(dao, name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.bench = bench;
|
this.dao = dao;
|
||||||
this.forum = forum;
|
|
||||||
this.availability = availability;
|
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
assignWork: new Action('assign work', scene),
|
assignWork: new Action('assign work'),
|
||||||
submitPost: new Action('submit post', scene),
|
submitPost: new Action('submit post'),
|
||||||
initiateValidationPool: new Action('initiate validation pool', scene),
|
initiateValidationPool: new Action('initiate validation pool'),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.requests = new Map();
|
this.requests = new Map();
|
||||||
|
@ -34,8 +36,8 @@ export class Business extends Actor {
|
||||||
async submitRequest(fee, content) {
|
async submitRequest(fee, content) {
|
||||||
const request = new Request(fee, content);
|
const request = new Request(fee, content);
|
||||||
this.requests.set(request.id, request);
|
this.requests.set(request.id, request);
|
||||||
await this.actions.assignWork.log(this, this.availability);
|
await this.actions.assignWork.log(this, this.dao.availability);
|
||||||
const worker = await this.availability.assignWork(request.id);
|
const worker = await this.dao.availability.assignWork(request.id);
|
||||||
request.worker = worker;
|
request.worker = worker;
|
||||||
return request.id;
|
return request.id;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +47,10 @@ export class Business extends Actor {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRequests() {
|
||||||
|
return Array.from(this.requests.values());
|
||||||
|
}
|
||||||
|
|
||||||
async submitWork(reputationPublicKey, requestId, workEvidence, { tokenLossRatio, duration }) {
|
async submitWork(reputationPublicKey, requestId, workEvidence, { tokenLossRatio, duration }) {
|
||||||
const request = this.requests.get(requestId);
|
const request = this.requests.get(requestId);
|
||||||
if (!request) {
|
if (!request) {
|
||||||
|
@ -60,16 +66,18 @@ export class Business extends Actor {
|
||||||
requestId,
|
requestId,
|
||||||
workEvidence,
|
workEvidence,
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestIndex = Array.from(this.requests.values())
|
const requestIndex = Array.from(this.requests.values())
|
||||||
.findIndex(({ id }) => id === request.id);
|
.findIndex(({ id }) => id === request.id);
|
||||||
|
|
||||||
post.setTitle(`Work Evidence ${requestIndex + 1}`);
|
post.setTitle(`Work Evidence ${requestIndex + 1}`);
|
||||||
|
|
||||||
await this.actions.submitPost.log(this, this.forum);
|
await this.actions.submitPost.log(this, this.dao);
|
||||||
const postId = await this.forum.addPost(reputationPublicKey, post);
|
const postId = await this.dao.forum.addPost(reputationPublicKey, post);
|
||||||
|
|
||||||
// Initiate a validation pool for this work evidence.
|
// Initiate a validation pool for this work evidence.
|
||||||
await this.actions.initiateValidationPool.log(this, this.bench);
|
await this.actions.initiateValidationPool.log(this, this.dao);
|
||||||
const pool = await this.bench.initiateValidationPool({
|
const pool = await this.dao.initiateValidationPool({
|
||||||
postId,
|
postId,
|
||||||
fee: request.fee,
|
fee: request.fee,
|
||||||
duration,
|
duration,
|
||||||
|
|
|
@ -1,22 +1,35 @@
|
||||||
import { Actor } from './actor.js';
|
|
||||||
import { ValidationPool } from './validation-pool.js';
|
|
||||||
import params from '../params.js';
|
|
||||||
import { Action } from './action.js';
|
import { Action } from './action.js';
|
||||||
|
import params from '../params.js';
|
||||||
|
import { Forum } from './forum.js';
|
||||||
import { ReputationTokenContract } from './reputation-token.js';
|
import { ReputationTokenContract } from './reputation-token.js';
|
||||||
|
import { ValidationPool } from './validation-pool.js';
|
||||||
|
import { Availability } from './availability.js';
|
||||||
|
import { Business } from './business.js';
|
||||||
|
import { Actor } from './actor.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purpose: Keep track of reputation holders
|
* Purpose:
|
||||||
|
* - Forum: Maintain a directed, acyclic, graph of positively and negatively weighted citations.
|
||||||
|
* and the value accrued via each post and citation.
|
||||||
|
* - Reputation: Keep track of reputation accrued to each expert
|
||||||
*/
|
*/
|
||||||
export class Bench extends Actor {
|
export class DAO extends Actor {
|
||||||
constructor(forum, name, scene) {
|
constructor(name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.forum = forum;
|
|
||||||
this.validationPools = new Map();
|
/* Contracts */
|
||||||
this.voters = new Map();
|
this.forum = new Forum(this, `${name} Forum`);
|
||||||
|
this.availability = new Availability(this, `${name} Availability`);
|
||||||
|
this.business = new Business(this, `${name} Business`);
|
||||||
this.reputation = new ReputationTokenContract();
|
this.reputation = new ReputationTokenContract();
|
||||||
|
|
||||||
|
/* Data */
|
||||||
|
this.validationPools = new Map();
|
||||||
|
this.experts = new Map();
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
createValidationPool: new Action('create validation pool', scene),
|
addPost: new Action('add post'),
|
||||||
|
createValidationPool: new Action('create validation pool'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +38,7 @@ export class Bench extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
listActiveVoters() {
|
listActiveVoters() {
|
||||||
return Array.from(this.voters.values()).filter((voter) => {
|
return Array.from(this.experts.values()).filter((voter) => {
|
||||||
const hasVoted = !!voter.dateLastVote;
|
const hasVoted = !!voter.dateLastVote;
|
||||||
const withinThreshold = !params.activeVoterThreshold
|
const withinThreshold = !params.activeVoterThreshold
|
||||||
|| new Date() - voter.dateLastVote >= params.activeVoterThreshold;
|
|| new Date() - voter.dateLastVote >= params.activeVoterThreshold;
|
||||||
|
@ -48,7 +61,7 @@ export class Bench extends Actor {
|
||||||
async initiateValidationPool(poolOptions, stakeOptions) {
|
async initiateValidationPool(poolOptions, stakeOptions) {
|
||||||
const validationPoolNumber = this.validationPools.size + 1;
|
const validationPoolNumber = this.validationPools.size + 1;
|
||||||
const name = `Pool${validationPoolNumber}`;
|
const name = `Pool${validationPoolNumber}`;
|
||||||
const pool = new ValidationPool(this, this.forum, poolOptions, name, this.scene);
|
const pool = new ValidationPool(this, poolOptions, name);
|
||||||
this.validationPools.set(pool.id, pool);
|
this.validationPools.set(pool.id, pool);
|
||||||
await this.actions.createValidationPool.log(this, pool);
|
await this.actions.createValidationPool.log(this, pool);
|
||||||
pool.activate();
|
pool.activate();
|
|
@ -4,16 +4,17 @@ import { CryptoUtil } from './crypto.js';
|
||||||
import { ReputationHolder } from './reputation-holder.js';
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
|
|
||||||
export class Expert extends ReputationHolder {
|
export class Expert extends ReputationHolder {
|
||||||
constructor(name, scene) {
|
constructor(dao, name) {
|
||||||
super(undefined, name, scene);
|
super(name);
|
||||||
|
this.dao = dao;
|
||||||
this.actions = {
|
this.actions = {
|
||||||
submitPostViaNetwork: new Action('submit post via network', scene),
|
submitPostViaNetwork: new Action('submit post via network'),
|
||||||
submitPost: new Action('submit post', scene),
|
submitPost: new Action('submit post'),
|
||||||
initiateValidationPool: new Action('initiate validation pool', scene),
|
initiateValidationPool: new Action('initiate validation pool'),
|
||||||
stake: new Action('stake on post', scene),
|
stake: new Action('stake on post'),
|
||||||
registerAvailability: new Action('register availability', scene),
|
registerAvailability: new Action('register availability'),
|
||||||
getAssignedWork: new Action('get assigned work', scene),
|
getAssignedWork: new Action('get assigned work'),
|
||||||
submitWork: new Action('submit work evidence', scene),
|
submitWork: new Action('submit work evidence'),
|
||||||
};
|
};
|
||||||
this.validationPools = new Map();
|
this.validationPools = new Map();
|
||||||
this.tokens = [];
|
this.tokens = [];
|
||||||
|
@ -36,29 +37,29 @@ export class Expert extends ReputationHolder {
|
||||||
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
|
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitPost(forum, postContent) {
|
async submitPost(postContent) {
|
||||||
// TODO: Include fee
|
// TODO: Include fee
|
||||||
await this.actions.submitPost.log(this, forum);
|
await this.actions.submitPost.log(this, this.dao.forum);
|
||||||
return forum.addPost(this.reputationPublicKey, postContent);
|
return this.dao.forum.addPost(this.reputationPublicKey, postContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitPostWithFee(bench, forum, postContent, poolOptions) {
|
async submitPostWithFee(postContent, poolOptions) {
|
||||||
await this.actions.submitPost.log(this, forum);
|
await this.actions.submitPost.log(this, this.dao.forum);
|
||||||
const postId = await forum.addPost(this.reputationPublicKey, postContent);
|
const postId = await this.dao.forum.addPost(this.reputationPublicKey, postContent);
|
||||||
const pool = await this.initiateValidationPool(bench, { ...poolOptions, postId });
|
const pool = await this.initiateValidationPool({ ...poolOptions, postId });
|
||||||
this.tokens.push(pool.tokenId);
|
this.tokens.push(pool.tokenId);
|
||||||
return { postId, pool };
|
return { postId, pool };
|
||||||
}
|
}
|
||||||
|
|
||||||
async initiateValidationPool(bench, poolOptions) {
|
async initiateValidationPool(poolOptions) {
|
||||||
// For now, directly call bench.initiateValidationPool();
|
// For now, directly call bench.initiateValidationPool();
|
||||||
poolOptions.reputationPublicKey = this.reputationPublicKey;
|
poolOptions.reputationPublicKey = this.reputationPublicKey;
|
||||||
await this.actions.initiateValidationPool.log(
|
await this.actions.initiateValidationPool.log(
|
||||||
this,
|
this,
|
||||||
bench,
|
this.dao,
|
||||||
`(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStakeAmount ?? 0})`,
|
`(fee: ${poolOptions.fee}, stake: ${poolOptions.authorStakeAmount ?? 0})`,
|
||||||
);
|
);
|
||||||
const pool = await bench.initiateValidationPool(poolOptions);
|
const pool = await this.dao.initiateValidationPool(poolOptions);
|
||||||
this.tokens.push(pool.tokenId);
|
this.tokens.push(pool.tokenId);
|
||||||
this.validationPools.set(pool.id, poolOptions);
|
this.validationPools.set(pool.id, poolOptions);
|
||||||
return pool;
|
return pool;
|
||||||
|
@ -79,23 +80,27 @@ export class Expert extends ReputationHolder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerAvailability(availability, stakeAmount, duration) {
|
async registerAvailability(stakeAmount, duration) {
|
||||||
await this.actions.registerAvailability.log(this, availability, `(stake: ${stakeAmount}, duration: ${duration})`);
|
await this.actions.registerAvailability.log(
|
||||||
this.workerId = await availability.register(this.reputationPublicKey, {
|
this,
|
||||||
|
this.dao.availability,
|
||||||
|
`(stake: ${stakeAmount}, duration: ${duration})`,
|
||||||
|
);
|
||||||
|
this.workerId = await this.dao.availability.register(this.reputationPublicKey, {
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
tokenId: this.tokens[0],
|
tokenId: this.tokens[0],
|
||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAssignedWork(availability, business) {
|
async getAssignedWork() {
|
||||||
const requestId = await availability.getAssignedWork(this.workerId);
|
const requestId = await this.dao.availability.getAssignedWork(this.workerId);
|
||||||
const request = await business.getRequest(requestId);
|
const request = await this.dao.business.getRequest(requestId);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitWork(business, requestId, evidence, { tokenLossRatio, duration }) {
|
async submitWork(requestId, evidence, { tokenLossRatio, duration }) {
|
||||||
await this.actions.submitWork.log(this, business);
|
await this.actions.submitWork.log(this, this.dao.business);
|
||||||
return business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
|
return this.dao.business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,17 @@ import { Action } from './action.js';
|
||||||
import {
|
import {
|
||||||
Message, PostMessage, PeerMessage, messageFromJSON,
|
Message, PostMessage, PeerMessage, messageFromJSON,
|
||||||
} from './message.js';
|
} from './message.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
|
||||||
import { ForumView } from './forum-view.js';
|
import { ForumView } from './forum-view.js';
|
||||||
import { NetworkNode } from './network-node.js';
|
import { NetworkNode } from './network-node.js';
|
||||||
|
import { randomID } from '../util.js';
|
||||||
|
|
||||||
export class ForumNode extends NetworkNode {
|
export class ForumNode extends NetworkNode {
|
||||||
constructor(name, scene) {
|
constructor(name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.forumView = new ForumView();
|
this.forumView = new ForumView();
|
||||||
this.actions = {
|
this.actions = {
|
||||||
...this.actions,
|
...this.actions,
|
||||||
storePost: new Action('store post', scene),
|
storePost: new Action('store post'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ export class ForumNode extends NetworkNode {
|
||||||
// Process an incoming post, received by whatever means
|
// Process an incoming post, received by whatever means
|
||||||
async processPost(authorId, post) {
|
async processPost(authorId, post) {
|
||||||
if (!post.id) {
|
if (!post.id) {
|
||||||
post.id = CryptoUtil.randomUUID();
|
post.id = randomID();
|
||||||
}
|
}
|
||||||
await this.actions.storePost.log(this, this);
|
await this.actions.storePost.log(this, this);
|
||||||
// this.forumView.addPost(authorId, post.id, post, stake);
|
// this.forumView.addPost(authorId, post.id, post, stake);
|
||||||
|
|
|
@ -1,69 +1,28 @@
|
||||||
import { Actor } from './actor.js';
|
|
||||||
import { WDAG } from './wdag.js';
|
import { WDAG } from './wdag.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from './action.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
|
||||||
import params from '../params.js';
|
import params from '../params.js';
|
||||||
import { ReputationHolder } from './reputation-holder.js';
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
import { displayNumber, EPSILON } from '../util.js';
|
import { EPSILON } from '../util.js';
|
||||||
|
import { Post } from './post.js';
|
||||||
|
|
||||||
const CITATION = 'citation';
|
const CITATION = 'citation';
|
||||||
const BALANCE = 'balance';
|
const BALANCE = 'balance';
|
||||||
|
|
||||||
class Post extends Actor {
|
|
||||||
constructor(forum, authorPublicKey, postContent) {
|
|
||||||
const index = forum.posts.countVertices();
|
|
||||||
const name = `Post${index + 1}`;
|
|
||||||
super(name, forum.scene);
|
|
||||||
this.id = postContent.id ?? name;
|
|
||||||
this.authorPublicKey = authorPublicKey;
|
|
||||||
this.value = 0;
|
|
||||||
this.initialValue = 0;
|
|
||||||
this.citations = postContent.citations;
|
|
||||||
this.title = postContent.title;
|
|
||||||
const leachingTotal = this.citations
|
|
||||||
.filter(({ weight }) => weight < 0)
|
|
||||||
.reduce((total, { weight }) => total += -weight, 0);
|
|
||||||
const donationTotal = this.citations
|
|
||||||
.filter(({ weight }) => weight > 0)
|
|
||||||
.reduce((total, { weight }) => total += weight, 0);
|
|
||||||
if (leachingTotal > params.revaluationLimit) {
|
|
||||||
throw new Error('Post leaching total exceeds revaluation limit '
|
|
||||||
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
|
||||||
}
|
|
||||||
if (donationTotal > params.revaluationLimit) {
|
|
||||||
throw new Error('Post donation total exceeds revaluation limit '
|
|
||||||
+ `(${donationTotal} > ${params.revaluationLimit})`);
|
|
||||||
}
|
|
||||||
if (this.citations.some(({ weight }) => Math.abs(weight) > params.revaluationLimit)) {
|
|
||||||
throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLabel() {
|
|
||||||
return `${this.name}
|
|
||||||
<table><tr>
|
|
||||||
<td>initial</td>
|
|
||||||
<td>${displayNumber(this.initialValue)}</td>
|
|
||||||
</tr><tr>
|
|
||||||
<td>value</td>
|
|
||||||
<td>${displayNumber(this.value)}</td>
|
|
||||||
</tr></table>`
|
|
||||||
.replaceAll(/\n\s*/g, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purpose: Maintain a directed, acyclic, weighted graph of posts referencing other posts
|
* Purpose:
|
||||||
|
* - Forum: Maintain a directed, acyclic, graph of positively and negatively weighted citations.
|
||||||
|
* and the value accrued via each post and citation.
|
||||||
*/
|
*/
|
||||||
export class Forum extends ReputationHolder {
|
export class Forum extends ReputationHolder {
|
||||||
constructor(name, scene) {
|
constructor(dao, name) {
|
||||||
super(`forum_${CryptoUtil.randomUUID()}`, name, scene);
|
super(name);
|
||||||
|
this.dao = dao;
|
||||||
this.id = this.reputationPublicKey;
|
this.id = this.reputationPublicKey;
|
||||||
this.posts = new WDAG(scene);
|
this.posts = new WDAG();
|
||||||
this.actions = {
|
this.actions = {
|
||||||
addPost: new Action('add post', scene),
|
addPost: new Action('add post'),
|
||||||
propagateValue: new Action('propagate', this.scene),
|
propagateValue: new Action('propagate'),
|
||||||
transfer: new Action('transfer', this.scene),
|
transfer: new Action('transfer'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,10 +55,10 @@ export class Forum extends ReputationHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onValidate({
|
async onValidate({
|
||||||
bench, postId, tokenId,
|
postId, tokenId,
|
||||||
}) {
|
}) {
|
||||||
this.activate();
|
this.activate();
|
||||||
const initialValue = bench.reputation.valueOf(tokenId);
|
const initialValue = this.dao.reputation.valueOf(tokenId);
|
||||||
const postVertex = this.posts.getVertex(postId);
|
const postVertex = this.posts.getVertex(postId);
|
||||||
const post = postVertex.data;
|
const post = postVertex.data;
|
||||||
post.setStatus('Validated');
|
post.setStatus('Validated');
|
||||||
|
@ -121,16 +80,16 @@ export class Forum extends ReputationHolder {
|
||||||
// Apply computed rewards to update values of tokens
|
// Apply computed rewards to update values of tokens
|
||||||
for (const [id, value] of rewardsAccumulator) {
|
for (const [id, value] of rewardsAccumulator) {
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
bench.reputation.transferValueFrom(id, post.tokenId, -value);
|
this.dao.reputation.transferValueFrom(id, post.tokenId, -value);
|
||||||
} else {
|
} else {
|
||||||
bench.reputation.transferValueFrom(post.tokenId, id, value);
|
this.dao.reputation.transferValueFrom(post.tokenId, id, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer ownership of the minted/staked token, from the forum to the post author
|
// Transfer ownership of the minted/staked token, from the posts to the post author
|
||||||
bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
|
this.dao.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
|
||||||
const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
||||||
const value = bench.reputation.valueOf(post.tokenId);
|
const value = this.dao.reputation.valueOf(post.tokenId);
|
||||||
this.actions.transfer.log(this, toActor, `(${value})`);
|
this.actions.transfer.log(this, toActor, `(${value})`);
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ import { CryptoUtil } from './crypto.js';
|
||||||
import { PrioritizedQueue } from './prioritized-queue.js';
|
import { PrioritizedQueue } from './prioritized-queue.js';
|
||||||
|
|
||||||
export class NetworkNode extends Actor {
|
export class NetworkNode extends Actor {
|
||||||
constructor(name, scene) {
|
constructor(name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.queue = new PrioritizedQueue();
|
this.queue = new PrioritizedQueue();
|
||||||
this.actions = {
|
this.actions = {
|
||||||
peerMessage: new Action('peer message', scene),
|
peerMessage: new Action('peer message'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Actor } from './actor.js';
|
||||||
|
import { displayNumber } from '../util.js';
|
||||||
|
import params from '../params.js';
|
||||||
|
|
||||||
|
export class Post extends Actor {
|
||||||
|
constructor(forum, authorPublicKey, postContent) {
|
||||||
|
const index = forum.posts.countVertices();
|
||||||
|
const name = `Post${index + 1}`;
|
||||||
|
super(name, forum.scene);
|
||||||
|
this.id = postContent.id ?? name;
|
||||||
|
this.authorPublicKey = authorPublicKey;
|
||||||
|
this.value = 0;
|
||||||
|
this.initialValue = 0;
|
||||||
|
this.citations = postContent.citations;
|
||||||
|
this.title = postContent.title;
|
||||||
|
const leachingTotal = this.citations
|
||||||
|
.filter(({ weight }) => weight < 0)
|
||||||
|
.reduce((total, { weight }) => total += -weight, 0);
|
||||||
|
const donationTotal = this.citations
|
||||||
|
.filter(({ weight }) => weight > 0)
|
||||||
|
.reduce((total, { weight }) => total += weight, 0);
|
||||||
|
if (leachingTotal > params.revaluationLimit) {
|
||||||
|
throw new Error('Post leaching total exceeds revaluation limit '
|
||||||
|
+ `(${leachingTotal} > ${params.revaluationLimit})`);
|
||||||
|
}
|
||||||
|
if (donationTotal > params.revaluationLimit) {
|
||||||
|
throw new Error('Post donation total exceeds revaluation limit '
|
||||||
|
+ `(${donationTotal} > ${params.revaluationLimit})`);
|
||||||
|
}
|
||||||
|
if (this.citations.some(({ weight }) => Math.abs(weight) > params.revaluationLimit)) {
|
||||||
|
throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLabel() {
|
||||||
|
return `${this.name}
|
||||||
|
<table><tr>
|
||||||
|
<td>initial</td>
|
||||||
|
<td>${displayNumber(this.initialValue)}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>value</td>
|
||||||
|
<td>${displayNumber(this.value)}</td>
|
||||||
|
</tr></table>`
|
||||||
|
.replaceAll(/\n\s*/g, '');
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,10 @@ import { Action } from './action.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from './actor.js';
|
||||||
|
|
||||||
export class Public extends Actor {
|
export class Public extends Actor {
|
||||||
constructor(name, scene) {
|
constructor(name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
submitRequest: new Action('submit work request', scene),
|
submitRequest: new Action('submit work request'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import { randomID } from '../util.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from './actor.js';
|
||||||
|
|
||||||
export class ReputationHolder extends Actor {
|
export class ReputationHolder extends Actor {
|
||||||
constructor(reputationPublicKey, name, scene) {
|
constructor(name) {
|
||||||
super(name, scene);
|
super(name);
|
||||||
this.reputationPublicKey = reputationPublicKey;
|
this.reputationPublicKey = `${name}_${randomID()}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { ERC721 } from './erc721.js';
|
import { ERC721 } from './erc721.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
|
||||||
|
|
||||||
import { EPSILON } from '../util.js';
|
import { EPSILON, randomID } from '../util.js';
|
||||||
|
|
||||||
class Lock {
|
class Lock {
|
||||||
constructor(tokenId, amount, duration) {
|
constructor(tokenId, amount, duration) {
|
||||||
|
@ -21,7 +20,7 @@ export class ReputationTokenContract extends ERC721 {
|
||||||
}
|
}
|
||||||
|
|
||||||
mint(to, value, context) {
|
mint(to, value, context) {
|
||||||
const tokenId = `token_${CryptoUtil.randomUUID()}`;
|
const tokenId = `token_${randomID()}`;
|
||||||
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 }]);
|
||||||
|
|
|
@ -173,7 +173,7 @@ export class Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
async addActor(name) {
|
async addActor(name) {
|
||||||
const actor = new Actor(name, this);
|
const actor = new Actor(name);
|
||||||
if (this.sequence) {
|
if (this.sequence) {
|
||||||
await this.sequence.log(`participant ${name}`);
|
await this.sequence.log(`participant ${name}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { CryptoUtil } from './crypto.js';
|
|
||||||
import { ReputationHolder } from './reputation-holder.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';
|
||||||
|
@ -16,8 +15,7 @@ const ValidationPoolStates = Object.freeze({
|
||||||
*/
|
*/
|
||||||
export class ValidationPool extends ReputationHolder {
|
export class ValidationPool extends ReputationHolder {
|
||||||
constructor(
|
constructor(
|
||||||
bench,
|
dao,
|
||||||
forum,
|
|
||||||
{
|
{
|
||||||
postId,
|
postId,
|
||||||
reputationPublicKey,
|
reputationPublicKey,
|
||||||
|
@ -27,14 +25,13 @@ export class ValidationPool extends ReputationHolder {
|
||||||
contentiousDebate = false,
|
contentiousDebate = false,
|
||||||
},
|
},
|
||||||
name,
|
name,
|
||||||
scene,
|
|
||||||
) {
|
) {
|
||||||
super(`pool_${CryptoUtil.randomUUID()}`, name, scene);
|
super(name);
|
||||||
this.id = this.reputationPublicKey;
|
this.id = this.reputationPublicKey;
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
reward: new Action('reward', scene),
|
reward: new Action('reward'),
|
||||||
transfer: new Action('transfer', scene),
|
transfer: new Action('transfer'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
||||||
|
@ -59,8 +56,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
}]; got ${duration}`,
|
}]; got ${duration}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.bench = bench;
|
this.dao = dao;
|
||||||
this.forum = forum;
|
|
||||||
this.postId = postId;
|
this.postId = postId;
|
||||||
this.state = ValidationPoolStates.OPEN;
|
this.state = ValidationPoolStates.OPEN;
|
||||||
this.setStatus('Open');
|
this.setStatus('Open');
|
||||||
|
@ -72,7 +68,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
this.tokenLossRatio = tokenLossRatio;
|
this.tokenLossRatio = tokenLossRatio;
|
||||||
this.contentiousDebate = contentiousDebate;
|
this.contentiousDebate = contentiousDebate;
|
||||||
this.mintedValue = fee * params.mintingRatio();
|
this.mintedValue = fee * params.mintingRatio();
|
||||||
this.tokenId = this.bench.reputation.mint(this.id, this.mintedValue);
|
this.tokenId = this.dao.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(this.id, {
|
this.stake(this.id, {
|
||||||
|
@ -87,9 +83,9 @@ export class ValidationPool extends ReputationHolder {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep a record of voters and their votes
|
// Keep a record of voters and their votes
|
||||||
const voter = this.bench.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
||||||
voter.addVoteRecord(this);
|
voter.addVoteRecord(this);
|
||||||
this.bench.voters.set(reputationPublicKey, voter);
|
this.dao.experts.set(reputationPublicKey, voter);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTokenLossRatio() {
|
getTokenLossRatio() {
|
||||||
|
@ -158,7 +154,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reputationPublicKey !== this.bench.reputation.ownerOf(tokenId)) {
|
if (reputationPublicKey !== this.dao.reputation.ownerOf(tokenId)) {
|
||||||
throw new Error('Reputation may only be staked by its owner!');
|
throw new Error('Reputation may only be staked by its owner!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,18 +164,18 @@ export class ValidationPool extends ReputationHolder {
|
||||||
this.stakes.add(stake);
|
this.stakes.add(stake);
|
||||||
|
|
||||||
// Transfer staked amount from the sender to the validation pool
|
// Transfer staked amount from the sender to the validation pool
|
||||||
this.bench.reputation.transferValueFrom(tokenId, this.tokenId, amount);
|
this.dao.reputation.transferValueFrom(tokenId, this.tokenId, amount);
|
||||||
|
|
||||||
// Keep a record of voters and their votes
|
// Keep a record of voters and their votes
|
||||||
if (tokenId !== this.tokenId) {
|
if (tokenId !== this.tokenId) {
|
||||||
const voter = this.bench.voters.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
||||||
voter.addVoteRecord(this);
|
voter.addVoteRecord(this);
|
||||||
this.bench.voters.set(reputationPublicKey, voter);
|
this.dao.experts.set(reputationPublicKey, voter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update computed display values
|
// Update computed display values
|
||||||
for (const voter of this.bench.voters.values()) {
|
for (const voter of this.dao.experts.values()) {
|
||||||
const actor = this.scene.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
const actor = window?.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
||||||
await actor.computeValues();
|
await actor.computeValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,7 +185,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
// we need to make sure any staked tokens are locked for the
|
// we need to make sure any staked tokens are locked for the
|
||||||
// specified amounts of time.
|
// specified amounts of time.
|
||||||
for (const { tokenId, amount, lockingTime } of this.stakes.values()) {
|
for (const { tokenId, amount, lockingTime } of this.stakes.values()) {
|
||||||
this.bench.reputation.lock(tokenId, amount, lockingTime);
|
this.dao.reputation.lock(tokenId, amount, lockingTime);
|
||||||
// TODO: If there is an exception here, the voter may have voted incorrectly. Consider penalties.
|
// TODO: If there is an exception here, the voter may have voted incorrectly. Consider penalties.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,7 +204,7 @@ export class ValidationPool extends ReputationHolder {
|
||||||
|
|
||||||
const upvoteValue = this.getTotalValueOfStakesForOutcome(true);
|
const upvoteValue = this.getTotalValueOfStakesForOutcome(true);
|
||||||
const downvoteValue = this.getTotalValueOfStakesForOutcome(false);
|
const downvoteValue = this.getTotalValueOfStakesForOutcome(false);
|
||||||
const activeAvailableReputation = this.bench.getActiveAvailableReputation();
|
const activeAvailableReputation = this.dao.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;
|
||||||
|
|
||||||
|
@ -220,13 +216,13 @@ export class ValidationPool extends ReputationHolder {
|
||||||
|
|
||||||
if (quorumMet) {
|
if (quorumMet) {
|
||||||
this.setStatus(`Resolved - ${votePasses ? 'Won' : 'Lost'}`);
|
this.setStatus(`Resolved - ${votePasses ? 'Won' : 'Lost'}`);
|
||||||
this.scene.sequence.log(`note over ${this.name} : ${votePasses ? 'Win' : 'Lose'}`);
|
window?.scene?.sequence.log(`note over ${this.name} : ${votePasses ? 'Win' : 'Lose'}`);
|
||||||
this.applyTokenLocking();
|
this.applyTokenLocking();
|
||||||
await this.distributeReputation({ votePasses });
|
await this.distributeReputation({ votePasses });
|
||||||
// TODO: distribute fees
|
// TODO: distribute fees
|
||||||
} else {
|
} else {
|
||||||
this.setStatus('Resolved - Quorum not met');
|
this.setStatus('Resolved - Quorum not met');
|
||||||
this.scene.sequence.log(`note over ${this.name} : Quorum not met`);
|
window?.scene?.sequence.log(`note over ${this.name} : Quorum not met`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
|
@ -250,26 +246,25 @@ export class ValidationPool extends ReputationHolder {
|
||||||
const value = stake.getStakeValue();
|
const value = stake.getStakeValue();
|
||||||
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
|
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
|
||||||
// Also return each winning voter their staked amount
|
// Also return each winning voter their staked amount
|
||||||
const reputationPublicKey = this.bench.reputation.ownerOf(tokenId);
|
const reputationPublicKey = this.dao.reputation.ownerOf(tokenId);
|
||||||
console.log(`reward for winning stake by ${reputationPublicKey}: ${reward}`);
|
console.log(`reward for winning stake by ${reputationPublicKey}: ${reward}`);
|
||||||
this.bench.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
|
this.dao.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
|
||||||
const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === reputationPublicKey);
|
const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === reputationPublicKey);
|
||||||
this.actions.reward.log(this, toActor, `(${reward})`);
|
this.actions.reward.log(this, toActor, `(${reward})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (votePasses && !!this.forum) {
|
if (votePasses) {
|
||||||
// Distribute awards to author via the forum
|
// Distribute awards to author via the forum
|
||||||
// const tokensForAuthor = this.mintedValue * params.stakeForAuthor + rewards.get(this.tokenId);
|
// 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)}`);
|
console.log(`sending reward for author stake to forum: ${this.dao.reputation.valueOf(this.tokenId)}`);
|
||||||
|
|
||||||
// Transfer ownership of the minted token, from the pool to the forum
|
// Transfer ownership of the minted token, from the pool to the forum
|
||||||
this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId);
|
this.dao.reputation.transferFrom(this.id, this.dao.forum.id, this.tokenId);
|
||||||
const value = this.bench.reputation.valueOf(this.tokenId);
|
const value = this.dao.reputation.valueOf(this.tokenId);
|
||||||
this.actions.transfer.log(this, this.forum, `(${value})`);
|
this.actions.transfer.log(this, this.dao.forum, `(${value})`);
|
||||||
|
|
||||||
// Recurse through forum to determine reputation effects
|
// Recurse through forum to determine reputation effects
|
||||||
await this.forum.onValidate({
|
await this.dao.forum.onValidate({
|
||||||
bench: this.bench,
|
|
||||||
pool: this,
|
pool: this,
|
||||||
postId: this.postId,
|
postId: this.postId,
|
||||||
tokenId: this.tokenId,
|
tokenId: this.tokenId,
|
||||||
|
@ -279,15 +274,12 @@ export class ValidationPool extends ReputationHolder {
|
||||||
console.log('pool complete');
|
console.log('pool complete');
|
||||||
|
|
||||||
// Update computed display values
|
// Update computed display values
|
||||||
for (const voter of this.bench.voters.values()) {
|
for (const voter of this.dao.experts.values()) {
|
||||||
const actor = this.scene.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
const actor = window?.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
||||||
await actor.computeValues();
|
await actor.computeValues();
|
||||||
}
|
}
|
||||||
await this.bench.computeValues();
|
await this.dao.forum.computeValues();
|
||||||
if (this.forum) {
|
|
||||||
await this.forum.computeValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.stateToTable(`validation pool ${this.name} complete`);
|
window?.scene?.stateToTable(`validation pool ${this.name} complete`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,17 +25,16 @@ export class Edge {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WDAG {
|
export class WDAG {
|
||||||
constructor(scene) {
|
constructor() {
|
||||||
this.scene = scene;
|
|
||||||
this.vertices = new Map();
|
this.vertices = new Map();
|
||||||
this.edgeLabels = new Map();
|
this.edgeLabels = new Map();
|
||||||
this.nextVertexId = 0;
|
this.nextVertexId = 0;
|
||||||
this.flowchart = scene?.flowchart ?? null;
|
this.flowchart = window?.scene?.flowchart ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
withFlowchart() {
|
withFlowchart() {
|
||||||
this.scene.withAdditionalFlowchart();
|
window?.scene?.withAdditionalFlowchart();
|
||||||
this.flowchart = this.scene.lastFlowchart();
|
this.flowchart = window?.scene?.lastFlowchart();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ export class WDAG {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getEdgeKey({ from, to }) {
|
static getEdgeKey({ from, to }) {
|
||||||
return btoa([from.id, to.id]).replaceAll(/[^A-Z]+/g, '');
|
return btoa([from.id, to.id]).replaceAll(/[^A-Za-z0-9]+/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
getEdge(label, from, to) {
|
getEdge(label, from, to) {
|
||||||
|
@ -122,7 +121,7 @@ export class WDAG {
|
||||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||||
if (this.getEdge(label, from, to)) {
|
if (this.getEdge(label, from, to)) {
|
||||||
throw new Error(`Edge ${label} from ${from} to ${to} already exists`);
|
throw new Error(`Edge ${label} from ${from.id} to ${to.id} already exists`);
|
||||||
}
|
}
|
||||||
const edge = this.setEdgeWeight(label, from, to, weight);
|
const edge = this.setEdgeWeight(label, from, to, weight);
|
||||||
from.edges.from.push(edge);
|
from.edges.from.push(edge);
|
||||||
|
|
|
@ -4,187 +4,7 @@
|
||||||
<link type="text/css" rel="stylesheet" href="../index.css" />
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="availability-test"></div>
|
<div id="scene"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module" src="./scripts/availability.test.js">
|
||||||
import { Box } from '../classes/box.js';
|
|
||||||
import { Scene } from '../classes/scene.js';
|
|
||||||
import { Expert } from '../classes/expert.js';
|
|
||||||
import { Bench } from '../classes/bench.js';
|
|
||||||
import { Business } from '../classes/business.js';
|
|
||||||
import { Availability } from '../classes/availability.js';
|
|
||||||
import { delay } from '../util.js';
|
|
||||||
import { Forum } from '../classes/forum.js';
|
|
||||||
import { Public } from '../classes/public.js';
|
|
||||||
import { PostContent } from '../classes/post-content.js';
|
|
||||||
|
|
||||||
const DELAY_INTERVAL = 500;
|
|
||||||
|
|
||||||
const rootElement = document.getElementById('availability-test');
|
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
|
||||||
|
|
||||||
const scene = (window.scene = new Scene('Availability test', rootBox));
|
|
||||||
scene.withSequenceDiagram();
|
|
||||||
scene.withFlowchart();
|
|
||||||
scene.withTable();
|
|
||||||
|
|
||||||
const experts = (window.experts = []);
|
|
||||||
const newExpert = async () => {
|
|
||||||
const index = experts.length;
|
|
||||||
const name = `Expert${index + 1}`;
|
|
||||||
const expert = await new Expert(name, scene).initialize();
|
|
||||||
experts.push(expert);
|
|
||||||
return expert;
|
|
||||||
};
|
|
||||||
|
|
||||||
const expert1 = await newExpert();
|
|
||||||
const expert2 = await newExpert();
|
|
||||||
const forum = (window.forum = new Forum('Forum', scene));
|
|
||||||
const bench = (window.bench = new Bench(forum, 'Bench', scene));
|
|
||||||
const availability = (window.availability = new Availability(
|
|
||||||
bench,
|
|
||||||
'Availability',
|
|
||||||
scene,
|
|
||||||
));
|
|
||||||
const business = (window.business = new Business(
|
|
||||||
bench,
|
|
||||||
forum,
|
|
||||||
availability,
|
|
||||||
'Business',
|
|
||||||
scene,
|
|
||||||
));
|
|
||||||
const requestor = new Public('Public', scene);
|
|
||||||
|
|
||||||
const updateDisplayValues = async () => {
|
|
||||||
for (const expert of experts) {
|
|
||||||
await expert.setValue(
|
|
||||||
'rep',
|
|
||||||
bench.reputation.valueOwnedBy(expert.reputationPublicKey),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await bench.setValue('total rep', bench.reputation.getTotal());
|
|
||||||
await scene.sequence.render();
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateDisplayValuesAndDelay = async (delayMs = DELAY_INTERVAL) => {
|
|
||||||
await updateDisplayValues();
|
|
||||||
await delay(delayMs);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActiveWorker = async () => {
|
|
||||||
let worker;
|
|
||||||
let request;
|
|
||||||
for (const expert of experts) {
|
|
||||||
request = await expert.getAssignedWork(availability, business);
|
|
||||||
if (request) {
|
|
||||||
worker = expert;
|
|
||||||
await worker.actions.getAssignedWork.log(worker, availability);
|
|
||||||
worker.activate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { worker, request };
|
|
||||||
};
|
|
||||||
|
|
||||||
const voteForWorkEvidence = async (worker, pool) => {
|
|
||||||
for (const expert of experts) {
|
|
||||||
if (expert !== worker) {
|
|
||||||
await expert.stake(pool, {
|
|
||||||
position: true,
|
|
||||||
amount: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// Experts gain initial reputation by submitting a post with fee
|
|
||||||
const { postId: postId1, pool: pool1 } = await expert1.submitPostWithFee(
|
|
||||||
bench,
|
|
||||||
forum,
|
|
||||||
new PostContent({ hello: 'there' }).setTitle('Post 1'),
|
|
||||||
{
|
|
||||||
fee: 10,
|
|
||||||
duration: 1000,
|
|
||||||
tokenLossRatio: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await updateDisplayValuesAndDelay(1000);
|
|
||||||
|
|
||||||
await pool1.evaluateWinningConditions();
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
const { pool: pool2 } = await expert2.submitPostWithFee(
|
|
||||||
bench,
|
|
||||||
forum,
|
|
||||||
new PostContent({ hello: 'to you as well' })
|
|
||||||
.setTitle('Post 2')
|
|
||||||
.addCitation(postId1, 0.5),
|
|
||||||
{
|
|
||||||
fee: 10,
|
|
||||||
duration: 1000,
|
|
||||||
tokenLossRatio: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await updateDisplayValuesAndDelay(1000);
|
|
||||||
|
|
||||||
await pool2.evaluateWinningConditions();
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// Populate availability pool
|
|
||||||
await expert1.registerAvailability(availability, 1, 10000);
|
|
||||||
await expert2.registerAvailability(availability, 1, 10000);
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// Submit work request
|
|
||||||
await requestor.submitRequest(
|
|
||||||
business,
|
|
||||||
{ fee: 100 },
|
|
||||||
{ please: 'do some work' },
|
|
||||||
);
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// Receive work request
|
|
||||||
const { worker, request } = await getActiveWorker();
|
|
||||||
|
|
||||||
// Submit work evidence
|
|
||||||
const pool = await worker.submitWork(
|
|
||||||
business,
|
|
||||||
request.id,
|
|
||||||
{
|
|
||||||
here: 'is some evidence of work product',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tokenLossRatio: 1,
|
|
||||||
duration: 1000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
worker.deactivate();
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// Stake on work evidence
|
|
||||||
await voteForWorkEvidence(worker, pool);
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// Wait for validation pool duration to elapse
|
|
||||||
await delay(1000);
|
|
||||||
|
|
||||||
// Distribute reputation awards and fees
|
|
||||||
await pool.evaluateWinningConditions();
|
|
||||||
await updateDisplayValuesAndDelay();
|
|
||||||
|
|
||||||
// This should throw an exception since the pool is already resolved
|
|
||||||
try {
|
|
||||||
await pool.evaluateWinningConditions();
|
|
||||||
} catch (e) {
|
|
||||||
if (e.message.match(/Validation pool has already been resolved/)) {
|
|
||||||
console.log(
|
|
||||||
'Caught expected error: Validation pool has already been resolved',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error('Unexpected error');
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
const scene = new Scene('Scene 1', rootBox).withSequenceDiagram();
|
const scene = (window.scene = new Scene('Scene 1', rootBox).withSequenceDiagram());
|
||||||
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');
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>Business</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mocha"></div>
|
||||||
|
<div id="scene"></div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js" integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script src="https://unpkg.com/mocha/mocha.js"></script>
|
||||||
|
<script src="https://unpkg.com/chai/chai.js"></script>
|
||||||
|
<script type="module" src="./scripts/business.test.js"></script>
|
||||||
|
<script defer class="mocha-init">
|
||||||
|
mocha.setup({
|
||||||
|
ui: 'bdd',
|
||||||
|
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
||||||
|
});
|
||||||
|
mocha.checkLeaks();
|
||||||
|
window.should = chai.should();
|
||||||
|
</script>
|
||||||
|
<script defer class="mocha-exec">
|
||||||
|
// TODO: Weird race condition -- resolve this in a better way
|
||||||
|
setTimeout(() => mocha.run(), 1000);
|
||||||
|
</script>
|
|
@ -16,7 +16,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'bench', 'forum', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
chai.should();
|
chai.should();
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'bench', 'forum', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
chai.should();
|
chai.should();
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'bench', 'forum', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
chai.should();
|
chai.should();
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'bench', 'forum', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
chai.should();
|
chai.should();
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { Box } from '../../classes/box.js';
|
||||||
|
import { Scene } from '../../classes/scene.js';
|
||||||
|
import { Expert } from '../../classes/expert.js';
|
||||||
|
import { delay } from '../../util.js';
|
||||||
|
import { DAO } from '../../classes/dao.js';
|
||||||
|
import { Public } from '../../classes/public.js';
|
||||||
|
import { PostContent } from '../../classes/post-content.js';
|
||||||
|
|
||||||
|
const DELAY_INTERVAL = 500;
|
||||||
|
let dao;
|
||||||
|
let experts;
|
||||||
|
let requestor;
|
||||||
|
const newExpert = async () => {
|
||||||
|
const index = experts.length;
|
||||||
|
const name = `Expert${index + 1}`;
|
||||||
|
const expert = await new Expert(dao, name).initialize();
|
||||||
|
experts.push(expert);
|
||||||
|
return expert;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function setup() {
|
||||||
|
const rootElement = document.getElementById('scene');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
|
const scene = (window.scene = new Scene('Availability test', rootBox));
|
||||||
|
scene.withSequenceDiagram();
|
||||||
|
scene.withFlowchart();
|
||||||
|
scene.withTable();
|
||||||
|
|
||||||
|
experts = (window.experts = []);
|
||||||
|
dao = (window.dao = new DAO('DGF'));
|
||||||
|
await newExpert();
|
||||||
|
await newExpert();
|
||||||
|
requestor = new Public('Public');
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateDisplayValues = async () => {
|
||||||
|
for (const expert of experts) {
|
||||||
|
await expert.setValue(
|
||||||
|
'rep',
|
||||||
|
dao.reputation.valueOwnedBy(expert.reputationPublicKey),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await dao.setValue('total rep', dao.reputation.getTotal());
|
||||||
|
await window.scene.sequence.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDisplayValuesAndDelay = async (delayMs = DELAY_INTERVAL) => {
|
||||||
|
await updateDisplayValues();
|
||||||
|
await delay(delayMs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActiveWorker = async () => {
|
||||||
|
let worker;
|
||||||
|
let request;
|
||||||
|
for (const expert of experts) {
|
||||||
|
request = await expert.getAssignedWork();
|
||||||
|
if (request) {
|
||||||
|
worker = expert;
|
||||||
|
await worker.actions.getAssignedWork.log(worker, dao.availability);
|
||||||
|
worker.activate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { worker, request };
|
||||||
|
};
|
||||||
|
|
||||||
|
const voteForWorkEvidence = async (worker, pool) => {
|
||||||
|
for (const expert of experts) {
|
||||||
|
if (expert !== worker) {
|
||||||
|
await expert.stake(pool, {
|
||||||
|
position: true,
|
||||||
|
amount: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await setup();
|
||||||
|
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// Experts gain initial reputation by submitting a post with fee
|
||||||
|
const { postId: postId1, pool: pool1 } = await experts[0].submitPostWithFee(
|
||||||
|
new PostContent({ hello: 'there' }).setTitle('Post 1'),
|
||||||
|
{
|
||||||
|
fee: 10,
|
||||||
|
duration: 1000,
|
||||||
|
tokenLossRatio: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await updateDisplayValuesAndDelay(1000);
|
||||||
|
|
||||||
|
await pool1.evaluateWinningConditions();
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
const { pool: pool2 } = await experts[1].submitPostWithFee(
|
||||||
|
new PostContent({ hello: 'to you as well' })
|
||||||
|
.setTitle('Post 2')
|
||||||
|
.addCitation(postId1, 0.5),
|
||||||
|
{
|
||||||
|
fee: 10,
|
||||||
|
duration: 1000,
|
||||||
|
tokenLossRatio: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await updateDisplayValuesAndDelay(1000);
|
||||||
|
|
||||||
|
await pool2.evaluateWinningConditions();
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// Populate availability pool
|
||||||
|
await experts[0].registerAvailability(1, 10000);
|
||||||
|
await experts[1].registerAvailability(1, 10000);
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// Submit work request
|
||||||
|
await requestor.submitRequest(
|
||||||
|
dao.business,
|
||||||
|
{ fee: 100 },
|
||||||
|
{ please: 'do some work' },
|
||||||
|
);
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// Receive work request
|
||||||
|
const { worker, request } = await getActiveWorker();
|
||||||
|
|
||||||
|
// Submit work evidence
|
||||||
|
const pool = await worker.submitWork(
|
||||||
|
request.id,
|
||||||
|
{
|
||||||
|
here: 'is some evidence of work product',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenLossRatio: 1,
|
||||||
|
duration: 1000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
worker.deactivate();
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// Stake on work evidence
|
||||||
|
await voteForWorkEvidence(worker, pool);
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// Wait for validation pool duration to elapse
|
||||||
|
await delay(1000);
|
||||||
|
|
||||||
|
// Distribute reputation awards and fees
|
||||||
|
await pool.evaluateWinningConditions();
|
||||||
|
await updateDisplayValuesAndDelay();
|
||||||
|
|
||||||
|
// This should throw an exception since the pool is already resolved
|
||||||
|
try {
|
||||||
|
await pool.evaluateWinningConditions();
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.match(/Validation pool has already been resolved/)) {
|
||||||
|
console.log(
|
||||||
|
'Caught expected error: Validation pool has already been resolved',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected error');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Business } from '../../classes/business.js';
|
||||||
|
import { Scene } from '../../classes/scene.js';
|
||||||
|
import { Box } from '../../classes/box.js';
|
||||||
|
|
||||||
|
describe('Business', () => {
|
||||||
|
let scene;
|
||||||
|
before(async () => {
|
||||||
|
const rootElement = document.getElementById('scene');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
scene = new Scene('Business', rootBox);
|
||||||
|
});
|
||||||
|
it('Should exist', () => {
|
||||||
|
const business = new Business(null, 'Business', scene);
|
||||||
|
should.exist(business);
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,8 +4,7 @@ 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 { Network } from '../../classes/network.js';
|
import { Network } from '../../classes/network.js';
|
||||||
import { CryptoUtil } from '../../classes/crypto.js';
|
import { delay, randomID } from '../../util.js';
|
||||||
import { delay } from '../../util.js';
|
|
||||||
|
|
||||||
const rootElement = document.getElementById('scene');
|
const rootElement = document.getElementById('scene');
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
@ -36,7 +35,7 @@ const processInterval = setInterval(async () => {
|
||||||
// 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 = randomID();
|
||||||
window.post2 = new PostContent({ message: 'hello' }).addCitation(
|
window.post2 = new PostContent({ message: 'hello' }).addCitation(
|
||||||
window.post1.id,
|
window.post1.id,
|
||||||
1.0,
|
1.0,
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
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 { delay } from '../../../util.js';
|
|
||||||
import { Forum } from '../../../classes/forum.js';
|
|
||||||
import { PostContent } from '../../../classes/post-content.js';
|
import { PostContent } from '../../../classes/post-content.js';
|
||||||
|
import { delay } from '../../../util.js';
|
||||||
import params from '../../../params.js';
|
import params from '../../../params.js';
|
||||||
|
import { DAO } from '../../../classes/dao.js';
|
||||||
|
|
||||||
export class ForumTest {
|
export class ForumTest {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.scene = null;
|
this.dao = null;
|
||||||
this.forum = null;
|
|
||||||
this.bench = null;
|
|
||||||
this.experts = null;
|
this.experts = null;
|
||||||
this.posts = null;
|
this.posts = null;
|
||||||
this.options = {
|
this.options = {
|
||||||
|
@ -24,16 +21,16 @@ export class ForumTest {
|
||||||
async newExpert() {
|
async newExpert() {
|
||||||
const index = this.experts.length;
|
const index = this.experts.length;
|
||||||
const name = `Expert${index + 1}`;
|
const name = `Expert${index + 1}`;
|
||||||
const expert = await new Expert(name, this.scene).initialize();
|
const expert = await new Expert(this.dao, name).initialize();
|
||||||
this.experts.push(expert);
|
this.experts.push(expert);
|
||||||
// expert.addValue('rep', () => this.bench.reputation.valueOwnedBy(expert.reputationPublicKey));
|
// expert.addValue('rep', () => this.dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
||||||
return expert;
|
return expert;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addPost(author, fee, citations = []) {
|
async addPost(author, fee, citations = []) {
|
||||||
const postIndex = this.posts.length;
|
const postIndex = this.posts.length;
|
||||||
const title = `posts[${postIndex}]`;
|
const title = `posts[${postIndex}]`;
|
||||||
await this.scene.startSection();
|
await window.scene.startSection();
|
||||||
|
|
||||||
const postContent = new PostContent({}).setTitle(title);
|
const postContent = new PostContent({}).setTitle(title);
|
||||||
for (const { postId, weight } of citations) {
|
for (const { postId, weight } of citations) {
|
||||||
|
@ -41,8 +38,6 @@ export class ForumTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { pool, postId } = await author.submitPostWithFee(
|
const { pool, postId } = await author.submitPostWithFee(
|
||||||
this.bench,
|
|
||||||
this.forum,
|
|
||||||
postContent,
|
postContent,
|
||||||
{
|
{
|
||||||
fee,
|
fee,
|
||||||
|
@ -53,7 +48,7 @@ export class ForumTest {
|
||||||
this.posts.push(postId);
|
this.posts.push(postId);
|
||||||
await delay(this.options.poolDurationMs);
|
await delay(this.options.poolDurationMs);
|
||||||
await pool.evaluateWinningConditions();
|
await pool.evaluateWinningConditions();
|
||||||
await this.scene.endSection();
|
await window.scene.endSection();
|
||||||
await delay(this.options.defaultDelayMs);
|
await delay(this.options.defaultDelayMs);
|
||||||
return postId;
|
return postId;
|
||||||
}
|
}
|
||||||
|
@ -62,21 +57,21 @@ export class ForumTest {
|
||||||
const rootElement = document.getElementById('scene');
|
const rootElement = document.getElementById('scene');
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
this.scene = (window.scene = new Scene('Forum test', rootBox));
|
const scene = (window.scene = new Scene('Forum test', rootBox));
|
||||||
this.scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
this.scene.withFlowchart();
|
scene.withFlowchart();
|
||||||
this.scene.withTable();
|
scene.withTable();
|
||||||
|
|
||||||
this.scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor);
|
scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor);
|
||||||
this.scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit);
|
scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit);
|
||||||
this.scene
|
scene
|
||||||
.addDisplayValue('q3. referenceChainLimit')
|
.addDisplayValue('q3. referenceChainLimit')
|
||||||
.set(params.referenceChainLimit);
|
.set(params.referenceChainLimit);
|
||||||
this.scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
|
scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
|
||||||
this.scene.addDisplayValue(' ');
|
scene.addDisplayValue(' ');
|
||||||
|
|
||||||
this.forum = (window.forum = new Forum('Forum', this.scene));
|
this.dao = (window.dao = new DAO('DGF'));
|
||||||
this.bench = (window.bench = new Bench(this.forum, 'Bench', this.scene));
|
this.forum = this.dao.forum;
|
||||||
this.experts = (window.experts = []);
|
this.experts = (window.experts = []);
|
||||||
this.posts = (window.posts = []);
|
this.posts = (window.posts = []);
|
||||||
|
|
||||||
|
@ -84,7 +79,7 @@ export class ForumTest {
|
||||||
// await newExpert();
|
// await newExpert();
|
||||||
// await newExpert();
|
// await newExpert();
|
||||||
|
|
||||||
// bench.addValue('total rep', () => bench.reputation.getTotal());
|
// this.dao.addValue('total rep', () => this.dao.reputation.getTotal());
|
||||||
this.forum.addValue('total value', () => this.forum.getTotalValue());
|
this.dao.forum.addValue('total value', () => this.dao.forum.getTotalValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
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 { Forum } from '../../classes/forum.js';
|
|
||||||
import { PostContent } from '../../classes/post-content.js';
|
import { PostContent } from '../../classes/post-content.js';
|
||||||
import { delay } from '../../util.js';
|
import { delay } from '../../util.js';
|
||||||
|
import { DAO } from '../../classes/dao.js';
|
||||||
|
|
||||||
const POOL_DURATION_MS = 100;
|
const POOL_DURATION_MS = 100;
|
||||||
const DEFAULT_DELAY_MS = 100;
|
const DEFAULT_DELAY_MS = 100;
|
||||||
|
|
||||||
let scene;
|
let scene;
|
||||||
let experts;
|
let experts;
|
||||||
let forum;
|
let dao;
|
||||||
let bench;
|
|
||||||
|
|
||||||
async function newExpert() {
|
async function newExpert() {
|
||||||
const index = experts.length;
|
const index = experts.length;
|
||||||
const name = `Expert${index + 1}`;
|
const name = `Expert${index + 1}`;
|
||||||
const expert = await new Expert(name, scene).initialize();
|
const expert = await new Expert(dao, name).initialize();
|
||||||
expert.addValue('rep', () => bench.reputation.valueOwnedBy(expert.reputationPublicKey));
|
expert.addValue('rep', () => dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
||||||
experts.push(expert);
|
experts.push(expert);
|
||||||
return expert;
|
return expert;
|
||||||
}
|
}
|
||||||
|
@ -31,11 +29,11 @@ async function setup() {
|
||||||
scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
scene.withTable();
|
scene.withTable();
|
||||||
|
|
||||||
|
dao = new DAO('DGF');
|
||||||
|
|
||||||
experts = (window.experts = []);
|
experts = (window.experts = []);
|
||||||
await newExpert();
|
await newExpert();
|
||||||
await newExpert();
|
await newExpert();
|
||||||
forum = (window.forum = new Forum('Forum', scene));
|
|
||||||
bench = (window.bench = new Bench(forum, 'Bench', scene));
|
|
||||||
|
|
||||||
await delay(DEFAULT_DELAY_MS);
|
await delay(DEFAULT_DELAY_MS);
|
||||||
}
|
}
|
||||||
|
@ -45,13 +43,9 @@ describe('Validation Pool', () => {
|
||||||
await setup();
|
await setup();
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await scene.deactivateAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('First expert can self-approve', async () => {
|
it('First expert can self-approve', async () => {
|
||||||
scene.startSection();
|
scene.startSection();
|
||||||
const { pool } = await experts[0].submitPostWithFee(bench, forum, new PostContent(), {
|
const { pool } = await experts[0].submitPostWithFee(new PostContent(), {
|
||||||
fee: 7,
|
fee: 7,
|
||||||
duration: POOL_DURATION_MS,
|
duration: POOL_DURATION_MS,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
|
@ -79,7 +73,7 @@ describe('Validation Pool', () => {
|
||||||
it('Failure example: second expert can not self-approve', async () => {
|
it('Failure example: second expert can not self-approve', async () => {
|
||||||
scene.startSection();
|
scene.startSection();
|
||||||
try {
|
try {
|
||||||
const { pool } = await experts[1].submitPostWithFee(bench, forum, new PostContent(), {
|
const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
|
||||||
fee: 1,
|
fee: 1,
|
||||||
duration: POOL_DURATION_MS,
|
duration: POOL_DURATION_MS,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
|
@ -95,7 +89,7 @@ describe('Validation Pool', () => {
|
||||||
|
|
||||||
it('Second expert must be approved by first expert', async () => {
|
it('Second expert must be approved by first expert', async () => {
|
||||||
scene.startSection();
|
scene.startSection();
|
||||||
const { pool } = await experts[1].submitPostWithFee(bench, forum, new PostContent(), {
|
const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
|
||||||
fee: 1,
|
fee: 1,
|
||||||
duration: POOL_DURATION_MS,
|
duration: POOL_DURATION_MS,
|
||||||
tokenLossRatio: 1,
|
tokenLossRatio: 1,
|
||||||
|
|
|
@ -5,10 +5,12 @@ import { WDAG } from '../../classes/wdag.js';
|
||||||
const rootElement = document.getElementById('scene');
|
const rootElement = document.getElementById('scene');
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
window.scene = new Scene('WDAG test', rootBox);
|
window.scene = new Scene('WDAG test', rootBox);
|
||||||
|
|
||||||
describe('Query the graph', () => {
|
describe('Query the graph', () => {
|
||||||
let graph;
|
let graph;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
graph = (window.graph = new WDAG(window.scene)).withFlowchart();
|
graph = (window.graph = new WDAG()).withFlowchart();
|
||||||
|
|
||||||
graph.addVertex({});
|
graph.addVertex({});
|
||||||
graph.addVertex({});
|
graph.addVertex({});
|
||||||
|
@ -21,14 +23,17 @@ describe('Query the graph', () => {
|
||||||
graph.addEdge('e1', 3, 1, 0.25);
|
graph.addEdge('e1', 3, 1, 0.25);
|
||||||
graph.addEdge('e1', 1, 4, 0.125);
|
graph.addEdge('e1', 1, 4, 0.125);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can query for all e1 edges', () => {
|
it('can query for all e1 edges', () => {
|
||||||
const edges = graph.getEdges('e1');
|
const edges = graph.getEdges('e1');
|
||||||
edges.should.have.length(4);
|
edges.should.have.length(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can query for all e1 edges from a particular vertex', () => {
|
it('can query for all e1 edges from a particular vertex', () => {
|
||||||
const edges = graph.getEdges('e1', 2);
|
const edges = graph.getEdges('e1', 2);
|
||||||
edges.map(({ from, to, weight }) => [from.id, to.id, weight]).should.have.deep.members([[2, 1, 0.5]]);
|
edges.map(({ from, to, weight }) => [from.id, to.id, weight]).should.have.deep.members([[2, 1, 0.5]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can query for all e1 edges to a particular vertex', () => {
|
it('can query for all e1 edges to a particular vertex', () => {
|
||||||
const edges = graph.getEdges('e1', null, 1);
|
const edges = graph.getEdges('e1', null, 1);
|
||||||
edges.map(({ from, to, weight }) => [from.id, to.id, weight]).should.have.deep.members([
|
edges.map(({ from, to, weight }) => [from.id, to.id, weight]).should.have.deep.members([
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'bench', 'forum', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
chai.should();
|
chai.should();
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'bench', 'forum', 'experts', 'posts', 'graph', '__REACT_DEVTOOLS_*'],
|
globals: ['graph', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
chai.should();
|
chai.should();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { CryptoUtil } from './classes/crypto.js';
|
||||||
|
|
||||||
const timers = new Map();
|
const timers = new Map();
|
||||||
|
|
||||||
export const EPSILON = 2.23e-16;
|
export const EPSILON = 2.23e-16;
|
||||||
|
@ -32,3 +34,5 @@ export const hexToRGB = (input) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const displayNumber = (value) => (value.toString().length > 6 ? value.toFixed(2) : value);
|
export const displayNumber = (value) => (value.toString().length > 6 ? value.toFixed(2) : value);
|
||||||
|
|
||||||
|
export const randomID = () => CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8);
|
||||||
|
|
Loading…
Reference in New Issue