Merge branch 'dev' into 'main'

Dev

See merge request dao-governance-framework/science-publishing-dao!7
This commit is contained in:
Ladd Hoffman 2023-06-28 21:14:40 +00:00
commit 8bb188ff13
38 changed files with 315 additions and 358 deletions

View File

@ -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 .

View File

@ -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.

View File

@ -1,5 +1,4 @@
import { Action } from '../display/action.js'; import { Action } from '../display/action.js';
import { PostMessage } from '../forum-network/message.js';
import { CryptoUtil } from '../supporting/crypto.js'; import { CryptoUtil } from '../supporting/crypto.js';
import { ReputationHolder } from '../reputation/reputation-holder.js'; import { ReputationHolder } from '../reputation/reputation-holder.js';
import { EdgeTypes } from '../../util/constants.js'; import { EdgeTypes } from '../../util/constants.js';
@ -18,7 +17,6 @@ export class Expert extends ReputationHolder {
getAssignedWork: new Action('get assigned work', scene), getAssignedWork: new Action('get assigned work', scene),
submitWork: new Action('submit work evidence', scene), submitWork: new Action('submit work evidence', scene),
}; };
this.validationPools = new Map();
this.tokens = []; this.tokens = [];
} }
@ -49,21 +47,23 @@ export class Expert extends ReputationHolder {
return this; return this;
} }
async submitPostWithFee(postContent, poolOptions) { async submitPostWithFee(postContent, { fee }, params) {
const post = await this.dao.forum.addPost(this.reputationPublicKey, postContent); const post = await this.dao.forum.addPost(this.reputationPublicKey, postContent);
await this.actions.submitPost.log(this, post); await this.actions.submitPost.log(this, post);
const postId = post.id; const postId = post.id;
const pool = await this.initiateValidationPool({ ...poolOptions, postId }); const pool = await this.initiateValidationPool({ fee, postId }, params);
this.tokens.push(pool.tokenId); this.tokens.push(pool.tokenId);
return { postId, pool }; return { postId, pool };
} }
async initiateValidationPool(poolOptions) { async initiateValidationPool({ postId, fee }, params) {
// For now, make direct call rather than network // For now, make direct call rather than network
poolOptions.reputationPublicKey = this.reputationPublicKey; const pool = await this.dao.initiateValidationPool(this, {
const pool = await this.dao.initiateValidationPool(this, poolOptions); reputationPublicKey: this.reputationPublicKey,
postId,
fee,
}, params);
this.tokens.push(pool.tokenId); this.tokens.push(pool.tokenId);
this.validationPools.set(pool.id, poolOptions);
return pool; return pool;
} }

View File

