diff --git a/forum-network/notes.md b/forum-network/notes.md
index 3dc9c52..6975cb0 100644
--- a/forum-network/notes.md
+++ b/forum-network/notes.md
@@ -3,3 +3,23 @@
- Receiving payments
- Distributing payments to participants
- Computing updates to forum graph
+
+## Receiving payments
+
+Business SC will need to implement a financial model.
+
+# Excerpts from DeSciPubDAOArchit22July19PrintCut.pdf
+
+> With today’s prices, however, we will begin by programming this all off-chain and simplify the reputation tokens to be less dynamic in their evaluation. Next iteration improves the decentralization commensurate with practical realities.
+
+# Questions
+
+## Validation pool termination
+
+How do we want to handle this?
+The validation pool specifies a duration.
+We don't want to compute results until the end of this duration.
+We're currently supporting anonymous voting.
+With anonymous voting, we need to wait until the end of the vote duration,
+and then have a separate interval in which voters reveal their identities.
+For now, we can let anonymous voters reveal their identities at any time
diff --git a/forum-network/public/availability-test.html b/forum-network/public/availability-test.html
index 459d14c..5f3db62 100644
--- a/forum-network/public/availability-test.html
+++ b/forum-network/public/availability-test.html
@@ -1,6 +1,6 @@
- Forum
+ Availability test
diff --git a/forum-network/public/availability-test.js b/forum-network/public/availability-test.js
index d758536..285e3f6 100644
--- a/forum-network/public/availability-test.js
+++ b/forum-network/public/availability-test.js
@@ -6,17 +6,32 @@ import { Business } from './classes/business.js';
import { Availability } from './classes/availability.js';
import { delay } from './util.js';
import { Forum } from './classes/forum.js';
+import { Public } from './classes/public.js';
+
+const DELAY_INTERVAL = 500;
const rootElement = document.getElementById('availability-test');
const rootBox = new Box('rootBox', rootElement).flex();
const scene = window.scene = new Scene('Availability test', rootBox).log('sequenceDiagram');
-const member1 = window.member1 = await new Member('Member1', scene).initialize();
-const member2 = window.member2 = await new Member('Member2', scene).initialize();
+
+const members = window.members = [];
+const newMember = async () => {
+ const index = members.length;
+ const name = `Member${index + 1}`;
+ const member = await new Member(name, scene).initialize();
+ members.push(member);
+ return member;
+};
+
+const member1 = await newMember();
+const member2 = await newMember();
+await newMember();
const bench = window.bench = new Bench('Bench', scene);
const forum = window.forum = new Forum(bench, 'Forum', scene);
const availability = window.bench = new Availability(bench, 'Availability', scene);
-const business = window.bench = new Business(bench, forum, availability, 'Business', scene);
+const business = window.business = new Business(bench, forum, availability, 'Business', scene);
+const requestor = window.requestor = new Public('Public', scene);
const updateDisplayValues = async () => {
member1.setValue('rep', bench.reputations.getTokens(member1.reputationPublicKey));
@@ -25,44 +40,65 @@ const updateDisplayValues = async () => {
await scene.renderSequenceDiagram();
};
-updateDisplayValues();
+const updateDisplayValuesAndDelay = async () => {
+ await updateDisplayValues();
+ await delay(DELAY_INTERVAL);
+};
-// const post1 = window.post1 = new PostContent({ message: 'hi' });
-// const post2 = window.post2 = new PostContent({ message: 'hello' }).addCitation(window.post1.id, 1.0);
+const getActiveWorker = async () => {
+ let worker;
+ let request;
+ for (const member of members) {
+ request = await member.getAssignedWork(availability, business);
+ if (request) {
+ worker = member;
+ worker.actions.getAssignedWork.log(worker, availability);
+ worker.activate();
+ break;
+ }
+ }
+ return { worker, request };
+};
+
+const voteForWorkEvidence = async (worker, pool) => {
+ for (const member of members) {
+ if (member !== worker) {
+ await member.castVote(pool, { position: true, stake: 1, anonymous: false });
+ }
+ }
+};
+
+await updateDisplayValuesAndDelay();
// Populate availability pool
-availability.register(member1.reputationPublicKey, 1);
-availability.register(member2.reputationPublicKey, 1);
-
-await delay(500);
+await member1.registerAvailability(availability, 1);
+await member2.registerAvailability(availability, 1);
+await updateDisplayValuesAndDelay();
// Submit work request
-const requestId = await business.submitRequest(100, { please: 'do some work' });
-await scene.renderSequenceDiagram();
-await delay(500);
+await requestor.submitRequest(business, { fee: 100 }, { please: 'do some work' });
+await updateDisplayValuesAndDelay();
+
+// Receive work request
+const { worker, request } = await getActiveWorker();
// Submit work evidence
-const pool = await business.submitWork(member1.reputationPublicKey, requestId, {
+const pool = await worker.submitWork(business, request.id, {
here: 'is some evidence of work product',
}, {
tokenLossRatio: 1,
duration: 1000,
});
-
-await scene.renderSequenceDiagram();
-await delay(500);
+worker.deactivate();
+await updateDisplayValuesAndDelay();
// Vote on work evidence
-await member2.castVote(pool, { position: true, stake: 1 });
-await scene.renderSequenceDiagram();
-await delay(500);
+await voteForWorkEvidence(worker, pool);
+await updateDisplayValuesAndDelay();
-await member2.revealIdentity(pool);
-await scene.renderSequenceDiagram();
-await delay(500);
+// Wait for validation pool duration to elapse
+await delay(1000);
-await member1.revealIdentity(pool, member1.reputationPublicKey);
-await scene.renderSequenceDiagram();
-
-// Distribute reputation awards
-// Distribute fees
+// Distribute reputation awards and fees
+await pool.evaluateWinningConditions();
+await updateDisplayValuesAndDelay();
diff --git a/forum-network/public/classes/availability.js b/forum-network/public/classes/availability.js
index b4cced6..7614c70 100644
--- a/forum-network/public/classes/availability.js
+++ b/forum-network/public/classes/availability.js
@@ -1,3 +1,4 @@
+import { Action } from './action.js';
import { Actor } from './actor.js';
class Worker {
@@ -21,6 +22,10 @@ export class Availability extends Actor {
constructor(bench, name, scene) {
super(name, scene);
this.bench = bench;
+
+ this.actions = {
+ assignWork: new Action('assign work', scene),
+ };
}
register(reputationPublicKey, stake) {
@@ -44,6 +49,11 @@ export class Availability extends Actor {
const worker = this.availableWorkers[index];
worker.available = false;
worker.assignedRequestId = requestId;
- // TOOD: Notify assignee
+ // TODO: Notify assignee
+ }
+
+ async getAssignedWork(reputationPublicKey) {
+ const worker = this.workers.get(reputationPublicKey);
+ return worker.assignedRequestId;
}
}
diff --git a/forum-network/public/classes/bench.js b/forum-network/public/classes/bench.js
index 5c849c2..1c9206e 100644
--- a/forum-network/public/classes/bench.js
+++ b/forum-network/public/classes/bench.js
@@ -65,6 +65,7 @@ export class Bench extends Actor {
contentiousDebate,
signingPublicKey,
authorStake,
+ anonymous,
},
) {
const validationPoolNumber = this.validationPools.size + 1;
@@ -79,6 +80,7 @@ export class Bench extends Actor {
contentiousDebate,
signingPublicKey,
authorStake,
+ anonymous,
},
`pool${validationPoolNumber}`,
this.scene,
diff --git a/forum-network/public/classes/business.js b/forum-network/public/classes/business.js
index 019009e..6b2b13a 100644
--- a/forum-network/public/classes/business.js
+++ b/forum-network/public/classes/business.js
@@ -1,3 +1,4 @@
+import { Action } from './action.js';
import { Actor } from './actor.js';
import { CryptoUtil } from './crypto.js';
import { PostContent } from './post.js';
@@ -21,6 +22,12 @@ export class Business extends Actor {
this.bench = bench;
this.forum = forum;
this.availability = availability;
+
+ this.actions = {
+ assignWork: new Action('assign work', scene),
+ addPost: new Action('add post', scene),
+ initiateValidationPool: new Action('initiate validation pool', scene),
+ };
}
/**
@@ -33,29 +40,41 @@ export class Business extends Actor {
async submitRequest(fee, content) {
const request = new Request(fee, content);
this.requests.set(request.id, request);
+ this.actions.assignWork.log(this, this.availability);
await this.availability.assignWork(request.id);
return request.id;
}
+ async getRequest(requestId) {
+ const request = this.requests.get(requestId);
+ return request;
+ }
+
async submitWork(reputationPublicKey, requestId, workEvidence, { tokenLossRatio, duration }) {
- const { fee } = this.requests.get(requestId);
+ const request = this.requests.get(requestId);
+ if (!request) {
+ throw new Error(`Request not found! id: ${requestId}`);
+ }
// Create a post representing this submission.
const post = new PostContent({
requestId,
workEvidence,
});
+ this.actions.addPost.log(this, this.forum);
await this.forum.addPost(reputationPublicKey, post);
// Initiate a validation pool for this work evidence.
// Validation pool supports secret ballots but we aren't using that here, since we want
// the post to be attributable to the reputation holder.
+ this.actions.initiateValidationPool.log(this, this.bench);
const pool = await this.bench.initiateValidationPool(reputationPublicKey, {
postId: post.id,
- fee,
+ fee: request.fee,
duration,
tokenLossRatio,
signingPublicKey: reputationPublicKey,
+ anonymous: false,
});
// When the validation pool concludes,
diff --git a/forum-network/public/classes/forum.js b/forum-network/public/classes/forum.js
index adb0dc5..c7eacae 100644
--- a/forum-network/public/classes/forum.js
+++ b/forum-network/public/classes/forum.js
@@ -34,14 +34,6 @@ export class Forum extends Actor {
for (const { postId: citedPostId, weight } of postContent.citations) {
this.posts.addEdge('citation', post.id, citedPostId, { weight });
}
- // this.applyReputationEffects(post);
- // initiateValidationPool(authorId, {postId, fee, duration, tokenLossRatio, contentiousDebate, signingPublicKey}) {
-
- // const pool = await this.bench.initiateValidationPool(authorId, {
- // ...poolParams,
- // postId,
- // });
- // return pool;
}
getPost(postId) {
diff --git a/forum-network/public/classes/member.js b/forum-network/public/classes/member.js
index 1374896..90b99e8 100644
--- a/forum-network/public/classes/member.js
+++ b/forum-network/public/classes/member.js
@@ -12,6 +12,9 @@ export class Member extends Actor {
initiateValidationPool: new Action('initiate validation pool', scene),
castVote: new Action('cast vote', scene),
revealIdentity: new Action('reveal identity', 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();
}
@@ -51,30 +54,49 @@ export class Member extends Actor {
return pool;
}
- async castVote(validationPool, { position, stake, lockingTime }) {
- const signingKey = await CryptoUtil.generateAsymmetricKey();
- const signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey);
- this.validationPools.set(validationPool.id, { signingPublicKey });
+ async castVote(validationPool, {
+ position, stake, lockingTime, anonymous = true,
+ }) {
+ let signingPublicKey;
+ if (anonymous) {
+ const signingKey = await CryptoUtil.generateAsymmetricKey();
+ signingPublicKey = await CryptoUtil.exportKey(signingKey.publicKey);
+ this.validationPools.set(validationPool.id, { signingPublicKey });
+ } else {
+ signingPublicKey = this.reputationPublicKey;
+ }
// TODO: encrypt vote
// TODO: sign message
this.actions.castVote.log(
this,
validationPool,
- `(${position ? 'for' : 'against'}, stake: ${stake})`,
+ `(${position ? 'for' : 'against'}, stake: ${stake}, anonymous: ${anonymous})`,
);
- validationPool.castVote(signingPublicKey, position, stake, lockingTime);
+ return validationPool.castVote(signingPublicKey, {
+ position, stake, lockingTime, anonymous,
+ });
}
- async revealIdentity(validationPool, signingPublicKey) {
- if (!signingPublicKey) {
- signingPublicKey = this.validationPools.get(validationPool.id).signingPublicKey;
- }
+ async revealIdentity(validationPool) {
+ const { signingPublicKey } = this.validationPools.get(validationPool.id);
// TODO: sign message
this.actions.revealIdentity.log(this, validationPool);
validationPool.revealIdentity(signingPublicKey, this.reputationPublicKey);
}
- async submitWork(business, requestId, evidence, { tokenLossRatio }) {
- await business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio });
+ async registerAvailability(availability, stake) {
+ this.actions.registerAvailability.log(this, availability, `(stake: ${stake})`);
+ await availability.register(this.reputationPublicKey, stake);
+ }
+
+ async getAssignedWork(availability, business) {
+ const requestId = await availability.getAssignedWork(this.reputationPublicKey);
+ const request = await business.getRequest(requestId);
+ return request;
+ }
+
+ async submitWork(business, requestId, evidence, { tokenLossRatio, duration }) {
+ this.actions.submitWork.log(this, business);
+ return business.submitWork(this.reputationPublicKey, requestId, evidence, { tokenLossRatio, duration });
}
}
diff --git a/forum-network/public/classes/public.js b/forum-network/public/classes/public.js
index e69de29..ad25f22 100644
--- a/forum-network/public/classes/public.js
+++ b/forum-network/public/classes/public.js
@@ -0,0 +1,16 @@
+import { Action } from './action.js';
+import { Actor } from './actor.js';
+
+export class Public extends Actor {
+ constructor(name, scene) {
+ super(name, scene);
+ this.actions = {
+ submitRequest: new Action('submit work request', scene),
+ };
+ }
+
+ async submitRequest(business, { fee }, content) {
+ this.actions.submitRequest.log(this, business, `(fee: ${fee})`);
+ return business.submitRequest(fee, content);
+ }
+}
diff --git a/forum-network/public/classes/validation-pool.js b/forum-network/public/classes/validation-pool.js
index 3e86788..53a97bc 100644
--- a/forum-network/public/classes/validation-pool.js
+++ b/forum-network/public/classes/validation-pool.js
@@ -7,6 +7,7 @@ import params from './params.js';
const ValidationPoolStates = Object.freeze({
OPEN: 'OPEN',
CLOSED: 'CLOSED',
+ RESOLVED: 'RESOLVED',
});
/**
@@ -24,6 +25,7 @@ export class ValidationPool extends Actor {
tokenLossRatio,
contentiousDebate = false,
authorStake = 0,
+ anonymous = true,
},
name,
scene,
@@ -71,10 +73,19 @@ export class ValidationPool extends Actor {
// tokens minted "for" the post go toward stake of author voting for their own post
// also, author can provide additional stakes, e.g. availability stakes for work evidence post
console.log('initiateValidationPool casting vote', { signingPublicKey });
- this.castVote(signingPublicKey, true, this.tokens.for + authorStake, 0);
+ this.castVote(signingPublicKey, {
+ position: true,
+ stake: this.tokens.for + authorStake,
+ anonymous,
+ });
}
- castVote(signingPublicKey, position, stake, lockingTime) {
+ async castVote(signingPublicKey, {
+ position, stake, lockingTime = 0, anonymous = true,
+ }) {
+ console.log('castVote', {
+ signingPublicKey, position, stake, anonymous,
+ });
const vote = new Vote(position, stake, lockingTime);
if (this.state === ValidationPoolStates.CLOSED) {
throw new Error(`Validation pool ${this.id} is closed`);
@@ -85,6 +96,10 @@ export class ValidationPool extends Actor {
);
}
this.votes.set(signingPublicKey, vote);
+ if (!anonymous) {
+ console.log('castVote: revealing identity since this is not an anonymous vote');
+ await this.revealIdentity(signingPublicKey, signingPublicKey);
+ }
}
listVotes(position) {
@@ -95,7 +110,7 @@ export class ValidationPool extends Actor {
);
}
- revealIdentity(signingPublicKey, reputationPublicKey) {
+ async revealIdentity(signingPublicKey, reputationPublicKey) {
if (!this.votes.get(signingPublicKey)) {
throw new Error('Must vote before revealing identity');
}
@@ -104,23 +119,6 @@ export class ValidationPool extends Actor {
voter.addVoteRecord(this);
this.bench.voters.set(reputationPublicKey, voter);
this.voters.set(signingPublicKey, voter);
- if (this.votes.size === this.voters.size) {
- // All voters have revealed their reputation public keys
- // Now we can evaluate winning conditions
- this.state = ValidationPoolStates.CLOSED;
- this.setStatus('Closed');
- const result = this.evaluateWinningConditions();
- if (result === null) {
- this.setStatus('Closed - Quorum not met');
- this.scene.log(`note over ${this.name} : Quorum not met`);
- } else {
- this.setStatus(`Closed - ${result ? 'Won' : 'Lost'}`);
- this.scene.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`);
- this.applyTokenLocking();
- this.distributeTokens(result);
- }
- this.deactivate();
- }
}
getTokenLossRatio() {
@@ -163,7 +161,21 @@ export class ValidationPool extends Actor {
}
}
- evaluateWinningConditions() {
+ async evaluateWinningConditions() {
+ if (this.state === ValidationPoolStates.RESOLVED) {
+ throw new Error('Validation pool has already been resolved!');
+ }
+ const elapsed = new Date() - this.dateStart;
+ if (elapsed < this.duration) {
+ throw new Error(`Validation pool duration has not yet elapsed! ${this.duration - elapsed} ms remaining.`);
+ }
+ if (this.voters.size < this.votes.size) {
+ throw new Error('Not all voters have revealed their reputation public keys!');
+ }
+ // Now we can evaluate winning conditions
+ this.state = ValidationPoolStates.CLOSED;
+ this.setStatus('Closed');
+
const getVoteValue = ({ stake, lockingTime }) => stake * lockingTime ** params.lockingTimeExponent;
const getTotalValue = (position) => Array.from(this.listVotes(position).values())
.map(getVoteValue)
@@ -175,7 +187,21 @@ export class ValidationPool extends Actor {
const votePasses = upvoteValue >= params.winningRatio * downvoteValue;
const quorumMet = upvoteValue + downvoteValue >= params.quorum * activeAvailableReputation;
- return quorumMet ? votePasses : null;
+ const result = quorumMet ? votePasses : null;
+
+ if (result === null) {
+ this.setStatus('Resolved - Quorum not met');
+ this.scene.log(`note over ${this.name} : Quorum not met`);
+ } else {
+ this.setStatus(`Resolved - ${result ? 'Won' : 'Lost'}`);
+ this.scene.log(`note over ${this.name} : ${result ? 'Win' : 'Lose'}`);
+ this.applyTokenLocking();
+ this.distributeTokens(result);
+ }
+ this.deactivate();
+ this.state = ValidationPoolStates.RESOLVED;
+
+ return result;
}
distributeTokens(result) {
diff --git a/forum-network/public/debounce-test.html b/forum-network/public/debounce-test.html
index 199e36d..028b237 100644
--- a/forum-network/public/debounce-test.html
+++ b/forum-network/public/debounce-test.html
@@ -1,6 +1,6 @@
- Forum Graph
+ Forum Graph: Debounce test
diff --git a/forum-network/public/forum-network-test.html b/forum-network/public/forum-network-test.html
index edabe94..21b7a39 100644
--- a/forum-network/public/forum-network-test.html
+++ b/forum-network/public/forum-network-test.html
@@ -1,6 +1,6 @@
- Forum
+ Forum Network test
diff --git a/forum-network/public/mermaid-test.html b/forum-network/public/mermaid-test.html
index ad33055..f3b0ab4 100644
--- a/forum-network/public/mermaid-test.html
+++ b/forum-network/public/mermaid-test.html
@@ -1,20 +1,24 @@
- Forum Network
+ Mermaid test
diff --git a/forum-network/public/validation-pool-test.html b/forum-network/public/validation-pool-test.html
index dc02c2b..7016a03 100644
--- a/forum-network/public/validation-pool-test.html
+++ b/forum-network/public/validation-pool-test.html
@@ -1,6 +1,6 @@
- Forum
+ Validation Pool test
diff --git a/forum-network/public/validation-pool-test.js b/forum-network/public/validation-pool-test.js
index cfba35b..db485a5 100644
--- a/forum-network/public/validation-pool-test.js
+++ b/forum-network/public/validation-pool-test.js
@@ -30,8 +30,9 @@ await delay(1000);
// First member can self-approve
{
const pool = await member1.initiateValidationPool(bench, { fee: 7, duration: 1000, tokenLossRatio: 1 });
- // await member1.castVote(pool, true, 0, 0);
- await member1.revealIdentity(pool); // Vote passes
+ await member1.revealIdentity(pool);
+ await delay(1000);
+ await pool.evaluateWinningConditions(); // Vote passes
await updateDisplayValues();
await delay(1000);
}
@@ -39,7 +40,9 @@ await delay(1000);
// Failure example: second member can not self-approve
try {
const pool = await member2.initiateValidationPool(bench, { fee: 1, duration: 1000, tokenLossRatio: 1 });
- await member2.revealIdentity(pool); // Quorum not met!
+ await member2.revealIdentity(pool);
+ await delay(1000);
+ await pool.evaluateWinningConditions(); // Quorum not met!
await updateDisplayValues();
await delay(1000);
} catch (e) {
@@ -56,7 +59,9 @@ try {
const pool = await member2.initiateValidationPool(bench, { fee: 1, duration: 1000, tokenLossRatio: 1 });
await member1.castVote(pool, { position: true, stake: 4, lockingTime: 0 });
await member1.revealIdentity(pool);
- await member2.revealIdentity(pool); // Vote passes
+ await member2.revealIdentity(pool);
+ await delay(1000);
+ await pool.evaluateWinningConditions(); // Vote passes
await updateDisplayValues();
await delay(1000);
}