Reorganization
This commit is contained in:
parent
4eced65dbf
commit
fc27cda81d
|
@ -2,9 +2,17 @@ module.exports = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true,
|
es2021: true,
|
||||||
|
mocha: true,
|
||||||
},
|
},
|
||||||
extends: ['airbnb-base'],
|
extends: ['airbnb-base'],
|
||||||
overrides: [],
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.test.js'],
|
||||||
|
rules: {
|
||||||
|
'no-unused-expressions': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
@ -33,16 +41,8 @@ module.exports = {
|
||||||
globals: {
|
globals: {
|
||||||
_: 'readonly',
|
_: 'readonly',
|
||||||
chai: 'readonly',
|
chai: 'readonly',
|
||||||
expect: 'readonly',
|
sinon: 'readonly',
|
||||||
mocha: 'readonly',
|
sinonChai: 'readonly',
|
||||||
describe: 'readonly',
|
|
||||||
context: 'readonly',
|
|
||||||
it: 'readonly',
|
|
||||||
specify: 'readonly',
|
|
||||||
should: 'readonly',
|
should: 'readonly',
|
||||||
before: 'readonly',
|
|
||||||
after: 'readonly',
|
|
||||||
beforeEach: 'readonly',
|
|
||||||
afterEach: 'readonly',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
A DAO is a group of cooperating entities
|
|
@ -1,6 +1,6 @@
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from '../util/crypto.js';
|
||||||
|
|
||||||
class Worker {
|
class Worker {
|
||||||
constructor(reputationPublicKey, tokenId, stakeAmount, duration) {
|
constructor(reputationPublicKey, tokenId, stakeAmount, duration) {
|
||||||
|
@ -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(dao, name) {
|
constructor(dao, name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
assignWork: new Action('assign work'),
|
assignWork: new Action('assign work', scene),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.workers = new Map();
|
this.workers = new Map();
|
|
@ -1,7 +1,7 @@
|
||||||
import { randomID } from '../util.js';
|
import { randomID } from '../../util.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
import { PostContent } from './post-content.js';
|
import { PostContent } from '../util/post-content.js';
|
||||||
|
|
||||||
class Request {
|
class Request {
|
||||||
static nextSeq = 0;
|
static nextSeq = 0;
|
||||||
|
@ -20,14 +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(dao, name) {
|
constructor(dao, name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
assignWork: new Action('assign work'),
|
assignWork: new Action('assign work', scene),
|
||||||
submitPost: new Action('submit post'),
|
submitPost: new Action('submit post', scene),
|
||||||
initiateValidationPool: new Action('initiate validation pool'),
|
initiateValidationPool: new Action('initiate validation pool', scene),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.requests = new Map();
|
this.requests = new Map();
|
|
@ -1,11 +1,10 @@
|
||||||
import { Action } from './action.js';
|
import params from '../../params.js';
|
||||||
import params from '../params.js';
|
|
||||||
import { Forum } from './forum.js';
|
import { Forum } from './forum.js';
|
||||||
import { ReputationTokenContract } from './reputation-token.js';
|
import { ReputationTokenContract } from '../contracts/reputation-token.js';
|
||||||
import { ValidationPool } from './validation-pool.js';
|
import { ValidationPool } from './validation-pool.js';
|
||||||
import { Availability } from './availability.js';
|
import { Availability } from './availability.js';
|
||||||
import { Business } from './business.js';
|
import { Business } from './business.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purpose:
|
* Purpose:
|
||||||
|
@ -14,13 +13,13 @@ import { Actor } from './actor.js';
|
||||||
* - Reputation: Keep track of reputation accrued to each expert
|
* - Reputation: Keep track of reputation accrued to each expert
|
||||||
*/
|
*/
|
||||||
export class DAO extends Actor {
|
export class DAO extends Actor {
|
||||||
constructor(name) {
|
constructor(name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
|
|
||||||
/* Contracts */
|
/* Contracts */
|
||||||
this.forum = new Forum(this, 'Forum');
|
this.forum = new Forum(this, 'Forum', scene);
|
||||||
this.availability = new Availability(this, 'Availability');
|
this.availability = new Availability(this, 'Availability', scene);
|
||||||
this.business = new Business(this, 'Business');
|
this.business = new Business(this, 'Business', scene);
|
||||||
this.reputation = new ReputationTokenContract();
|
this.reputation = new ReputationTokenContract();
|
||||||
|
|
||||||
/* Data */
|
/* Data */
|
||||||
|
@ -59,9 +58,8 @@ export class DAO 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, poolOptions, name);
|
const pool = new ValidationPool(this, poolOptions, name, this.scene);
|
||||||
this.validationPools.set(pool.id, pool);
|
this.validationPools.set(pool.id, pool);
|
||||||
pool.activate();
|
|
||||||
|
|
||||||
if (stakeOptions) {
|
if (stakeOptions) {
|
||||||
const { reputationPublicKey, tokenId, authorStakeAmount } = stakeOptions;
|
const { reputationPublicKey, tokenId, authorStakeAmount } = stakeOptions;
|
||||||
|
@ -74,4 +72,9 @@ export class DAO extends Actor {
|
||||||
|
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async submitPost(reputationPublicKey, postContent) {
|
||||||
|
const post = await this.forum.addPost(reputationPublicKey, postContent);
|
||||||
|
return post.id;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { PostMessage } from './message.js';
|
import { PostMessage } from '../forum-network/message.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from '../util/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(dao, name) {
|
constructor(dao, name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
this.actions = {
|
this.actions = {
|
||||||
submitPostViaNetwork: new Action('submit post via network'),
|
submitPostViaNetwork: new Action('submit post via network', scene),
|
||||||
submitPost: new Action('submit post'),
|
submitPost: new Action('submit post', scene),
|
||||||
initiateValidationPool: new Action('initiate validation pool'),
|
initiateValidationPool: new Action('initiate validation pool', scene),
|
||||||
stake: new Action('stake on post'),
|
stake: new Action('stake on post', scene),
|
||||||
registerAvailability: new Action('register availability'),
|
registerAvailability: new Action('register availability', scene),
|
||||||
getAssignedWork: new Action('get assigned work'),
|
getAssignedWork: new Action('get assigned work', scene),
|
||||||
submitWork: new Action('submit work evidence'),
|
submitWork: new Action('submit work evidence', scene),
|
||||||
};
|
};
|
||||||
this.validationPools = new Map();
|
this.validationPools = new Map();
|
||||||
this.tokens = [];
|
this.tokens = [];
|
|
@ -1,8 +1,8 @@
|
||||||
import { WDAG } from './wdag.js';
|
import { WDAG } from '../supporting/wdag.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import params from '../params.js';
|
import params from '../../params.js';
|
||||||
import { ReputationHolder } from './reputation-holder.js';
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
import { EPSILON } from '../util.js';
|
import { displayNumber, EPSILON } from '../../util.js';
|
||||||
import { Post } from './post.js';
|
import { Post } from './post.js';
|
||||||
|
|
||||||
const CITATION = 'citation';
|
const CITATION = 'citation';
|
||||||
|
@ -14,14 +14,16 @@ const BALANCE = 'balance';
|
||||||
* and the value accrued via each post and citation.
|
* and the value accrued via each post and citation.
|
||||||
*/
|
*/
|
||||||
export class Forum extends ReputationHolder {
|
export class Forum extends ReputationHolder {
|
||||||
constructor(dao, name) {
|
constructor(dao, name, scene) {
|
||||||
super(name);
|
|
||||||
|
super(name, scene);
|
||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
this.id = this.reputationPublicKey;
|
this.id = this.reputationPublicKey;
|
||||||
this.posts = new WDAG();
|
this.posts = new WDAG();
|
||||||
this.actions = {
|
this.actions = {
|
||||||
propagateValue: new Action('propagate'),
|
propagate: new Action('propagate', scene),
|
||||||
transfer: new Action('transfer'),
|
confirm: new Action('confirm', scene),
|
||||||
|
transfer: new Action('transfer', scene),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ export class Forum extends ReputationHolder {
|
||||||
|
|
||||||
// Transfer ownership of the minted/staked token, from the posts to the post author
|
// Transfer ownership of the minted/staked token, from the posts to the post author
|
||||||
this.dao.reputation.transfer(this.id, post.authorPublicKey, post.tokenId);
|
this.dao.reputation.transfer(this.id, post.authorPublicKey, post.tokenId);
|
||||||
// const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
// const toActor = this.scene?.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
|
||||||
// const value = this.dao.reputation.valueOf(post.tokenId);
|
// const value = this.dao.reputation.valueOf(post.tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +100,10 @@ export class Forum extends ReputationHolder {
|
||||||
}) {
|
}) {
|
||||||
const postVertex = edge.to;
|
const postVertex = edge.to;
|
||||||
const post = postVertex?.data;
|
const post = postVertex?.data;
|
||||||
this.actions.propagateValue.log(edge.from.data, post, `(${increment})`);
|
this.actions.propagate.log(edge.from.data, post, `(${increment})`);
|
||||||
|
|
||||||
if (!!params.referenceChainLimit && depth > params.referenceChainLimit) {
|
if (!!params.referenceChainLimit && depth > params.referenceChainLimit) {
|
||||||
this.actions.propagateValue.log(
|
this.actions.propagate.log(
|
||||||
edge.from.data,
|
edge.from.data,
|
||||||
post,
|
post,
|
||||||
`referenceChainLimit (${params.referenceChainLimit}) reached`,
|
`referenceChainLimit (${params.referenceChainLimit}) reached`,
|
||||||
|
@ -141,9 +143,18 @@ export class Forum extends ReputationHolder {
|
||||||
depth: depth + 1,
|
depth: depth + 1,
|
||||||
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
|
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
|
||||||
});
|
});
|
||||||
|
|
||||||
outboundAmount -= refundFromOutbound;
|
outboundAmount -= refundFromOutbound;
|
||||||
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
|
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
|
||||||
totalOutboundAmount += outboundAmount;
|
totalOutboundAmount += outboundAmount;
|
||||||
|
|
||||||
|
this.actions.confirm.log(
|
||||||
|
citationEdge.to.data,
|
||||||
|
citationEdge.from.data,
|
||||||
|
`(refund: ${displayNumber(refundFromOutbound)}, leach: ${outboundAmount * params.leachingValue})`,
|
||||||
|
undefined,
|
||||||
|
'-->>',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return totalOutboundAmount;
|
return totalOutboundAmount;
|
|
@ -1,6 +1,6 @@
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
import { displayNumber } from '../util.js';
|
import { displayNumber } from '../../util.js';
|
||||||
import params from '../params.js';
|
import params from '../../params.js';
|
||||||
|
|
||||||
export class Post extends Actor {
|
export class Post extends Actor {
|
||||||
constructor(forum, authorPublicKey, postContent) {
|
constructor(forum, authorPublicKey, postContent) {
|
|
@ -1,11 +1,11 @@
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
|
|
||||||
export class Public extends Actor {
|
export class Public extends Actor {
|
||||||
constructor(name) {
|
constructor(name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.actions = {
|
this.actions = {
|
||||||
submitRequest: new Action('submit work request'),
|
submitRequest: new Action('submit work request', scene),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { randomID } from '../../util.js';
|
||||||
|
import { Actor } from '../display/actor.js';
|
||||||
|
|
||||||
|
export class ReputationHolder extends Actor {
|
||||||
|
constructor(name, scene) {
|
||||||
|
super(name, scene);
|
||||||
|
this.reputationPublicKey = `${name}_${randomID()}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import { ReputationHolder } from './reputation-holder.js';
|
import { ReputationHolder } from './reputation-holder.js';
|
||||||
import { Stake } from './stake.js';
|
import { Stake } from '../supporting/stake.js';
|
||||||
import { Voter } from './voter.js';
|
import { Voter } from '../supporting/voter.js';
|
||||||
import params from '../params.js';
|
import params from '../../params.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
|
import { displayNumber } from '../../util.js';
|
||||||
|
|
||||||
const ValidationPoolStates = Object.freeze({
|
const ValidationPoolStates = Object.freeze({
|
||||||
OPEN: 'OPEN',
|
OPEN: 'OPEN',
|
||||||
|
@ -25,13 +26,15 @@ export class ValidationPool extends ReputationHolder {
|
||||||
contentiousDebate = false,
|
contentiousDebate = false,
|
||||||
},
|
},
|
||||||
name,
|
name,
|
||||||
|
scene,
|
||||||
) {
|
) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.id = this.reputationPublicKey;
|
this.id = this.reputationPublicKey;
|
||||||
|
|
||||||
this.actions = {
|
this.actions = {
|
||||||
reward: new Action('reward'),
|
reward: new Action('reward', scene),
|
||||||
transfer: new Action('transfer'),
|
transfer: new Action('transfer', scene),
|
||||||
|
mint: new Action('mint', scene),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
// If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio()
|
||||||
|
@ -82,10 +85,14 @@ export class ValidationPool extends ReputationHolder {
|
||||||
tokenId: this.tokenId,
|
tokenId: this.tokenId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.actions.mint.log(this, this, `(${this.mintedValue})`);
|
||||||
|
|
||||||
// Keep a record of voters and their votes
|
// Keep a record of voters and their votes
|
||||||
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
||||||
voter.addVoteRecord(this);
|
voter.addVoteRecord(this);
|
||||||
this.dao.experts.set(reputationPublicKey, voter);
|
this.dao.experts.set(reputationPublicKey, voter);
|
||||||
|
|
||||||
|
this.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTokenLossRatio() {
|
getTokenLossRatio() {
|
||||||
|
@ -167,15 +174,13 @@ export class ValidationPool extends ReputationHolder {
|
||||||
this.dao.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 (reputationPublicKey !== this.id) {
|
||||||
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey);
|
||||||
voter.addVoteRecord(this);
|
voter.addVoteRecord(this);
|
||||||
this.dao.experts.set(reputationPublicKey, voter);
|
this.dao.experts.set(reputationPublicKey, voter);
|
||||||
}
|
|
||||||
|
|
||||||
// Update computed display values
|
// Update computed display 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,16 +221,28 @@ export class ValidationPool extends ReputationHolder {
|
||||||
|
|
||||||
if (quorumMet) {
|
if (quorumMet) {
|
||||||
this.setStatus(`Resolved - ${votePasses ? 'Won' : 'Lost'}`);
|
this.setStatus(`Resolved - ${votePasses ? 'Won' : 'Lost'}`);
|
||||||
window?.scene?.sequence.log(`note over ${this.name} : ${votePasses ? 'Win' : 'Lose'}`);
|
this.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');
|
||||||
window?.scene?.sequence.log(`note over ${this.name} : Quorum not met`);
|
this.scene?.sequence.log(`note over ${this.name} : Quorum not met`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deactivate();
|
// Update computed display values
|
||||||
|
for (const voter of this.dao.experts.values()) {
|
||||||
|
const actor = this.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
||||||
|
if (!actor) {
|
||||||
|
throw new Error('Actor not found!');
|
||||||
|
}
|
||||||
|
await actor.computeValues();
|
||||||
|
}
|
||||||
|
await this.dao.computeValues();
|
||||||
|
|
||||||
|
this.scene?.stateToTable(`validation pool ${this.name} complete`);
|
||||||
|
|
||||||
|
await this.deactivate();
|
||||||
this.state = ValidationPoolStates.RESOLVED;
|
this.state = ValidationPoolStates.RESOLVED;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -249,8 +266,8 @@ export class ValidationPool extends ReputationHolder {
|
||||||
const reputationPublicKey = this.dao.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.dao.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
|
this.dao.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount);
|
||||||
const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === reputationPublicKey);
|
const toActor = this.scene?.findActor((actor) => actor.reputationPublicKey === reputationPublicKey);
|
||||||
this.actions.reward.log(this, toActor, `(${reward})`);
|
this.actions.reward.log(this, toActor, `(${displayNumber(reward)})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (votePasses) {
|
if (votePasses) {
|
||||||
|
@ -272,14 +289,5 @@ export class ValidationPool extends ReputationHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('pool complete');
|
console.log('pool complete');
|
||||||
|
|
||||||
// Update computed display values
|
|
||||||
for (const voter of this.dao.experts.values()) {
|
|
||||||
const actor = window?.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey);
|
|
||||||
await actor.computeValues();
|
|
||||||
}
|
|
||||||
await this.dao.computeValues();
|
|
||||||
|
|
||||||
window?.scene?.stateToTable(`validation pool ${this.name} complete`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { ERC721 } from './erc721.js';
|
import { ERC721 } from './erc721.js';
|
||||||
|
|
||||||
import { EPSILON, randomID } from '../util.js';
|
import { EPSILON, randomID } from '../../util.js';
|
||||||
|
|
||||||
class Lock {
|
class Lock {
|
||||||
constructor(tokenId, amount, duration) {
|
constructor(tokenId, amount, duration) {
|
|
@ -1,10 +1,11 @@
|
||||||
export class Action {
|
export class Action {
|
||||||
constructor(name) {
|
constructor(name, scene) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.scene = scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
async log(src, dest, msg, obj, symbol = '->>') {
|
async log(src, dest, msg, obj, symbol = '->>') {
|
||||||
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,20 +1,22 @@
|
||||||
import { displayNumber } from '../util.js';
|
import { displayNumber } from '../../util.js';
|
||||||
|
|
||||||
export class Actor {
|
export class Actor {
|
||||||
constructor(name) {
|
constructor(name, scene) {
|
||||||
|
if (!scene) throw new Error('An actor without a scene!');
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.scene = scene;
|
||||||
this.callbacks = new Map();
|
this.callbacks = new Map();
|
||||||
this.status = window?.scene?.addDisplayValue(`${this.name} status`);
|
this.status = 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;
|
||||||
window?.scene?.registerActor(this);
|
scene?.registerActor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
this.active += 1;
|
this.active += 1;
|
||||||
window?.scene?.sequence.log(`activate ${this.name}`, false);
|
this.scene?.sequence?.activate(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deactivate() {
|
async deactivate() {
|
||||||
|
@ -22,7 +24,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 window?.scene?.sequence.log(`deactivate ${this.name}`);
|
await this.scene?.sequence?.deactivate(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(dest, action, detail) {
|
async send(dest, action, detail) {
|
||||||
|
@ -35,7 +37,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(
|
||||||
`[${window?.scene?.name} actor ${this.name} does not have a callback registered for ${action.name}`,
|
`[${this.scene?.name} actor ${this.name} does not have a callback registered for ${action.name}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await cb(src, detail);
|
await cb(src, detail);
|
||||||
|
@ -52,41 +54,32 @@ export class Actor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addValue(label, fn) {
|
async addComputedValue(label, fn) {
|
||||||
this.values.set(label, window?.scene?.addDisplayValue(`${this.name} ${label}`));
|
this.values.set(label, this.scene?.addDisplayValue(`${this.name} ${label}`));
|
||||||
if (fn) {
|
if (fn) {
|
||||||
this.valueFunctions.set(label, fn);
|
this.valueFunctions.set(label, fn);
|
||||||
|
await this.computeValues();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setValue(label, value) {
|
async setValue(label, value) {
|
||||||
if (typeof value === 'number') {
|
if (typeof value === 'function') {
|
||||||
value = displayNumber(value);
|
return this.addComputedValue(label, value);
|
||||||
}
|
|
||||||
let displayValue = this.values.get(label);
|
|
||||||
if (!displayValue) {
|
|
||||||
displayValue = window?.scene?.addDisplayValue(`${this.name} ${label}`);
|
|
||||||
this.values.set(label, displayValue);
|
|
||||||
}
|
}
|
||||||
|
const displayValue = this.values.get(label) ?? this.scene?.addDisplayValue(`${this.name} ${label}`);
|
||||||
if (value !== displayValue.get()) {
|
if (value !== displayValue.get()) {
|
||||||
await window?.scene?.sequence.log(`note over ${this.name} : ${label} = ${value}`);
|
await this.scene?.sequence?.log(`note over ${this.name} : ${label} = ${displayNumber(value)}`);
|
||||||
}
|
}
|
||||||
displayValue.set(value);
|
displayValue.set(value);
|
||||||
|
this.values.set(label, displayValue);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async computeValues() {
|
async computeValues() {
|
||||||
for (const [label, fn] of this.valueFunctions.entries()) {
|
for (const [label, fn] of this.valueFunctions.entries()) {
|
||||||
const displayValue = this.values.get(label);
|
const value = fn();
|
||||||
let value = fn();
|
await this.setValue(label, value);
|
||||||
if (typeof value === 'number') {
|
|
||||||
value = displayNumber(value);
|
|
||||||
}
|
|
||||||
if (value !== displayValue.get()) {
|
|
||||||
await window?.scene?.sequence.log(`note over ${this.name} : ${label} = ${value}`);
|
|
||||||
}
|
|
||||||
displayValue.set(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { DisplayValue } from './display-value.js';
|
import { DisplayValue } from './display-value.js';
|
||||||
import { randomID } from '../util.js';
|
import { randomID } from '../../util.js';
|
||||||
|
|
||||||
export class Box {
|
export class Box {
|
||||||
constructor(name, parentEl, elementType = 'div') {
|
constructor(name, parentEl, elementType = 'div') {
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { displayNumber } from '../../util.js';
|
||||||
|
|
||||||
export class DisplayValue {
|
export class DisplayValue {
|
||||||
constructor(name, box) {
|
constructor(name, box) {
|
||||||
this.value = undefined;
|
this.value = undefined;
|
||||||
|
@ -9,7 +11,7 @@ export class DisplayValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.valueBox.setInnerHTML(this.value);
|
this.valueBox.setInnerHTML(typeof this.value === 'number' ? displayNumber(this.value, 6) : this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value) {
|
set(value) {
|
|
@ -0,0 +1,65 @@
|
||||||
|
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
|
||||||
|
import { debounce } from '../../util.js';
|
||||||
|
|
||||||
|
export class MermaidDiagram {
|
||||||
|
constructor(box, logBox) {
|
||||||
|
this.box = box;
|
||||||
|
this.container = this.box.addBox('Container');
|
||||||
|
this.element = this.box.addBox('Element');
|
||||||
|
this.renderBox = this.box.addBox('Render');
|
||||||
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
|
this.logBoxPre = logBox.el.appendChild(document.createElement('pre'));
|
||||||
|
this.inSection = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static initializeAPI() {
|
||||||
|
mermaid.mermaidAPI.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
theme: 'base',
|
||||||
|
themeVariables: {
|
||||||
|
darkMode: true,
|
||||||
|
primaryColor: '#2a5b6c',
|
||||||
|
primaryTextColor: '#b6b6b6',
|
||||||
|
// lineColor: '#349cbd',
|
||||||
|
lineColor: '#57747d',
|
||||||
|
signalColor: '#57747d',
|
||||||
|
// signalColor: '#349cbd',
|
||||||
|
noteBkgColor: '#516f77',
|
||||||
|
noteTextColor: '#cecece',
|
||||||
|
activationBkgColor: '#1d3f49',
|
||||||
|
activationBorderColor: '#569595',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async log(msg, render = true) {
|
||||||
|
if (this.logBoxPre.textContent && !this.logBoxPre.textContent.endsWith('\n')) {
|
||||||
|
this.logBoxPre.textContent = `${this.logBoxPre.textContent}\n`;
|
||||||
|
}
|
||||||
|
this.logBoxPre.textContent = `${this.logBoxPre.textContent}${msg}\n`;
|
||||||
|
if (render) {
|
||||||
|
await this.render();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getText() {
|
||||||
|
return this.logBoxPre.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
async render() {
|
||||||
|
return debounce(async () => {
|
||||||
|
const text = this.getText();
|
||||||
|
try {
|
||||||
|
const graph = await mermaid.mermaidAPI.render(
|
||||||
|
this.element.getId(),
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
this.renderBox.setInnerHTML(graph);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`render text:\n${text}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { Action } from './action.js';
|
||||||
|
import { CryptoUtil } from '../util/crypto.js';
|
||||||
|
import { MermaidDiagram } from './mermaid.js';
|
||||||
|
import { SequenceDiagram } from './sequence.js';
|
||||||
|
import { Table } from './table.js';
|
||||||
|
|
||||||
|
export class Scene {
|
||||||
|
constructor(name, rootBox) {
|
||||||
|
this.name = name;
|
||||||
|
this.box = rootBox.addBox(name);
|
||||||
|
this.titleBox = this.box.addBox('Title').setInnerHTML(name);
|
||||||
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
|
this.topSection = this.box.addBox('Top section').flex();
|
||||||
|
this.displayValuesBox = this.topSection.addBox('Values');
|
||||||
|
this.middleSection = this.box.addBox('Middle section');
|
||||||
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
|
this.actors = new Set();
|
||||||
|
this.dateStart = new Date();
|
||||||
|
this.flowcharts = new Map();
|
||||||
|
|
||||||
|
MermaidDiagram.initializeAPI();
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
edgeNodeColor: '#4d585c',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
withSequenceDiagram() {
|
||||||
|
const box = this.box.addBox('Sequence diagram');
|
||||||
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
|
const logBox = this.box.addBox('Sequence diagram text').addClass('dim');
|
||||||
|
this.sequence = new SequenceDiagram(box, logBox);
|
||||||
|
this.sequence.log('sequenceDiagram', false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withFlowchart({ direction = 'BT' } = {}) {
|
||||||
|
const box = this.topSection.addBox('Flowchart').addClass('padded');
|
||||||
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
|
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
||||||
|
this.flowchart = new MermaidDiagram(box, logBox);
|
||||||
|
this.flowchart.log(`graph ${direction}`, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) {
|
||||||
|
const index = this.flowcharts.size;
|
||||||
|
name = name ?? `Flowchart ${index}`;
|
||||||
|
id = id ?? `flowchart_${CryptoUtil.randomUUID().slice(0, 4)}`;
|
||||||
|
const container = this.middleSection.addBox(name).flex();
|
||||||
|
const box = container.addBox('Flowchart').addClass('padded');
|
||||||
|
const logBox = container.addBox('Flowchart text').addClass('dim');
|
||||||
|
const flowchart = new MermaidDiagram(box, logBox);
|
||||||
|
flowchart.log(`graph ${direction}`, false);
|
||||||
|
this.flowcharts.set(id, flowchart);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFlowchart() {
|
||||||
|
if (!this.flowcharts.size) {
|
||||||
|
if (this.flowchart) {
|
||||||
|
return this.flowchart;
|
||||||
|
}
|
||||||
|
throw new Error('lastFlowchart: No additional flowcharts have been added.');
|
||||||
|
}
|
||||||
|
const flowcharts = Array.from(this.flowcharts.values());
|
||||||
|
return flowcharts[flowcharts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
withTable() {
|
||||||
|
if (this.table) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
const box = this.middleSection.addBox('Table').addClass('padded');
|
||||||
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
|
this.table = new Table(box);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerActor(actor) {
|
||||||
|
this.actors.add(actor);
|
||||||
|
// this.sequence?.log(`participant ${actor.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
findActor(fn) {
|
||||||
|
return Array.from(this.actors.values()).find(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAction(name) {
|
||||||
|
const action = new Action(name, this);
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDisplayValue(name) {
|
||||||
|
const dv = this.displayValuesBox.addDisplayValue(name);
|
||||||
|
return dv;
|
||||||
|
}
|
||||||
|
|
||||||
|
stateToTable(label) {
|
||||||
|
const row = new Map();
|
||||||
|
const columns = [];
|
||||||
|
columns.push({ key: 'seqNum', title: '#' });
|
||||||
|
columns.push({ key: 'elapsedMs', title: 'Time (ms)' });
|
||||||
|
row.set('seqNum', this.table.rows.length + 1);
|
||||||
|
row.set('elapsedMs', new Date() - this.dateStart);
|
||||||
|
row.set('label', label);
|
||||||
|
for (const actor of this.actors) {
|
||||||
|
for (const [aKey, { name, value }] of actor.getValuesMap()) {
|
||||||
|
const key = `${actor.name}:${aKey}`;
|
||||||
|
columns.push({ key, title: name });
|
||||||
|
row.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columns.push({ key: 'label', title: '' });
|
||||||
|
this.table.setColumns(columns);
|
||||||
|
this.table.addRow(row);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { hexToRGB } from '../../util.js';
|
||||||
|
import { MermaidDiagram } from './mermaid.js';
|
||||||
|
|
||||||
|
export class SequenceDiagram extends MermaidDiagram {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.activations = [];
|
||||||
|
this.sections = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async log(...args) {
|
||||||
|
this.sections.forEach(async (section, index) => {
|
||||||
|
const {
|
||||||
|
empty, r, g, b,
|
||||||
|
} = section;
|
||||||
|
if (empty) {
|
||||||
|
section.empty = false;
|
||||||
|
await super.log(`rect rgb(${r}, ${g}, ${b}) # ${index}`, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return super.log(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(name) {
|
||||||
|
this.log(`activate ${name}`, false);
|
||||||
|
this.activations.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deactivate(name) {
|
||||||
|
const index = this.activations.findLastIndex((n) => n === name);
|
||||||
|
if (index === -1) throw new Error(`${name} does not appear to be active!`);
|
||||||
|
this.activations.splice(index, 1);
|
||||||
|
await this.log(`deactivate ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeactivationsText() {
|
||||||
|
const text = Array.from(this.activations).reverse().reduce((str, name) => str += `deactivate ${name}\n`, '');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async startSection(color = '#08252c') {
|
||||||
|
let { r, g, b } = hexToRGB(color);
|
||||||
|
for (let i = 0; i < this.sections.length; i++) {
|
||||||
|
r += (0xff - r) / 16;
|
||||||
|
g += (0xff - g) / 16;
|
||||||
|
b += (0xff - b) / 16;
|
||||||
|
}
|
||||||
|
this.sections.push({
|
||||||
|
empty: true, r, g, b,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async endSection() {
|
||||||
|
const section = this.sections.pop();
|
||||||
|
if (section && !section.empty) {
|
||||||
|
await this.log('end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSectionEndText() {
|
||||||
|
if (this.sections[this.sections.length - 1]?.empty) {
|
||||||
|
this.sections.pop();
|
||||||
|
}
|
||||||
|
return this.sections.map(() => 'end\n').join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
getText() {
|
||||||
|
let text = super.getText();
|
||||||
|
if (!text.endsWith('\n')) text = `${text}\n`;
|
||||||
|
text += this.getDeactivationsText();
|
||||||
|
text += this.getSectionEndText();
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { displayNumber } from '../../util.js';
|
||||||
|
|
||||||
|
export class Table {
|
||||||
|
constructor(box) {
|
||||||
|
this.box = box;
|
||||||
|
this.columns = [];
|
||||||
|
this.rows = [];
|
||||||
|
this.table = box.el.appendChild(document.createElement('table'));
|
||||||
|
this.headings = this.table.appendChild(document.createElement('tr'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setColumns(columns) {
|
||||||
|
if (JSON.stringify(columns) === JSON.stringify(this.columns)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.columns.length) {
|
||||||
|
this.table.innerHTML = '';
|
||||||
|
this.headings = this.table.appendChild(document.createElement('tr'));
|
||||||
|
this.columns = [];
|
||||||
|
}
|
||||||
|
this.columns = columns;
|
||||||
|
for (const { title } of columns) {
|
||||||
|
const heading = document.createElement('th');
|
||||||
|
this.headings.appendChild(heading);
|
||||||
|
heading.innerHTML = title ?? '';
|
||||||
|
}
|
||||||
|
if (this.rows.length) {
|
||||||
|
const { rows } = this;
|
||||||
|
this.rows = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
this.addRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addRow(rowMap) {
|
||||||
|
this.rows.push(rowMap);
|
||||||
|
const row = this.table.appendChild(document.createElement('tr'));
|
||||||
|
for (const { key } of this.columns) {
|
||||||
|
const value = rowMap.get(key);
|
||||||
|
const cell = row.appendChild(document.createElement('td'));
|
||||||
|
cell.innerHTML = typeof value === 'number' ? displayNumber(value) : value ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import {
|
import {
|
||||||
Message, PostMessage, PeerMessage, messageFromJSON,
|
Message, PostMessage, PeerMessage, messageFromJSON,
|
||||||
} from './message.js';
|
} from './message.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';
|
import { randomID } from '../../util.js';
|
||||||
|
|
||||||
export class ForumNode extends NetworkNode {
|
export class ForumNode extends NetworkNode {
|
||||||
constructor(name) {
|
constructor(name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.forumView = new ForumView();
|
this.forumView = new ForumView();
|
||||||
this.actions = {
|
this.actions = {
|
||||||
...this.actions,
|
...this.actions,
|
||||||
storePost: new Action('store post'),
|
storePost: new Action('store post', scene),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { WDAG } from './wdag.js';
|
import { WDAG } from '../supporting/wdag.js';
|
||||||
|
|
||||||
class Author {
|
class Author {
|
||||||
constructor() {
|
constructor() {
|
|
@ -1,5 +1,5 @@
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from '../util/crypto.js';
|
||||||
import { PostContent } from './post-content.js';
|
import { PostContent } from '../util/post-content.js';
|
||||||
|
|
||||||
export class Message {
|
export class Message {
|
||||||
constructor(content) {
|
constructor(content) {
|
|
@ -1,14 +1,14 @@
|
||||||
import { Actor } from './actor.js';
|
import { Actor } from '../display/actor.js';
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
import { CryptoUtil } from './crypto.js';
|
import { CryptoUtil } from '../util/crypto.js';
|
||||||
import { PrioritizedQueue } from './prioritized-queue.js';
|
import { PrioritizedQueue } from '../util/prioritized-queue.js';
|
||||||
|
|
||||||
export class NetworkNode extends Actor {
|
export class NetworkNode extends Actor {
|
||||||
constructor(name) {
|
constructor(name, scene) {
|
||||||
super(name);
|
super(name, scene);
|
||||||
this.queue = new PrioritizedQueue();
|
this.queue = new PrioritizedQueue();
|
||||||
this.actions = {
|
this.actions = {
|
||||||
peerMessage: new Action('peer message'),
|
peerMessage: new Action('peer message', scene),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
export class List {
|
|
||||||
constructor() {
|
|
||||||
this.items = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
add(item) {
|
|
||||||
this.items.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { randomID } from '../util.js';
|
|
||||||
import { Actor } from './actor.js';
|
|
||||||
|
|
||||||
export class ReputationHolder extends Actor {
|
|
||||||
constructor(name) {
|
|
||||||
super(name);
|
|
||||||
this.reputationPublicKey = `${name}_${randomID()}`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
class Lock {
|
|
||||||
constructor(tokens, duration) {
|
|
||||||
this.dateCreated = new Date();
|
|
||||||
this.tokens = tokens;
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Reputation {
|
|
||||||
constructor() {
|
|
||||||
this.tokens = 0;
|
|
||||||
this.locks = new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
addTokens(tokens) {
|
|
||||||
if (this.tokens + tokens < 0) {
|
|
||||||
throw new Error('Token balance can not become negative');
|
|
||||||
}
|
|
||||||
this.tokens += tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
lockTokens(tokens, duration) {
|
|
||||||
if (tokens > this.getAvailableTokens()) {
|
|
||||||
throw new Error('Can not lock more tokens than are available');
|
|
||||||
}
|
|
||||||
const lock = new Lock(tokens, duration);
|
|
||||||
this.locks.add(lock);
|
|
||||||
// TODO: Prune locks once expired
|
|
||||||
}
|
|
||||||
|
|
||||||
getTokens() {
|
|
||||||
return this.tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAvailableTokens() {
|
|
||||||
const now = new Date();
|
|
||||||
const tokensLocked = Array.from(this.locks.values())
|
|
||||||
.filter(({ dateCreated, duration }) => now - dateCreated < duration)
|
|
||||||
.reduce((acc, cur) => acc += cur.tokens, 0);
|
|
||||||
if (tokensLocked > this.tokens) {
|
|
||||||
throw new Error('Assertion failure. tokensLocked > tokens');
|
|
||||||
}
|
|
||||||
return this.tokens - tokensLocked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Reputations extends Map {
|
|
||||||
getTokens(reputationPublicKey) {
|
|
||||||
const reputation = this.get(reputationPublicKey);
|
|
||||||
if (!reputation) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return reputation.getTokens();
|
|
||||||
}
|
|
||||||
|
|
||||||
getAvailableTokens(reputationPublicKey) {
|
|
||||||
const reputation = this.get(reputationPublicKey);
|
|
||||||
if (!reputation) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return reputation.getAvailableTokens();
|
|
||||||
}
|
|
||||||
|
|
||||||
addTokens(reputationPublicKey, tokens) {
|
|
||||||
const reputation = this.get(reputationPublicKey) ?? new Reputation();
|
|
||||||
reputation.addTokens(tokens);
|
|
||||||
this.set(reputationPublicKey, reputation);
|
|
||||||
}
|
|
||||||
|
|
||||||
lockTokens(reputationPublicKey, tokens, duration) {
|
|
||||||
if (!tokens || !duration) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const reputation = this.get(reputationPublicKey);
|
|
||||||
if (!reputation) {
|
|
||||||
throw new Error(`${reputationPublicKey} has no tokens to lock`);
|
|
||||||
}
|
|
||||||
reputation.lockTokens(tokens, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotal() {
|
|
||||||
return Array.from(this.values()).reduce((acc, cur) => acc += cur.getTokens(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalAvailable() {
|
|
||||||
return Array.from(this.values()).reduce((acc, cur) => acc += cur.getAvailableTokens(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,239 +0,0 @@
|
||||||
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
|
|
||||||
import { Actor } from './actor.js';
|
|
||||||
import { Action } from './action.js';
|
|
||||||
import { debounce, hexToRGB } from '../util.js';
|
|
||||||
import { CryptoUtil } from './crypto.js';
|
|
||||||
|
|
||||||
class MermaidDiagram {
|
|
||||||
constructor(box, logBox) {
|
|
||||||
this.box = box;
|
|
||||||
this.container = this.box.addBox('Container');
|
|
||||||
this.element = this.box.addBox('Element');
|
|
||||||
this.renderBox = this.box.addBox('Render');
|
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
||||||
this.logBoxPre = logBox.el.appendChild(document.createElement('pre'));
|
|
||||||
this.inSection = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async log(msg, render = true) {
|
|
||||||
this.logBoxPre.textContent = `${this.logBoxPre.textContent}\n${msg}`;
|
|
||||||
if (render) {
|
|
||||||
await this.render();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
async render() {
|
|
||||||
const render = async () => {
|
|
||||||
let text = this.logBoxPre.textContent;
|
|
||||||
for (let i = 0; i < this.inSection; i++) {
|
|
||||||
text += '\nend';
|
|
||||||
}
|
|
||||||
const graph = await mermaid.mermaidAPI.render(
|
|
||||||
this.element.getId(),
|
|
||||||
text,
|
|
||||||
);
|
|
||||||
this.renderBox.setInnerHTML(graph);
|
|
||||||
};
|
|
||||||
await debounce(render, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Table {
|
|
||||||
constructor(box) {
|
|
||||||
this.box = box;
|
|
||||||
this.columns = [];
|
|
||||||
this.rows = [];
|
|
||||||
this.table = box.el.appendChild(document.createElement('table'));
|
|
||||||
this.headings = this.table.appendChild(document.createElement('tr'));
|
|
||||||
}
|
|
||||||
|
|
||||||
setColumns(columns) {
|
|
||||||
if (JSON.stringify(columns) === JSON.stringify(this.columns)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.columns.length) {
|
|
||||||
this.table.innerHTML = '';
|
|
||||||
this.headings = this.table.appendChild(document.createElement('tr'));
|
|
||||||
this.columns = [];
|
|
||||||
}
|
|
||||||
this.columns = columns;
|
|
||||||
for (const { title } of columns) {
|
|
||||||
const heading = document.createElement('th');
|
|
||||||
this.headings.appendChild(heading);
|
|
||||||
heading.innerHTML = title ?? '';
|
|
||||||
}
|
|
||||||
if (this.rows.length) {
|
|
||||||
const { rows } = this;
|
|
||||||
this.rows = [];
|
|
||||||
for (const row of rows) {
|
|
||||||
this.addRow(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addRow(rowMap) {
|
|
||||||
this.rows.push(rowMap);
|
|
||||||
const row = this.table.appendChild(document.createElement('tr'));
|
|
||||||
for (const { key } of this.columns) {
|
|
||||||
const value = rowMap.get(key);
|
|
||||||
const cell = row.appendChild(document.createElement('td'));
|
|
||||||
cell.innerHTML = value ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Scene {
|
|
||||||
constructor(name, rootBox) {
|
|
||||||
this.name = name;
|
|
||||||
this.box = rootBox.addBox(name);
|
|
||||||
this.titleBox = this.box.addBox('Title').setInnerHTML(name);
|
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
||||||
this.topSection = this.box.addBox('Top section').flex();
|
|
||||||
this.displayValuesBox = this.topSection.addBox('Values');
|
|
||||||
this.middleSection = this.box.addBox('Middle section');
|
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
||||||
this.actors = new Set();
|
|
||||||
this.dateStart = new Date();
|
|
||||||
this.flowcharts = new Map();
|
|
||||||
|
|
||||||
mermaid.mermaidAPI.initialize({
|
|
||||||
startOnLoad: false,
|
|
||||||
theme: 'base',
|
|
||||||
themeVariables: {
|
|
||||||
darkMode: true,
|
|
||||||
primaryColor: '#2a5b6c',
|
|
||||||
primaryTextColor: '#b6b6b6',
|
|
||||||
// lineColor: '#349cbd',
|
|
||||||
lineColor: '#57747d',
|
|
||||||
signalColor: '#57747d',
|
|
||||||
// signalColor: '#349cbd',
|
|
||||||
noteBkgColor: '#516f77',
|
|
||||||
noteTextColor: '#cecece',
|
|
||||||
activationBkgColor: '#1d3f49',
|
|
||||||
activationBorderColor: '#569595',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.options = {
|
|
||||||
edgeNodeColor: '#4d585c',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
withSequenceDiagram() {
|
|
||||||
const box = this.box.addBox('Sequence diagram');
|
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
||||||
const logBox = this.box.addBox('Sequence diagram text').addClass('dim');
|
|
||||||
this.sequence = new MermaidDiagram(box, logBox);
|
|
||||||
this.sequence.log('sequenceDiagram', false);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
withFlowchart({ direction = 'BT' } = {}) {
|
|
||||||
const box = this.topSection.addBox('Flowchart').addClass('padded');
|
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
||||||
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
|
||||||
this.flowchart = new MermaidDiagram(box, logBox);
|
|
||||||
this.flowchart.log(`graph ${direction}`, false);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) {
|
|
||||||
const index = this.flowcharts.size;
|
|
||||||
name = name ?? `Flowchart ${index}`;
|
|
||||||
id = id ?? `flowchart_${CryptoUtil.randomUUID().slice(0, 4)}`;
|
|
||||||
const container = this.middleSection.addBox(name).flex();
|
|
||||||
const box = container.addBox('Flowchart').addClass('padded');
|
|
||||||
const logBox = container.addBox('Flowchart text').addClass('dim');
|
|
||||||
const flowchart = new MermaidDiagram(box, logBox);
|
|
||||||
flowchart.log(`graph ${direction}`, false);
|
|
||||||
this.flowcharts.set(id, flowchart);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFlowchart() {
|
|
||||||
if (!this.flowcharts.size) {
|
|
||||||
if (this.flowchart) {
|
|
||||||
return this.flowchart;
|
|
||||||
}
|
|
||||||
throw new Error('lastFlowchart: No additional flowcharts have been added.');
|
|
||||||
}
|
|
||||||
const flowcharts = Array.from(this.flowcharts.values());
|
|
||||||
return flowcharts[flowcharts.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
withTable() {
|
|
||||||
if (this.table) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
const box = this.middleSection.addBox('Table').addClass('padded');
|
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
||||||
this.table = new Table(box);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
async addActor(name) {
|
|
||||||
const actor = new Actor(name);
|
|
||||||
if (this.sequence) {
|
|
||||||
await this.sequence.log(`participant ${name}`);
|
|
||||||
}
|
|
||||||
return actor;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerActor(actor) {
|
|
||||||
this.actors.add(actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
findActor(fn) {
|
|
||||||
return Array.from(this.actors.values()).find(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
addAction(name) {
|
|
||||||
const action = new Action(name, this);
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
addDisplayValue(name) {
|
|
||||||
const dv = this.displayValuesBox.addDisplayValue(name);
|
|
||||||
return dv;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deactivateAll() {
|
|
||||||
for (const actor of this.actors.values()) {
|
|
||||||
while (actor.active) {
|
|
||||||
await actor.deactivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async startSection(color = '#08252c') {
|
|
||||||
const { r, g, b } = hexToRGB(color);
|
|
||||||
this.sequence.inSection++;
|
|
||||||
this.sequence.log(`rect rgb(${r}, ${g}, ${b})`, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async endSection() {
|
|
||||||
this.sequence.inSection--;
|
|
||||||
this.sequence.log('end');
|
|
||||||
}
|
|
||||||
|
|
||||||
stateToTable(label) {
|
|
||||||
const row = new Map();
|
|
||||||
const columns = [];
|
|
||||||
columns.push({ key: 'seqNum', title: '#' });
|
|
||||||
columns.push({ key: 'elapsedMs', title: 'Time (ms)' });
|
|
||||||
row.set('seqNum', this.table.rows.length + 1);
|
|
||||||
row.set('elapsedMs', new Date() - this.dateStart);
|
|
||||||
row.set('label', label);
|
|
||||||
for (const actor of this.actors) {
|
|
||||||
for (const [aKey, { name, value }] of actor.getValuesMap()) {
|
|
||||||
const key = `${actor.name}:${aKey}`;
|
|
||||||
columns.push({ key, title: name });
|
|
||||||
row.set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns.push({ key: 'label', title: '' });
|
|
||||||
this.table.setColumns(columns);
|
|
||||||
this.table.addRow(row);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import params from '../params.js';
|
import params from '../../params.js';
|
||||||
|
|
||||||
export class Stake {
|
export class Stake {
|
||||||
constructor({
|
constructor({
|
|
@ -1,4 +1,4 @@
|
||||||
import { Action } from './action.js';
|
import { Action } from '../display/action.js';
|
||||||
|
|
||||||
class ContractRecord {
|
class ContractRecord {
|
||||||
constructor(id, instance) {
|
constructor(id, instance) {
|
||||||
|
@ -12,7 +12,8 @@ export class VMHandle {
|
||||||
this.vm = vm;
|
this.vm = vm;
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.actions = {
|
this.actions = {
|
||||||
call: new Action('call'),
|
call: new Action('call', vm.scene),
|
||||||
|
return: new Action('return', vm.scene),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,17 +21,20 @@ export class VMHandle {
|
||||||
* @param {string} id Contract ID
|
* @param {string} id Contract ID
|
||||||
* @param {string} method
|
* @param {string} method
|
||||||
*/
|
*/
|
||||||
callContract(id, method, ...args) {
|
async callContract(id, method, ...args) {
|
||||||
const instance = this.vm.getContractInstance(id);
|
const instance = this.vm.getContractInstance(id);
|
||||||
const fn = instance[method];
|
const fn = instance[method];
|
||||||
if (!fn) throw new Error(`Contract ${id} method ${method} not found!`);
|
if (!fn) throw new Error(`Contract ${id} method ${method} not found!`);
|
||||||
this.actions.call.log(this.sender, instance, method);
|
await this.actions.call.log(this.sender, instance, method);
|
||||||
return fn.call(instance, this.sender, ...args);
|
const result = await fn.call(instance, this.sender, ...args);
|
||||||
|
await this.actions.return.log(instance, this.sender, undefined, undefined, '-->>');
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VM {
|
export class VM {
|
||||||
constructor() {
|
constructor(scene) {
|
||||||
|
this.scene = scene;
|
||||||
this.contracts = new Map();
|
this.contracts = new Map();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,16 +25,17 @@ export class Edge {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WDAG {
|
export class WDAG {
|
||||||
constructor() {
|
constructor(scene) {
|
||||||
|
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 = window?.scene?.flowchart ?? null;
|
this.flowchart = scene?.flowchart;
|
||||||
}
|
}
|
||||||
|
|
||||||
withFlowchart() {
|
withFlowchart() {
|
||||||
window?.scene?.withAdditionalFlowchart();
|
this.scene?.withAdditionalFlowchart();
|
||||||
this.flowchart = window?.scene?.lastFlowchart();
|
this.flowchart = this.scene?.lastFlowchart();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<h3>Forum</h3>
|
<h3>Forum</h3>
|
||||||
<ul>
|
<ol>
|
||||||
<li><a href="./tests/forum1.test.html">Negative citation of a negative citation</a></li>
|
<li><a href="./tests/forum1.test.html">Negative citation of a negative citation</a></li>
|
||||||
<li><a href="./tests/forum2.test.html">Negative citation of a weaker negative citation</a></li>
|
<li><a href="./tests/forum2.test.html">Negative citation of a weaker negative citation</a></li>
|
||||||
<li><a href="./tests/forum3.test.html">Redistribute power</a></li>
|
<li><a href="./tests/forum3.test.html">Redistribute power</a></li>
|
||||||
<li><a href="./tests/forum4.test.html">Redistribute power through subsequent support</a></li>
|
<li><a href="./tests/forum4.test.html">Redistribute power through subsequent support</a></li>
|
||||||
<li><a href="./tests/forum5.test.html"> Destroy a post after it has received positive citations</a></li>
|
<li><a href="./tests/forum5.test.html"> Destroy a post after it has received positive citations</a></li>
|
||||||
</ul>
|
</ol>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="./tests/forum-network.test.html">Forum Network</a></li>
|
<li><a href="./tests/forum-network.test.html">Forum Network</a></li>
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="./tests/basic.test.html">Basic Sequencing</a></li>
|
<li><a href="./tests/basic.test.html">Basic Sequencing</a></li>
|
||||||
|
<li><a href="./tests/basic2.test.html">Basic Sequencing 2</a></li>
|
||||||
<li><a href="./tests/wdag.test.html">WDAG</a></li>
|
<li><a href="./tests/wdag.test.html">WDAG</a></li>
|
||||||
<li><a href="./tests/debounce.test.html">Debounce</a></li>
|
<li><a href="./tests/debounce.test.html">Debounce</a></li>
|
||||||
<li><a href="./tests/flowchart.test.html">Flowchart</a></li>
|
<li><a href="./tests/flowchart.test.html">Flowchart</a></li>
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
globals: ['scene', 'dao', 'experts', 'posts', '__REACT_DEVTOOLS_*'],
|
globals: ['scene', 'dao', 'experts', 'posts', 'vm', 'graph', '__REACT_DEVTOOLS_*'],
|
||||||
});
|
});
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
window.should = chai.should();
|
window.should = chai.should();
|
||||||
|
|
|
@ -2,12 +2,36 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Availability test</title>
|
<title>Availability test</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" />
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2><a href="../">DGF Tests</a></h2>
|
<h2><a href="../">DGF Tests</a></h2>
|
||||||
|
<div id="mocha"></div>
|
||||||
<div id="scene"></div>
|
<div id="scene"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module" src="./scripts/availability.test.js">
|
|
||||||
|
<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://cdnjs.cloudflare.com/ajax/libs/mocha/10.2.0/mocha.min.js"
|
||||||
|
integrity="sha512-jsP/sG70bnt0xNVJt+k9NxQqGYvRrLzWhI+46SSf7oNJeCwdzZlBvoyrAN0zhtVyolGcHNh/9fEgZppG2pH+eA=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.7/chai.min.js"
|
||||||
|
integrity="sha512-Pwgr3yHn4Gvztp1GKl0ihhAWLZfqgp4/SbMt4HKW7AymuTQODMCNPE7v1uGapTeOoQQ5Hoz367b4seKpx6j7Zg=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script type="module" src="./scripts/availability.test.js"></script>
|
||||||
|
<script defer class="mocha-init">
|
||||||
|
mocha.setup({
|
||||||
|
ui: 'bdd',
|
||||||
|
globals: ['scene', 'dao', 'experts', 'requestor', '__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>
|
</script>
|
||||||
|
|
|
@ -9,198 +9,5 @@
|
||||||
<h2><a href="../">DGF Tests</a></h2>
|
<h2><a href="../">DGF Tests</a></h2>
|
||||||
<div id="basic"></div>
|
<div id="basic"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module" src="./scripts/basic.test.js">
|
||||||
import { Box } from '../classes/box.js';
|
|
||||||
import { Scene } from '../classes/scene.js';
|
|
||||||
|
|
||||||
const rootElement = document.getElementById('basic');
|
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
|
||||||
|
|
||||||
function randomDelay(min, max) {
|
|
||||||
const delayMs = min + Math.random() * max;
|
|
||||||
return delayMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
function delay(min, max = min) {
|
|
||||||
const delayMs = min + Math.random() * (max - min);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, delayMs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
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');
|
|
||||||
|
|
||||||
const webClient = await scene.addActor('web client');
|
|
||||||
const node1 = await scene.addActor('node 1');
|
|
||||||
const blockchain = await scene.addActor('blockchain');
|
|
||||||
const requestForumPage = scene.addAction('requestForumPage');
|
|
||||||
const readBlockchainData = scene.addAction('readBlockchainData');
|
|
||||||
const blockchainData = scene.addAction('blockchainData');
|
|
||||||
const forumPage = scene.addAction('forumPage');
|
|
||||||
|
|
||||||
webClientStatus.set('Initialized');
|
|
||||||
node1Status.set('Idle');
|
|
||||||
blockchainStatus.set('Idle');
|
|
||||||
|
|
||||||
node1.on(requestForumPage, (src, detail) => {
|
|
||||||
node1Status.set('Processing request');
|
|
||||||
node1.on(blockchainData, (_src, data) => {
|
|
||||||
node1Status.set('Processing response');
|
|
||||||
setTimeout(() => {
|
|
||||||
node1.send(src, forumPage, data);
|
|
||||||
node1Status.set('Idle');
|
|
||||||
}, randomDelay(500, 1000));
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
node1.send(blockchain, readBlockchainData, detail);
|
|
||||||
}, randomDelay(500, 1500));
|
|
||||||
});
|
|
||||||
|
|
||||||
blockchain.on(readBlockchainData, (src, _detail) => {
|
|
||||||
blockchainStatus.set('Processing request');
|
|
||||||
setTimeout(() => {
|
|
||||||
blockchain.send(src, blockchainData, {});
|
|
||||||
blockchainStatus.set('Idle');
|
|
||||||
}, randomDelay(500, 1500));
|
|
||||||
});
|
|
||||||
|
|
||||||
webClient.on(forumPage, (_src, _detail) => {
|
|
||||||
webClientStatus.set('Received forum page');
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
webClient.send(node1, requestForumPage);
|
|
||||||
webClientStatus.set('Requested forum page');
|
|
||||||
}, randomDelay(6000, 12000));
|
|
||||||
}
|
|
||||||
|
|
||||||
(async function run() {
|
|
||||||
const scene = new Scene('Scene 2', rootBox).withSequenceDiagram();
|
|
||||||
|
|
||||||
const webClient = await scene.addActor('webClient');
|
|
||||||
|
|
||||||
const nodes = [];
|
|
||||||
const memories = [];
|
|
||||||
const storages = [];
|
|
||||||
|
|
||||||
async function addNode() {
|
|
||||||
const idx = nodes.length;
|
|
||||||
const node = await scene.addActor(`node${idx}`);
|
|
||||||
const memory = await scene.addActor(`memory${idx}`);
|
|
||||||
const storage = await scene.addActor(`storage${idx}`);
|
|
||||||
node.memory = memory;
|
|
||||||
node.storage = storage;
|
|
||||||
nodes.push(node);
|
|
||||||
memories.push(memory);
|
|
||||||
storages.push(storage);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPeer(node) {
|
|
||||||
const peers = nodes.filter((peer) => peer !== node);
|
|
||||||
const idx = Math.floor(Math.random() * peers.length);
|
|
||||||
return peers[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
await addNode();
|
|
||||||
await addNode();
|
|
||||||
|
|
||||||
const [
|
|
||||||
seekTruth,
|
|
||||||
considerInfo,
|
|
||||||
evaluateConfidence,
|
|
||||||
chooseResponse,
|
|
||||||
qualifiedOpinions,
|
|
||||||
requestMemoryData,
|
|
||||||
memoryData,
|
|
||||||
requestStorageData,
|
|
||||||
storageData,
|
|
||||||
] = [
|
|
||||||
'seek truth',
|
|
||||||
'consider available information',
|
|
||||||
'evaluate confidence',
|
|
||||||
'choose response',
|
|
||||||
'qualified opinions',
|
|
||||||
'request in-memory data',
|
|
||||||
'in-memory data',
|
|
||||||
'request storage data',
|
|
||||||
'storage data',
|
|
||||||
].map((name) => scene.addAction(name));
|
|
||||||
|
|
||||||
memories.forEach((memory) => {
|
|
||||||
memory.setStatus('Idle');
|
|
||||||
memory.on(requestMemoryData, async (src, _detail) => {
|
|
||||||
memory.setStatus('Retrieving data');
|
|
||||||
await delay(1000);
|
|
||||||
memory.send(src, memoryData, {});
|
|
||||||
memory.setStatus('Idle');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
storages.forEach((storage) => {
|
|
||||||
storage.setStatus('Idle');
|
|
||||||
storage.on(requestStorageData, async (src, _detail) => {
|
|
||||||
storage.setStatus('Retrieving data');
|
|
||||||
await delay(1000);
|
|
||||||
storage.send(src, storageData, {});
|
|
||||||
storage.setStatus('Idle');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
node.setStatus('Idle');
|
|
||||||
node.on(seekTruth, async (seeker, detail) => {
|
|
||||||
node.setStatus('Processing request');
|
|
||||||
|
|
||||||
node.on(chooseResponse, async (_src, _info) => {
|
|
||||||
node.setStatus('Choosing response');
|
|
||||||
await delay(1000);
|
|
||||||
node.send(seeker, qualifiedOpinions, {});
|
|
||||||
node.setStatus('Idle');
|
|
||||||
});
|
|
||||||
|
|
||||||
node.on(evaluateConfidence, async (_src, _info) => {
|
|
||||||
node.setStatus('Evaluating confidence');
|
|
||||||
await delay(1000);
|
|
||||||
node.send(node, chooseResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
node.on(considerInfo, async (_src, _info) => {
|
|
||||||
node.setStatus('Considering info');
|
|
||||||
await delay(1000);
|
|
||||||
node.send(node, evaluateConfidence);
|
|
||||||
});
|
|
||||||
|
|
||||||
node.on(memoryData, (_src, _data) => {
|
|
||||||
node.on(storageData, (__src, __data) => {
|
|
||||||
if (detail?.readConcern === 'single') {
|
|
||||||
node.send(node, considerInfo, {});
|
|
||||||
} else {
|
|
||||||
const peer = getPeer(node);
|
|
||||||
node.on(qualifiedOpinions, (___src, info) => {
|
|
||||||
node.send(node, considerInfo, info);
|
|
||||||
});
|
|
||||||
node.send(peer, seekTruth, { readConcern: 'single' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
node.send(node.storage, requestStorageData);
|
|
||||||
});
|
|
||||||
|
|
||||||
await delay(1000);
|
|
||||||
node.send(node.memory, requestMemoryData);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
webClient.on(qualifiedOpinions, (_src, _detail) => {
|
|
||||||
webClient.setStatus('Received opinions and qualifications');
|
|
||||||
});
|
|
||||||
|
|
||||||
await delay(1000);
|
|
||||||
webClient.setStatus('Seek truth');
|
|
||||||
webClient.send(nodes[0], seekTruth);
|
|
||||||
}());
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Forum Network</title>
|
||||||
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2><a href="../">DGF Tests</a></h2>
|
||||||
|
<div id="basic"></div>
|
||||||
|
</body>
|
||||||
|
<script type="module" src="./scripts/basic2.test.js">
|
||||||
|
</script>
|
|
@ -2,35 +2,35 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Debounce test</title>
|
<title>Debounce test</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" />
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2><a href="../">DGF Tests</a></h2>
|
<h2><a href="../">DGF Tests</a></h2>
|
||||||
<div id="debounce-test"></div>
|
<div id="mocha"></div>
|
||||||
|
<div id="scene"></div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
|
||||||
import { Box } from '../classes/box.js';
|
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
|
||||||
import { Scene } from '../classes/scene.js';
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
import { debounce, delay } from '../util.js';
|
<script src="https://unpkg.com/mocha/mocha.js"></script>
|
||||||
|
<script src="https://unpkg.com/chai/chai.js"></script>
|
||||||
const rootElement = document.getElementById('debounce-test');
|
<script src="https://cdn.jsdelivr.net/npm/sinon-chai@3.7.0/lib/sinon-chai.min.js"></script>
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/10.0.1/sinon.min.js"
|
||||||
|
integrity="sha512-pNdrcn83nlZaY1zDLGVtHH2Baxe86kDMqspVOChVjxN71s6DZtcZVqyHXofexm/d8K/1qbJfNUGku3wHIbjSpw=="
|
||||||
window.scene = new Scene('Debounce test', rootBox);
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script type="module" src="./scripts/debounce.test.js"></script>
|
||||||
let eventCount = 0;
|
<script defer class="mocha-init">
|
||||||
const event = () => {
|
mocha.setup({
|
||||||
eventCount++;
|
ui: 'bdd',
|
||||||
console.log(`event ${eventCount}`);
|
globals: ['scene', 'dao', 'experts', 'posts', 'vm', 'graph', '__REACT_DEVTOOLS_*'],
|
||||||
};
|
});
|
||||||
await debounce(event, 500);
|
mocha.checkLeaks();
|
||||||
await debounce(event, 500);
|
window.should = chai.should();
|
||||||
await delay(500);
|
</script>
|
||||||
await debounce(event, 500);
|
<script defer class="mocha-exec">
|
||||||
await debounce(event, 500);
|
// TODO: Weird race condition -- resolve this in a better way
|
||||||
if (eventCount !== 2) {
|
setTimeout(() => mocha.run(), 1000);
|
||||||
throw new Error(`Expected 2 events, got ${eventCount}`);
|
|
||||||
}
|
|
||||||
console.log(`eventCount: ${eventCount}`);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,12 +2,32 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Forum Network test</title>
|
<title>Forum Network test</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" />
|
<link type="text/css" rel="stylesheet" href="../index.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2><a href="../">DGF Tests</a></h2>
|
<h2><a href="../">DGF Tests</a></h2>
|
||||||
|
<div id="mocha"></div>
|
||||||
<div id="scene"></div>
|
<div id="scene"></div>
|
||||||
</body>
|
</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/forum-network.test.js"></script>
|
<script type="module" src="./scripts/forum-network.test.js"></script>
|
||||||
|
<script defer class="mocha-init">
|
||||||
|
mocha.setup({
|
||||||
|
ui: 'bdd',
|
||||||
|
globals: ['scene', 'dao', 'experts', 'posts', 'vm', 'graph', '__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>
|
||||||
|
|
|
@ -1,53 +1,83 @@
|
||||||
import { Box } from '../../classes/box.js';
|
import { Box } from '../../classes/display/box.js';
|
||||||
import { Scene } from '../../classes/scene.js';
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
import { Expert } from '../../classes/expert.js';
|
import { Expert } from '../../classes/actors/expert.js';
|
||||||
import { delay } from '../../util.js';
|
import { delay } from '../../util.js';
|
||||||
import { DAO } from '../../classes/dao.js';
|
import { DAO } from '../../classes/actors/dao.js';
|
||||||
import { Public } from '../../classes/public.js';
|
import { Public } from '../../classes/actors/public.js';
|
||||||
import { PostContent } from '../../classes/post-content.js';
|
import { PostContent } from '../../classes/util/post-content.js';
|
||||||
|
|
||||||
const DELAY_INTERVAL = 500;
|
const DELAY_INTERVAL = 100;
|
||||||
|
const POOL_DURATION = 200;
|
||||||
let dao;
|
let dao;
|
||||||
let experts;
|
let experts;
|
||||||
let requestor;
|
let requestor;
|
||||||
|
let scene;
|
||||||
|
|
||||||
const newExpert = async () => {
|
const newExpert = async () => {
|
||||||
const index = experts.length;
|
const index = experts.length;
|
||||||
const name = `Expert${index + 1}`;
|
const name = `Expert${index + 1}`;
|
||||||
const expert = await new Expert(dao, name).initialize();
|
const expert = await new Expert(dao, name, scene).initialize();
|
||||||
|
expert.setValue(
|
||||||
|
'rep',
|
||||||
|
() => dao.reputation.valueOwnedBy(expert.reputationPublicKey),
|
||||||
|
);
|
||||||
experts.push(expert);
|
experts.push(expert);
|
||||||
return expert;
|
return expert;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function setup() {
|
const setup = async () => {
|
||||||
const rootElement = document.getElementById('scene');
|
const rootElement = document.getElementById('scene');
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
const scene = (window.scene = new Scene('Availability test', rootBox));
|
scene = new Scene('Availability test', rootBox);
|
||||||
scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
scene.withFlowchart();
|
scene.withFlowchart();
|
||||||
scene.withTable();
|
scene.withTable();
|
||||||
|
|
||||||
experts = (window.experts = []);
|
dao = new DAO('DGF', scene);
|
||||||
dao = (window.dao = new DAO('DGF'));
|
await dao.setValue('total rep', () => dao.reputation.getTotal());
|
||||||
|
|
||||||
|
experts = [];
|
||||||
|
|
||||||
await newExpert();
|
await newExpert();
|
||||||
await newExpert();
|
await newExpert();
|
||||||
requestor = new Public('Public');
|
requestor = new Public('Public', scene);
|
||||||
}
|
|
||||||
|
|
||||||
const updateDisplayValues = async () => {
|
await delay(DELAY_INTERVAL);
|
||||||
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) => {
|
// Experts gain initial reputation by submitting a post with fee
|
||||||
await updateDisplayValues();
|
const { postId: postId1, pool: pool1 } = await experts[0].submitPostWithFee(
|
||||||
await delay(delayMs);
|
new PostContent({ hello: 'there' }).setTitle('Post 1'),
|
||||||
|
{
|
||||||
|
fee: 10,
|
||||||
|
duration: POOL_DURATION,
|
||||||
|
tokenLossRatio: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await delay(POOL_DURATION);
|
||||||
|
|
||||||
|
await pool1.evaluateWinningConditions();
|
||||||
|
await delay(DELAY_INTERVAL);
|
||||||
|
|
||||||
|
dao.reputation.valueOwnedBy(experts[0].reputationPublicKey).should.equal(10);
|
||||||
|
|
||||||
|
const { pool: pool2 } = await experts[1].submitPostWithFee(
|
||||||
|
new PostContent({ hello: 'to you as well' })
|
||||||
|
.setTitle('Post 2')
|
||||||
|
.addCitation(postId1, 0.5),
|
||||||
|
{
|
||||||
|
fee: 10,
|
||||||
|
duration: POOL_DURATION,
|
||||||
|
tokenLossRatio: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await delay(POOL_DURATION);
|
||||||
|
|
||||||
|
await pool2.evaluateWinningConditions();
|
||||||
|
await delay(DELAY_INTERVAL);
|
||||||
|
|
||||||
|
dao.reputation.valueOwnedBy(experts[0].reputationPublicKey).should.equal(15);
|
||||||
|
dao.reputation.valueOwnedBy(experts[1].reputationPublicKey).should.equal(5);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActiveWorker = async () => {
|
const getActiveWorker = async () => {
|
||||||
|
@ -76,90 +106,64 @@ const voteForWorkEvidence = async (worker, pool) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await setup();
|
describe('Availability + Business', () => {
|
||||||
|
before(async () => {
|
||||||
|
await setup();
|
||||||
|
});
|
||||||
|
|
||||||
await updateDisplayValuesAndDelay();
|
beforeEach(async () => {
|
||||||
|
// await scene.sequence.startSection();
|
||||||
|
});
|
||||||
|
|
||||||
// Experts gain initial reputation by submitting a post with fee
|
afterEach(async () => {
|
||||||
const { postId: postId1, pool: pool1 } = await experts[0].submitPostWithFee(
|
// await scene.sequence.endSection();
|
||||||
new PostContent({ hello: 'there' }).setTitle('Post 1'),
|
});
|
||||||
{
|
|
||||||
fee: 10,
|
|
||||||
duration: 1000,
|
|
||||||
tokenLossRatio: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await updateDisplayValuesAndDelay(1000);
|
|
||||||
|
|
||||||
await pool1.evaluateWinningConditions();
|
it('Experts can register their availability for some duration', async () => {
|
||||||
await updateDisplayValuesAndDelay();
|
await experts[0].registerAvailability(1, 10000);
|
||||||
|
await experts[1].registerAvailability(1, 10000);
|
||||||
|
await delay(DELAY_INTERVAL);
|
||||||
|
});
|
||||||
|
|
||||||
const { pool: pool2 } = await experts[1].submitPostWithFee(
|
it('Public can submit a work request', async () => {
|
||||||
new PostContent({ hello: 'to you as well' })
|
await requestor.submitRequest(
|
||||||
.setTitle('Post 2')
|
dao.business,
|
||||||
.addCitation(postId1, 0.5),
|
{ fee: 100 },
|
||||||
{
|
{ please: 'do some work' },
|
||||||
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 {
|
await delay(DELAY_INTERVAL);
|
||||||
console.error('Unexpected error');
|
});
|
||||||
throw e;
|
|
||||||
}
|
it('Expert can submit work evidence', async () => {
|
||||||
}
|
// Receive work request
|
||||||
|
const { worker, request } = await getActiveWorker();
|
||||||
|
const pool3 = await worker.submitWork(
|
||||||
|
request.id,
|
||||||
|
{
|
||||||
|
here: 'is some evidence of work product',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenLossRatio: 1,
|
||||||
|
duration: POOL_DURATION,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await worker.deactivate();
|
||||||
|
|
||||||
|
// Stake on work evidence
|
||||||
|
await voteForWorkEvidence(worker, pool3);
|
||||||
|
|
||||||
|
// Wait for validation pool duration to elapse
|
||||||
|
await delay(POOL_DURATION);
|
||||||
|
|
||||||
|
// Distribute reputation awards and fees
|
||||||
|
await pool3.evaluateWinningConditions();
|
||||||
|
await delay(DELAY_INTERVAL);
|
||||||
|
|
||||||
|
// This should throw an exception since the pool is already resolved
|
||||||
|
try {
|
||||||
|
await pool3.evaluateWinningConditions();
|
||||||
|
} catch (e) {
|
||||||
|
e.should.match(/Validation pool has already been resolved/);
|
||||||
|
}
|
||||||
|
}).timeout(10000);
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Box } from '../../classes/display/box.js';
|
||||||
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
|
import { Actor } from '../../classes/display/actor.js';
|
||||||
|
import { Action } from '../../classes/display/action.js';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('basic');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
|
function randomDelay(min, max) {
|
||||||
|
const delayMs = min + Math.random() * max;
|
||||||
|
return delayMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
(function run() {
|
||||||
|
const 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');
|
||||||
|
|
||||||
|
const webClient = new Actor('web client', scene);
|
||||||
|
const node1 = new Actor('node 1', scene);
|
||||||
|
const blockchain = new Actor('blockchain', scene);
|
||||||
|
const requestForumPage = new Action('requestForumPage', scene);
|
||||||
|
const readBlockchainData = new Action('readBlockchainData', scene);
|
||||||
|
const blockchainData = new Action('blockchainData', scene);
|
||||||
|
const forumPage = new Action('forumPage', scene);
|
||||||
|
|
||||||
|
webClientStatus.set('Initialized');
|
||||||
|
node1Status.set('Idle');
|
||||||
|
blockchainStatus.set('Idle');
|
||||||
|
|
||||||
|
node1.on(requestForumPage, (src, detail) => {
|
||||||
|
node1Status.set('Processing request');
|
||||||
|
node1.on(blockchainData, (_src, data) => {
|
||||||
|
node1Status.set('Processing response');
|
||||||
|
setTimeout(() => {
|
||||||
|
node1.send(src, forumPage, data);
|
||||||
|
node1Status.set('Idle');
|
||||||
|
}, randomDelay(500, 1000));
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
node1.send(blockchain, readBlockchainData, detail);
|
||||||
|
}, randomDelay(500, 1500));
|
||||||
|
});
|
||||||
|
|
||||||
|
blockchain.on(readBlockchainData, (src, _detail) => {
|
||||||
|
blockchainStatus.set('Processing request');
|
||||||
|
setTimeout(() => {
|
||||||
|
blockchain.send(src, blockchainData, {});
|
||||||
|
blockchainStatus.set('Idle');
|
||||||
|
}, randomDelay(500, 1500));
|
||||||
|
});
|
||||||
|
|
||||||
|
webClient.on(forumPage, (_src, _detail) => {
|
||||||
|
webClientStatus.set('Received forum page');
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
webClient.send(node1, requestForumPage);
|
||||||
|
webClientStatus.set('Requested forum page');
|
||||||
|
}, randomDelay(500, 1500));
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
webClient.send(node1, requestForumPage);
|
||||||
|
webClientStatus.set('Requested forum page');
|
||||||
|
}, randomDelay(6000, 12000));
|
||||||
|
}());
|
|
@ -0,0 +1,139 @@
|
||||||
|
import { Box } from '../../classes/display/box.js';
|
||||||
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
|
import { Actor } from '../../classes/display/actor.js';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('basic');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
|
function delay(min, max = min) {
|
||||||
|
const delayMs = min + Math.random() * (max - min);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, delayMs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(async function run() {
|
||||||
|
const scene = new Scene('Scene 2', rootBox).withSequenceDiagram();
|
||||||
|
|
||||||
|
const webClient = new Actor('webClient', scene);
|
||||||
|
|
||||||
|
const nodes = [];
|
||||||
|
const memories = [];
|
||||||
|
const storages = [];
|
||||||
|
|
||||||
|
async function addNode() {
|
||||||
|
const idx = nodes.length;
|
||||||
|
const node = new Actor(`node${idx}`, scene);
|
||||||
|
const memory = new Actor(`memory${idx}`, scene);
|
||||||
|
const storage = new Actor(`storage${idx}`, scene);
|
||||||
|
node.memory = memory;
|
||||||
|
node.storage = storage;
|
||||||
|
nodes.push(node);
|
||||||
|
memories.push(memory);
|
||||||
|
storages.push(storage);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPeer(node) {
|
||||||
|
const peers = nodes.filter((peer) => peer !== node);
|
||||||
|
const idx = Math.floor(Math.random() * peers.length);
|
||||||
|
return peers[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
await addNode();
|
||||||
|
await addNode();
|
||||||
|
|
||||||
|
const [
|
||||||
|
seekTruth,
|
||||||
|
considerInfo,
|
||||||
|
evaluateConfidence,
|
||||||
|
chooseResponse,
|
||||||
|
qualifiedOpinions,
|
||||||
|
requestMemoryData,
|
||||||
|
memoryData,
|
||||||
|
requestStorageData,
|
||||||
|
storageData,
|
||||||
|
] = [
|
||||||
|
'seek truth',
|
||||||
|
'consider available information',
|
||||||
|
'evaluate confidence',
|
||||||
|
'choose response',
|
||||||
|
'qualified opinions',
|
||||||
|
'request in-memory data',
|
||||||
|
'in-memory data',
|
||||||
|
'request storage data',
|
||||||
|
'storage data',
|
||||||
|
].map((name) => scene.addAction(name));
|
||||||
|
|
||||||
|
memories.forEach((memory) => {
|
||||||
|
memory.setStatus('Idle');
|
||||||
|
memory.on(requestMemoryData, async (src, _detail) => {
|
||||||
|
memory.setStatus('Retrieving data');
|
||||||
|
await delay(1000);
|
||||||
|
memory.send(src, memoryData, {});
|
||||||
|
memory.setStatus('Idle');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
storages.forEach((storage) => {
|
||||||
|
storage.setStatus('Idle');
|
||||||
|
storage.on(requestStorageData, async (src, _detail) => {
|
||||||
|
storage.setStatus('Retrieving data');
|
||||||
|
await delay(1000);
|
||||||
|
storage.send(src, storageData, {});
|
||||||
|
storage.setStatus('Idle');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
node.setStatus('Idle');
|
||||||
|
node.on(seekTruth, async (seeker, detail) => {
|
||||||
|
node.setStatus('Processing request');
|
||||||
|
|
||||||
|
node.on(chooseResponse, async (_src, _info) => {
|
||||||
|
node.setStatus('Choosing response');
|
||||||
|
await delay(1000);
|
||||||
|
node.send(seeker, qualifiedOpinions, {});
|
||||||
|
node.setStatus('Idle');
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on(evaluateConfidence, async (_src, _info) => {
|
||||||
|
node.setStatus('Evaluating confidence');
|
||||||
|
await delay(1000);
|
||||||
|
node.send(node, chooseResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on(considerInfo, async (_src, _info) => {
|
||||||
|
node.setStatus('Considering info');
|
||||||
|
await delay(1000);
|
||||||
|
node.send(node, evaluateConfidence);
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on(memoryData, (_src, _data) => {
|
||||||
|
node.on(storageData, (__src, __data) => {
|
||||||
|
if (detail?.readConcern === 'single') {
|
||||||
|
node.send(node, considerInfo, {});
|
||||||
|
} else {
|
||||||
|
const peer = getPeer(node);
|
||||||
|
node.on(qualifiedOpinions, (___src, info) => {
|
||||||
|
node.send(node, considerInfo, info);
|
||||||
|
});
|
||||||
|
node.send(peer, seekTruth, { readConcern: 'single' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
node.send(node.storage, requestStorageData);
|
||||||
|
});
|
||||||
|
|
||||||
|
await delay(1000);
|
||||||
|
node.send(node.memory, requestMemoryData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
webClient.on(qualifiedOpinions, (_src, _detail) => {
|
||||||
|
webClient.setStatus('Received opinions and qualifications');
|
||||||
|
});
|
||||||
|
|
||||||
|
await delay(1000);
|
||||||
|
webClient.setStatus('Seek truth');
|
||||||
|
webClient.send(nodes[0], seekTruth);
|
||||||
|
}());
|
|
@ -1,6 +1,6 @@
|
||||||
import { Business } from '../../classes/business.js';
|
import { Business } from '../../classes/actors/business.js';
|
||||||
import { Scene } from '../../classes/scene.js';
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
import { Box } from '../../classes/box.js';
|
import { Box } from '../../classes/display/box.js';
|
||||||
|
|
||||||
describe('Business', () => {
|
describe('Business', () => {
|
||||||
let scene;
|
let scene;
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Box } from '../../classes/display/box.js';
|
||||||
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
|
import { Action } from '../../classes/display/action.js';
|
||||||
|
import { Actor } from '../../classes/display/actor.js';
|
||||||
|
import { debounce, delay } from '../../util.js';
|
||||||
|
|
||||||
|
describe('Debounce', () => {
|
||||||
|
let scene;
|
||||||
|
let caller;
|
||||||
|
let debouncer;
|
||||||
|
let method;
|
||||||
|
|
||||||
|
let call;
|
||||||
|
let execute;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
const rootElement = document.getElementById('scene');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
|
scene = new Scene('Debounce test', rootBox).withSequenceDiagram();
|
||||||
|
caller = new Actor('Caller', scene);
|
||||||
|
debouncer = new Actor('Debouncer', scene);
|
||||||
|
method = new Actor('Target method', scene);
|
||||||
|
|
||||||
|
call = new Action('call', scene);
|
||||||
|
execute = new Action('execute', scene);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Suppresses extra events that occur within the specified window', async () => {
|
||||||
|
let eventCount = 0;
|
||||||
|
const event = sinon.spy(async () => {
|
||||||
|
eventCount++;
|
||||||
|
await execute.log(debouncer, method, eventCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
await scene.sequence.startSection();
|
||||||
|
await call.log(caller, debouncer, '1');
|
||||||
|
await debounce(event, 500);
|
||||||
|
await call.log(caller, debouncer, '2');
|
||||||
|
await debounce(event, 500);
|
||||||
|
|
||||||
|
await delay(500);
|
||||||
|
event.should.have.been.calledOnce;
|
||||||
|
|
||||||
|
await call.log(caller, debouncer, '3');
|
||||||
|
await debounce(event, 500);
|
||||||
|
await call.log(caller, debouncer, '4');
|
||||||
|
await debounce(event, 500);
|
||||||
|
|
||||||
|
eventCount.should.equal(2);
|
||||||
|
event.should.have.been.calledTwice;
|
||||||
|
await scene.sequence.endSection();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Propagates exceptions', async () => {
|
||||||
|
const event = sinon.spy(async () => {
|
||||||
|
await execute.log(debouncer, method, undefined, undefined, '-x');
|
||||||
|
throw new Error('An error occurs in the callback');
|
||||||
|
});
|
||||||
|
await scene.sequence.startSection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await call.log(caller, debouncer);
|
||||||
|
await debounce(event, 500);
|
||||||
|
} catch (e) {
|
||||||
|
event.should.have.been.calledOnce;
|
||||||
|
e.should.exist;
|
||||||
|
e.should.match(/An error occurs in the callback/);
|
||||||
|
}
|
||||||
|
await scene.sequence.endSection();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,58 +1,76 @@
|
||||||
import { Box } from '../../classes/box.js';
|
import { Box } from '../../classes/display/box.js';
|
||||||
import { Scene } from '../../classes/scene.js';
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
import { PostContent } from '../../classes/post-content.js';
|
import { PostContent } from '../../classes/util/post-content.js';
|
||||||
import { Expert } from '../../classes/expert.js';
|
import { Expert } from '../../classes/actors/expert.js';
|
||||||
import { ForumNode } from '../../classes/forum-node.js';
|
import { ForumNode } from '../../classes/forum-network/forum-node.js';
|
||||||
import { Network } from '../../classes/network.js';
|
import { Network } from '../../classes/forum-network/network.js';
|
||||||
import { delay, randomID } from '../../util.js';
|
import { delay, randomID } from '../../util.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById('scene');
|
describe('Forum Network', () => {
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
let scene;
|
||||||
|
let author1;
|
||||||
|
let author2;
|
||||||
|
let forumNetwork;
|
||||||
|
let forumNode1;
|
||||||
|
let forumNode2;
|
||||||
|
let forumNode3;
|
||||||
|
let processInterval;
|
||||||
|
|
||||||
window.scene = new Scene('Forum Network test', rootBox).withSequenceDiagram();
|
before(async () => {
|
||||||
|
const rootElement = document.getElementById('scene');
|
||||||
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
window.author1 = await new Expert('author1', window.scene).initialize();
|
scene = new Scene('Forum Network test', rootBox).withSequenceDiagram();
|
||||||
window.author2 = await new Expert('author2', window.scene).initialize();
|
|
||||||
|
|
||||||
window.forumNetwork = new Network();
|
author1 = await new Expert(null, 'author1', scene).initialize();
|
||||||
|
author2 = await new Expert(null, 'author2', scene).initialize();
|
||||||
|
|
||||||
window.forumNode1 = await new ForumNode('node1', window.scene).initialize(
|
forumNetwork = new Network();
|
||||||
window.forumNetwork,
|
|
||||||
);
|
|
||||||
window.forumNode2 = await new ForumNode('node2', window.scene).initialize(
|
|
||||||
window.forumNetwork,
|
|
||||||
);
|
|
||||||
window.forumNode3 = await new ForumNode('node3', window.scene).initialize(
|
|
||||||
window.forumNetwork,
|
|
||||||
);
|
|
||||||
|
|
||||||
const processInterval = setInterval(async () => {
|
forumNode1 = await new ForumNode('node1', scene).initialize(
|
||||||
await window.forumNode1.processNextMessage();
|
forumNetwork,
|
||||||
await window.forumNode2.processNextMessage();
|
);
|
||||||
await window.forumNode3.processNextMessage();
|
forumNode2 = await new ForumNode('node2', scene).initialize(
|
||||||
}, 100);
|
forumNetwork,
|
||||||
|
);
|
||||||
|
forumNode3 = await new ForumNode('node3', scene).initialize(
|
||||||
|
forumNetwork,
|
||||||
|
);
|
||||||
|
|
||||||
// const blockchain = new Blockchain();
|
processInterval = setInterval(async () => {
|
||||||
|
await forumNode1.processNextMessage();
|
||||||
|
await forumNode2.processNextMessage();
|
||||||
|
await forumNode3.processNextMessage();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
window.post1 = new PostContent({ message: 'hi' });
|
after(() => {
|
||||||
window.post1.id = randomID();
|
clearInterval(processInterval);
|
||||||
window.post2 = new PostContent({ message: 'hello' }).addCitation(
|
});
|
||||||
window.post1.id,
|
|
||||||
1.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
await delay(1000);
|
// const blockchain = new Blockchain();
|
||||||
await window.author1.submitPostViaNetwork(
|
|
||||||
window.forumNode1,
|
|
||||||
window.post1,
|
|
||||||
50,
|
|
||||||
);
|
|
||||||
await delay(1000);
|
|
||||||
await window.author2.submitPostViaNetwork(
|
|
||||||
window.forumNode2,
|
|
||||||
window.post2,
|
|
||||||
100,
|
|
||||||
);
|
|
||||||
|
|
||||||
await delay(1000);
|
specify('Author can submit a post to the network', async () => {
|
||||||
clearInterval(processInterval);
|
const post1 = new PostContent({ message: 'hi' });
|
||||||
|
post1.id = randomID();
|
||||||
|
const post2 = new PostContent({ message: 'hello' }).addCitation(
|
||||||
|
post1.id,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
await delay(1000);
|
||||||
|
await author1.submitPostViaNetwork(
|
||||||
|
forumNode1,
|
||||||
|
post1,
|
||||||
|
50,
|
||||||
|
);
|
||||||
|
await delay(1000);
|
||||||
|
await author2.submitPostViaNetwork(
|
||||||
|
forumNode2,
|
||||||
|
post2,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
|
||||||
|
await delay(1000);
|
||||||
|
}).timeout(10000);
|
||||||
|
});
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Box } from '../../../classes/box.js';
|
import { Box } from '../../../classes/display/box.js';
|
||||||
import { Scene } from '../../../classes/scene.js';
|
import { Scene } from '../../../classes/display/scene.js';
|
||||||
import { Expert } from '../../../classes/expert.js';
|
import { Expert } from '../../../classes/actors/expert.js';
|
||||||
import { PostContent } from '../../../classes/post-content.js';
|
import { PostContent } from '../../../classes/util/post-content.js';
|
||||||
import { delay } from '../../../util.js';
|
import { delay } from '../../../util.js';
|
||||||
import params from '../../../params.js';
|
import params from '../../../params.js';
|
||||||
import { DAO } from '../../../classes/dao.js';
|
import { DAO } from '../../../classes/actors/dao.js';
|
||||||
|
|
||||||
export class ForumTest {
|
export class ForumTest {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
this.scene = null;
|
||||||
this.dao = null;
|
this.dao = null;
|
||||||
this.experts = null;
|
this.experts = null;
|
||||||
this.posts = null;
|
this.posts = null;
|
||||||
|
@ -18,19 +19,10 @@ export class ForumTest {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async newExpert() {
|
|
||||||
const index = this.experts.length;
|
|
||||||
const name = `Expert${index + 1}`;
|
|
||||||
const expert = await new Expert(this.dao, name).initialize();
|
|
||||||
this.experts.push(expert);
|
|
||||||
// expert.addValue('rep', () => this.dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
|
||||||
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 window.scene.startSection();
|
await this.scene.sequence.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) {
|
||||||
|
@ -48,16 +40,25 @@ 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 window.scene.endSection();
|
await this.scene.sequence.endSection();
|
||||||
await delay(this.options.defaultDelayMs);
|
await delay(this.options.defaultDelayMs);
|
||||||
return postId;
|
return postId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async newExpert() {
|
||||||
|
const index = this.experts.length;
|
||||||
|
const name = `Expert${index + 1}`;
|
||||||
|
const expert = await new Expert(this.dao, name, this.scene).initialize();
|
||||||
|
this.experts.push(expert);
|
||||||
|
// await expert.addComputedValue('rep', () => this.dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
||||||
|
return expert;
|
||||||
|
}
|
||||||
|
|
||||||
async setup() {
|
async setup() {
|
||||||
const rootElement = document.getElementById('scene');
|
const rootElement = document.getElementById('scene');
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
|
|
||||||
const scene = (window.scene = new Scene('Forum test', rootBox));
|
const scene = this.scene = new Scene('Forum test', rootBox);
|
||||||
scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
scene.withFlowchart();
|
scene.withFlowchart();
|
||||||
scene.withTable();
|
scene.withTable();
|
||||||
|
@ -70,17 +71,17 @@ export class ForumTest {
|
||||||
scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
|
scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
|
||||||
scene.addDisplayValue(' ');
|
scene.addDisplayValue(' ');
|
||||||
|
|
||||||
this.dao = (window.dao = new DAO('DAO'));
|
this.dao = new DAO('DAO', scene);
|
||||||
this.forum = this.dao.forum;
|
this.forum = this.dao.forum;
|
||||||
this.experts = (window.experts = []);
|
this.experts = [];
|
||||||
this.posts = (window.posts = []);
|
this.posts = [];
|
||||||
|
|
||||||
await this.newExpert();
|
await this.newExpert();
|
||||||
// await newExpert();
|
// await newExpert();
|
||||||
// await newExpert();
|
// await newExpert();
|
||||||
|
|
||||||
this.dao.addValue('total value', () => this.dao.reputation.getTotal());
|
await this.dao.addComputedValue('total value', () => this.dao.reputation.getTotal());
|
||||||
// this.dao.addValue('total reputation', () => this.dao.forum.getTotalValue());
|
// await this.dao.addComputedValue('total reputation', () => this.dao.forum.getTotalValue());
|
||||||
this.dao.computeValues();
|
this.dao.computeValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,6 @@ describe('Forum', () => {
|
||||||
await forumTest.addPost(experts[0], 10, [
|
await forumTest.addPost(experts[0], 10, [
|
||||||
{ postId: posts[2], weight: 1 },
|
{ postId: posts[2], weight: 1 },
|
||||||
]);
|
]);
|
||||||
console.log('test5', { posts });
|
|
||||||
forum.getPost(posts[0]).value.should.equal(0);
|
forum.getPost(posts[0]).value.should.equal(0);
|
||||||
forum.getPost(posts[1]).value.should.equal(40);
|
forum.getPost(posts[1]).value.should.equal(40);
|
||||||
forum.getPost(posts[2]).value.should.equal(0);
|
forum.getPost(posts[2]).value.should.equal(0);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Box } from '../../classes/box.js';
|
import { Box } from '../../classes/display/box.js';
|
||||||
import { Scene } from '../../classes/scene.js';
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
import { Expert } from '../../classes/expert.js';
|
import { Expert } from '../../classes/actors/expert.js';
|
||||||
import { PostContent } from '../../classes/post-content.js';
|
import { PostContent } from '../../classes/util/post-content.js';
|
||||||
import { delay } from '../../util.js';
|
import { delay } from '../../util.js';
|
||||||
import { DAO } from '../../classes/dao.js';
|
import { DAO } from '../../classes/actors/dao.js';
|
||||||
|
|
||||||
const POOL_DURATION_MS = 100;
|
const POOL_DURATION_MS = 100;
|
||||||
const DEFAULT_DELAY_MS = 100;
|
const DEFAULT_DELAY_MS = 100;
|
||||||
|
@ -15,8 +15,8 @@ let dao;
|
||||||
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(dao, name).initialize();
|
const expert = await new Expert(dao, name, scene).initialize();
|
||||||
expert.addValue('rep', () => dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
await expert.addComputedValue('rep', () => dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
||||||
experts.push(expert);
|
experts.push(expert);
|
||||||
return expert;
|
return expert;
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,9 @@ async function setup() {
|
||||||
scene.withSequenceDiagram();
|
scene.withSequenceDiagram();
|
||||||
scene.withTable();
|
scene.withTable();
|
||||||
|
|
||||||
dao = new DAO('DGF');
|
dao = new DAO('DGF', scene);
|
||||||
|
|
||||||
experts = (window.experts = []);
|
experts = [];
|
||||||
await newExpert();
|
await newExpert();
|
||||||
await newExpert();
|
await newExpert();
|
||||||
|
|
||||||
|
@ -43,8 +43,16 @@ describe('Validation Pool', () => {
|
||||||
await setup();
|
await setup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await scene.sequence.startSection();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await scene.sequence.endSection();
|
||||||
|
});
|
||||||
|
|
||||||
it('First expert can self-approve', async () => {
|
it('First expert can self-approve', async () => {
|
||||||
scene.startSection();
|
await scene.sequence.startSection();
|
||||||
const { pool } = await experts[0].submitPostWithFee(new PostContent(), {
|
const { pool } = await experts[0].submitPostWithFee(new PostContent(), {
|
||||||
fee: 7,
|
fee: 7,
|
||||||
duration: POOL_DURATION_MS,
|
duration: POOL_DURATION_MS,
|
||||||
|
@ -64,14 +72,13 @@ describe('Validation Pool', () => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await scene.sequence.endSection();
|
||||||
await delay(POOL_DURATION_MS);
|
await delay(POOL_DURATION_MS);
|
||||||
await pool.evaluateWinningConditions(); // Vote passes
|
await pool.evaluateWinningConditions(); // Vote passes
|
||||||
await delay(DEFAULT_DELAY_MS);
|
await delay(DEFAULT_DELAY_MS);
|
||||||
scene.endSection();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Failure example: second expert can not self-approve', async () => {
|
it('Failure example: second expert can not self-approve', async () => {
|
||||||
scene.startSection();
|
|
||||||
try {
|
try {
|
||||||
const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
|
const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
|
||||||
fee: 1,
|
fee: 1,
|
||||||
|
@ -84,11 +91,9 @@ describe('Validation Pool', () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message.should.match(/Quorum is not met/);
|
e.message.should.match(/Quorum is not met/);
|
||||||
}
|
}
|
||||||
scene.endSection();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Second expert must be approved by first expert', async () => {
|
it('Second expert must be approved by first expert', async () => {
|
||||||
scene.startSection();
|
|
||||||
const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
|
const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
|
||||||
fee: 1,
|
fee: 1,
|
||||||
duration: POOL_DURATION_MS,
|
duration: POOL_DURATION_MS,
|
||||||
|
@ -102,6 +107,5 @@ describe('Validation Pool', () => {
|
||||||
await delay(POOL_DURATION_MS);
|
await delay(POOL_DURATION_MS);
|
||||||
await pool.evaluateWinningConditions(); // Stake passes
|
await pool.evaluateWinningConditions(); // Stake passes
|
||||||
await delay(DEFAULT_DELAY_MS);
|
await delay(DEFAULT_DELAY_MS);
|
||||||
scene.endSection();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
import { Actor } from '../../classes/actor.js';
|
import { Actor } from '../../classes/display/actor.js';
|
||||||
import { Box } from '../../classes/box.js';
|
import { Box } from '../../classes/display/box.js';
|
||||||
import { Scene } from '../../classes/scene.js';
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
import { VM } from '../../classes/vm.js';
|
import { VM } from '../../classes/supporting/vm.js';
|
||||||
|
|
||||||
const rootElement = document.getElementById('scene');
|
const contractIds = ['contract-id-1', 'contract-id-2'];
|
||||||
const rootBox = new Box('rootBox', rootElement).flex();
|
|
||||||
window.scene = new Scene('VM test', rootBox).withSequenceDiagram();
|
|
||||||
|
|
||||||
const testContractId = 'test-contract-id';
|
class Greeter extends Actor {
|
||||||
class TestContract extends Actor {
|
constructor(vm, value, scene) {
|
||||||
constructor(vm, value) {
|
super('Greeter', scene);
|
||||||
super('TestContract');
|
|
||||||
this.vm = vm;
|
this.vm = vm;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
hello(sender) {
|
hello(sender, message) {
|
||||||
return `${sender.name} ${this.value}`;
|
return `${sender.name} ${this.value}: ${message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Repeater extends Actor {
|
||||||
|
constructor(vm, greeter, scene) {
|
||||||
|
super('Repeater', scene);
|
||||||
|
this.vmHandle = vm.getHandle(this);
|
||||||
|
this.greeter = greeter;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(__sender, method, message) {
|
||||||
|
return this.vmHandle.callContract(this.greeter, method, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,19 +33,38 @@ describe('VM', () => {
|
||||||
let vm;
|
let vm;
|
||||||
let sender;
|
let sender;
|
||||||
let vmHandle;
|
let vmHandle;
|
||||||
|
let scene;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
vm = (window.vm = new VM());
|
const rootElement = document.getElementById('scene');
|
||||||
sender = new Actor('Sender');
|
const rootBox = new Box('rootBox', rootElement).flex();
|
||||||
vm.addContract(testContractId, TestContract, 'world');
|
scene = new Scene('VM test', rootBox).withSequenceDiagram();
|
||||||
|
vm = new VM(scene);
|
||||||
|
sender = new Actor('Sender', scene);
|
||||||
|
vm.addContract(contractIds[0], Greeter, 'world', scene);
|
||||||
|
vm.addContract(contractIds[1], Repeater, contractIds[0], scene);
|
||||||
vmHandle = vm.getHandle(sender);
|
vmHandle = vm.getHandle(sender);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await scene.sequence.startSection();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await scene.sequence.endSection();
|
||||||
|
});
|
||||||
|
|
||||||
it('Should exist', () => {
|
it('Should exist', () => {
|
||||||
should.exist(vm);
|
should.exist(vm);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Call a contract method', () => {
|
it('Call a contract method', async () => {
|
||||||
vmHandle.callContract(testContractId, 'hello').should.equal('Sender world');
|
(await Promise.resolve(vmHandle.callContract(contractIds[0], 'hello', 'good morning')))
|
||||||
|
.should.equal('Sender world: good morning');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Call a contract method which calls another contract method', async () => {
|
||||||
|
(await Promise.resolve(vmHandle.callContract(contractIds[1], 'forward', 'hello', 'good day')))
|
||||||
|
.should.equal('Repeater world: good day');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box } from '../../classes/box.js';
|
import { Box } from '../../classes/display/box.js';
|
||||||
import { Scene } from '../../classes/scene.js';
|
import { Scene } from '../../classes/display/scene.js';
|
||||||
import { WDAG } from '../../classes/wdag.js';
|
import { WDAG } from '../../classes/supporting/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();
|
||||||
|
|
|
@ -15,8 +15,12 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
|
||||||
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
|
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://unpkg.com/mocha/mocha.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/10.2.0/mocha.min.js"
|
||||||
<script src="https://unpkg.com/chai/chai.js"></script>
|
integrity="sha512-jsP/sG70bnt0xNVJt+k9NxQqGYvRrLzWhI+46SSf7oNJeCwdzZlBvoyrAN0zhtVyolGcHNh/9fEgZppG2pH+eA=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.7/chai.min.js"
|
||||||
|
integrity="sha512-Pwgr3yHn4Gvztp1GKl0ihhAWLZfqgp4/SbMt4HKW7AymuTQODMCNPE7v1uGapTeOoQQ5Hoz367b4seKpx6j7Zg=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script type="module" src="./scripts/validation-pool.test.js"></script>
|
<script type="module" src="./scripts/validation-pool.test.js"></script>
|
||||||
<script defer class="mocha-init">
|
<script defer class="mocha-init">
|
||||||
mocha.setup({
|
mocha.setup({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CryptoUtil } from './classes/crypto.js';
|
import { CryptoUtil } from './classes/util/crypto.js';
|
||||||
|
|
||||||
const timers = new Map();
|
const timers = new Map();
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ export const hexToRGB = (input) => {
|
||||||
return { r, g, b };
|
return { r, g, b };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const displayNumber = (value) => (value.toString().length > 6 ? value.toFixed(2) : value);
|
export const displayNumber = (value, decimals = 2) => (value.toString().length > decimals + 4
|
||||||
|
? value.toFixed(decimals)
|
||||||
|
: value);
|
||||||
|
|
||||||
export const randomID = () => CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8);
|
export const randomID = () => CryptoUtil.randomUUID().replaceAll('-', '').slice(0, 8);
|
||||||
|
|
Loading…
Reference in New Issue