From 21a0ef6bda730e979f6f5e62f2b58c7cbf047cb9 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 23 Apr 2023 08:06:13 -0500 Subject: [PATCH 1/7] Enforce total author weight == 1 --- forum-network/src/classes/dao/validation-pool.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index c46f213..8300f54 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -113,6 +113,11 @@ export class ValidationPool extends ReputationHolder { throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`); } + const totalAuthorWeight = (post.authors ?? []).reduce((total, { weight }) => total += weight, 0); + if (totalAuthorWeight !== 1) { + throw new Error(`Total author weight ${totalAuthorWeight} !== 1`); + } + this.state = ValidationPoolStates.OPEN; this.setStatus('Open'); this.stakes = new Set(); From 7eddd663851a08b4d68f318989ff748dd1a8a030 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 23 Apr 2023 08:10:19 -0500 Subject: [PATCH 2/7] Support case where post sender is the only author --- forum-network/src/classes/dao/validation-pool.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index 8300f54..f3cbfa5 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -113,9 +113,11 @@ export class ValidationPool extends ReputationHolder { throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`); } - const totalAuthorWeight = (post.authors ?? []).reduce((total, { weight }) => total += weight, 0); - if (totalAuthorWeight !== 1) { - throw new Error(`Total author weight ${totalAuthorWeight} !== 1`); + if (post.authors?.length) { + const totalAuthorWeight = post.authors.reduce((total, { weight }) => total += weight, 0); + if (totalAuthorWeight !== 1) { + throw new Error(`Total author weight ${totalAuthorWeight} !== 1`); + } } this.state = ValidationPoolStates.OPEN; From e6024668003fe9d2ada954d29e0319650781bc81 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 23 Apr 2023 08:16:30 -0500 Subject: [PATCH 3/7] Fixup, add trailing commas --- forum-network/src/classes/dao/forum.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js index 0038745..2ac0d5b 100644 --- a/forum-network/src/classes/dao/forum.js +++ b/forum-network/src/classes/dao/forum.js @@ -190,7 +190,7 @@ export class Forum extends ReputationHolder { depth = 0, initialNegative = false, referenceChainLimit, - leachingValue + leachingValue, }) { const postVertex = edge.to; const post = postVertex.data; @@ -260,7 +260,7 @@ export class Forum extends ReputationHolder { depth: depth + 1, initialNegative: initialNegative || (depth === 0 && outboundAmount < 0), referenceChainLimit, - leachingValue + leachingValue, }); // Any excess (negative) amount that could not be propagated, From 7e74773242dd890e4587c45646733315576118d3 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 23 Apr 2023 11:55:03 -0500 Subject: [PATCH 4/7] Refactor to clarify input parameters for validation pool --- forum-network/src/classes/actors/expert.js | 16 +++++++------- forum-network/src/classes/dao/business.js | 10 ++++++--- forum-network/src/classes/dao/dao.js | 22 +++++++++---------- .../src/classes/dao/validation-pool.js | 13 +++++------ .../src/tests/scripts/availability.test.js | 4 ++++ .../tests/scripts/forum/forum.test-util.js | 2 ++ .../src/tests/scripts/validation-pool.test.js | 3 +++ 7 files changed, 40 insertions(+), 30 deletions(-) diff --git a/forum-network/src/classes/actors/expert.js b/forum-network/src/classes/actors/expert.js index 6928e45..112f867 100644 --- a/forum-network/src/classes/actors/expert.js +++ b/forum-network/src/classes/actors/expert.js @@ -1,5 +1,4 @@ import { Action } from '../display/action.js'; -import { PostMessage } from '../forum-network/message.js'; import { CryptoUtil } from '../supporting/crypto.js'; import { ReputationHolder } from '../reputation/reputation-holder.js'; import { EdgeTypes } from '../../util/constants.js'; @@ -18,7 +17,6 @@ export class Expert extends ReputationHolder { getAssignedWork: new Action('get assigned work', scene), submitWork: new Action('submit work evidence', scene), }; - this.validationPools = new Map(); this.tokens = []; } @@ -49,21 +47,23 @@ export class Expert extends ReputationHolder { return this; } - async submitPostWithFee(postContent, poolOptions) { + async submitPostWithFee(postContent, { fee }, params) { const post = await this.dao.forum.addPost(this.reputationPublicKey, postContent); await this.actions.submitPost.log(this, post); const postId = post.id; - const pool = await this.initiateValidationPool({ ...poolOptions, postId }); + const pool = await this.initiateValidationPool({ fee, postId }, params); this.tokens.push(pool.tokenId); return { postId, pool }; } - async initiateValidationPool(poolOptions) { + async initiateValidationPool({ postId, fee }, params) { // For now, make direct call rather than network - poolOptions.reputationPublicKey = this.reputationPublicKey; - const pool = await this.dao.initiateValidationPool(this, poolOptions); + const pool = await this.dao.initiateValidationPool(this, { + reputationPublicKey: this.reputationPublicKey, + postId, + fee, + }, params); this.tokens.push(pool.tokenId); - this.validationPools.set(pool.id, poolOptions); return pool; } diff --git a/forum-network/src/classes/dao/business.js b/forum-network/src/classes/dao/business.js index da603ed..7dcf53d 100644 --- a/forum-network/src/classes/dao/business.js +++ b/forum-network/src/classes/dao/business.js @@ -80,12 +80,16 @@ export class Business extends Actor { const pool = await this.dao.initiateValidationPool(this, { postId, fee: request.fee, + reputationPublicKey, + }, { duration, tokenLossRatio, - }, { - reputationPublicKey, - authorStakeAmount: request.worker.stakeAmount, + }); + + await pool.stake(reputationPublicKey, { tokenId: request.worker.tokenId, + amount: request.worker.stakeAmount, + position: true, }); // When the validation pool concludes, diff --git a/forum-network/src/classes/dao/dao.js b/forum-network/src/classes/dao/dao.js index 867423c..4e31fa8 100644 --- a/forum-network/src/classes/dao/dao.js +++ b/forum-network/src/classes/dao/dao.js @@ -3,6 +3,7 @@ import { ReputationTokenContract } from '../reputation/reputation-token.js'; import { ValidationPool } from './validation-pool.js'; import { Availability } from './availability.js'; import { Business } from './business.js'; +import { Voter } from '../supporting/voter.js'; import { Actor } from '../display/actor.js'; /** @@ -33,6 +34,12 @@ export class DAO extends Actor { Array.from(this.validationPools.values()); } + addVoteRecord(reputationPublicKey, validationPool) { + const voter = this.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey); + voter.addVoteRecord(validationPool); + this.experts.set(reputationPublicKey, voter); + } + listActiveVoters({ activeVoterThreshold } = {}) { return Array.from(this.experts.values()).filter((voter) => { const hasVoted = !!voter.dateLastVote; @@ -54,21 +61,14 @@ export class DAO extends Actor { .reduce((acc, cur) => (acc += cur), 0); } - async initiateValidationPool(fromActor, poolOptions, stakeOptions) { + async initiateValidationPool(fromActor, { postId, reputationPublicKey, fee }, params) { const validationPoolNumber = this.validationPools.size + 1; const name = `Pool${validationPoolNumber}`; - const pool = new ValidationPool(this, poolOptions, name, this.scene, fromActor); + const pool = new ValidationPool(this, { + postId, reputationPublicKey, fee, + }, params, name, this.scene, fromActor); this.validationPools.set(pool.id, pool); - if (stakeOptions) { - const { reputationPublicKey, tokenId, authorStakeAmount } = stakeOptions; - await pool.stake(reputationPublicKey, { - tokenId, - position: true, - amount: authorStakeAmount, - }); - } - return pool; } } diff --git a/forum-network/src/classes/dao/validation-pool.js b/forum-network/src/classes/dao/validation-pool.js index f3cbfa5..520fc83 100644 --- a/forum-network/src/classes/dao/validation-pool.js +++ b/forum-network/src/classes/dao/validation-pool.js @@ -1,6 +1,5 @@ import { ReputationHolder } from '../reputation/reputation-holder.js'; import { Stake } from '../supporting/stake.js'; -import { Voter } from '../supporting/voter.js'; import { Action } from '../display/action.js'; import { displayNumber } from '../../util/helpers.js'; @@ -47,6 +46,8 @@ export class ValidationPool extends ReputationHolder { postId, reputationPublicKey, fee, + }, + { duration, tokenLossRatio, contentiousDebate = false, @@ -147,9 +148,7 @@ export class ValidationPool extends ReputationHolder { 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.dao.addVoteRecord(reputationPublicKey, this); } getTokenLossRatio() { @@ -232,12 +231,10 @@ export class ValidationPool extends ReputationHolder { // Keep a record of voters and their votes if (reputationPublicKey !== this.id) { - const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey); - voter.addVoteRecord(this); - this.dao.experts.set(reputationPublicKey, voter); + this.dao.addVoteRecord(reputationPublicKey, this); // Update computed display values - const actor = this.scene?.findActor((a) => a.reputationPublicKey === voter.reputationPublicKey); + const actor = this.scene?.findActor((a) => a.reputationPublicKey === reputationPublicKey); await actor.computeDisplayValues(); } } diff --git a/forum-network/src/tests/scripts/availability.test.js b/forum-network/src/tests/scripts/availability.test.js index 418335c..64c45a7 100644 --- a/forum-network/src/tests/scripts/availability.test.js +++ b/forum-network/src/tests/scripts/availability.test.js @@ -51,6 +51,8 @@ const setup = async () => { new PostContent({ hello: 'there' }).setTitle('Post 1'), { fee: 10, + }, + { duration: POOL_DURATION, tokenLossRatio: 1, }, @@ -68,6 +70,8 @@ const setup = async () => { .addCitation(postId1, 0.5), { fee: 10, + }, + { duration: POOL_DURATION, tokenLossRatio: 1, }, diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum/forum.test-util.js index cd8deac..9aa34d5 100644 --- a/forum-network/src/tests/scripts/forum/forum.test-util.js +++ b/forum-network/src/tests/scripts/forum/forum.test-util.js @@ -42,6 +42,8 @@ export class ForumTest { postContent, { fee, + }, + { duration: this.options.poolDurationMs, tokenLossRatio: 1, }, diff --git a/forum-network/src/tests/scripts/validation-pool.test.js b/forum-network/src/tests/scripts/validation-pool.test.js index 337ff74..b7a5b05 100644 --- a/forum-network/src/tests/scripts/validation-pool.test.js +++ b/forum-network/src/tests/scripts/validation-pool.test.js @@ -57,6 +57,7 @@ describe('Validation Pool', function tests() { await scene.sequence.startSection(); const { pool } = await experts[0].submitPostWithFee(new PostContent(), { fee: 7, + }, { duration: POOL_DURATION_MS, tokenLossRatio: 1, }); @@ -84,6 +85,7 @@ describe('Validation Pool', function tests() { try { const { pool } = await experts[1].submitPostWithFee(new PostContent(), { fee: 1, + }, { duration: POOL_DURATION_MS, tokenLossRatio: 1, }); @@ -98,6 +100,7 @@ describe('Validation Pool', function tests() { it('Second expert must be approved by first expert', async () => { const { pool } = await experts[1].submitPostWithFee(new PostContent(), { fee: 1, + }, { duration: POOL_DURATION_MS, tokenLossRatio: 1, }); From 36acc56fa26efcb9e4f018329b2c7acf36c4935c Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 23 Apr 2023 11:57:37 -0500 Subject: [PATCH 5/7] Remove forum-network stub code --- .../src/classes/forum-network/forum-node.js | 67 --------------- .../src/classes/forum-network/message.js | 65 --------------- .../src/classes/forum-network/network-node.js | 58 ------------- .../src/classes/forum-network/network.js | 14 ---- forum-network/src/index.html | 1 - forum-network/src/tests/all.test.html | 1 - .../src/tests/forum-network.test.html | 31 ------- .../src/tests/scripts/forum-network.test.js | 81 ------------------- 8 files changed, 318 deletions(-) delete mode 100644 forum-network/src/classes/forum-network/forum-node.js delete mode 100644 forum-network/src/classes/forum-network/message.js delete mode 100644 forum-network/src/classes/forum-network/network-node.js delete mode 100644 forum-network/src/classes/forum-network/network.js delete mode 100644 forum-network/src/tests/forum-network.test.html delete mode 100644 forum-network/src/tests/scripts/forum-network.test.js diff --git a/forum-network/src/classes/forum-network/forum-node.js b/forum-network/src/classes/forum-network/forum-node.js deleted file mode 100644 index ff76ed7..0000000 --- a/forum-network/src/classes/forum-network/forum-node.js +++ /dev/null @@ -1,67 +0,0 @@ -import { Action } from '../display/action.js'; -import { - Message, PostMessage, PeerMessage, messageFromJSON, -} from './message.js'; -import { NetworkNode } from './network-node.js'; -import { randomID } from '../../util/helpers.js'; - -export class ForumNode extends NetworkNode { - constructor(name, scene) { - super(name, scene); - this.actions = { - ...this.actions, - storePost: new Action('store post', scene), - }; - } - - // Process a message from the queue - async processMessage(messageJson) { - try { - await Message.verify(messageJson); - } catch (e) { - await this.actions.processMessage.log(this, this, 'invalid signature', null, '-x'); - console.log(`${this.name}: received message with invalid signature`); - return; - } - - const { publicKey } = messageJson; - const message = messageFromJSON(messageJson); - - if (message instanceof PostMessage) { - await this.processPostMessage(publicKey, message.content); - } else if (message instanceof PeerMessage) { - await this.processPeerMessage(publicKey, message.content); - } else { - // Unknown message type - // Penalize sender for wasting our time - } - } - - // Process an incoming post, received by whatever means - async processPost(authorId, post) { - if (!post.id) { - post.id = randomID(); - } - await this.actions.storePost.log(this, this); - // this.forumView.addPost(authorId, post.id, post, stake); - } - - // Process a post we received in a message - async processPostMessage(authorId, { post, stake }) { - this.processPost(authorId, post, stake); - await this.broadcast( - new PeerMessage({ - posts: [{ authorId, post, stake }], - }), - ); - } - - // Process a message we receive from a peer - async processPeerMessage(peerId, { posts }) { - // We are trusting that the peer verified the signatures of the posts they're forwarding. - // We could instead have the peer forward the signed messages and re-verify them. - for (const { authorId, post, stake } of posts) { - this.processPost(authorId, post, stake); - } - } -} diff --git a/forum-network/src/classes/forum-network/message.js b/forum-network/src/classes/forum-network/message.js deleted file mode 100644 index c0cb1be..0000000 --- a/forum-network/src/classes/forum-network/message.js +++ /dev/null @@ -1,65 +0,0 @@ -import { CryptoUtil } from '../supporting/crypto.js'; -import { PostContent } from '../supporting/post-content.js'; - -export class Message { - constructor(content) { - this.content = content; - } - - async sign({ publicKey, privateKey }) { - this.publicKey = await CryptoUtil.exportKey(publicKey); - // Call toJSON before signing, to match what we'll later send - this.signature = await CryptoUtil.sign(this.contentToJSON(), privateKey); - return this; - } - - static async verify({ content, publicKey, signature }) { - return CryptoUtil.verify(content, publicKey, signature); - } - - contentToJSON() { - return this.content; - } - - toJSON() { - return { - type: this.type, - content: this.contentToJSON(), - publicKey: this.publicKey, - signature: this.signature, - }; - } -} - -export class PostMessage extends Message { - type = 'post'; - - constructor({ post, stake }) { - super({ - post: PostContent.fromJSON(post), - stake, - }); - } - - contentToJSON() { - return { - post: this.content.post.toJSON(), - stakeAmount: this.content.stake, - }; - } -} - -export class PeerMessage extends Message { - type = 'peer'; -} - -const messageTypes = new Map([ - ['post', PostMessage], - ['peer', PeerMessage], -]); - -export const messageFromJSON = ({ type, content }) => { - const MessageType = messageTypes.get(type) || Message; - // const messageContent = MessageType.contentFromJSON(content); - return new MessageType(content); -}; diff --git a/forum-network/src/classes/forum-network/network-node.js b/forum-network/src/classes/forum-network/network-node.js deleted file mode 100644 index 345e3ad..0000000 --- a/forum-network/src/classes/forum-network/network-node.js +++ /dev/null @@ -1,58 +0,0 @@ -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, scene) { - super(name, scene); - this.queue = new PrioritizedQueue(); - this.actions = { - peerMessage: new Action('peer message', scene), - }; - } - - // Generate a signing key pair and connect to the network - async initialize(forumNetwork) { - this.keyPair = await CryptoUtil.generateAsymmetricKey(); - this.forumNetwork = forumNetwork.addNode(this); - this.status.set('Initialized'); - return this; - } - - // Send a message to all other nodes in the network - async broadcast(message) { - await message.sign(this.keyPair); - const otherForumNodes = this.forumNetwork - .listNodes() - .filter((forumNode) => forumNode.keyPair.publicKey !== this.keyPair.publicKey); - for (const forumNode of otherForumNodes) { - // For now just call receiveMessage on the target node - // await this.actions.peerMessage.log(this, forumNode, null, message.content); - await this.actions.peerMessage.log(this, forumNode); - await forumNode.receiveMessage(JSON.stringify(message.toJSON())); - } - } - - // Perform minimal processing to ingest a message. - // Enqueue it for further processing. - async receiveMessage(messageStr) { - const messageJson = JSON.parse(messageStr); - // const senderReputation = this.forumView.getReputation(messageJson.publicKey) || 0; - const senderReputation = 0; - this.queue.add(messageJson, senderReputation); - } - - // Process next highest priority message in the queue - async processNextMessage() { - const messageJson = this.queue.pop(); - if (!messageJson) { - return null; - } - return this.processMessage(messageJson); - } - - // Process a message from the queue - // async processMessage(messageJson) { - // } -} diff --git a/forum-network/src/classes/forum-network/network.js b/forum-network/src/classes/forum-network/network.js deleted file mode 100644 index 6e7e4cd..0000000 --- a/forum-network/src/classes/forum-network/network.js +++ /dev/null @@ -1,14 +0,0 @@ -export class Network { - constructor() { - this.nodes = new Map(); - } - - addNode(node) { - this.nodes.set(node.keyPair.publicKey, node); - return this; - } - - listNodes() { - return Array.from(this.nodes.values()); - } -} diff --git a/forum-network/src/index.html b/forum-network/src/index.html index c0d4a65..86d1514 100644 --- a/forum-network/src/index.html +++ b/forum-network/src/index.html @@ -29,7 +29,6 @@
    diff --git a/forum-network/src/tests/all.test.html b/forum-network/src/tests/all.test.html index 1cb7779..067797a 100644 --- a/forum-network/src/tests/all.test.html +++ b/forum-network/src/tests/all.test.html @@ -22,7 +22,6 @@ - diff --git a/forum-network/src/tests/forum-network.test.html b/forum-network/src/tests/forum-network.test.html deleted file mode 100644 index 488950b..0000000 --- a/forum-network/src/tests/forum-network.test.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - Forum Network test - - - - - - -

    DGF Tests

    -
    -
    - - - - - - - diff --git a/forum-network/src/tests/scripts/forum-network.test.js b/forum-network/src/tests/scripts/forum-network.test.js deleted file mode 100644 index c40a394..0000000 --- a/forum-network/src/tests/scripts/forum-network.test.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Box } from '../../classes/display/box.js'; -import { Scene } from '../../classes/display/scene.js'; -import { PostContent } from '../../classes/supporting/post-content.js'; -import { Expert } from '../../classes/actors/expert.js'; -import { ForumNode } from '../../classes/forum-network/forum-node.js'; -import { Network } from '../../classes/forum-network/network.js'; -import { mochaRun, randomID } from '../../util/helpers.js'; -import { delayOrWait } from '../../classes/display/controls.js'; - -describe('Forum Network', function tests() { - this.timeout(0); - - let scene; - let author1; - let author2; - let forumNetwork; - let forumNode1; - let forumNode2; - let forumNode3; - let processInterval; - - before(async () => { - const rootElement = document.getElementById('scene'); - const rootBox = new Box('rootBox', rootElement).flex(); - - scene = new Scene('Forum Network test', rootBox).withSequenceDiagram(); - - author1 = await new Expert(null, 'author1', scene).initialize(); - author2 = await new Expert(null, 'author2', scene).initialize(); - - forumNetwork = new Network(); - - forumNode1 = await new ForumNode('node1', scene).initialize( - forumNetwork, - ); - forumNode2 = await new ForumNode('node2', scene).initialize( - forumNetwork, - ); - forumNode3 = await new ForumNode('node3', scene).initialize( - forumNetwork, - ); - - processInterval = setInterval(async () => { - await forumNode1.processNextMessage(); - await forumNode2.processNextMessage(); - await forumNode3.processNextMessage(); - }, 100); - }); - - after(() => { - clearInterval(processInterval); - }); - - // const blockchain = new Blockchain(); - - specify('Author can submit a post to the network', async () => { - const post1 = new PostContent({ message: 'hi' }); - post1.id = randomID(); - const post2 = new PostContent({ message: 'hello' }).addCitation( - post1.id, - 1.0, - ); - - await delayOrWait(1000); - await author1.submitPostViaNetwork( - forumNode1, - post1, - 50, - ); - await delayOrWait(1000); - await author2.submitPostViaNetwork( - forumNode2, - post2, - 100, - ); - - await delayOrWait(1000); - }).timeout(10000); -}); - -mochaRun(); From a8544dfd390064e0fc7cdd57cafd6ba25d621aec Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Wed, 28 Jun 2023 08:40:19 -0500 Subject: [PATCH 6/7] Preliminary support for user input --- forum-network/notes/client-or-ui.md | 9 --- forum-network/notes/client.md | 19 ++++++- forum-network/src/classes/dao/client.js | 6 ++ forum-network/src/classes/display/box.js | 4 +- forum-network/src/classes/display/document.js | 30 ++++++++++ forum-network/src/classes/display/form.js | 57 +++++++++++++++++++ forum-network/src/classes/display/scene.js | 19 +++++++ forum-network/src/index.html | 7 +++ forum-network/src/tests/all.test.html | 1 + forum-network/src/tests/client1.test.html | 26 +++++++++ forum-network/src/tests/input.test.html | 26 +++++++++ .../src/tests/scripts/client/client1.test.js | 26 +++++++++ .../scripts/{forum => }/forum.test-util.js | 12 ++-- .../src/tests/scripts/forum/forum1.test.js | 2 +- .../src/tests/scripts/forum/forum10.test.js | 2 +- .../src/tests/scripts/forum/forum11.test.js | 2 +- .../src/tests/scripts/forum/forum2.test.js | 2 +- .../src/tests/scripts/forum/forum3.test.js | 2 +- .../src/tests/scripts/forum/forum4.test.js | 2 +- .../src/tests/scripts/forum/forum5.test.js | 2 +- .../src/tests/scripts/forum/forum6.test.js | 2 +- .../src/tests/scripts/forum/forum7.test.js | 2 +- .../src/tests/scripts/forum/forum8.test.js | 2 +- .../src/tests/scripts/forum/forum9.test.js | 2 +- forum-network/src/tests/scripts/input.test.js | 35 ++++++++++++ 25 files changed, 269 insertions(+), 30 deletions(-) delete mode 100644 forum-network/notes/client-or-ui.md create mode 100644 forum-network/src/classes/dao/client.js create mode 100644 forum-network/src/classes/display/document.js create mode 100644 forum-network/src/classes/display/form.js create mode 100644 forum-network/src/tests/client1.test.html create mode 100644 forum-network/src/tests/input.test.html create mode 100644 forum-network/src/tests/scripts/client/client1.test.js rename forum-network/src/tests/scripts/{forum => }/forum.test-util.js (86%) create mode 100644 forum-network/src/tests/scripts/input.test.js diff --git a/forum-network/notes/client-or-ui.md b/forum-network/notes/client-or-ui.md deleted file mode 100644 index ad24afd..0000000 --- a/forum-network/notes/client-or-ui.md +++ /dev/null @@ -1,9 +0,0 @@ -## Client/UI - -Voting consists of staking operations performed by software operated by owners of EOA. - -This software may be referred to as "The UI". It may also be considered "a client". - -It will need to be a network-connected application. It will need a certain minimum of RAM, -and for some features disk storage, -and for some features uptime . diff --git a/forum-network/notes/client.md b/forum-network/notes/client.md index 55df288..ee6db86 100644 --- a/forum-network/notes/client.md +++ b/forum-network/notes/client.md @@ -1,5 +1,18 @@ -# Client Operations +## Client -Client must communicate with one or more servers. +Clients play a key role in an MVPR DAO. -Client must build a local view +Clients must be operated by reputation holders. + +Clients are the agents that submit posts to the forum, initiate validation pools, and vote in validation pools. + +We sometimes refer to the client as "the UI". + +It will need to be a network-connected application. It will need a certain minimum of RAM, +and for some features disk storage, +and for some features uptime . + +The behavior of the client constitutes what we refer to as the DAO's "soft protocols". + +Malicious actors may freely modify their own client's behavior. +Therefore honest clients must engage in policing to preserve the integrity of the network. diff --git a/forum-network/src/classes/dao/client.js b/forum-network/src/classes/dao/client.js new file mode 100644 index 0000000..366fa7f --- /dev/null +++ b/forum-network/src/classes/dao/client.js @@ -0,0 +1,6 @@ +export class Client { + constructor(dao, expert) { + this.dao = dao; + this.expert = expert; + } +} diff --git a/forum-network/src/classes/display/box.js b/forum-network/src/classes/display/box.js index bee1f88..1ed666a 100644 --- a/forum-network/src/classes/display/box.js +++ b/forum-network/src/classes/display/box.js @@ -5,7 +5,9 @@ export class Box { constructor(name, parentEl, options = {}) { this.name = name; this.el = document.createElement('div'); - this.el.id = `box_${randomID()}`; + this.el.box = this; + const id = options.id ?? randomID(); + this.el.id = `${parentEl.id}_box_${id}`; this.el.classList.add('box'); if (name) { this.el.setAttribute('box-name', name); diff --git a/forum-network/src/classes/display/document.js b/forum-network/src/classes/display/document.js new file mode 100644 index 0000000..87c6bbf --- /dev/null +++ b/forum-network/src/classes/display/document.js @@ -0,0 +1,30 @@ +import { Box } from './box.js'; +import { Form } from './form.js'; + +/** + * @example + * ```typescript + * const doc = new Document(); + * const form1 = doc.form(); + * ``` + */ +export class Document extends Box { + form() { + return this.addElement(new Form(this)); + } + + remarks(text, opts) { + return this.addElement(new Box('Remark', this.el, opts).setInnerHTML(text)); + } + + addElement(element) { + this.elements = this.elements ?? []; + this.elements.push(element); + return this; + } + + get lastElement() { + if (!this.elements?.length) return null; + return this.elements[this.elements.length - 1]; + } +} diff --git a/forum-network/src/classes/display/form.js b/forum-network/src/classes/display/form.js new file mode 100644 index 0000000..80b6cb2 --- /dev/null +++ b/forum-network/src/classes/display/form.js @@ -0,0 +1,57 @@ +import { randomID } from '../../util/helpers.js'; +import { Box } from './box.js'; + +const updateValuesOnEventTypes = ['keyup', 'mouseup']; + +export class FormElement extends Box { + constructor(name, parentEl, opts) { + super(name, parentEl, opts); + this.id = opts.id ?? name; + const { cb } = opts; + if (cb) { + updateValuesOnEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => { + cb(this); + })); + cb(this); + } + } +} + +export class Button extends FormElement { } + +export class TextField extends FormElement { + constructor(name, parentEl, opts) { + super(name, parentEl, opts); + this.input = document.createElement('input'); + this.el.appendChild(this.input); + } + + get value() { + return this.input?.value; + } +} + +export class TextArea extends FormElement { } + +export class Form { + constructor(document, opts = {}) { + this.document = document; + this.items = []; + this.id = opts.id ?? `form_${randomID()}`; + } + + button(opts) { + this.items.push(new Button(opts.name, this.document.el, opts)); + return this; + } + + textField(opts) { + this.items.push(new TextField(opts.name, this.document.el, opts)); + return this; + } + + textArea(opts) { + this.items.push(new TextArea(opts.name, this.document.el, opts)); + return this; + } +} diff --git a/forum-network/src/classes/display/scene.js b/forum-network/src/classes/display/scene.js index 8f62ad1..3b14ec6 100644 --- a/forum-network/src/classes/display/scene.js +++ b/forum-network/src/classes/display/scene.js @@ -6,6 +6,7 @@ import { Table } from './table.js'; import { Flowchart } from './flowchart.js'; import { Controls } from './controls.js'; import { Box } from './box.js'; +import { Document } from './document.js'; export class Scene { constructor(name, rootBox) { @@ -86,6 +87,24 @@ export class Scene { return this; } + /** + * + * @param {string} name + * @param {(Document): Document} cb + * @returns {Scene} + */ + withDocument(name, cb) { + this.documents = this.documents ?? []; + const doc = new Document(name, this.middleSection.el); + this.documents.push(cb ? cb(doc) : doc); + return this; + } + + get lastDocument() { + if (!this.documents?.length) return null; + return this.documents[this.documents.length - 1]; + } + registerActor(actor) { this.actors.add(actor); if (actor.options.announce) { diff --git a/forum-network/src/index.html b/forum-network/src/index.html index 86d1514..3c3e1fe 100644 --- a/forum-network/src/index.html +++ b/forum-network/src/index.html @@ -28,6 +28,12 @@
  • Multiple posts with overlapping authors
+ @@ -38,6 +44,7 @@
  • Debounce
  • Flowchart
  • Mocha
  • +
  • Input
    • All

      diff --git a/forum-network/src/tests/all.test.html b/forum-network/src/tests/all.test.html index 067797a..12ca110 100644 --- a/forum-network/src/tests/all.test.html +++ b/forum-network/src/tests/all.test.html @@ -37,6 +37,7 @@ + + + + + diff --git a/forum-network/src/tests/input.test.html b/forum-network/src/tests/input.test.html new file mode 100644 index 0000000..b2b35e1 --- /dev/null +++ b/forum-network/src/tests/input.test.html @@ -0,0 +1,26 @@ + + + + Input + + + + + + +

      DGF Tests

      +
      +
      + + + + + + diff --git a/forum-network/src/tests/scripts/client/client1.test.js b/forum-network/src/tests/scripts/client/client1.test.js new file mode 100644 index 0000000..b5e7e9c --- /dev/null +++ b/forum-network/src/tests/scripts/client/client1.test.js @@ -0,0 +1,26 @@ +import { mochaRun } from '../../../util/helpers.js'; +import { ForumTest } from '../forum.test-util.js'; +import { Client } from '../../../classes/dao/client.js'; + +describe('Forum', function tests() { + this.timeout(0); + + const forumTest = new ForumTest({ displayAuthors: false }); + let client; + + before(async () => { + await forumTest.setup(); + await forumTest.newExpert(); + await forumTest.newExpert(); + + client = new Client(forumTest.dao, forumTest.experts[0]); + }); + + it('Expert can run a client', async () => { + client.should.not.be.undefined; + client.dao.should.equal(forumTest.dao); + client.expert.should.equal(forumTest.experts[0]); + }); +}); + +mochaRun(); diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum.test-util.js similarity index 86% rename from forum-network/src/tests/scripts/forum/forum.test-util.js rename to forum-network/src/tests/scripts/forum.test-util.js index 9aa34d5..02e85d5 100644 --- a/forum-network/src/tests/scripts/forum/forum.test-util.js +++ b/forum-network/src/tests/scripts/forum.test-util.js @@ -1,9 +1,9 @@ -import { Box } from '../../../classes/display/box.js'; -import { Scene } from '../../../classes/display/scene.js'; -import { Expert } from '../../../classes/actors/expert.js'; -import { PostContent } from '../../../classes/supporting/post-content.js'; -import { DAO } from '../../../classes/dao/dao.js'; -import { delayOrWait } from '../../../classes/display/controls.js'; +import { Box } from '../../classes/display/box.js'; +import { Scene } from '../../classes/display/scene.js'; +import { Expert } from '../../classes/actors/expert.js'; +import { PostContent } from '../../classes/supporting/post-content.js'; +import { DAO } from '../../classes/dao/dao.js'; +import { delayOrWait } from '../../classes/display/controls.js'; export class ForumTest { constructor(options) { diff --git a/forum-network/src/tests/scripts/forum/forum1.test.js b/forum-network/src/tests/scripts/forum/forum1.test.js index b2e8895..38536e1 100644 --- a/forum-network/src/tests/scripts/forum/forum1.test.js +++ b/forum-network/src/tests/scripts/forum/forum1.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum10.test.js b/forum-network/src/tests/scripts/forum/forum10.test.js index 6327ae0..21430a3 100644 --- a/forum-network/src/tests/scripts/forum/forum10.test.js +++ b/forum-network/src/tests/scripts/forum/forum10.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum11.test.js b/forum-network/src/tests/scripts/forum/forum11.test.js index 0b144cc..04dcc92 100644 --- a/forum-network/src/tests/scripts/forum/forum11.test.js +++ b/forum-network/src/tests/scripts/forum/forum11.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum2.test.js b/forum-network/src/tests/scripts/forum/forum2.test.js index 71b1e76..fa98a90 100644 --- a/forum-network/src/tests/scripts/forum/forum2.test.js +++ b/forum-network/src/tests/scripts/forum/forum2.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum3.test.js b/forum-network/src/tests/scripts/forum/forum3.test.js index 1fd49e1..529298b 100644 --- a/forum-network/src/tests/scripts/forum/forum3.test.js +++ b/forum-network/src/tests/scripts/forum/forum3.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum4.test.js b/forum-network/src/tests/scripts/forum/forum4.test.js index 1a35b13..097d7e2 100644 --- a/forum-network/src/tests/scripts/forum/forum4.test.js +++ b/forum-network/src/tests/scripts/forum/forum4.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum5.test.js b/forum-network/src/tests/scripts/forum/forum5.test.js index b6c0a34..5988001 100644 --- a/forum-network/src/tests/scripts/forum/forum5.test.js +++ b/forum-network/src/tests/scripts/forum/forum5.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum6.test.js b/forum-network/src/tests/scripts/forum/forum6.test.js index 69ddd39..ab32aba 100644 --- a/forum-network/src/tests/scripts/forum/forum6.test.js +++ b/forum-network/src/tests/scripts/forum/forum6.test.js @@ -1,6 +1,6 @@ import { mochaRun } from '../../../util/helpers.js'; import { EPSILON } from '../../../util/constants.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum7.test.js b/forum-network/src/tests/scripts/forum/forum7.test.js index 83cbf59..1a0d293 100644 --- a/forum-network/src/tests/scripts/forum/forum7.test.js +++ b/forum-network/src/tests/scripts/forum/forum7.test.js @@ -1,5 +1,5 @@ import { mochaRun } from '../../../util/helpers.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum8.test.js b/forum-network/src/tests/scripts/forum/forum8.test.js index d695782..63da1ed 100644 --- a/forum-network/src/tests/scripts/forum/forum8.test.js +++ b/forum-network/src/tests/scripts/forum/forum8.test.js @@ -1,6 +1,6 @@ import { mochaRun } from '../../../util/helpers.js'; import { INCINERATOR_ADDRESS } from '../../../util/constants.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/forum/forum9.test.js b/forum-network/src/tests/scripts/forum/forum9.test.js index 8fbbf02..76eaf41 100644 --- a/forum-network/src/tests/scripts/forum/forum9.test.js +++ b/forum-network/src/tests/scripts/forum/forum9.test.js @@ -1,6 +1,6 @@ import { mochaRun } from '../../../util/helpers.js'; import { INCINERATOR_ADDRESS } from '../../../util/constants.js'; -import { ForumTest } from './forum.test-util.js'; +import { ForumTest } from '../forum.test-util.js'; describe('Forum', function tests() { this.timeout(0); diff --git a/forum-network/src/tests/scripts/input.test.js b/forum-network/src/tests/scripts/input.test.js new file mode 100644 index 0000000..46e286d --- /dev/null +++ b/forum-network/src/tests/scripts/input.test.js @@ -0,0 +1,35 @@ +import { Box } from '../../classes/display/box.js'; +// import { Document } from '../../classes/display/document.js'; +import { Scene } from '../../classes/display/scene.js'; +import { mochaRun } from '../../util/helpers.js'; + +const rootElement = document.getElementById('scene'); +const rootBox = new Box('rootBox', rootElement).flex(); +const scene = window.scene = new Scene('Input test', rootBox); + +scene.withDocument(); + +describe('Document', () => { + it('Exists', () => { + scene.withDocument('Document', (doc) => doc.remarks('Hello')); + }); + + describe('Input', () => { + it('Accepts input', () => { + scene.withDocument('Document', (doc) => doc.form()); + const doc = scene.lastDocument; + const form1 = doc.lastElement; + const dvMap = new Map(); + const updateFieldValueDisplay = ({ name, value }) => { + const dv = dvMap.get(name) ?? scene.addDisplayValue(name); + dvMap.set(name, dv); + dv.set(value); + }; + + form1.textField({ id: 'input1', name: 'Input 1', cb: updateFieldValueDisplay }); + doc.remarks('Hmm...!'); + }); + }); +}); + +mochaRun(); From 9974712aa99d6cef990a21b2a0510cfe86c1ad75 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Wed, 28 Jun 2023 09:22:12 -0500 Subject: [PATCH 7/7] slight refactor --- forum-network/src/classes/display/document.js | 11 +++++++++-- forum-network/src/classes/display/form.js | 7 +++++-- forum-network/src/index.css | 9 +++++++++ forum-network/src/tests/scripts/input.test.js | 4 ++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/forum-network/src/classes/display/document.js b/forum-network/src/classes/display/document.js index 87c6bbf..69545be 100644 --- a/forum-network/src/classes/display/document.js +++ b/forum-network/src/classes/display/document.js @@ -1,6 +1,13 @@ import { Box } from './box.js'; import { Form } from './form.js'; +export class Remark extends Box { + constructor(doc, text, opts) { + super('Remark', doc.el, opts); + this.setInnerHTML(text); + } +} + /** * @example * ```typescript @@ -13,8 +20,8 @@ export class Document extends Box { return this.addElement(new Form(this)); } - remarks(text, opts) { - return this.addElement(new Box('Remark', this.el, opts).setInnerHTML(text)); + remark(text, opts) { + return this.addElement(new Remark(this, text, opts)); } addElement(element) { diff --git a/forum-network/src/classes/display/form.js b/forum-network/src/classes/display/form.js index 80b6cb2..a818f11 100644 --- a/forum-network/src/classes/display/form.js +++ b/forum-network/src/classes/display/form.js @@ -22,12 +22,15 @@ export class Button extends FormElement { } export class TextField extends FormElement { constructor(name, parentEl, opts) { super(name, parentEl, opts); + this.label = document.createElement('label'); + this.label.innerHTML = name; this.input = document.createElement('input'); - this.el.appendChild(this.input); + this.label.appendChild(this.input); + this.el.appendChild(this.label); } get value() { - return this.input?.value; + return this.input?.value || null; } } diff --git a/forum-network/src/index.css b/forum-network/src/index.css index 1ff9007..237a290 100644 --- a/forum-network/src/index.css +++ b/forum-network/src/index.css @@ -71,3 +71,12 @@ button:disabled { background-color: #2a535e; color: #919191; } +label > input { + margin-left: 1em; +} +label { + font-family: monospace; + font-weight: bold; + font-size: smaller; + color: #999999; +} diff --git a/forum-network/src/tests/scripts/input.test.js b/forum-network/src/tests/scripts/input.test.js index 46e286d..c9bab13 100644 --- a/forum-network/src/tests/scripts/input.test.js +++ b/forum-network/src/tests/scripts/input.test.js @@ -11,7 +11,7 @@ scene.withDocument(); describe('Document', () => { it('Exists', () => { - scene.withDocument('Document', (doc) => doc.remarks('Hello')); + scene.withDocument('Document', (doc) => doc.remark('Hello')); }); describe('Input', () => { @@ -27,7 +27,7 @@ describe('Document', () => { }; form1.textField({ id: 'input1', name: 'Input 1', cb: updateFieldValueDisplay }); - doc.remarks('Hmm...!'); + doc.remark('Hmm...!'); }); }); });