@ -80,12 +80,16 @@ export class Business extends Actor {
const pool = await this.dao.initiateValidationPool(this, { const pool = await this.dao.initiateValidationPool(this, {
postId, postId,
fee: request.fee, fee: request.fee,
reputationPublicKey,
}, {
duration, duration,
tokenLossRatio, tokenLossRatio,
}, { });
reputationPublicKey,
authorStakeAmount: request.worker.stakeAmount, await pool.stake(reputationPublicKey, {
tokenId: request.worker.tokenId, tokenId: request.worker.tokenId,
amount: request.worker.stakeAmount,
position: true,
}); });
// When the validation pool concludes, // When the validation pool concludes,

View File

@ -0,0 +1,6 @@
export class Client {
constructor(dao, expert) {
this.dao = dao;
this.expert = expert;
}
}

View File

@ -3,6 +3,7 @@ import { ReputationTokenContract } from '../reputation/reputation-token.js';
import { ValidationPool } from './validation-pool.js'; import { ValidationPool } from './validation-pool.js';
import { Availability } from './availability.js'; import { Availability } from './availability.js';
import { Business } from './business.js'; import { Business } from './business.js';
import { Voter } from '../supporting/voter.js';
import { Actor } from '../display/actor.js'; import { Actor } from '../display/actor.js';
/** /**
@ -33,6 +34,12 @@ export class DAO extends Actor {
Array.from(this.validationPools.values()); 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 } = {}) { listActiveVoters({ activeVoterThreshold } = {}) {
return Array.from(this.experts.values()).filter((voter) => { return Array.from(this.experts.values()).filter((voter) => {
const hasVoted = !!voter.dateLastVote; const hasVoted = !!voter.dateLastVote;
@ -54,21 +61,14 @@ export class DAO extends Actor {
.reduce((acc, cur) => (acc += cur), 0); .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 validationPoolNumber = this.validationPools.size + 1;
const name = `Pool${validationPoolNumber}`; 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); this.validationPools.set(pool.id, pool);
if (stakeOptions) {
const { reputationPublicKey, tokenId, authorStakeAmount } = stakeOptions;
await pool.stake(reputationPublicKey, {
tokenId,
position: true,
amount: authorStakeAmount,
});
}
return pool; return pool;
} }
} }

View File

@ -190,7 +190,7 @@ export class Forum extends ReputationHolder {
depth = 0, depth = 0,
initialNegative = false, initialNegative = false,
referenceChainLimit, referenceChainLimit,
leachingValue leachingValue,
}) { }) {
const postVertex = edge.to; const postVertex = edge.to;
const post = postVertex.data; const post = postVertex.data;
@ -260,7 +260,7 @@ export class Forum extends ReputationHolder {
depth: depth + 1, depth: depth + 1,
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0), initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
referenceChainLimit, referenceChainLimit,
leachingValue leachingValue,
}); });
// Any excess (negative) amount that could not be propagated, // Any excess (negative) amount that could not be propagated,

View File

@ -1,6 +1,5 @@
import { ReputationHolder } from '../reputation/reputation-holder.js'; import { ReputationHolder } from '../reputation/reputation-holder.js';
import { Stake } from '../supporting/stake.js'; import { Stake } from '../supporting/stake.js';
import { Voter } from '../supporting/voter.js';
import { Action } from '../display/action.js'; import { Action } from '../display/action.js';
import { displayNumber } from '../../util/helpers.js'; import { displayNumber } from '../../util/helpers.js';
@ -47,6 +46,8 @@ export class ValidationPool extends ReputationHolder {
postId, postId,
reputationPublicKey, reputationPublicKey,
fee, fee,
},
{
duration, duration,
tokenLossRatio, tokenLossRatio,
contentiousDebate = false, contentiousDebate = false,
@ -113,6 +114,13 @@ export class ValidationPool extends ReputationHolder {
throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`); throw new Error(`Each citation magnitude must not exceed revaluation limit ${params.revaluationLimit}`);
} }
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; this.state = ValidationPoolStates.OPEN;
this.setStatus('Open'); this.setStatus('Open');
this.stakes = new Set(); this.stakes = new Set();
@ -140,9 +148,7 @@ export class ValidationPool extends ReputationHolder {
this.actions.mint.log(this, this, `(${this.mintedValue})`); this.actions.mint.log(this, this, `(${this.mintedValue})`);
// Keep a record of voters and their votes // Keep a record of voters and their votes
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey); this.dao.addVoteRecord(reputationPublicKey, this);
voter.addVoteRecord(this);
this.dao.experts.set(reputationPublicKey, voter);
} }
getTokenLossRatio() { getTokenLossRatio() {
@ -225,12 +231,10 @@ export class ValidationPool extends ReputationHolder {
// Keep a record of voters and their votes // Keep a record of voters and their votes
if (reputationPublicKey !== this.id) { if (reputationPublicKey !== this.id) {
const voter = this.dao.experts.get(reputationPublicKey) ?? new Voter(reputationPublicKey); this.dao.addVoteRecord(reputationPublicKey, this);
voter.addVoteRecord(this);
this.dao.experts.set(reputationPublicKey, voter);
// Update computed display values // 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(); await actor.computeDisplayValues();
} }
} }

View File

@ -5,7 +5,9 @@ export class Box {
constructor(name, parentEl, options = {}) { constructor(name, parentEl, options = {}) {
this.name = name; this.name = name;
this.el = document.createElement('div'); 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'); this.el.classList.add('box');
if (name) { if (name) {
this.el.setAttribute('box-name', name); this.el.setAttribute('box-name', name);

View File

@ -0,0 +1,37 @@
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
* const doc = new Document();
* const form1 = doc.form();
* ```
*/
export class Document extends Box {
form() {
return this.addElement(new Form(this));
}
remark(text, opts) {
return this.addElement(new Remark(this, text, opts));
}
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];
}
}

View File

@ -0,0 +1,60 @@
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.label = document.createElement('label');
this.label.innerHTML = name;
this.input = document.createElement('input');
this.label.appendChild(this.input);
this.el.appendChild(this.label);
}
get value() {
return this.input?.value || null;
}
}
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;
}
}

