diff --git a/forum-network/.eslintrc.js b/forum-network/.eslintrc.js
index 1d9fdd3..4601ff3 100644
--- a/forum-network/.eslintrc.js
+++ b/forum-network/.eslintrc.js
@@ -39,6 +39,7 @@ module.exports = {
context: 'readonly',
it: 'readonly',
specify: 'readonly',
+ should: 'readonly',
before: 'readonly',
after: 'readonly',
beforeEach: 'readonly',
diff --git a/forum-network/notes/sequences.md b/forum-network/notes/sequences.md
new file mode 100644
index 0000000..d873d30
--- /dev/null
+++ b/forum-network/notes/sequences.md
@@ -0,0 +1,5 @@
+expert Expert1
+expert Expert2
+forum Forum
+
+source -- action --> destination
diff --git a/forum-network/notes/work.md b/forum-network/notes/work.md
index 46a0fb5..6530075 100644
--- a/forum-network/notes/work.md
+++ b/forum-network/notes/work.md
@@ -59,14 +59,8 @@ In a messaging system, the work is
- Receiving messages
- Processing messages
- Sending messages
-
-and may include
-
- Maintaining context related to incoming messages for the duration of some operation
- Performing computations
-- Storing data
-- Retrieving data
-- Performing other actions
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?
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
diff --git a/forum-network/src/classes/action.js b/forum-network/src/classes/action.js
index 0d9c5c4..69df2a3 100644
--- a/forum-network/src/classes/action.js
+++ b/forum-network/src/classes/action.js
@@ -1,16 +1,13 @@
export class Action {
- constructor(name, scene) {
+ constructor(name) {
this.name = name;
- this.scene = scene;
}
async log(src, dest, msg, obj, symbol = '->>') {
- if (this.scene.sequence) {
- await this.scene.sequence.log(
- `${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${
- JSON.stringify(obj) ?? ''
- }`,
- );
- }
+ await window?.scene?.sequence?.log(
+ `${src.name} ${symbol} ${dest.name} : ${this.name} ${msg ?? ''} ${
+ JSON.stringify(obj) ?? ''
+ }`,
+ );
}
}
diff --git a/forum-network/src/classes/actor.js b/forum-network/src/classes/actor.js
index 609cec4..05b2302 100644
--- a/forum-network/src/classes/actor.js
+++ b/forum-network/src/classes/actor.js
@@ -1,21 +1,20 @@
import { displayNumber } from '../util.js';
export class Actor {
- constructor(name, scene) {
+ constructor(name) {
this.name = name;
- this.scene = scene;
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.values = new Map();
this.valueFunctions = new Map();
this.active = 0;
- this.scene.registerActor(this);
+ window?.scene?.registerActor(this);
}
activate() {
this.active += 1;
- this.scene.sequence.log(`activate ${this.name}`, false);
+ window?.scene?.sequence.log(`activate ${this.name}`, false);
}
async deactivate() {
@@ -23,7 +22,7 @@ export class Actor {
throw new Error(`${this.name} is not active, can not deactivate`);
}
this.active -= 1;
- await this.scene.sequence.log(`deactivate ${this.name}`);
+ await window?.scene?.sequence.log(`deactivate ${this.name}`);
}
async send(dest, action, detail) {
@@ -36,7 +35,7 @@ export class Actor {
const cb = this.callbacks.get(action.name);
if (!cb) {
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);
@@ -54,7 +53,7 @@ export class Actor {
}
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) {
this.valueFunctions.set(label, fn);
}
@@ -67,11 +66,11 @@ export class Actor {
}
let displayValue = this.values.get(label);
if (!displayValue) {
- displayValue = this.scene.addDisplayValue(`${this.name} ${label}`);
+ displayValue = window?.scene?.addDisplayValue(`${this.name} ${label}`);
this.values.set(label, displayValue);
}
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);
return this;
@@ -85,7 +84,7 @@ export class Actor {
value = displayNumber(value);
}
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);
}
diff --git a/forum-network/src/classes/availability.js b/forum-network/src/classes/availability.js
index cf34d3d..c2c1b00 100644
--- a/forum-network/src/classes/availability.js
+++ b/forum-network/src/classes/availability.js
@@ -17,12 +17,12 @@ class Worker {
* Purpose: Enable staking reputation to enter the pool of workers
*/
export class Availability extends Actor {
- constructor(bench, name, scene) {
- super(name, scene);
- this.bench = bench;
+ constructor(dao, name) {
+ super(name);
+ this.dao = dao;
this.actions = {
- assignWork: new Action('assign work', scene),
+ assignWork: new Action('assign work'),
};
this.workers = new Map();
@@ -30,7 +30,7 @@ export class Availability extends Actor {
register(reputationPublicKey, { stakeAmount, tokenId, duration }) {
// 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();
this.workers.set(workerId, new Worker(reputationPublicKey, tokenId, stakeAmount, duration));
return workerId;
diff --git a/forum-network/src/classes/box.js b/forum-network/src/classes/box.js
index 0588f30..ecc4f77 100644
--- a/forum-network/src/classes/box.js
+++ b/forum-network/src/classes/box.js
@@ -1,11 +1,11 @@
import { DisplayValue } from './display-value.js';
-import { CryptoUtil } from './crypto.js';
+import { randomID } from '../util.js';
export class Box {
constructor(name, parentEl, elementType = 'div') {
this.name = name;
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');
if (name) {
this.el.setAttribute('box-name', name);
diff --git a/forum-network/src/classes/business.js b/forum-network/src/classes/business.js
index 4dd5f42..9ddd799 100644
--- a/forum-network/src/classes/business.js
+++ b/forum-network/src/classes/business.js
@@ -1,11 +1,15 @@
+import { randomID } from '../util.js';
import { Action } from './action.js';
import { Actor } from './actor.js';
-import { CryptoUtil } from './crypto.js';
import { PostContent } from './post-content.js';
class Request {
+ static nextSeq = 0;
+
constructor(fee, content) {
- this.id = CryptoUtil.randomUUID();
+ this.seq = this.nextSeq;
+ this.nextSeq += 1;
+ this.id = `req_${randomID()}`;
this.fee = fee;
this.content = content;
this.worker = null;
@@ -16,16 +20,14 @@ class Request {
* Purpose: Enable fee-driven work requests, to be completed by workers from the availability pool
*/
export class Business extends Actor {
- constructor(bench, forum, availability, name, scene) {
- super(name, scene);
- this.bench = bench;
- this.forum = forum;
- this.availability = availability;
+ constructor(dao, name) {
+ super(name);
+ this.dao = dao;
this.actions = {
- assignWork: new Action('assign work', scene),
- submitPost: new Action('submit post', scene),
- initiateValidationPool: new Action('initiate validation pool', scene),
+ assignWork: new Action('assign work'),
+ submitPost: new Action('submit post'),
+ initiateValidationPool: new Action('initiate validation pool'),
};
this.requests = new Map();
@@ -34,8 +36,8 @@ export class Business extends Actor {
async submitRequest(fee, content) {
const request = new Request(fee, content);
this.requests.set(request.id, request);
- await this.actions.assignWork.log(this, this.availability);
- const worker = await this.availability.assignWork(request.id);
+ await this.actions.assignWork.log(this, this.dao.availability);
+ const worker = await this.dao.availability.assignWork(request.id);
request.worker = worker;
return request.id;
}
@@ -45,6 +47,10 @@ export class Business extends Actor {
return request;
}
+ async getRequests() {
+ return Array.from(this.requests.values());
+ }
+
async submitWork(reputationPublicKey, requestId, workEvidence, { tokenLossRatio, duration }) {
const request = this.requests.get(requestId);
if (!request) {
@@ -60,16 +66,18 @@ export class Business extends Actor {
requestId,
workEvidence,
});
+
const requestIndex = Array.from(this.requests.values())
.findIndex(({ id }) => id === request.id);
+
post.setTitle(`Work Evidence ${requestIndex + 1}`);
- await this.actions.submitPost.log(this, this.forum);
- const postId = await this.forum.addPost(reputationPublicKey, post);
+ await this.actions.submitPost.log(this, this.dao);
+ const postId = await this.dao.forum.addPost(reputationPublicKey, post);
// Initiate a validation pool for this work evidence.
- await this.actions.initiateValidationPool.log(this, this.bench);
- const pool = await this.bench.initiateValidationPool({
+ await this.actions.initiateValidationPool.log(this, this.dao);
+ const pool = await this.dao.initiateValidationPool({
postId,
fee: request.fee,
duration,
diff --git a/forum-network/src/classes/bench.js b/forum-network/src/classes/dao.js
similarity index 64%
rename from forum-network/src/classes/bench.js
rename to forum-network/src/classes/dao.js
index 7354e50..4a8df80 100644
--- a/forum-network/src/classes/bench.js
+++ b/forum-network/src/classes/dao.js
@@ -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 params from '../params.js';
+import { Forum } from './forum.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 {
- constructor(forum, name, scene) {
- super(name, scene);
- this.forum = forum;
- this.validationPools = new Map();
- this.voters = new Map();
+export class DAO extends Actor {
+ constructor(name) {
+ super(name);
+
+ /* Contracts */
+ 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();
+ /* Data */
+ this.validationPools = new Map();
+ this.experts = new Map();
+
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() {
- return Array.from(this.voters.values()).filter((voter) => {
+ return Array.from(this.experts.values()).filter((voter) => {
const hasVoted = !!voter.dateLastVote;
const withinThreshold = !params.activeVoterThreshold
|| new Date() - voter.dateLastVote >= params.activeVoterThreshold;
@@ -48,7 +61,7 @@ export class Bench extends Actor {
async initiateValidationPool(poolOptions, stakeOptions) {
const validationPoolNumber = this.validationPools.size + 1;
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);
await this.actions.createValidationPool.log(this, pool);
pool.activate();
diff --git a/forum-network/src/classes/expert.js b/forum-network/src/classes/expert.js
index a30f692..04f80a9 100644
--- a/forum-network/src/classes/expert.js
+++ b/forum-network/src/classes/expert.js
@@ -4,16 +4,17 @@ import { CryptoUtil } from './crypto.js';
import { ReputationHolder } from './reputation-holder.js';
export class Expert extends ReputationHolder {
- constructor(name, scene) {
- super(undefined, name, scene);
+ constructor(dao, name) {
+ super(name);
+ this.dao = dao;
this.actions = {
- submitPostViaNetwork: new Action('submit post via network', scene),
- submitPost: new Action('submit post', scene),
- initiateValidationPool: new Action('initiate validation pool', scene),
- stake: new Action('stake on post', scene),
- registerAvailability: new Action('register availability', scene),
- getAssignedWork: new Action('get assigned work', scene),
- submitWork: new Action('submit work evidence', scene),
+ submitPostViaNetwork: new Action('submit post via network'),
+ submitPost: new Action('submit post'),
+ initiateValidationPool: new Action('initiate validation pool'),
+ stake: new Action('stake on post'),
+ registerAvailability: new Action('register availability'),
+ getAssignedWork: new Action('get assigned work'),
+ submitWork: new Action('submit work evidence'),
};
this.validationPools = new Map();
this.tokens = [];
@@ -36,29 +37,29 @@ export class Expert extends ReputationHolder {
await forumNode.receiveMessage(JSON.stringify(postMessage.toJSON()));
}
- async submitPost(forum, postContent) {
+ async submitPost(postContent) {
// TODO: Include fee
- await this.actions.submitPost.log(this, forum);
- return forum.addPost(this.reputationPublicKey, postContent);
+ await this.actions.submitPost.log(this, this.dao.forum);
+ return this.dao.forum.addPost(this.reputationPublicKey, postContent);
}
- async submitPostWithFee(bench, forum, postContent, poolOptions) {
- await this.actions.submitPost.log(this, forum);
- const postId = await forum.addPost(this.reputationPublicKey, postContent);
- const pool = await this.initiateValidationPool(bench, { ...poolOptions, postId });
+ async submitPostWithFee(postContent, poolOptions) {
+ await this.actions.submitPost.log(this, this.dao.forum);
+ const postId = await this.dao.forum.addPost(this.reputationPublicKey, postContent);
+ const pool = await this.initiateValidationPool({ ...poolOptions, postId });
this.tokens.push(pool.tokenId);
return { postId, pool };
}
- async initiateValidationPool(bench, poolOptions) {
+ async initiateValidationPool(poolOptions) {
// For now, directly call bench.initiateValidationPool();
poolOptions.reputationPublicKey = this.reputationPublicKey;
await this.actions.initiateValidationPool.log(
this,
- bench,
+ this.dao,
`(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.validationPools.set(pool.id, poolOptions);
return pool;
@@ -79,23 +80,27 @@ export class Expert extends ReputationHolder {
});
}
- async registerAvailability(availability, stakeAmount, duration) {
- await this.actions.registerAvailability.log(this, availability, `(stake: ${stakeAmount}, duration: ${duration})`);
- this.workerId = await availability.register(this.reputationPublicKey, {
+ async registerAvailability(stakeAmount, duration) {
+ await this.actions.registerAvailability.log(
+ this,
+ this.dao.availability,
+ `(stake: ${stakeAmount}, duration: ${duration})`,
+ );
+ this.workerId = await this.dao.availability.register(this.reputationPublicKey, {
stakeAmount,
tokenId: this.tokens[0],
duration,
});
}
- async getAssignedWork(availability, business) {
- const requestId = await availability.getAssignedWork(this.workerId);
- const request = await business.getRequest(requestId);
+ async getAssignedWork() {
+ const requestId = await this.dao.availability.getAssignedWork(this.workerId);
+ const request = await this.dao.business.getRequest(requestId);
return request;
}
- async submitWork(business, requestId, evidence, { tokenLossRatio, duration }) {
- await this.actions.submitWork.log(this, business);
- return business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
+ async submitWork(requestId, evidence, { tokenLossRatio, duration }) {
+ await this.actions.submitWork.log(this, this.dao.business);
+ return this.dao.business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
}
}
diff --git a/forum-network/src/classes/forum-node.js b/forum-network/src/classes/forum-node.js
index 992ce33..832ad7d 100644
--- a/forum-network/src/classes/forum-node.js
+++ b/forum-network/src/classes/forum-node.js
@@ -2,17 +2,17 @@ import { Action } from './action.js';
import {
Message, PostMessage, PeerMessage, messageFromJSON,
} from './message.js';
-import { CryptoUtil } from './crypto.js';
import { ForumView } from './forum-view.js';
import { NetworkNode } from './network-node.js';
+import { randomID } from '../util.js';
export class ForumNode extends NetworkNode {
- constructor(name, scene) {
- super(name, scene);
+ constructor(name) {
+ super(name);
this.forumView = new ForumView();
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
async processPost(authorId, post) {
if (!post.id) {
- post.id = CryptoUtil.randomUUID();
+ post.id = randomID();
}
await this.actions.storePost.log(this, this);
// this.forumView.addPost(authorId, post.id, post, stake);
diff --git a/forum-network/src/classes/forum.js b/forum-network/src/classes/forum.js
index 7ea03b1..ef0b477 100644
--- a/forum-network/src/classes/forum.js
+++ b/forum-network/src/classes/forum.js
@@ -1,69 +1,28 @@
-import { Actor } from './actor.js';
import { WDAG } from './wdag.js';
import { Action } from './action.js';
-import { CryptoUtil } from './crypto.js';
import params from '../params.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 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}
-
- initial |
- ${displayNumber(this.initialValue)} |
-
- value |
- ${displayNumber(this.value)} |
-
`
- .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 {
- constructor(name, scene) {
- super(`forum_${CryptoUtil.randomUUID()}`, name, scene);
+ constructor(dao, name) {
+ super(name);
+ this.dao = dao;
this.id = this.reputationPublicKey;
- this.posts = new WDAG(scene);
+ this.posts = new WDAG();
this.actions = {
- addPost: new Action('add post', scene),
- propagateValue: new Action('propagate', this.scene),
- transfer: new Action('transfer', this.scene),
+ addPost: new Action('add post'),
+ propagateValue: new Action('propagate'),
+ transfer: new Action('transfer'),
};
}
@@ -96,10 +55,10 @@ export class Forum extends ReputationHolder {
}
async onValidate({
- bench, postId, tokenId,
+ postId, tokenId,
}) {
this.activate();
- const initialValue = bench.reputation.valueOf(tokenId);
+ const initialValue = this.dao.reputation.valueOf(tokenId);
const postVertex = this.posts.getVertex(postId);
const post = postVertex.data;
post.setStatus('Validated');
@@ -121,16 +80,16 @@ export class Forum extends ReputationHolder {
// Apply computed rewards to update values of tokens
for (const [id, value] of rewardsAccumulator) {
if (value < 0) {
- bench.reputation.transferValueFrom(id, post.tokenId, -value);
+ this.dao.reputation.transferValueFrom(id, post.tokenId, -value);
} 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
- bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
- const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
- const value = bench.reputation.valueOf(post.tokenId);
+ // Transfer ownership of the minted/staked token, from the posts to the post author
+ this.dao.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
+ const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
+ const value = this.dao.reputation.valueOf(post.tokenId);
this.actions.transfer.log(this, toActor, `(${value})`);
this.deactivate();
}
diff --git a/forum-network/src/classes/network-node.js b/forum-network/src/classes/network-node.js
index d365c9d..8e96b5f 100644
--- a/forum-network/src/classes/network-node.js
+++ b/forum-network/src/classes/network-node.js
@@ -4,11 +4,11 @@ import { CryptoUtil } from './crypto.js';
import { PrioritizedQueue } from './prioritized-queue.js';
export class NetworkNode extends Actor {
- constructor(name, scene) {
- super(name, scene);
+ constructor(name) {
+ super(name);
this.queue = new PrioritizedQueue();
this.actions = {
- peerMessage: new Action('peer message', scene),
+ peerMessage: new Action('peer message'),
};
}
diff --git a/forum-network/src/classes/post.js b/forum-network/src/classes/post.js
new file mode 100644
index 0000000..ffdfef3
--- /dev/null
+++ b/forum-network/src/classes/post.js
@@ -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}
+
+ initial |
+ ${displayNumber(this.initialValue)} |
+
+ value |
+ ${displayNumber(this.value)} |
+
`
+ .replaceAll(/\n\s*/g, '');
+ }
+}
diff --git a/forum-network/src/classes/public.js b/forum-network/src/classes/public.js
index ad25f22..1eb142a 100644
--- a/forum-network/src/classes/public.js
+++ b/forum-network/src/classes/public.js
@@ -2,10 +2,10 @@ import { Action } from './action.js';
import { Actor } from './actor.js';
export class Public extends Actor {
- constructor(name, scene) {
- super(name, scene);
+ constructor(name) {
+ super(name);
this.actions = {
- submitRequest: new Action('submit work request', scene),
+ submitRequest: new Action('submit work request'),
};
}
diff --git a/forum-network/src/classes/reputation-holder.js b/forum-network/src/classes/reputation-holder.js
index 5bfcc42..7ad195e 100644
--- a/forum-network/src/classes/reputation-holder.js
+++ b/forum-network/src/classes/reputation-holder.js
@@ -1,8 +1,9 @@
+import { randomID } from '../util.js';
import { Actor } from './actor.js';
export class ReputationHolder extends Actor {
- constructor(reputationPublicKey, name, scene) {
- super(name, scene);
- this.reputationPublicKey = reputationPublicKey;
+ constructor(name) {
+ super(name);
+ this.reputationPublicKey = `${name}_${randomID()}`;
}
}
diff --git a/forum-network/src/classes/reputation-token.js b/forum-network/src/classes/reputation-token.js
index 76ab770..a12b4e8 100644
--- a/forum-network/src/classes/reputation-token.js
+++ b/forum-network/src/classes/reputation-token.js
@@ -1,7 +1,6 @@
import { ERC721 } from './erc721.js';
-import { CryptoUtil } from './crypto.js';
-import { EPSILON } from '../util.js';
+import { EPSILON, randomID } from '../util.js';
class Lock {
constructor(tokenId, amount, duration) {
@@ -21,7 +20,7 @@ export class ReputationTokenContract extends ERC721 {
}
mint(to, value, context) {
- const tokenId = `token_${CryptoUtil.randomUUID()}`;
+ const tokenId = `token_${randomID()}`;
super.mint(to, tokenId);
this.values.set(tokenId, value);
this.histories.set(tokenId, [{ increment: value, context }]);
diff --git a/forum-network/src/classes/scene.js b/forum-network/src/classes/scene.js
index 0b0f082..e487ba1 100644
--- a/forum-network/src/classes/scene.js
+++ b/forum-network/src/classes/scene.js
@@ -173,7 +173,7 @@ export class Scene {
}
async addActor(name) {
- const actor = new Actor(name, this);
+ const actor = new Actor(name);
if (this.sequence) {
await this.sequence.log(`participant ${name}`);
}
diff --git a/forum-network/src/classes/validation-pool.js b/forum-network/src/classes/validation-pool.js
index ac2583d..e344cc9 100644
--- a/forum-network/src/classes/validation-pool.js
+++ b/forum-network/src/classes/validation-pool.js
@@ -1,4 +1,3 @@
-import { CryptoUtil } from './crypto.js';
import { ReputationHolder } from './reputation-holder.js';
import { Stake } from './stake.js';
import { Voter } from './voter.js';
@@ -16,8 +15,7 @@ const ValidationPoolStates = Object.freeze({
*/
export class ValidationPool extends ReputationHolder {
constructor(
- bench,
- forum,
+ dao,
{
postId,
reputationPublicKey,
@@ -27,14 +25,13 @@ export class ValidationPool extends ReputationHolder {
contentiousDebate = false,
},
name,
- scene,
) {
- super(`pool_${CryptoUtil.randomUUID()}`, name, scene);
+ super(name);
this.id = this.reputationPublicKey;
this.actions = {
- reward: new Action('reward', scene),
- transfer: new Action('transfer', scene),
+ reward: new Action('reward'),
+ transfer: new Action('transfer'),
};
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
@@ -59,8 +56,7 @@ export class ValidationPool extends ReputationHolder {
}]; got ${duration}`,
);
}
- this.bench = bench;
- this.forum = forum;
+ this.dao = dao;
this.postId = postId;
this.state = ValidationPoolStates.OPEN;
this.setStatus('Open');
@@ -72,7 +68,7 @@ export class ValidationPool extends ReputationHolder {
this.tokenLossRatio = tokenLossRatio;
this.contentiousDebate = contentiousDebate;
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.
// Also, author can provide additional stakes, e.g. availability stakes for work evidence post.
this.stake(this.id, {
@@ -87,9 +83,9 @@ export class ValidationPool extends ReputationHolder {
});
// 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);
- this.bench.voters.set(reputationPublicKey, voter);
+ this.dao.experts.set(reputationPublicKey, voter);
}
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!');
}
@@ -168,18 +164,18 @@ export class ValidationPool extends ReputationHolder {
this.stakes.add(stake);
// 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
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);
- this.bench.voters.set(reputationPublicKey, voter);
+ this.dao.experts.set(reputationPublicKey, voter);
}
// Update computed display values
- for (const voter of this.bench.voters.values()) {
- const actor = this.scene.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
+ for (const voter of this.dao.experts.values()) {
+ const actor = window?.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
await actor.computeValues();
}
}
@@ -189,7 +185,7 @@ export class ValidationPool extends ReputationHolder {
// 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);
+ this.dao.reputation.lock(tokenId, amount, lockingTime);
// 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 downvoteValue = this.getTotalValueOfStakesForOutcome(false);
- const activeAvailableReputation = this.bench.getActiveAvailableReputation();
+ const activeAvailableReputation = this.dao.getActiveAvailableReputation();
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
@@ -220,13 +216,13 @@ export class ValidationPool extends ReputationHolder {
if (quorumMet) {
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();
await this.distributeReputation({ votePasses });
// TODO: distribute fees
} else {
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();
@@ -250,26 +246,25 @@ export class ValidationPool extends ReputationHolder {
const value = stake.getStakeValue();
const reward = tokensForWinners * (value / totalValueOfStakesForWin);
// 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}`);
- this.bench.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
- const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === reputationPublicKey);
+ this.dao.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
+ const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === reputationPublicKey);
this.actions.reward.log(this, toActor, `(${reward})`);
}
- if (votePasses && !!this.forum) {
+ if (votePasses) {
// 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)}`);
+ 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
- this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId);
- const value = this.bench.reputation.valueOf(this.tokenId);
- this.actions.transfer.log(this, this.forum, `(${value})`);
+ this.dao.reputation.transferFrom(this.id, this.dao.forum.id, this.tokenId);
+ const value = this.dao.reputation.valueOf(this.tokenId);
+ this.actions.transfer.log(this, this.dao.forum, `(${value})`);
// Recurse through forum to determine reputation effects
- await this.forum.onValidate({
- bench: this.bench,
+ await this.dao.forum.onValidate({
pool: this,
postId: this.postId,
tokenId: this.tokenId,
@@ -279,15 +274,12 @@ export class ValidationPool extends ReputationHolder {
console.log('pool complete');
// Update computed display values
- for (const voter of this.bench.voters.values()) {
- const actor = this.scene.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
+ for (const voter of this.dao.experts.values()) {
+ const actor = window?.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
await actor.computeValues();
}
- await this.bench.computeValues();
- if (this.forum) {
- await this.forum.computeValues();
- }
+ await this.dao.forum.computeValues();
- this.scene.stateToTable(`validation pool ${this.name} complete`);
+ window?.scene?.stateToTable(`validation pool ${this.name} complete`);
}
}
diff --git a/forum-network/src/classes/wdag.js b/forum-network/src/classes/wdag.js
index 06e3dc0..28871b8 100644
--- a/forum-network/src/classes/wdag.js
+++ b/forum-network/src/classes/wdag.js
@@ -25,17 +25,16 @@ export class Edge {
}
export class WDAG {
- constructor(scene) {
- this.scene = scene;
+ constructor() {
this.vertices = new Map();
this.edgeLabels = new Map();
this.nextVertexId = 0;
- this.flowchart = scene?.flowchart ?? null;
+ this.flowchart = window?.scene?.flowchart ?? null;
}
withFlowchart() {
- this.scene.withAdditionalFlowchart();
- this.flowchart = this.scene.lastFlowchart();
+ window?.scene?.withAdditionalFlowchart();
+ this.flowchart = window?.scene?.lastFlowchart();
return this;
}
@@ -71,7 +70,7 @@ export class WDAG {
}
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) {
@@ -122,7 +121,7 @@ export class WDAG {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(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);
from.edges.from.push(edge);
diff --git a/forum-network/src/tests/availability.test.html b/forum-network/src/tests/availability.test.html
index ac9ffcb..8c4f609 100644
--- a/forum-network/src/tests/availability.test.html
+++ b/forum-network/src/tests/availability.test.html
@@ -4,187 +4,7 @@
-
+
-
diff --git a/forum-network/src/tests/basic.test.html b/forum-network/src/tests/basic.test.html
index 8bd95c1..47380f4 100644
--- a/forum-network/src/tests/basic.test.html
+++ b/forum-network/src/tests/basic.test.html
@@ -26,7 +26,7 @@
}
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 node1Status = scene.addDisplayValue('Node 1 Status');
const blockchainStatus = scene.addDisplayValue('Blockchain Status');
diff --git a/forum-network/src/tests/business.test.html b/forum-network/src/tests/business.test.html
new file mode 100644
index 0000000..c46ff0f
--- /dev/null
+++ b/forum-network/src/tests/business.test.html
@@ -0,0 +1,27 @@
+
+
+ Business
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/forum-network/src/tests/forum1.test.html b/forum-network/src/tests/forum1.test.html
index 1de1bba..5ddcaf8 100644
--- a/forum-network/src/tests/forum1.test.html
+++ b/forum-network/src/tests/forum1.test.html
@@ -16,7 +16,7 @@