From fc27cda81d87c542509cf2f5dd19b1713f8b89c6 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sat, 4 Feb 2023 22:32:47 -0600 Subject: [PATCH] Reorganization --- forum-network/.eslintrc.js | 22 +- forum-network/notes/dao.md | 1 + .../src/classes/{ => actors}/availability.js | 12 +- .../src/classes/{ => actors}/business.js | 18 +- forum-network/src/classes/{ => actors}/dao.js | 25 +- .../src/classes/{ => actors}/expert.js | 24 +- .../src/classes/{ => actors}/forum.js | 33 ++- .../src/classes/{ => actors}/post.js | 6 +- .../src/classes/{ => actors}/public.js | 10 +- .../src/classes/actors/reputation-holder.js | 9 + .../classes/{ => actors}/validation-pool.js | 60 +++-- .../src/classes/{ => contracts}/erc20.js | 0 .../src/classes/{ => contracts}/erc721.js | 0 .../{ => contracts}/reputation-token.js | 2 +- .../src/classes/{ => display}/action.js | 5 +- .../src/classes/{ => display}/actor.js | 45 ++-- .../src/classes/{ => display}/box.js | 2 +- .../classes/{ => display}/display-value.js | 4 +- forum-network/src/classes/display/mermaid.js | 65 +++++ forum-network/src/classes/display/scene.js | 118 +++++++++ forum-network/src/classes/display/sequence.js | 74 ++++++ forum-network/src/classes/display/table.js | 45 ++++ .../classes/{ => forum-network}/forum-node.js | 10 +- .../classes/{ => forum-network}/forum-view.js | 2 +- .../classes/{ => forum-network}/message.js | 4 +- .../{ => forum-network}/network-node.js | 14 +- .../classes/{ => forum-network}/network.js | 0 .../classes/{ => ideas}/block-consensus.js | 0 .../src/classes/{ => ideas}/exchange.js | 0 .../src/classes/{ => ideas}/finance.js | 0 .../src/classes/{ => ideas}/question.js | 0 .../src/classes/{ => ideas}/storage.js | 0 forum-network/src/classes/list.js | 9 - .../src/classes/reputation-holder.js | 9 - forum-network/src/classes/reputation.js | 88 ------- forum-network/src/classes/scene.js | 239 ------------------ .../src/classes/{ => supporting}/stake.js | 2 +- .../src/classes/{ => supporting}/vm.js | 16 +- .../src/classes/{ => supporting}/voter.js | 0 .../src/classes/{ => supporting}/wdag.js | 9 +- .../src/classes/{ => util}/crypto.js | 0 .../src/classes/{ => util}/post-content.js | 0 .../classes/{ => util}/prioritized-queue.js | 0 forum-network/src/index.html | 5 +- forum-network/src/tests/all.test.html | 2 +- .../src/tests/availability.test.html | 26 +- forum-network/src/tests/basic.test.html | 195 +------------- forum-network/src/tests/basic2.test.html | 13 + forum-network/src/tests/debounce.test.html | 50 ++-- .../src/tests/forum-network.test.html | 22 +- .../src/tests/scripts/availability.test.js | 222 ++++++++-------- forum-network/src/tests/scripts/basic.test.js | 67 +++++ .../src/tests/scripts/basic2.test.js | 139 ++++++++++ .../src/tests/scripts/business.test.js | 6 +- .../src/tests/scripts/debounce.test.js | 72 ++++++ .../src/tests/scripts/forum-network.test.js | 112 ++++---- .../tests/scripts/forum/forum.test-util.js | 45 ++-- .../src/tests/scripts/forum/forum4.test.js | 1 - .../src/tests/scripts/validation-pool.test.js | 34 +-- forum-network/src/tests/scripts/vm.test.js | 64 +++-- forum-network/src/tests/scripts/wdag.test.js | 6 +- .../src/tests/validation-pool.test.html | 8 +- forum-network/src/util.js | 6 +- 63 files changed, 1135 insertions(+), 942 deletions(-) create mode 100644 forum-network/notes/dao.md rename forum-network/src/classes/{ => actors}/availability.js (88%) rename forum-network/src/classes/{ => actors}/business.js (86%) rename forum-network/src/classes/{ => actors}/dao.js (75%) rename forum-network/src/classes/{ => actors}/expert.js (85%) rename forum-network/src/classes/{ => actors}/forum.js (86%) rename forum-network/src/classes/{ => actors}/post.js (92%) rename forum-network/src/classes/{ => actors}/public.js (54%) create mode 100644 forum-network/src/classes/actors/reputation-holder.js rename forum-network/src/classes/{ => actors}/validation-pool.js (87%) rename forum-network/src/classes/{ => contracts}/erc20.js (100%) rename forum-network/src/classes/{ => contracts}/erc721.js (100%) rename forum-network/src/classes/{ => contracts}/reputation-token.js (98%) rename forum-network/src/classes/{ => display}/action.js (71%) rename forum-network/src/classes/{ => display}/actor.js (54%) rename forum-network/src/classes/{ => display}/box.js (96%) rename forum-network/src/classes/{ => display}/display-value.js (75%) create mode 100644 forum-network/src/classes/display/mermaid.js create mode 100644 forum-network/src/classes/display/scene.js create mode 100644 forum-network/src/classes/display/sequence.js create mode 100644 forum-network/src/classes/display/table.js rename forum-network/src/classes/{ => forum-network}/forum-node.js (91%) rename forum-network/src/classes/{ => forum-network}/forum-view.js (97%) rename forum-network/src/classes/{ => forum-network}/message.js (92%) rename forum-network/src/classes/{ => forum-network}/network-node.js (83%) rename forum-network/src/classes/{ => forum-network}/network.js (100%) rename forum-network/src/classes/{ => ideas}/block-consensus.js (100%) rename forum-network/src/classes/{ => ideas}/exchange.js (100%) rename forum-network/src/classes/{ => ideas}/finance.js (100%) rename forum-network/src/classes/{ => ideas}/question.js (100%) rename forum-network/src/classes/{ => ideas}/storage.js (100%) delete mode 100644 forum-network/src/classes/list.js delete mode 100644 forum-network/src/classes/reputation-holder.js delete mode 100644 forum-network/src/classes/reputation.js delete mode 100644 forum-network/src/classes/scene.js rename forum-network/src/classes/{ => supporting}/stake.js (89%) rename forum-network/src/classes/{ => supporting}/vm.js (71%) rename forum-network/src/classes/{ => supporting}/voter.js (100%) rename forum-network/src/classes/{ => supporting}/wdag.js (95%) rename forum-network/src/classes/{ => util}/crypto.js (100%) rename forum-network/src/classes/{ => util}/post-content.js (100%) rename forum-network/src/classes/{ => util}/prioritized-queue.js (100%) create mode 100644 forum-network/src/tests/basic2.test.html create mode 100644 forum-network/src/tests/scripts/basic.test.js create mode 100644 forum-network/src/tests/scripts/basic2.test.js create mode 100644 forum-network/src/tests/scripts/debounce.test.js diff --git a/forum-network/.eslintrc.js b/forum-network/.eslintrc.js index 4601ff3..3a4f0e3 100644 --- a/forum-network/.eslintrc.js +++ b/forum-network/.eslintrc.js @@ -2,9 +2,17 @@ module.exports = { env: { browser: true, es2021: true, + mocha: true, }, extends: ['airbnb-base'], - overrides: [], + overrides: [ + { + files: ['*.test.js'], + rules: { + 'no-unused-expressions': 'off', + }, + }, + ], parserOptions: { ecmaVersion: 'latest', sourceType: 'module', @@ -33,16 +41,8 @@ module.exports = { globals: { _: 'readonly', chai: 'readonly', - expect: 'readonly', - mocha: 'readonly', - describe: 'readonly', - context: 'readonly', - it: 'readonly', - specify: 'readonly', + sinon: 'readonly', + sinonChai: 'readonly', should: 'readonly', - before: 'readonly', - after: 'readonly', - beforeEach: 'readonly', - afterEach: 'readonly', }, }; diff --git a/forum-network/notes/dao.md b/forum-network/notes/dao.md new file mode 100644 index 0000000..48a026e --- /dev/null +++ b/forum-network/notes/dao.md @@ -0,0 +1 @@ +A DAO is a group of cooperating entities diff --git a/forum-network/src/classes/availability.js b/forum-network/src/classes/actors/availability.js similarity index 88% rename from forum-network/src/classes/availability.js rename to forum-network/src/classes/actors/availability.js index c2c1b00..47d936d 100644 --- a/forum-network/src/classes/availability.js +++ b/forum-network/src/classes/actors/availability.js @@ -1,6 +1,6 @@ -import { Action } from './action.js'; -import { Actor } from './actor.js'; -import { CryptoUtil } from './crypto.js'; +import { Action } from '../display/action.js'; +import { Actor } from '../display/actor.js'; +import { CryptoUtil } from '../util/crypto.js'; class Worker { constructor(reputationPublicKey, tokenId, stakeAmount, duration) { @@ -17,12 +17,12 @@ class Worker { * Purpose: Enable staking reputation to enter the pool of workers */ export class Availability extends Actor { - constructor(dao, name) { - super(name); + constructor(dao, name, scene) { + super(name, scene); this.dao = dao; this.actions = { - assignWork: new Action('assign work'), + assignWork: new Action('assign work', scene), }; this.workers = new Map(); diff --git a/forum-network/src/classes/business.js b/forum-network/src/classes/actors/business.js similarity index 86% rename from forum-network/src/classes/business.js rename to forum-network/src/classes/actors/business.js index 23ff6e3..67a2705 100644 --- a/forum-network/src/classes/business.js +++ b/forum-network/src/classes/actors/business.js @@ -1,7 +1,7 @@ -import { randomID } from '../util.js'; -import { Action } from './action.js'; -import { Actor } from './actor.js'; -import { PostContent } from './post-content.js'; +import { randomID } from '../../util.js'; +import { Action } from '../display/action.js'; +import { Actor } from '../display/actor.js'; +import { PostContent } from '../util/post-content.js'; class Request { static nextSeq = 0; @@ -20,14 +20,14 @@ class Request { * Purpose: Enable fee-driven work requests, to be completed by workers from the availability pool */ export class Business extends Actor { - constructor(dao, name) { - super(name); + constructor(dao, name, scene) { + super(name, scene); this.dao = dao; this.actions = { - assignWork: new Action('assign work'), - submitPost: new Action('submit post'), - initiateValidationPool: new Action('initiate validation pool'), + assignWork: new Action('assign work', scene), + submitPost: new Action('submit post', scene), + initiateValidationPool: new Action('initiate validation pool', scene), }; this.requests = new Map(); diff --git a/forum-network/src/classes/dao.js b/forum-network/src/classes/actors/dao.js similarity index 75% rename from forum-network/src/classes/dao.js rename to forum-network/src/classes/actors/dao.js index 59c9535..ee6f6a3 100644 --- a/forum-network/src/classes/dao.js +++ b/forum-network/src/classes/actors/dao.js @@ -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 { ReputationTokenContract } from './reputation-token.js'; +import { ReputationTokenContract } from '../contracts/reputation-token.js'; import { ValidationPool } from './validation-pool.js'; import { Availability } from './availability.js'; import { Business } from './business.js'; -import { Actor } from './actor.js'; +import { Actor } from '../display/actor.js'; /** * Purpose: @@ -14,13 +13,13 @@ import { Actor } from './actor.js'; * - Reputation: Keep track of reputation accrued to each expert */ export class DAO extends Actor { - constructor(name) { - super(name); + constructor(name, scene) { + super(name, scene); /* Contracts */ - this.forum = new Forum(this, 'Forum'); - this.availability = new Availability(this, 'Availability'); - this.business = new Business(this, 'Business'); + this.forum = new Forum(this, 'Forum', scene); + this.availability = new Availability(this, 'Availability', scene); + this.business = new Business(this, 'Business', scene); this.reputation = new ReputationTokenContract(); /* Data */ @@ -59,9 +58,8 @@ export class DAO extends Actor { async initiateValidationPool(poolOptions, stakeOptions) { const validationPoolNumber = this.validationPools.size + 1; 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); - pool.activate(); if (stakeOptions) { const { reputationPublicKey, tokenId, authorStakeAmount } = stakeOptions; @@ -74,4 +72,9 @@ export class DAO extends Actor { return pool; } + + async submitPost(reputationPublicKey, postContent) { + const post = await this.forum.addPost(reputationPublicKey, postContent); + return post.id; + } } diff --git a/forum-network/src/classes/expert.js b/forum-network/src/classes/actors/expert.js similarity index 85% rename from forum-network/src/classes/expert.js rename to forum-network/src/classes/actors/expert.js index 2fff24d..b8ecda0 100644 --- a/forum-network/src/classes/expert.js +++ b/forum-network/src/classes/actors/expert.js @@ -1,20 +1,20 @@ -import { Action } from './action.js'; -import { PostMessage } from './message.js'; -import { CryptoUtil } from './crypto.js'; +import { Action } from '../display/action.js'; +import { PostMessage } from '../forum-network/message.js'; +import { CryptoUtil } from '../util/crypto.js'; import { ReputationHolder } from './reputation-holder.js'; export class Expert extends ReputationHolder { - constructor(dao, name) { - super(name); + constructor(dao, name, scene) { + super(name, scene); this.dao = dao; this.actions = { - submitPostViaNetwork: new Action('submit post via network'), - submitPost: new Action('submit post'), - initiateValidationPool: new Action('initiate validation pool'), - stake: new Action('stake on post'), - registerAvailability: new Action('register availability'), - getAssignedWork: new Action('get assigned work'), - submitWork: new Action('submit work evidence'), + submitPostViaNetwork: new Action('submit post via network', scene), + submitPost: new Action('submit post', scene), + initiateValidationPool: new Action('initiate validation pool', scene), + stake: new Action('stake on post', scene), + registerAvailability: new Action('register availability', scene), + getAssignedWork: new Action('get assigned work', scene), + submitWork: new Action('submit work evidence', scene), }; this.validationPools = new Map(); this.tokens = []; diff --git a/forum-network/src/classes/forum.js b/forum-network/src/classes/actors/forum.js similarity index 86% rename from forum-network/src/classes/forum.js rename to forum-network/src/classes/actors/forum.js index 9a1c671..a034f84 100644 --- a/forum-network/src/classes/forum.js +++ b/forum-network/src/classes/actors/forum.js @@ -1,8 +1,8 @@ -import { WDAG } from './wdag.js'; -import { Action } from './action.js'; -import params from '../params.js'; +import { WDAG } from '../supporting/wdag.js'; +import { Action } from '../display/action.js'; +import params from '../../params.js'; import { ReputationHolder } from './reputation-holder.js'; -import { EPSILON } from '../util.js'; +import { displayNumber, EPSILON } from '../../util.js'; import { Post } from './post.js'; const CITATION = 'citation'; @@ -14,14 +14,16 @@ const BALANCE = 'balance'; * and the value accrued via each post and citation. */ export class Forum extends ReputationHolder { - constructor(dao, name) { - super(name); + constructor(dao, name, scene) { + + super(name, scene); this.dao = dao; this.id = this.reputationPublicKey; this.posts = new WDAG(); this.actions = { - propagateValue: new Action('propagate'), - transfer: new Action('transfer'), + propagate: new Action('propagate', scene), + 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 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); } @@ -98,10 +100,10 @@ export class Forum extends ReputationHolder { }) { const postVertex = edge.to; 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) { - this.actions.propagateValue.log( + this.actions.propagate.log( edge.from.data, post, `referenceChainLimit (${params.referenceChainLimit}) reached`, @@ -141,9 +143,18 @@ export class Forum extends ReputationHolder { depth: depth + 1, initialNegative: initialNegative || (depth === 0 && outboundAmount < 0), }); + outboundAmount -= refundFromOutbound; this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount); totalOutboundAmount += outboundAmount; + + this.actions.confirm.log( + citationEdge.to.data, + citationEdge.from.data, + `(refund: ${displayNumber(refundFromOutbound)}, leach: ${outboundAmount * params.leachingValue})`, + undefined, + '-->>', + ); } } return totalOutboundAmount; diff --git a/forum-network/src/classes/post.js b/forum-network/src/classes/actors/post.js similarity index 92% rename from forum-network/src/classes/post.js rename to forum-network/src/classes/actors/post.js index ffdfef3..a214c6d 100644 --- a/forum-network/src/classes/post.js +++ b/forum-network/src/classes/actors/post.js @@ -1,6 +1,6 @@ -import { Actor } from './actor.js'; -import { displayNumber } from '../util.js'; -import params from '../params.js'; +import { Actor } from '../display/actor.js'; +import { displayNumber } from '../../util.js'; +import params from '../../params.js'; export class Post extends Actor { constructor(forum, authorPublicKey, postContent) { diff --git a/forum-network/src/classes/public.js b/forum-network/src/classes/actors/public.js similarity index 54% rename from forum-network/src/classes/public.js rename to forum-network/src/classes/actors/public.js index 1eb142a..5c49b92 100644 --- a/forum-network/src/classes/public.js +++ b/forum-network/src/classes/actors/public.js @@ -1,11 +1,11 @@ -import { Action } from './action.js'; -import { Actor } from './actor.js'; +import { Action } from '../display/action.js'; +import { Actor } from '../display/actor.js'; export class Public extends Actor { - constructor(name) { - super(name); + constructor(name, scene) { + super(name, scene); this.actions = { - submitRequest: new Action('submit work request'), + submitRequest: new Action('submit work request', scene), }; } diff --git a/forum-network/src/classes/actors/reputation-holder.js b/forum-network/src/classes/actors/reputation-holder.js new file mode 100644 index 0000000..b2aaf0e --- /dev/null +++ b/forum-network/src/classes/actors/reputation-holder.js @@ -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()}`; + } +} diff --git a/forum-network/src/classes/validation-pool.js b/forum-network/src/classes/actors/validation-pool.js similarity index 87% rename from forum-network/src/classes/validation-pool.js rename to forum-network/src/classes/actors/validation-pool.js index 39b1638..5accbf6 100644 --- a/forum-network/src/classes/validation-pool.js +++ b/forum-network/src/classes/actors/validation-pool.js @@ -1,8 +1,9 @@ import { ReputationHolder } from './reputation-holder.js'; -import { Stake } from './stake.js'; -import { Voter } from './voter.js'; -import params from '../params.js'; -import { Action } from './action.js'; +import { Stake } from '../supporting/stake.js'; +import { Voter } from '../supporting/voter.js'; +import params from '../../params.js'; +import { Action } from '../display/action.js'; +import { displayNumber } from '../../util.js'; const ValidationPoolStates = Object.freeze({ OPEN: 'OPEN', @@ -25,13 +26,15 @@ export class ValidationPool extends ReputationHolder { contentiousDebate = false, }, name, + scene, ) { - super(name); + super(name, scene); this.id = this.reputationPublicKey; this.actions = { - reward: new Action('reward'), - transfer: new Action('transfer'), + reward: new Action('reward', scene), + transfer: new Action('transfer', scene), + mint: new Action('mint', scene), }; // If contentiousDebate = true, we will follow the progression defined by getTokenLossRatio() @@ -82,10 +85,14 @@ export class ValidationPool extends ReputationHolder { tokenId: this.tokenId, }); + this.actions.mint.log(this, this, `(${this.mintedValue})`); + // Keep a record of voters and their votes const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey); voter.addVoteRecord(this); this.dao.experts.set(reputationPublicKey, voter); + + this.activate(); } getTokenLossRatio() { @@ -167,15 +174,13 @@ export class ValidationPool extends ReputationHolder { this.dao.reputation.transferValueFrom(tokenId, this.tokenId, amount); // 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); voter.addVoteRecord(this); this.dao.experts.set(reputationPublicKey, voter); - } - // Update computed display values - for (const voter of this.dao.experts.values()) { - const actor = window?.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey); + // Update computed display values + const actor = this.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey); await actor.computeValues(); } } @@ -216,16 +221,28 @@ export class ValidationPool extends ReputationHolder { if (quorumMet) { 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(); await this.distributeReputation({ votePasses }); // TODO: distribute fees } else { 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; return result; } @@ -249,8 +266,8 @@ export class ValidationPool extends ReputationHolder { const reputationPublicKey = this.dao.reputation.ownerOf(tokenId); console.log(`reward for winning stake by ${reputationPublicKey}: ${reward}`); this.dao.reputation.transferValueFrom(this.tokenId, tokenId, reward + amount); - const toActor = window?.scene?.findActor((actor) => actor.reputationPublicKey === reputationPublicKey); - this.actions.reward.log(this, toActor, `(${reward})`); + const toActor = this.scene?.findActor((actor) => actor.reputationPublicKey === reputationPublicKey); + this.actions.reward.log(this, toActor, `(${displayNumber(reward)})`); } if (votePasses) { @@ -272,14 +289,5 @@ export class ValidationPool extends ReputationHolder { } 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`); } } diff --git a/forum-network/src/classes/erc20.js b/forum-network/src/classes/contracts/erc20.js similarity index 100% rename from forum-network/src/classes/erc20.js rename to forum-network/src/classes/contracts/erc20.js diff --git a/forum-network/src/classes/erc721.js b/forum-network/src/classes/contracts/erc721.js similarity index 100% rename from forum-network/src/classes/erc721.js rename to forum-network/src/classes/contracts/erc721.js diff --git a/forum-network/src/classes/reputation-token.js b/forum-network/src/classes/contracts/reputation-token.js similarity index 98% rename from forum-network/src/classes/reputation-token.js rename to forum-network/src/classes/contracts/reputation-token.js index a12b4e8..c39af4d 100644 --- a/forum-network/src/classes/reputation-token.js +++ b/forum-network/src/classes/contracts/reputation-token.js @@ -1,6 +1,6 @@ import { ERC721 } from './erc721.js'; -import { EPSILON, randomID } from '../util.js'; +import { EPSILON, randomID } from '../../util.js'; class Lock { constructor(tokenId, amount, duration) { diff --git a/forum-network/src/classes/action.js b/forum-network/src/classes/display/action.js similarity index 71% rename from forum-network/src/classes/action.js rename to forum-network/src/classes/display/action.js index 69df2a3..9c1329e 100644 --- a/forum-network/src/classes/action.js +++ b/forum-network/src/classes/display/action.js @@ -1,10 +1,11 @@ export class Action { - constructor(name) { + constructor(name, scene) { this.name = name; + this.scene = scene; } 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 ?? ''} ${ JSON.stringify(obj) ?? '' }`, diff --git a/forum-network/src/classes/actor.js b/forum-network/src/classes/display/actor.js similarity index 54% rename from forum-network/src/classes/actor.js rename to forum-network/src/classes/display/actor.js index 05b2302..efdd08c 100644 --- a/forum-network/src/classes/actor.js +++ b/forum-network/src/classes/display/actor.js @@ -1,20 +1,22 @@ -import { displayNumber } from '../util.js'; +import { displayNumber } from '../../util.js'; export class Actor { - constructor(name) { + constructor(name, scene) { + if (!scene) throw new Error('An actor without a scene!'); this.name = name; + this.scene = scene; this.callbacks = new Map(); - this.status = window?.scene?.addDisplayValue(`${this.name} status`); + this.status = scene.addDisplayValue(`${this.name} status`); this.status.set('Created'); this.values = new Map(); this.valueFunctions = new Map(); this.active = 0; - window?.scene?.registerActor(this); + scene?.registerActor(this); } activate() { this.active += 1; - window?.scene?.sequence.log(`activate ${this.name}`, false); + this.scene?.sequence?.activate(this.name); } async deactivate() { @@ -22,7 +24,7 @@ export class Actor { throw new Error(`${this.name} is not active, can not deactivate`); } this.active -= 1; - await window?.scene?.sequence.log(`deactivate ${this.name}`); + await this.scene?.sequence?.deactivate(this.name); } async send(dest, action, detail) { @@ -35,7 +37,7 @@ export class Actor { const cb = this.callbacks.get(action.name); if (!cb) { 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); @@ -52,41 +54,32 @@ export class Actor { return this; } - addValue(label, fn) { - this.values.set(label, window?.scene?.addDisplayValue(`${this.name} ${label}`)); + async addComputedValue(label, fn) { + this.values.set(label, this.scene?.addDisplayValue(`${this.name} ${label}`)); if (fn) { this.valueFunctions.set(label, fn); + await this.computeValues(); } return this; } async setValue(label, value) { - if (typeof value === 'number') { - value = displayNumber(value); - } - let displayValue = this.values.get(label); - if (!displayValue) { - displayValue = window?.scene?.addDisplayValue(`${this.name} ${label}`); - this.values.set(label, displayValue); + if (typeof value === 'function') { + return this.addComputedValue(label, value); } + const displayValue = this.values.get(label) ?? this.scene?.addDisplayValue(`${this.name} ${label}`); 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); + this.values.set(label, displayValue); return this; } async computeValues() { for (const [label, fn] of this.valueFunctions.entries()) { - const displayValue = this.values.get(label); - let value = fn(); - 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); + const value = fn(); + await this.setValue(label, value); } } diff --git a/forum-network/src/classes/box.js b/forum-network/src/classes/display/box.js similarity index 96% rename from forum-network/src/classes/box.js rename to forum-network/src/classes/display/box.js index ecc4f77..faacf17 100644 --- a/forum-network/src/classes/box.js +++ b/forum-network/src/classes/display/box.js @@ -1,5 +1,5 @@ import { DisplayValue } from './display-value.js'; -import { randomID } from '../util.js'; +import { randomID } from '../../util.js'; export class Box { constructor(name, parentEl, elementType = 'div') { diff --git a/forum-network/src/classes/display-value.js b/forum-network/src/classes/display/display-value.js similarity index 75% rename from forum-network/src/classes/display-value.js rename to forum-network/src/classes/display/display-value.js index 7f5b48d..e92de70 100644 --- a/forum-network/src/classes/display-value.js +++ b/forum-network/src/classes/display/display-value.js @@ -1,3 +1,5 @@ +import { displayNumber } from '../../util.js'; + export class DisplayValue { constructor(name, box) { this.value = undefined; @@ -9,7 +11,7 @@ export class DisplayValue { } render() { - this.valueBox.setInnerHTML(this.value); + this.valueBox.setInnerHTML(typeof this.value === 'number' ? displayNumber(this.value, 6) : this.value); } set(value) { diff --git a/forum-network/src/classes/display/mermaid.js b/forum-network/src/classes/display/mermaid.js new file mode 100644 index 0000000..9d42760 --- /dev/null +++ b/forum-network/src/classes/display/mermaid.js @@ -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); + } +} diff --git a/forum-network/src/classes/display/scene.js b/forum-network/src/classes/display/scene.js new file mode 100644 index 0000000..b8fa561 --- /dev/null +++ b/forum-network/src/classes/display/scene.js @@ -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); + } +} diff --git a/forum-network/src/classes/display/sequence.js b/forum-network/src/classes/display/sequence.js new file mode 100644 index 0000000..456f165 --- /dev/null +++ b/forum-network/src/classes/display/sequence.js @@ -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; + } +} diff --git a/forum-network/src/classes/display/table.js b/forum-network/src/classes/display/table.js new file mode 100644 index 0000000..10dbd0b --- /dev/null +++ b/forum-network/src/classes/display/table.js @@ -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 ?? ''; + } + } +} diff --git a/forum-network/src/classes/forum-node.js b/forum-network/src/classes/forum-network/forum-node.js similarity index 91% rename from forum-network/src/classes/forum-node.js rename to forum-network/src/classes/forum-network/forum-node.js index 832ad7d..f36032e 100644 --- a/forum-network/src/classes/forum-node.js +++ b/forum-network/src/classes/forum-network/forum-node.js @@ -1,18 +1,18 @@ -import { Action } from './action.js'; +import { Action } from '../display/action.js'; import { Message, PostMessage, PeerMessage, messageFromJSON, } from './message.js'; import { ForumView } from './forum-view.js'; import { NetworkNode } from './network-node.js'; -import { randomID } from '../util.js'; +import { randomID } from '../../util.js'; export class ForumNode extends NetworkNode { - constructor(name) { - super(name); + constructor(name, scene) { + super(name, scene); this.forumView = new ForumView(); this.actions = { ...this.actions, - storePost: new Action('store post'), + storePost: new Action('store post', scene), }; } diff --git a/forum-network/src/classes/forum-view.js b/forum-network/src/classes/forum-network/forum-view.js similarity index 97% rename from forum-network/src/classes/forum-view.js rename to forum-network/src/classes/forum-network/forum-view.js index 709e944..a7e19a0 100644 --- a/forum-network/src/classes/forum-view.js +++ b/forum-network/src/classes/forum-network/forum-view.js @@ -1,4 +1,4 @@ -import { WDAG } from './wdag.js'; +import { WDAG } from '../supporting/wdag.js'; class Author { constructor() { diff --git a/forum-network/src/classes/message.js b/forum-network/src/classes/forum-network/message.js similarity index 92% rename from forum-network/src/classes/message.js rename to forum-network/src/classes/forum-network/message.js index 03b7592..2e0ab81 100644 --- a/forum-network/src/classes/message.js +++ b/forum-network/src/classes/forum-network/message.js @@ -1,5 +1,5 @@ -import { CryptoUtil } from './crypto.js'; -import { PostContent } from './post-content.js'; +import { CryptoUtil } from '../util/crypto.js'; +import { PostContent } from '../util/post-content.js'; export class Message { constructor(content) { diff --git a/forum-network/src/classes/network-node.js b/forum-network/src/classes/forum-network/network-node.js similarity index 83% rename from forum-network/src/classes/network-node.js rename to forum-network/src/classes/forum-network/network-node.js index 8e96b5f..72072b9 100644 --- a/forum-network/src/classes/network-node.js +++ b/forum-network/src/classes/forum-network/network-node.js @@ -1,14 +1,14 @@ -import { Actor } from './actor.js'; -import { Action } from './action.js'; -import { CryptoUtil } from './crypto.js'; -import { PrioritizedQueue } from './prioritized-queue.js'; +import { Actor } from '../display/actor.js'; +import { Action } from '../display/action.js'; +import { CryptoUtil } from '../util/crypto.js'; +import { PrioritizedQueue } from '../util/prioritized-queue.js'; export class NetworkNode extends Actor { - constructor(name) { - super(name); + constructor(name, scene) { + super(name, scene); this.queue = new PrioritizedQueue(); this.actions = { - peerMessage: new Action('peer message'), + peerMessage: new Action('peer message', scene), }; } diff --git a/forum-network/src/classes/network.js b/forum-network/src/classes/forum-network/network.js similarity index 100% rename from forum-network/src/classes/network.js rename to forum-network/src/classes/forum-network/network.js diff --git a/forum-network/src/classes/block-consensus.js b/forum-network/src/classes/ideas/block-consensus.js similarity index 100% rename from forum-network/src/classes/block-consensus.js rename to forum-network/src/classes/ideas/block-consensus.js diff --git a/forum-network/src/classes/exchange.js b/forum-network/src/classes/ideas/exchange.js similarity index 100% rename from forum-network/src/classes/exchange.js rename to forum-network/src/classes/ideas/exchange.js diff --git a/forum-network/src/classes/finance.js b/forum-network/src/classes/ideas/finance.js similarity index 100% rename from forum-network/src/classes/finance.js rename to forum-network/src/classes/ideas/finance.js diff --git a/forum-network/src/classes/question.js b/forum-network/src/classes/ideas/question.js similarity index 100% rename from forum-network/src/classes/question.js rename to forum-network/src/classes/ideas/question.js diff --git a/forum-network/src/classes/storage.js b/forum-network/src/classes/ideas/storage.js similarity index 100% rename from forum-network/src/classes/storage.js rename to forum-network/src/classes/ideas/storage.js diff --git a/forum-network/src/classes/list.js b/forum-network/src/classes/list.js deleted file mode 100644 index 8dc58ac..0000000 --- a/forum-network/src/classes/list.js +++ /dev/null @@ -1,9 +0,0 @@ -export class List { - constructor() { - this.items = []; - } - - add(item) { - this.items.push(item); - } -} diff --git a/forum-network/src/classes/reputation-holder.js b/forum-network/src/classes/reputation-holder.js deleted file mode 100644 index 7ad195e..0000000 --- a/forum-network/src/classes/reputation-holder.js +++ /dev/null @@ -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()}`; - } -} diff --git a/forum-network/src/classes/reputation.js b/forum-network/src/classes/reputation.js deleted file mode 100644 index 7718825..0000000 --- a/forum-network/src/classes/reputation.js +++ /dev/null @@ -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); - } -} diff --git a/forum-network/src/classes/scene.js b/forum-network/src/classes/scene.js deleted file mode 100644 index e487ba1..0000000 --- a/forum-network/src/classes/scene.js +++ /dev/null @@ -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); - } -} diff --git a/forum-network/src/classes/stake.js b/forum-network/src/classes/supporting/stake.js similarity index 89% rename from forum-network/src/classes/stake.js rename to forum-network/src/classes/supporting/stake.js index 5a5d02a..42eb016 100644 --- a/forum-network/src/classes/stake.js +++ b/forum-network/src/classes/supporting/stake.js @@ -1,4 +1,4 @@ -import params from '../params.js'; +import params from '../../params.js'; export class Stake { constructor({ diff --git a/forum-network/src/classes/vm.js b/forum-network/src/classes/supporting/vm.js similarity index 71% rename from forum-network/src/classes/vm.js rename to forum-network/src/classes/supporting/vm.js index ee6f1a6..03e5dbf 100644 --- a/forum-network/src/classes/vm.js +++ b/forum-network/src/classes/supporting/vm.js @@ -1,4 +1,4 @@ -import { Action } from './action.js'; +import { Action } from '../display/action.js'; class ContractRecord { constructor(id, instance) { @@ -12,7 +12,8 @@ export class VMHandle { this.vm = vm; this.sender = sender; 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} method */ - callContract(id, method, ...args) { + async callContract(id, method, ...args) { const instance = this.vm.getContractInstance(id); const fn = instance[method]; if (!fn) throw new Error(`Contract ${id} method ${method} not found!`); - this.actions.call.log(this.sender, instance, method); - return fn.call(instance, this.sender, ...args); + await this.actions.call.log(this.sender, instance, method); + const result = await fn.call(instance, this.sender, ...args); + await this.actions.return.log(instance, this.sender, undefined, undefined, '-->>'); + return result; } } export class VM { - constructor() { + constructor(scene) { + this.scene = scene; this.contracts = new Map(); } diff --git a/forum-network/src/classes/voter.js b/forum-network/src/classes/supporting/voter.js similarity index 100% rename from forum-network/src/classes/voter.js rename to forum-network/src/classes/supporting/voter.js diff --git a/forum-network/src/classes/wdag.js b/forum-network/src/classes/supporting/wdag.js similarity index 95% rename from forum-network/src/classes/wdag.js rename to forum-network/src/classes/supporting/wdag.js index 28871b8..bec7a34 100644 --- a/forum-network/src/classes/wdag.js +++ b/forum-network/src/classes/supporting/wdag.js @@ -25,16 +25,17 @@ export class Edge { } export class WDAG { - constructor() { + constructor(scene) { + this.scene = scene; this.vertices = new Map(); this.edgeLabels = new Map(); this.nextVertexId = 0; - this.flowchart = window?.scene?.flowchart ?? null; + this.flowchart = scene?.flowchart; } withFlowchart() { - window?.scene?.withAdditionalFlowchart(); - this.flowchart = window?.scene?.lastFlowchart(); + this.scene?.withAdditionalFlowchart(); + this.flowchart = this.scene?.lastFlowchart(); return this; } diff --git a/forum-network/src/classes/crypto.js b/forum-network/src/classes/util/crypto.js similarity index 100% rename from forum-network/src/classes/crypto.js rename to forum-network/src/classes/util/crypto.js diff --git a/forum-network/src/classes/post-content.js b/forum-network/src/classes/util/post-content.js similarity index 100% rename from forum-network/src/classes/post-content.js rename to forum-network/src/classes/util/post-content.js diff --git a/forum-network/src/classes/prioritized-queue.js b/forum-network/src/classes/util/prioritized-queue.js similarity index 100% rename from forum-network/src/classes/prioritized-queue.js rename to forum-network/src/classes/util/prioritized-queue.js diff --git a/forum-network/src/index.html b/forum-network/src/index.html index 7f6fa7b..473347e 100644 --- a/forum-network/src/index.html +++ b/forum-network/src/index.html @@ -14,13 +14,13 @@