View File

@ -6,6 +6,7 @@ import { Table } from './table.js';
import { Flowchart } from './flowchart.js'; import { Flowchart } from './flowchart.js';
import { Controls } from './controls.js'; import { Controls } from './controls.js';
import { Box } from './box.js'; import { Box } from './box.js';
import { Document } from './document.js';
export class Scene { export class Scene {
constructor(name, rootBox) { constructor(name, rootBox) {
@ -86,6 +87,24 @@ export class Scene {
return this; 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) { registerActor(actor) {
this.actors.add(actor); this.actors.add(actor);
if (actor.options.announce) { if (actor.options.announce) {

View File

@ -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);
}
}
}

View File

@ -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);
};

View File

@ -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) {
// }
}

View File

@ -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());
}
}

View File

@ -71,3 +71,12 @@ button:disabled {
background-color: #2a535e; background-color: #2a535e;
color: #919191; color: #919191;
} }
label > input {
margin-left: 1em;
}
label {
font-family: monospace;
font-weight: bold;
font-size: smaller;
color: #999999;
}

View File

@ -29,7 +29,12 @@
</ol> </ol>
</ul> </ul>
<ul> <ul>
<li><a href="./tests/forum-network.test.html">Forum Network</a></li> <h3>Client</h3>
<ol>
<li><a href="./tests/client1.test.html">Expert can run a client</a></li>
</ol>
</ul>
<ul>
<li><a href="./tests/vm.test.html">VM</a></li> <li><a href="./tests/vm.test.html">VM</a></li>
</ul> </ul>
<ul> <ul>
@ -39,6 +44,7 @@
<li><a href="./tests/debounce.test.html">Debounce</a></li> <li><a href="./tests/debounce.test.html">Debounce</a></li>
<li><a href="./tests/flowchart.test.html">Flowchart</a></li> <li><a href="./tests/flowchart.test.html">Flowchart</a></li>
<li><a href="./tests/mocha.test.html">Mocha</a></li> <li><a href="./tests/mocha.test.html">Mocha</a></li>
<li><a href="./tests/input.test.html">Input</a></li>
</ul> </ul>
<ul> <ul>
<h4><a href="./tests/all.test.html">All</a></h4> <h4><a href="./tests/all.test.html">All</a></h4>

View File

@ -22,7 +22,6 @@
<script src="https://unpkg.com/chai/chai.js"></script> <script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/availability.test.js"></script> <script type="module" src="./scripts/availability.test.js"></script>
<script type="module" src="./scripts/business.test.js"></script> <script type="module" src="./scripts/business.test.js"></script>
<script type="module" src="./scripts/forum-network.test.js"></script>
<script type="module" src="./scripts/mocha.test.js"></script> <script type="module" src="./scripts/mocha.test.js"></script>
<script type="module" src="./scripts/validation-pool.test.js"></script> <script type="module" src="./scripts/validation-pool.test.js"></script>
<script type="module" src="./scripts/vm.test.js"></script> <script type="module" src="./scripts/vm.test.js"></script>
@ -38,6 +37,7 @@
<script type="module" src="./scripts/forum/forum9.test.js"></script> <script type="module" src="./scripts/forum/forum9.test.js"></script>
<script type="module" src="./scripts/forum/forum10.test.js"></script> <script type="module" src="./scripts/forum/forum10.test.js"></script>
<script type="module" src="./scripts/forum/forum11.test.js"></script> <script type="module" src="./scripts/forum/forum11.test.js"></script>
<script type="module" src="./scripts/input.test.js"></script>
<script defer class="mocha-init"> <script defer class="mocha-init">
mocha.setup({ mocha.setup({
ui: 'bdd', ui: 'bdd',

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<head>
<title>Client test 1</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" />
</head>
<body>
<h2><a href="../">DGF Tests</a></h2>
<div id="mocha"></div>
<div id="scene"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/client/client1.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',
});
chai.should();
</script>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Forum Network test</title> <title>Input</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" /> <link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" /> <link type="text/css" rel="stylesheet" href="../index.css" />
@ -12,17 +12,12 @@
<div id="mocha"></div> <div id="mocha"></div>
<div id="scene"></div> <div id="scene"></div>
</body> </body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js" <script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg==" integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script> crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/10.2.0/mocha.min.js" <script src="https://unpkg.com/mocha/mocha.js"></script>
integrity="sha512-jsP/sG70bnt0xNVJt+k9NxQqGYvRrLzWhI+46SSf7oNJeCwdzZlBvoyrAN0zhtVyolGcHNh/9fEgZppG2pH+eA==" <script src="https://unpkg.com/chai/chai.js"></script>
crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script type="module" src="./scripts/input.test.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.7/chai.min.js"
integrity="sha512-tfLUmTr4u39/6Pykb8v/LjLaQ9u/uSgbHtZXFCtT9bOsZd1ZPZabIrwhif/YzashftTOhwwQUC0cQyrnIC1vEQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="module" src="./scripts/forum-network.test.js"></script>
<script defer class="mocha-init"> <script defer class="mocha-init">
mocha.setup({ mocha.setup({
ui: 'bdd', ui: 'bdd',

View File

@ -51,6 +51,8 @@ const setup = async () => {
new PostContent({ hello: 'there' }).setTitle('Post 1'), new PostContent({ hello: 'there' }).setTitle('Post 1'),
{ {
fee: 10, fee: 10,
},
{
duration: POOL_DURATION, duration: POOL_DURATION,
tokenLossRatio: 1, tokenLossRatio: 1,
}, },
@ -68,6 +70,8 @@ const setup = async () => {
.addCitation(postId1, 0.5), .addCitation(postId1, 0.5),
{ {
fee: 10, fee: 10,
},
{
duration: POOL_DURATION, duration: POOL_DURATION,
tokenLossRatio: 1, tokenLossRatio: 1,
}, },

View File

@ -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();

View File

@ -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();

View File

@ -1,9 +1,9 @@
import { Box } from '../../../classes/display/box.js'; import { Box } from '../../classes/display/box.js';
import { Scene } from '../../../classes/display/scene.js'; import { Scene } from '../../classes/display/scene.js';
import { Expert } from '../../../classes/actors/expert.js'; import { Expert } from '../../classes/actors/expert.js';
import { PostContent } from '../../../classes/supporting/post-content.js'; import { PostContent } from '../../classes/supporting/post-content.js';
import { DAO } from '../../../classes/dao/dao.js'; import { DAO } from '../../classes/dao/dao.js';
import { delayOrWait } from '../../../classes/display/controls.js'; import { delayOrWait } from '../../classes/display/controls.js';
export class ForumTest { export class ForumTest {
constructor(options) { constructor(options) {
@ -42,6 +42,8 @@ export class ForumTest {
postContent, postContent,
{ {
fee, fee,
},
{
duration: this.options.poolDurationMs, duration: this.options.poolDurationMs,
tokenLossRatio: 1, tokenLossRatio: 1,
}, },

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { EPSILON } from '../../../util/constants.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() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { INCINERATOR_ADDRESS } from '../../../util/constants.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() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { INCINERATOR_ADDRESS } from '../../../util/constants.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() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -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.remark('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.remark('Hmm...!');
});
});
});
mochaRun();

View File

@ -57,6 +57,7 @@ describe('Validation Pool', function tests() {
await scene.sequence.startSection(); await scene.sequence.startSection();
const { pool } = await experts[0].submitPostWithFee(new PostContent(), { const { pool } = await experts[0].submitPostWithFee(new PostContent(), {
fee: 7, fee: 7,
}, {
duration: POOL_DURATION_MS, duration: POOL_DURATION_MS,
tokenLossRatio: 1, tokenLossRatio: 1,
}); });
@ -84,6 +85,7 @@ describe('Validation Pool', function tests() {
try { try {
const { pool } = await experts[1].submitPostWithFee(new PostContent(), { const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
fee: 1, fee: 1,
}, {
duration: POOL_DURATION_MS, duration: POOL_DURATION_MS,
tokenLossRatio: 1, tokenLossRatio: 1,
}); });
@ -98,6 +100,7 @@ describe('Validation Pool', function tests() {
it('Second expert must be approved by first expert', async () => { it('Second expert must be approved by first expert', async () => {
const { pool } = await experts[1].submitPostWithFee(new PostContent(), { const { pool } = await experts[1].submitPostWithFee(new PostContent(), {
fee: 1, fee: 1,
}, {
duration: POOL_DURATION_MS, duration: POOL_DURATION_MS,
tokenLossRatio: 1, tokenLossRatio: 1,
}); });