From 9abbd0e45735e97be2ce978d38d6385888872689 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Wed, 17 Apr 2019 01:41:35 +0300 Subject: [PATCH 01/25] Changed frn generation function, implemented cert number --- clients/block-producer.js | 30 ++++++++---------------------- core/account.js | 2 +- lib/math.js | 13 +++++++++---- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/clients/block-producer.js b/clients/block-producer.js index ae4e6ba..4ffd190 100644 --- a/clients/block-producer.js +++ b/clients/block-producer.js @@ -6,6 +6,7 @@ 'use strict'; +const math = require('lib/math'); const events = require('lib/events'); const pool = require('core/pool'); const tp = require('core/transport'); @@ -21,17 +22,6 @@ const blockProducer = require('services/wallet'); */ const DELEGATES = +process.env.DELEGATES || 33; -/** - * Total number of certificates in the network. - * - * @todo remake this part of app - * - * ENV: TOTAL_CERTIFICATES - * @default 100000 - * @type {Number} - */ -const TOTAL_CERTIFICATES = process.env.TOTAL_CERTIFICATES || 100000; - (async function init() { // Sync with other nodes if there are @@ -75,7 +65,7 @@ async function waitAndProduce() { const isProducer = await isMyRound(randoms[0].data); if (!isProducer) { - console.log('I AM NO PRODUCER, '); + console.log('I AM NO PRODUCER'); return; } @@ -147,17 +137,13 @@ async function isMyRound(frn) { } }); - // get FRN percentage from all certificates. - const percentage = frn / TOTAL_CERTIFICATES; - - // select certificate with the same percentage owned by active producer. - const certificateIndex = Math.floor(percentage * orderedCertificates.length); - const chosenCertificate = orderedCertificates[certificateIndex]; - const chosenProducer = block.state.find(el => el.certificates.includes(chosenCertificate)); + const index = math.findCertificateIndex(frn, orderedCertificates.length); + const chosenCert = orderedCertificates[index]; + const chosenBp = block.state.find(el => el.certificates.includes(chosenCert)); - console.log('PERC, FRN, TOTAL', percentage, frn, TOTAL_CERTIFICATES); - console.log('CHOSEN', chosenProducer); + console.log('PERC, FRN, TOTAL', index, frn, orderedCertificates.length); + console.log('CHOSEN', chosenBp); console.log('MY IS', blockProducer.address.toString('hex')); - return (chosenProducer.address === blockProducer.address.toString('hex')); + return (chosenBp.address === blockProducer.address.toString('hex')); } diff --git a/core/account.js b/core/account.js index 6c6d37d..6ea382f 100644 --- a/core/account.js +++ b/core/account.js @@ -85,7 +85,7 @@ Account.prototype.tx = function tx(to, value, data='0x00') { * @returns {string} Address as hex string. */ Account.prototype.getHexAddress = function getHexAddress() { - return '0x' + this.address.toString('hex'); + return '0x' + this.address.toString('hex'); }; /** diff --git a/lib/math.js b/lib/math.js index 85033af..3851e67 100644 --- a/lib/math.js +++ b/lib/math.js @@ -26,7 +26,7 @@ exports.random = function random() { * @return {Number} Final random number */ exports.finalRandom = function finalRandom(arr) { - return arr.reduce((a, v) => (a + v), 0); + return parseInt(arr.reduce((a, v) => (a + v), 0) / arr.length); }; /** @@ -50,8 +50,13 @@ exports.votingResults = function calculateResults(arr) { }; /** - * TODO: Add certificate matching from voting results + * To find certificate index we calculate FRN percent from MAX_RANDOM value + * and multiply it by total number of issued certificates (which may be changed on each block). + * + * @param {Number} frn Final random to use + * @param {Number} total Number of issued certificates + * @return {Number} Index of a matching certificate in ordered Array */ -exports.findCertificateNumber = function findCertificateNumber() { - return 0; +exports.findCertificateIndex = function findCertificateIndex(frn, total) { + return Math.floor(frn / MAX_RANDOM * total); }; From 2b80d62a6938714dfbc062b6e45bf31842cc00b5 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Thu, 18 Apr 2019 01:42:28 +0300 Subject: [PATCH 02/25] Added LevelDB scratch for future development - my suggestion is to create single point of entry for all DBs - chaindata is replaced with core/db --- bp.js | 2 +- clients/block-producer.js | 10 +++- clients/delegate.js | 2 +- core/blockchain.js | 5 +- core/db.js | 117 +++++++++++++++++++++++++++++++++++++ core/file-peer.js | 22 +++++-- index.js | 77 +----------------------- package-lock.json | 119 ++++++++++++++++++++++++++++++++++++++ package.json | 2 + services/observer.js | 10 ++-- services/sync.js | 5 +- test/unit/blockchain.js | 5 +- 12 files changed, 281 insertions(+), 95 deletions(-) create mode 100644 core/db.js diff --git a/bp.js b/bp.js index 75d2aad..556e285 100644 --- a/bp.js +++ b/bp.js @@ -8,7 +8,7 @@ const wait = require('util').promisify(setTimeout); const Account = require('core/account'); const transport = require('core/transport'); const pool = require('core/pool'); -const chaindata = require('core/chaindata'); +const chaindata = require('core/db').chain; const events = require('lib/events'); const peer = require('core/file-peer'); diff --git a/clients/block-producer.js b/clients/block-producer.js index 4ffd190..e2abfd9 100644 --- a/clients/block-producer.js +++ b/clients/block-producer.js @@ -10,7 +10,7 @@ const math = require('lib/math'); const events = require('lib/events'); const pool = require('core/pool'); const tp = require('core/transport'); -const chaindata = require('core/chaindata'); +const chaindata = require('core/db').chain; const peer = require('core/file-peer'); const sync = require('services/sync'); const waiter = require('services/waiter'); @@ -69,6 +69,8 @@ async function waitAndProduce() { return; } + console.log('AMA PRODUCER, BITCHES'); + // Drain a pool and create new block const parentBlock = await chaindata.getLatest(); const transactions = await pool.drain(); @@ -130,6 +132,8 @@ async function isMyRound(frn) { return true; } + console.log(block, typeof block); + // get all certificates from latest block block.state.forEach((account) => { if (activeProducers.includes(account.address)) { @@ -137,10 +141,14 @@ async function isMyRound(frn) { } }); + console.log(orderedCertificates); + const index = math.findCertificateIndex(frn, orderedCertificates.length); const chosenCert = orderedCertificates[index]; const chosenBp = block.state.find(el => el.certificates.includes(chosenCert)); + // console.log(index, chosenCert); + console.log('PERC, FRN, TOTAL', index, frn, orderedCertificates.length); console.log('CHOSEN', chosenBp); console.log('MY IS', blockProducer.address.toString('hex')); diff --git a/clients/delegate.js b/clients/delegate.js index f6f26b4..f29a9f4 100644 --- a/clients/delegate.js +++ b/clients/delegate.js @@ -11,7 +11,7 @@ const math = require('lib/math'); const events = require('lib/events'); const Delegate = require('core/account'); const tp = require('core/transport'); -const chaindata = require('core/chaindata'); +const chaindata = require('core/db').chain; const waiter = require('services/waiter'); const sync = require('services/sync'); const peer = require('core/file-peer'); diff --git a/core/blockchain.js b/core/blockchain.js index 001fbf9..1b06acb 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -11,7 +11,7 @@ const keccak256 = require('keccak256'); const ethRpc = require('eth-json-rpc')('http://localhost:8545'); const helpers = require('lib/helpers'); const constants = require('lib/constants'); -const chainData = require('core/chaindata'); +const chainData = require('core/db').chain; exports.generateReceipt = generateReceipt; exports.getBlockProducer = getBlockProducer; @@ -26,8 +26,7 @@ exports.getDelegates = getDelegates; */ exports.initiateGenesisState = function initiateGenesisState(genesisBlock, block) { for (const allocObject of genesisBlock.alloc) { - const address = Object.keys(allocObject)[0]; - + const address = Object.keys(allocObject)[0]; const allocatedAccount = allocObject[address]; const account = helpers.emptyAccount(address); diff --git a/core/db.js b/core/db.js new file mode 100644 index 0000000..aa4d2b7 --- /dev/null +++ b/core/db.js @@ -0,0 +1,117 @@ +/** + * @module core/db + */ + +'use strict'; + +const path = require('path'); +const util = require('util'); +const stream = require('stream'); +const levelup = require('levelup'); +const leveldown = require('leveldown'); +const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); + +/** + * Data directory for client + * + * ENV: datadir + * @default 'data' + * @type {String} + */ +const DATADIR = process.env.DATADIR || 'data'; + +const LATEST = 'latest'; +const CHAIN = 'chain'; +const POOL = 'pool'; + +exports.chain = spawnChainData(); +exports.pool = spawnDatabase('pool'); + +/** + * @return {Object} leveldb instance + */ +function spawnChainData() { + const db = spawnDatabase(CHAIN); + const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); + + return { + add(number, block) { + return db.put(number, block); + }, + + getBlock(number) { + return (number === 0) + && Promise.resolve(genesis) + || getOrNull(db, number); + }, + + getLatest() { + return new Promise((resolve, reject) => { + return db + .iterator({reverse: true, limit: 1}) + .next((err, key, value) => { + return (err) + && reject(err) + || resolve(value && JSON.parse(value.toString()) || genesis); + }); + }); + }, + + createReadableStream() { + return db + .createValueStream() + .pipe(new stream.Transform({ + transform(obj, enc, cb) { + cb(null, obj.toString()); + } + })); + }, + + createWritableStream() { + const writeStream = new stream.Writable({ + write(chunk, encoding, cb) { + console.log(chunk.toString()); + return cb(null, chunk.toString()); + } + }); + + return writeStream; + }, + + destroy() { + return db.close().then(() => destroyDatabase(CHAIN)).then(() => db.open()); + } + }; +} + + +/** + * Create instance of database to work with + * + * @param {String} name Name of the future database + * @return {Object} Levelup database + */ +function spawnDatabase(name) { + return levelup(leveldown(path.join(DATADIR, name))); +} + +/** + * Destroy database by it's name + * + * @param {String} name Name of the database to destroy + * @return {Promise} Promise that's resolved on destruction end + */ +function destroyDatabase(name) { + return util.promisify(leveldown.destroy.bind(leveldown))(path.join(DATADIR, name)); +} + +/** + * Get value from database and parse it or return null + * + * @param {Object} db Leveldb instance + * @param {String} key Key to get from database + * @return {Promise.} Parsed JSON or null when no value + */ +async function getOrNull(db, key) { + return db.get(key).then((res) => JSON.parse(res.toString())).catch(() => null); +} diff --git a/core/file-peer.js b/core/file-peer.js index b7529ff..2f063b7 100644 --- a/core/file-peer.js +++ b/core/file-peer.js @@ -18,7 +18,8 @@ 'use strict'; -const http = require('http'); +const http = require('http'); +const stream = require('stream'); /** * Create one-request server on random port with given file contents @@ -31,20 +32,29 @@ const http = require('http'); exports.peer = function createFileServer(fileStream, connections = 1, aliveFor = 5000) { const port = randomPort(); + const streams = Array.from(Array(connections)).map(() => fileStream.pipe(new stream.PassThrough)); const promise = new Promise((resolve, reject) => { - let requests = 0; - const server = http.createServer(); - const success = () => server.close() && resolve({requests, aliveFor}); + let requests = 0; + const server = http.createServer(); server.on('error', reject); server.on('request', (req, res) => { - res.on('finish', () => (++requests === connections) && success()); - fileStream.pipe(res); + if (requests < connections) { + const reqStream = streams[requests]; + res.on('finish', () => (++requests === (connections - 1)) && success()); + reqStream.pipe(res); + } }); server.listen(port); setTimeout(success, aliveFor); + + function success() { + server.close(); + streams.slice(requests).map((stream) => stream.destroy); + resolve({requests, aliveFor}); + } }); return {port, promise}; diff --git a/index.js b/index.js index fbad21d..49de678 100644 --- a/index.js +++ b/index.js @@ -8,33 +8,16 @@ process.stdin.resume(); -const events = require('lib/events'); -const tp = require('core/transport'); -const peer = require('core/file-peer'); -const chain = require('core/chaindata'); -const pool = require('core/pool'); - -const waiter = require('services/waiter'); -const wait = require('util').promisify(setTimeout); +const tp = require('core/transport'); +const wait = require('util').promisify(setTimeout); (async function initServices() { await wait(3500); - console.log('Node info:'); - console.log(' - chain filename is %s', chain.FILENAME); - console.log(' - pool filename is %s', pool.FILENAME); - // More than one node in network if (tp.knownNodes.size > 1) { - - await Promise.all([ - syncChain(), - syncPool() - ]); - - console.log('Node is synced and ready to receive new blocks'); - + console.log('Sync is currently unavailable'); } })().then(function runClient() { @@ -43,57 +26,3 @@ const wait = require('util').promisify(setTimeout); require('services/observer'); }); - - -/** - * Max event wait time in syncs - * @type {Number} - */ -const WAIT_FOR = 3000; - -/** - * @return {Promise} - */ -async function syncPool() { - - tp.send(events.REQUEST_POOL); - - const nodes = await waiter.waitForAll(events.SHARE_POOL, 10, WAIT_FOR); - const myNode = nodes.sort((a, b) => a.data - b.data)[0]; - - tp.send(events.CREATE_POOL_SERVER, null, myNode.msg.sender); - - const peerData = await waiter.waitFor(events.POOL_SERVER_CREATED, WAIT_FOR); - const peerPort = peerData.data; - - console.log('Pool port is %d', peerPort); - - if (peerPort !== null) { - await peer.pull('localhost', peerPort, pool.createWritableStream(false)).catch(console.error); - } else { - console.log('No pool peer was created in %d ms time', WAIT_FOR); - } - -} - -/** - * @return {Promise} - */ -async function syncChain() { - - tp.send(events.REQUEST_CHAIN, null, '*'); - - const responses = await waiter.waitForAll(events.SHARE_CHAIN, 10, 3000); - const oneAndOnly = responses[0]; - - tp.send(events.CREATE_CHAINDATA_SERVER, null, oneAndOnly.msg.sender); - - const peerData = await waiter.waitFor(events.CHAINDATA_SERVER_CREATED, WAIT_FOR); - const peerPort = peerData.data; - - if (peerPort !== null) { - await peer.pull('localhost', peerPort, chain.createWritableStream(false)).catch(console.error); - } else { - console.log('No chaindata peer was created in %d ms time', WAIT_FOR); - } -} diff --git a/package-lock.json b/package-lock.json index 9d09e00..cea700b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,15 @@ "through": ">=2.2.7 <3" } }, + "abstract-leveldown": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.0.2.tgz", + "integrity": "sha512-AaEmQKBazexOeFp2tu+TnGWFhTudFMFK7yOdzJ5VlQyvZDmP6ff8R75JDDVtLCCi+hK5L8DVWaDe51w3uONqCA==", + "requires": { + "level-concat-iterator": "~2.0.0", + "xtend": "~4.0.0" + } + }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -491,6 +500,15 @@ "type-detect": "^4.0.0" } }, + "deferred-leveldown": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.0.1.tgz", + "integrity": "sha512-BXohsvTedWOLkj2n/TY+yqVlrCWa2Zs8LSxh3uCAgFOru7/pjxKyZAexGa1j83BaKloER4PqUyQ9rGPJLt9bqA==", + "requires": { + "abstract-leveldown": "~6.0.0", + "inherits": "^2.0.3" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -591,6 +609,14 @@ "once": "^1.4.0" } }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, "es-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", @@ -905,6 +931,11 @@ "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" }, + "fast-future": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fast-future/-/fast-future-1.0.2.tgz", + "integrity": "sha1-hDWpqqAteSSNF9cE52JZMB2ZKAo=" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1448,6 +1479,51 @@ "invert-kv": "^2.0.0" } }, + "level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.1.tgz", + "integrity": "sha512-pSZWqXK6/yHQkZKCHrR59nKpU5iqorKM22C/BOHTb/cwNQ2EOZG+bovmFFGcOgaBoF3KxqJEI27YwewhJQTzsw==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^3.0.2", + "xtend": "^4.0.0" + } + }, + "leveldown": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.0.1.tgz", + "integrity": "sha512-G4iodNkOhJmZ3uHInsvr1OZrvxxn24+BmfgQCLXdYCFr6diGdshFGCVZfmA4vgcxqvuzPF2P+KnwXdbyeabWqA==", + "requires": { + "abstract-leveldown": "~6.0.0", + "fast-future": "~1.0.2", + "napi-macros": "~1.8.1", + "node-gyp-build": "~3.8.0" + } + }, + "levelup": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.0.1.tgz", + "integrity": "sha512-l7KXOkINXHgNqmz0v9bxvRnMCUG4gmShFrzFSZXXhcqFnfvKAW8NerVsTICpZtVhGOMAmhY6JsVoVh/tUPBmdg==", + "requires": { + "deferred-leveldown": "~5.0.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "xtend": "~4.0.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -1678,6 +1754,11 @@ "to-regex": "^3.0.1" } }, + "napi-macros": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-1.8.2.tgz", + "integrity": "sha512-Tr0DNY4RzTaBG2W2m3l7ZtFuJChTH6VZhXVhkGGjF/4cZTt+i8GcM9ozD+30Lmr4mDoZ5Xx34t2o4GJqYWDGcg==" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -1693,6 +1774,11 @@ "object.getownpropertydescriptors": "^2.0.3" } }, + "node-gyp-build": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.8.0.tgz", + "integrity": "sha512-bYbpIHyRqZ7sVWXxGpz8QIRug5JZc/hzZH4GbdT9HTZi6WmKCZ8GLvP8OZ9TTiIBvwPFKgtGrlWQSXDAvYdsPw==" + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -1889,6 +1975,11 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1899,6 +1990,16 @@ "once": "^1.3.1" } }, + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -2260,6 +2361,14 @@ "strip-ansi": "^4.0.0" } }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -2443,6 +2552,11 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -2525,6 +2639,11 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index d632de2..7952333 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "ethereumjs-tx": "^1.3.7", "ethereumjs-util": "^6.1.0", "keccak256": "^1.0.0", + "leveldown": "^5.0.1", + "levelup": "^4.0.1", "merkletreejs": "0.0.22", "rlp": "^2.2.2", "secp256k1": "^3.6.2" diff --git a/services/observer.js b/services/observer.js index 1f8eb00..b8ffe55 100644 --- a/services/observer.js +++ b/services/observer.js @@ -7,7 +7,7 @@ const pool = require('core/pool'); const events = require('lib/events'); const peer = require('core/file-peer'); -const chaindata = require('core/chaindata'); +const chaindata = require('core/db').chain; const wallet = require('services/wallet'); require('core/transport') @@ -26,11 +26,9 @@ require('core/transport') if (lastBlock.number >= block.number) { return; } - console.log('New block accepted'); - const newBlock = await peer.pullString(meta.address, port); - await chaindata.add(newBlock); + await chaindata.add(block.number, newBlock); }); }) @@ -67,10 +65,12 @@ require('core/transport') }) .on(events.PING, function areYouLookingForMe(data, msg) { - const addr = '0x' + wallet.address.toString('hex'); + const addr = wallet.getHexAddress(); if (data === addr) { this.send(events.PONG, addr, msg.sender); } }) + + ; diff --git a/services/sync.js b/services/sync.js index 100f50c..4c41aa9 100644 --- a/services/sync.js +++ b/services/sync.js @@ -13,7 +13,7 @@ // consts, core, service - order suggestion const events = require('lib/events'); const pool = require('core/pool'); -const chain = require('core/chaindata'); +const chain = require('core/db').chain; const peer = require('core/file-peer'); const tp = require('core/transport'); const waiter = require('services/waiter'); @@ -63,6 +63,9 @@ exports.chain = async function syncChain() { const peerData = await waiter.waitFor(events.CHAINDATA_SERVER_CREATED, WAIT_FOR); const peerPort = peerData.data; + // Clean up before syncing + await chain.destroy(); + return (peerPort !== null) && peer.pull('localhost', peerPort, chain.createWritableStream(false)).catch(console.error) || null; diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js index 0a2dd7a..6b92d93 100644 --- a/test/unit/blockchain.js +++ b/test/unit/blockchain.js @@ -6,9 +6,8 @@ require('chai').should(); -const Account = require('core/account'); -const chaindata = require('core/chaindata'); -const genesis = require('genesis'); +const Account = require('core/account'); +const genesis = require('genesis'); const blockchain = require('core/blockchain'); From bef86781f8b9743821daa14a523a7c9df1f1040d Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Thu, 18 Apr 2019 01:44:07 +0300 Subject: [PATCH 03/25] Removed chaindata component --- core/chaindata.js | 118 ---------------------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 core/chaindata.js diff --git a/core/chaindata.js b/core/chaindata.js deleted file mode 100644 index 019410f..0000000 --- a/core/chaindata.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * @module core/chaindata - */ - -'use strict'; - -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const prom = require('util').promisify; -const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); - -/** - * Directory where local/chain data is stored - * ENV: DATADIR - * - * @default data - * @type {String} - */ -const DATADIR = process.env.DATADIR || 'data'; - -/** - * Name of the file where pool is stored - * ENV: CHAINDATA_FILENAME - * - * @type {String} - */ -const FILENAME = exports.FILENAME = process.env.CHAINDATA_FILENAME || 'chain_' + require('crypto').randomBytes(2).toString('hex'); - -/** - * Full path (joined DIRNAME and FILENAME) - * - * @type {String} - */ -const PATH = path.resolve(path.join(DATADIR, FILENAME)); - -/** - * File descriptor for open pool - * - * @type {Number} - */ -const fd = fs.openSync(PATH, 'a+'); - -/** - * Append data to file - * - * @param {String|Buffer} data TX to append - * @return {Promise} - */ -exports.add = function add(data) { - if (data.constructor === Object) { - data = JSON.stringify(data); - } - - return prom(fs.write)(fd, data.toString() + os.EOL); -}; - -/** - * Get all the transactions in pool - * - * @return {Promise} - */ -exports.getAll = function getAll() { - return prom(fs.readFile)(PATH) - .then((data) => [genesis].concat(data.toString().split(os.EOL).slice(0, -1).map(JSON.parse))) - .catch(() => [genesis]); -}; - -/** - * Get block by number - * - * @param {Number} number Number of block to get - * @return {Promise} Requested block or null - */ -exports.getBlock = function getBlock(number) { - return exports.getAll().then((blocks) => blocks[number]); -}; - -/** - * Get latest block - * - * @return {Promise} Latest block or null - */ -exports.getLatest = function getLatestBlock() { - return exports.getAll().then((blocks) => blocks[blocks.length - 1]); -}; - -/** - * Stat pool file. Null returned when there's no file - * - * @return {Promise} File stats - */ -exports.stat = function stat() { - return prom(fs.stat)(PATH) - .catch((err) => { - if (err.code === 'ENOENT') { return null; } - throw err; - }); -}; - -/** - * Create stream.Readable to chaindata - * - * @return {stream.Readable} Readable stream with file contents - */ -exports.createReadableStream = function createReadableStream() { - return fs.createReadStream(PATH); -}; - -/** - * Create stream.Writable to chaindata - * - * @param {Boolean} [append=true] Whether append-only mode (true) or to rewrite (false) - * @return {stream.Writable} Writable stream - */ -exports.createWritableStream = function createWritableStream(append = true) { - return fs.createWriteStream(PATH, {flags: append && 'a+' || 'w'}); -}; From 20e2a120f83ee61c61077739493ce7bcc247e4f4 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Thu, 18 Apr 2019 02:38:25 +0300 Subject: [PATCH 04/25] Removed core/pool, replaced it with core/db - now core/db is the only point of access to data - backward compability is saved, so only few other changes were made - few bugs in stream pulling were fixed and now allow async writing - fewer console logs --- bp.js | 6 +- clients/block-producer.js | 6 +- core/db.js | 59 ++++++++++++++++--- core/file-peer.js | 2 +- core/pool.js | 119 -------------------------------------- index.js | 13 ++++- services/observer.js | 2 +- services/sync.js | 5 +- 8 files changed, 72 insertions(+), 140 deletions(-) delete mode 100644 core/pool.js diff --git a/bp.js b/bp.js index 556e285..5080835 100644 --- a/bp.js +++ b/bp.js @@ -7,7 +7,7 @@ const wait = require('util').promisify(setTimeout); const Account = require('core/account'); const transport = require('core/transport'); -const pool = require('core/pool'); +const pool = require('core/db').pool; const chaindata = require('core/db').chain; const events = require('lib/events'); const peer = require('core/file-peer'); @@ -36,9 +36,7 @@ require('services/observer'); (async function newBlock() { const parentBlock = await chaindata.getLatest(); - const transactions = await pool.getAll(); - - await pool.drain(); + const transactions = await pool.drain(); const block = producer.produceBlock(parentBlock, transactions); diff --git a/clients/block-producer.js b/clients/block-producer.js index e2abfd9..eb71d05 100644 --- a/clients/block-producer.js +++ b/clients/block-producer.js @@ -8,7 +8,7 @@ const math = require('lib/math'); const events = require('lib/events'); -const pool = require('core/pool'); +const pool = require('core/db').pool; const tp = require('core/transport'); const chaindata = require('core/db').chain; const peer = require('core/file-peer'); @@ -69,11 +69,9 @@ async function waitAndProduce() { return; } - console.log('AMA PRODUCER, BITCHES'); - // Drain a pool and create new block const parentBlock = await chaindata.getLatest(); - const transactions = await pool.drain(); + const transactions = await pool.drain().catch(console.error); const block = blockProducer.produceBlock(parentBlock, transactions); block.randomNumber = randoms[0].data; diff --git a/core/db.js b/core/db.js index aa4d2b7..e8bf678 100644 --- a/core/db.js +++ b/core/db.js @@ -9,7 +9,6 @@ const util = require('util'); const stream = require('stream'); const levelup = require('levelup'); const leveldown = require('leveldown'); -const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); /** * Data directory for client @@ -20,17 +19,61 @@ const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); */ const DATADIR = process.env.DATADIR || 'data'; -const LATEST = 'latest'; const CHAIN = 'chain'; const POOL = 'pool'; -exports.chain = spawnChainData(); -exports.pool = spawnDatabase('pool'); +exports.chain = spawnChainDB(); +exports.pool = spawnPoolDB(); + +function spawnPoolDB() { + const db = spawnDatabase(POOL); + + return { + add(tx) { + return db.put(tx, tx); + }, + + getAll() { + const stream = db.createValueStream(); + return new Promise((resolve, reject) => { + const res = []; + stream.on('data', (tx) => res.push(tx.toString())); + stream.on('end', () => resolve(res)); + stream.on('error', reject); + }); + }, + + async drain() { + const txs = await this.getAll(); + await this.destroy(); + return txs; + }, + + createReadableStream() { + return db.createValueStream(); + }, + + createWritableStream() { + const writeStream = new stream.Writable({ + write(chunk, encoding, cb) { + const tx = chunk.toString(); + return db.put(tx, tx).then(() => cb(null, tx)); + } + }); + + return writeStream; + }, + + destroy() { + return db.close().then(() => destroyDatabase(POOL)).then(() => db.open()); + } + }; +} /** * @return {Object} leveldb instance */ -function spawnChainData() { +function spawnChainDB() { const db = spawnDatabase(CHAIN); const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); @@ -70,8 +113,10 @@ function spawnChainData() { createWritableStream() { const writeStream = new stream.Writable({ write(chunk, encoding, cb) { - console.log(chunk.toString()); - return cb(null, chunk.toString()); + const string = chunk.toString(); + const number = JSON.parse(string).number; + + return db.put(number, string).then(() => cb(null, string)); } }); diff --git a/core/file-peer.js b/core/file-peer.js index 2f063b7..297fa10 100644 --- a/core/file-peer.js +++ b/core/file-peer.js @@ -73,8 +73,8 @@ exports.pull = function pullFromPeer(host, port, stream) { return new Promise((resolve, reject) => { http.get({host, port}, (res) => { res.pipe(stream); - res.on('end', resolve); res.on('error', reject); + stream.on('finish', resolve); }); }); }; diff --git a/core/pool.js b/core/pool.js deleted file mode 100644 index 86ec8d0..0000000 --- a/core/pool.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @module core/pool - */ - -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const prom = require('util').promisify; - -/** - * Directory where local/chain data is stored - * ENV: DATADIR - * - * @default data - * @type {String} - */ -const DATADIR = process.env.DATADIR || 'data'; - -/** - * Name of the file where pool is stored - * ENV: POOL_FILENAME - * - * @type {String} - */ -const FILENAME = exports.FILENAME = process.env.POOL_FILENAME || 'pool_' + require('crypto').randomBytes(2).toString('hex'); - -/** - * Full path (joined DIRNAME and FILENAME) - * - * @type {String} - */ -const PATH = path.join(DATADIR, FILENAME); - -/** - * File descriptor for open pool - * - * @type {Number} - */ -const fd = fs.openSync(PATH, 'a+'); - -/** - * Append data to file - * - * @param {String|Buffer} data TX to append - * @return {Promise} - */ -exports.add = function add(data) { - return prom(fs.write)(fd, data.toString() + '\n'); -}; - -/** - * Remove transactions from pool - * - * @param {Array} [toRem=[]] Array of transactions to remove - * @return {Promise} - */ -exports.remove = function remove(toRem = []) { - if (toRem.constructor !== Array) { - toRem = [toRem]; - } - - return exports.getAll() - .then((txs) => new Set(txs)) - .then((set) => toRem.map((e) => set.delete(e)) && Array.from(set)) - .then((res) => prom(fs.writeFile)(PATH, res.join('\n'))); -}; - -/** - * Get all the transactions in pool - * - * @return {Promise} - */ -exports.getAll = function getAll() { - return prom(fs.readFile)(PATH).then((data) => data.toString().split('\n').slice(0, -1)); -}; - -/** - * Empty pool. Clear file contents - * - * @return {Promise} - */ -exports.drain = async function drainPool() { - const all = await exports.getAll(); - await prom(fs.truncate)(PATH); - return all; -}; - -/** - * Stat pool file. Null returned when there's no file - * - * @return {Promise} File stats - */ -exports.stat = function stat() { - return prom(fs.stat)(PATH) - .catch((err) => { - if (err.code === 'ENOENT') { return null; } - throw err; - }); -}; - -/** - * Create stream.Readable to pool - * - * @return {stream.Readable} Readable stream with file contents - */ -exports.createReadableStream = function createReadableStream() { - return fs.createReadStream(PATH); -}; - -/** - * Create stream.Writable to pool - * - * @param {Boolean} [append=true] Whether append-only mode (true) or to rewrite (false) - * @return {stream.Writable} Writable stream - */ -exports.createWritableStream = function createWritableStream(append = true) { - return fs.createWriteStream(PATH, {flags: append && 'a+' || 'w'}); -}; diff --git a/index.js b/index.js index 49de678..52ac2d7 100644 --- a/index.js +++ b/index.js @@ -8,8 +8,9 @@ process.stdin.resume(); -const tp = require('core/transport'); const wait = require('util').promisify(setTimeout); +const tp = require('core/transport'); +const sync = require('services/sync'); (async function initServices() { @@ -17,10 +18,16 @@ const wait = require('util').promisify(setTimeout); // More than one node in network if (tp.knownNodes.size > 1) { - console.log('Sync is currently unavailable'); + + await Promise.all([ + sync.chain(), + sync.pool() + ]); + + console.log('Synced'); } -})().then(function runClient() { +})().then(async function runClient() { console.log('Starting observer'); diff --git a/services/observer.js b/services/observer.js index b8ffe55..6df8d69 100644 --- a/services/observer.js +++ b/services/observer.js @@ -4,7 +4,7 @@ 'use strict'; -const pool = require('core/pool'); +const pool = require('core/db').pool; const events = require('lib/events'); const peer = require('core/file-peer'); const chaindata = require('core/db').chain; diff --git a/services/sync.js b/services/sync.js index 4c41aa9..1f5b951 100644 --- a/services/sync.js +++ b/services/sync.js @@ -12,7 +12,7 @@ // consts, core, service - order suggestion const events = require('lib/events'); -const pool = require('core/pool'); +const pool = require('core/db').pool; const chain = require('core/db').chain; const peer = require('core/file-peer'); const tp = require('core/transport'); @@ -41,6 +41,9 @@ exports.pool = async function syncPool() { const peerData = await waiter.waitFor(events.POOL_SERVER_CREATED, WAIT_FOR); const peerPort = peerData.data; + // Clean up before syncing + await pool.drain(); + return (peerPort !== null) && peer.pull('localhost', peerPort, pool.createWritableStream(false)).catch(console.error) || null; From 1fe3950a472b45239f3908e82dd2af654de11291 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Fri, 19 Apr 2019 01:57:43 +0300 Subject: [PATCH 05/25] Built class-architecture in core/db --- core/db.js | 273 +++++++++++++++++++++++++------------------ index.js | 4 +- services/observer.js | 4 +- services/sync.js | 4 +- 4 files changed, 163 insertions(+), 122 deletions(-) diff --git a/core/db.js b/core/db.js index e8bf678..eba50f4 100644 --- a/core/db.js +++ b/core/db.js @@ -9,6 +9,7 @@ const util = require('util'); const stream = require('stream'); const levelup = require('levelup'); const leveldown = require('leveldown'); +const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); /** * Data directory for client @@ -22,134 +23,174 @@ const DATADIR = process.env.DATADIR || 'data'; const CHAIN = 'chain'; const POOL = 'pool'; -exports.chain = spawnChainDB(); -exports.pool = spawnPoolDB(); - -function spawnPoolDB() { - const db = spawnDatabase(POOL); - - return { - add(tx) { - return db.put(tx, tx); - }, - - getAll() { - const stream = db.createValueStream(); - return new Promise((resolve, reject) => { - const res = []; - stream.on('data', (tx) => res.push(tx.toString())); - stream.on('end', () => resolve(res)); - stream.on('error', reject); - }); - }, - - async drain() { - const txs = await this.getAll(); - await this.destroy(); - return txs; - }, - - createReadableStream() { - return db.createValueStream(); - }, - - createWritableStream() { - const writeStream = new stream.Writable({ - write(chunk, encoding, cb) { - const tx = chunk.toString(); - return db.put(tx, tx).then(() => cb(null, tx)); - } - }); - - return writeStream; - }, - - destroy() { - return db.close().then(() => destroyDatabase(POOL)).then(() => db.open()); - } - }; -} - /** - * @return {Object} leveldb instance + * @desc Base class for DataBases */ -function spawnChainDB() { - const db = spawnDatabase(CHAIN); - const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); - - return { - add(number, block) { - return db.put(number, block); - }, - - getBlock(number) { - return (number === 0) - && Promise.resolve(genesis) - || getOrNull(db, number); - }, - - getLatest() { - return new Promise((resolve, reject) => { - return db - .iterator({reverse: true, limit: 1}) - .next((err, key, value) => { - return (err) - && reject(err) - || resolve(value && JSON.parse(value.toString()) || genesis); - }); - }); - }, - - createReadableStream() { - return db - .createValueStream() - .pipe(new stream.Transform({ - transform(obj, enc, cb) { - cb(null, obj.toString()); - } - })); - }, - - createWritableStream() { - const writeStream = new stream.Writable({ - write(chunk, encoding, cb) { - const string = chunk.toString(); - const number = JSON.parse(string).number; - - return db.put(number, string).then(() => cb(null, string)); - } - }); - - return writeStream; - }, - - destroy() { - return db.close().then(() => destroyDatabase(CHAIN)).then(() => db.open()); - } - }; +class DB { + + /** + * @param {String} name Name of the database (also name of the folder in DATADIR) + */ + constructor(name) { + const db = levelup(leveldown(path.join(DATADIR, name))); + + Object.defineProperty(this, 'name', {value: name}); + Object.defineProperty(this, 'db', {value: db}); + } + + /** + * Get values stream + * + * @return {stream.Readable} + */ + createReadStream() { + return this.db.createValueStream(); + } + + /** + * Destroy database by it's name + * + * @param {String} name Name of the database to destroy + * @return {Promise} Promise that's resolved on destruction end + */ + destroy() { + return this.db + .close() + .then(() => util.promisify(leveldown.destroy.bind(leveldown))(path.join(DATADIR, this.name))) + .then(() => this.db.open()); + } } - /** - * Create instance of database to work with - * - * @param {String} name Name of the future database - * @return {Object} Levelup database + * @desc Pool logic class */ -function spawnDatabase(name) { - return levelup(leveldown(path.join(DATADIR, name))); +class Pool extends DB { + + /** + * Add transaction to the pool + * + * @param {String} tx Transaction to store + */ + add(tx) { + return this.db.put(tx, tx); + } + + /** + * Get all transactions from pool + * + * @return {Promise.} Array of transactions in Promise + */ + getAll() { + const stream = this.db.createValueStream(); + return new Promise((resolve, reject) => { + const res = []; + stream.on('data', (tx) => res.push(tx.toString())); + stream.on('end', () => resolve(res)); + stream.on('error', reject); + }); + } + + /** + * Get all transactions and destroy pool + * + * @return {Promise} Promise with all the transactions + */ + async drain() { + const txs = await this.getAll(); + await this.destroy(); + return txs; + } + + /** + * Create stream to write data into pool + * + * @return {stream.Writable} Writable stream + */ + createWriteStream() { + const writeStream = new stream.Writable({ + write(chunk, encoding, cb) { + const tx = chunk.toString(); + return this.db.put(tx, tx).then(() => cb(null, tx)); + } + }); + + return writeStream; + } } /** - * Destroy database by it's name - * - * @param {String} name Name of the database to destroy - * @return {Promise} Promise that's resolved on destruction end + * @desc Chain logic class */ -function destroyDatabase(name) { - return util.promisify(leveldown.destroy.bind(leveldown))(path.join(DATADIR, name)); +class Chain extends DB { + + /** + * Add new block into chaindata + * + * @param {Number} number Number of the block + * @param {String} block Block to store + */ + add(number, block) { + return this.db.put(number, block); + } + + /** + * Get block by it's number + * + * @param {Number} number Number of the block to get + * @return {Promise} Promise with parsed block + */ + getBlock(number) { + return (number === 0) + && Promise.resolve(genesis) + || getOrNull(this.db, number); + } + + /** + * Get latest block stored. + * Implementation is tricky, so be careful with this method. + * + * @return {Promise} Promise with latest block or genesis + */ + getLatest() { + return new Promise((resolve, reject) => { + return this.db + .iterator({reverse: true, limit: 1}) + .next((err, key, value) => { + return (err) + && reject(err) + || resolve(value && JSON.parse(value.toString()) || genesis); + }); + }); + } + + /** + * Create a stream to write data into chain. + * + * @return {stream.Writable} Writable stream + */ + createWriteStream() { + const writeStream = new stream.Writable({ + write(chunk, encoding, cb) { + const string = chunk.toString(); + const number = JSON.parse(string).number; + + return this.db.put(number, string).then(() => cb(null, string)); + } + }); + + return writeStream; + } } +// Export storages +exports.chain = new Chain(CHAIN); +exports.pool = new Pool(POOL); + +// Export classes +exports.Chain = Chain; +exports.Pool = Pool; +exports.DB = DB; + /** * Get value from database and parse it or return null * diff --git a/index.js b/index.js index 52ac2d7..0cc2f36 100644 --- a/index.js +++ b/index.js @@ -20,9 +20,9 @@ const sync = require('services/sync'); if (tp.knownNodes.size > 1) { await Promise.all([ - sync.chain(), + // sync.chain(), sync.pool() - ]); + ]).catch(console.error); console.log('Synced'); } diff --git a/services/observer.js b/services/observer.js index 6df8d69..c9b5dbb 100644 --- a/services/observer.js +++ b/services/observer.js @@ -52,14 +52,14 @@ require('core/transport') .on(events.CREATE_POOL_SERVER, function createServer(data, msg) { if (msg.sender !== this.transportId) { - const {port} = peer.peer(pool.createReadableStream()); + const {port} = peer.peer(pool.createReadStream()); this.send(events.POOL_SERVER_CREATED, port, msg.sender); } }) .on(events.CREATE_CHAINDATA_SERVER, function createServer(data, msg) { if (msg.sender !== this.transportId) { - const {port} = peer.peer(chaindata.createReadableStream()); + const {port} = peer.peer(chaindata.createReadStream()); this.send(events.CHAINDATA_SERVER_CREATED, port, msg.sender); } }) diff --git a/services/sync.js b/services/sync.js index 1f5b951..cadfefe 100644 --- a/services/sync.js +++ b/services/sync.js @@ -45,7 +45,7 @@ exports.pool = async function syncPool() { await pool.drain(); return (peerPort !== null) - && peer.pull('localhost', peerPort, pool.createWritableStream(false)).catch(console.error) + && peer.pull('localhost', peerPort, pool.createWriteStream(false)).catch(console.error) || null; }; @@ -70,6 +70,6 @@ exports.chain = async function syncChain() { await chain.destroy(); return (peerPort !== null) - && peer.pull('localhost', peerPort, chain.createWritableStream(false)).catch(console.error) + && peer.pull('localhost', peerPort, chain.createWriteStream(false)).catch(console.error) || null; }; From 908ea9a4cd245e7deab1b32ace39765b8b83899b Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Fri, 19 Apr 2019 15:46:17 +0300 Subject: [PATCH 06/25] Improved lib/genesis to support multi-role records - now both addProducer and addDelegate can refer to same address - removed lib/parser as it was only used in tests - removed console logs in tests --- lib/genesis.js | 56 ++++++++++++++++++++++------------------- lib/parser.js | 36 -------------------------- test/unit/account.js | 11 -------- test/unit/blockchain.js | 22 ++++++---------- test/unit/genesis.js | 17 ++----------- 5 files changed, 40 insertions(+), 102 deletions(-) delete mode 100644 lib/parser.js diff --git a/lib/genesis.js b/lib/genesis.js index 30bbb56..26b07b9 100644 --- a/lib/genesis.js +++ b/lib/genesis.js @@ -29,11 +29,11 @@ function Genesis() { } Object.defineProperties(this, { - number: {value: 0, writable: true, enumerable: true}, - hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, - parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, - timestamp: {value: '0x00', writable: true, enumerable: true}, - producer: {value: '0x0000000000000000000000000000000000000000', writable: true, enumerable: true}, + number: {value: 0, writable: true}, + hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, + parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, + timestamp: {value: '0x00', writable: true}, + producer: {value: '0x0000000000000000000000000000000000000000', writable: true}, alloc: {value: [], writable: true, enumerable: true} }); } @@ -45,14 +45,12 @@ function Genesis() { * @param {Number} nCertificates Number of certificates to generate. */ Genesis.prototype.addProducer = function addProducer(address, nCertificates) { - const allocObject = {}; - - allocObject[address] = { - certificates: Array.apply(null, {length: nCertificates}).map(() => '0x' + rb(32).toString('hex')), - balance: 0 - }; - - this.alloc.push(allocObject); + return this.writeOrExtend(address, { + [address]: { + certificates: Array.apply(null, {length: nCertificates}).map(() => '0x' + rb(32).toString('hex')), + // balance: 0 // TODO: find out whether this line is required + } + }); }; /** @@ -62,14 +60,9 @@ Genesis.prototype.addProducer = function addProducer(address, nCertificates) { * @param {Number} balance Balance (i.e. voting power). */ Genesis.prototype.addDelegate = function addDelegate(address, balance) { - const allocObject = {}; - - allocObject[address] = { - votes: [address], - balance - }; - - this.alloc.push(allocObject); + return this.writeOrExtend(address, { + [address]: {votes: [address], balance} + }); }; /** @@ -79,11 +72,7 @@ Genesis.prototype.addDelegate = function addDelegate(address, balance) { * @param {Number} balance Amount of coins to allocate. */ Genesis.prototype.addAccount = function addAccount(address, balance) { - const allocObject = {}; - - allocObject[address] = {balance}; - - this.alloc.push(allocObject); + return this.writeOrExtend(address, {[address]: {balance}}); }; /** @@ -94,3 +83,18 @@ Genesis.prototype.addAccount = function addAccount(address, balance) { Genesis.prototype.writeToFile = function writeToFile(path = DEFAULT_PATH) { fs.writeFileSync(path, JSON.stringify(this, null, 4)); }; + +/** + * Extend existing record with new data or write new record. + * + * @param {String} address Address to work with + * @param {Object} allocObject Object to write or extend with + * @return {Object} This instance + */ +Genesis.prototype.writeOrExtend = function extend(address, allocObject) { + const existing = this.alloc.find((el) => Object.keys(el).includes(address)) || null; + + return (existing === null) + && this.alloc.push(allocObject) + || Object.assign(existing[address], allocObject[address]); +}; diff --git a/lib/parser.js b/lib/parser.js deleted file mode 100644 index 9a00664..0000000 --- a/lib/parser.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Block state parser. - * - * @module lib/parser - */ - -'use strict'; - -/** - * Retrieve map of delegates from block state. - * - * @param {Object} state - * @return {Object} - */ -module.exports = function parse(state) { - let delegates = new Map(); - - let totalCertificates = 0; - - state.forEach(({certificates, votes, balance}) => { - totalCertificates += certificates.length; - - votes.forEach(delegate => { - const existingDelegate = delegates.get(delegate); - - return existingDelegate - && delegates.set(delegate, existingDelegate + balance) - || delegates.set(delegate, +balance); - }); - }); - - return { - delegates, - totalCertificates - }; -}; diff --git a/test/unit/account.js b/test/unit/account.js index c8c8010..dfa683a 100644 --- a/test/unit/account.js +++ b/test/unit/account.js @@ -9,7 +9,6 @@ require('chai').should(); const Account = require('core/account'); const helpers = require('lib/helpers'); const genesis = require('genesis'); -const parser = require('lib/parser'); /** * Secret key used for testing. @@ -92,8 +91,6 @@ describe('Accounts', () => { const accountState = block.state[0]; - console.log('Account state:', accountState); - const currentAmount = INITIAL_AMOUNT - AMOUNT_TO_SEND - AMOUNT_TO_VOTE; accountState.balance.should.be.equal(currentAmount); @@ -101,18 +98,10 @@ describe('Accounts', () => { accountState.certificates.length.should.be.equal(1); }); - it('parse block state', () => { - const normalizedState = parser(block.state); - - console.log('Delegates:', normalizedState.delegates); - console.log('Amount of certificates:', normalizedState.totalCertificates); - }); - it('sign message with account secret key', () => { signature = account.signMessage(MESSAGE); signature.length.should.be.equal(64); - console.log('Signature: ', signature); }); it('verify signed message', () => { diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js index 6b92d93..7ce8d87 100644 --- a/test/unit/blockchain.js +++ b/test/unit/blockchain.js @@ -7,8 +7,8 @@ require('chai').should(); const Account = require('core/account'); -const genesis = require('genesis'); const blockchain = require('core/blockchain'); +const genesis = require('genesis'); /** @@ -47,22 +47,18 @@ describe('Chain', () => { const balance = await blockchain.getBalance(account.getHexAddress()); balance.should.be.a('number'); - - console.log('Balance:', balance); }); xit('get account stake', async () => { const stake = await blockchain.getStake(account.getHexAddress()); stake.should.be.a('number'); - - console.log('Stake:', stake); }); it('get account votes', async () => { const votes = await blockchain.getVotesFor(delegate.getHexAddress()); - console.log('Votes:', votes); + // TODO: finish test }); it('get all delegates', async () => { @@ -70,19 +66,19 @@ describe('Chain', () => { delegates.should.be.a('array'); - console.log('Delegates:', delegates); + // TODO: TEST }); it('get active delegates', async () => { const activeDelegates = await blockchain.getActiveDelegates(); - console.log('Active delegates:', activeDelegates); + // TODO: TEST }); it('get successor delegates', async () => { const successorDelegates = await blockchain.getSuccessorDelegates(); - console.log('Successor delegates:', successorDelegates); + // TODO: TEST }); it('get delegates count', async () => { @@ -90,21 +86,19 @@ describe('Chain', () => { count.should.be.a('number'); - console.log('Count:', count); + // TODO: TEST }); it('check if account is delegate', async () => { const isDelegate = await blockchain.isDelegate(delegates[0]); isDelegate.should.be.true; - - console.log('is delegate:', isDelegate); }); it('get block producers', async () => { blockProducers = await blockchain.getBlockProducers(); - console.log('block producers:', blockProducers); + // TODO: TEST }); it('check if account is block producer', async () => { @@ -112,6 +106,6 @@ describe('Chain', () => { isBlockProducer.should.be.true; - console.log('is block producer:', isBlockProducer); + // TODO: TEST }); }); diff --git a/test/unit/genesis.js b/test/unit/genesis.js index 08d8de8..5ea244e 100644 --- a/test/unit/genesis.js +++ b/test/unit/genesis.js @@ -8,7 +8,6 @@ const chai = require('chai'); chai.use(require('chai-like')); require('chai').should(); -const fs = require('fs'); const rb = require('crypto').randomBytes; const Genesis = require('lib/genesis'); @@ -32,7 +31,7 @@ describe('Create genesis', () => { const allocObject = findAllocatedObject(genesis, address); allocObject.certificates.length.should.be.equal(amount); - allocObject.balance.should.be.equal(0); + // allocObject.balance.should.be.equal(0); QUESTION: should balance be there or not? }); it('add delegate', () => { @@ -51,18 +50,6 @@ describe('Create genesis', () => { allocObject.balance.should.be.equal(amount); }); - - it('save as file', () => { - const path = 'test/genesis.test.json'; - - genesis.writeToFile(path); - - const genesisFromFile = require(path); - - genesisFromFile.number.should.be.like(genesis.number); - - fs.unlinkSync(path); - }); }); /** @@ -73,5 +60,5 @@ describe('Create genesis', () => { * @return {Object} Found account. */ function findAllocatedObject(genesis, address) { - return (genesis.alloc.find(el => (Object.keys(el)[0] === address)))[address]; + return (genesis.alloc.find(el => (Object.keys(el).includes(address))))[address]; } From 8893d6b83a13108ec01d90d8218f92bbfeabaef1 Mon Sep 17 00:00:00 2001 From: Damir Date: Fri, 19 Apr 2019 18:42:38 +0300 Subject: [PATCH 07/25] Improved lib/genesis to support multi-role records (#35) - improved lib/genesis to support multi-role records - now both addProducer and addDelegate can refer to same address - removed lib/parser as it was only used in tests - removed console logs in tests - mkdir -p after installation, removed state from initial genesis, fixed genesis block state - added adapter to generate state from genesis block --- core/account.js | 4 -- core/blockchain.js | 26 ------------ core/db.js | 21 +++++----- genesis.json | 3 +- lib/genesis.js | 90 ++++++++++++++++++++++++++++------------- lib/parser.js | 36 ----------------- package.json | 2 +- test/unit/account.js | 16 ++------ test/unit/blockchain.js | 36 +++++++---------- test/unit/genesis.js | 17 +------- 10 files changed, 95 insertions(+), 156 deletions(-) delete mode 100644 lib/parser.js diff --git a/core/account.js b/core/account.js index 6ea382f..40f7894 100644 --- a/core/account.js +++ b/core/account.js @@ -158,10 +158,6 @@ Account.prototype.produceBlock = function produceBlock(parentBlock, transactions block.receipts = parentBlock.receipts || []; block.txRoot = helpers.merkleRoot(transactions); - if (parentBlock.alloc && parentBlock.number === 0) { - block = blockchain.initiateGenesisState(parentBlock, block); - } - for (let txIndex = 0; txIndex < transactions.length; txIndex++) { const serializedTx = block.transactions[txIndex]; const tx = helpers.toTxObject(serializedTx); diff --git a/core/blockchain.js b/core/blockchain.js index 1b06acb..10606a7 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -17,32 +17,6 @@ exports.generateReceipt = generateReceipt; exports.getBlockProducer = getBlockProducer; exports.getDelegates = getDelegates; -/** - * Initial allocation of account balances from genesis. - * - * @param {Object} genesisBlock Genesis block. - * @param {Object} block - * @return {Object} - */ -exports.initiateGenesisState = function initiateGenesisState(genesisBlock, block) { - for (const allocObject of genesisBlock.alloc) { - const address = Object.keys(allocObject)[0]; - const allocatedAccount = allocObject[address]; - - const account = helpers.emptyAccount(address); - - Object.assign(account, { - balance: allocatedAccount.balance || 0, - votes: allocatedAccount.votes || [], - certificates: allocatedAccount.certificates || [] - }); - - block.state.push(account); - } - - return block; -}; - /** * Transaction handling entrypoint. * diff --git a/core/db.js b/core/db.js index eba50f4..b98dd54 100644 --- a/core/db.js +++ b/core/db.js @@ -4,12 +4,13 @@ 'use strict'; -const path = require('path'); -const util = require('util'); -const stream = require('stream'); -const levelup = require('levelup'); -const leveldown = require('leveldown'); -const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); +const path = require('path'); +const util = require('util'); +const stream = require('stream'); +const levelup = require('levelup'); +const leveldown = require('leveldown'); +const Genesis = require('lib/genesis'); +const genesis = require(process.env.GENESIS_PATH || 'genesis.json'); /** * Data directory for client @@ -32,10 +33,12 @@ class DB { * @param {String} name Name of the database (also name of the folder in DATADIR) */ constructor(name) { - const db = levelup(leveldown(path.join(DATADIR, name))); + const db = levelup(leveldown(path.join(DATADIR, name))); + const genesisBlock = Genesis.genesisToBlock(genesis); Object.defineProperty(this, 'name', {value: name}); Object.defineProperty(this, 'db', {value: db}); + Object.defineProperty(this, 'genesis', {value: genesisBlock}); } /** @@ -141,7 +144,7 @@ class Chain extends DB { */ getBlock(number) { return (number === 0) - && Promise.resolve(genesis) + && Promise.resolve(this.genesis) || getOrNull(this.db, number); } @@ -158,7 +161,7 @@ class Chain extends DB { .next((err, key, value) => { return (err) && reject(err) - || resolve(value && JSON.parse(value.toString()) || genesis); + || resolve(value && JSON.parse(value.toString()) || this.genesis); }); }); } diff --git a/genesis.json b/genesis.json index 5f57e91..09f532c 100644 --- a/genesis.json +++ b/genesis.json @@ -7,6 +7,5 @@ "alloc": [ {"0x05afba62afe7a92d4b6954741990a9e2f5b5e2ad": {"balance": 100000000}} - ], - "state": [] + ] } diff --git a/lib/genesis.js b/lib/genesis.js index 30bbb56..dc85aa3 100644 --- a/lib/genesis.js +++ b/lib/genesis.js @@ -6,8 +6,9 @@ 'use strict'; -const rb = require('crypto').randomBytes; -const fs = require('fs'); +const rb = require('crypto').randomBytes; +const fs = require('fs'); +const helpers = require('lib/helpers'); module.exports = Genesis; @@ -29,15 +30,42 @@ function Genesis() { } Object.defineProperties(this, { - number: {value: 0, writable: true, enumerable: true}, - hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, - parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, - timestamp: {value: '0x00', writable: true, enumerable: true}, - producer: {value: '0x0000000000000000000000000000000000000000', writable: true, enumerable: true}, - alloc: {value: [], writable: true, enumerable: true} + number: {value: 0, writable: true}, + hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, + parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, + timestamp: {value: '0x00', writable: true}, + producer: {value: '0x0000000000000000000000000000000000000000', writable: true}, + alloc: {value: [], writable: true} }); } +/** + * Generate state for genesis block. + * + * @param {Object} genesis Process genesis block allocation data and generate initial state. + * @returns {Object} Genesis block with state. + */ +Genesis.genesisToBlock = function genesisToBlock(genesis) { + genesis.state = []; + + for (const allocObject of genesis.alloc) { + const address = Object.keys(allocObject)[0]; + const allocatedAccount = allocObject[address]; + + const account = helpers.emptyAccount(address); + + Object.assign(account, { + balance: allocatedAccount.balance || 0, + votes: allocatedAccount.votes || [], + certificates: allocatedAccount.certificates || [] + }); + + genesis.state.push(account); + } + + return genesis; +}; + /** * Add account with allocated certificates. * @@ -45,14 +73,12 @@ function Genesis() { * @param {Number} nCertificates Number of certificates to generate. */ Genesis.prototype.addProducer = function addProducer(address, nCertificates) { - const allocObject = {}; - - allocObject[address] = { - certificates: Array.apply(null, {length: nCertificates}).map(() => '0x' + rb(32).toString('hex')), - balance: 0 - }; - - this.alloc.push(allocObject); + return this.writeOrExtend(address, { + [address]: { + certificates: Array.apply(null, {length: nCertificates}).map(() => '0x' + rb(32).toString('hex')), + // balance: 0 // TODO: find out whether this line is required + } + }); }; /** @@ -62,14 +88,9 @@ Genesis.prototype.addProducer = function addProducer(address, nCertificates) { * @param {Number} balance Balance (i.e. voting power). */ Genesis.prototype.addDelegate = function addDelegate(address, balance) { - const allocObject = {}; - - allocObject[address] = { - votes: [address], - balance - }; - - this.alloc.push(allocObject); + return this.writeOrExtend(address, { + [address]: {votes: [address], balance} + }); }; /** @@ -79,11 +100,7 @@ Genesis.prototype.addDelegate = function addDelegate(address, balance) { * @param {Number} balance Amount of coins to allocate. */ Genesis.prototype.addAccount = function addAccount(address, balance) { - const allocObject = {}; - - allocObject[address] = {balance}; - - this.alloc.push(allocObject); + return this.writeOrExtend(address, {[address]: {balance}}); }; /** @@ -94,3 +111,18 @@ Genesis.prototype.addAccount = function addAccount(address, balance) { Genesis.prototype.writeToFile = function writeToFile(path = DEFAULT_PATH) { fs.writeFileSync(path, JSON.stringify(this, null, 4)); }; + +/** + * Extend existing record with new data or write new record. + * + * @param {String} address Address to work with + * @param {Object} allocObject Object to write or extend with + * @return {Object} This instance + */ +Genesis.prototype.writeOrExtend = function extend(address, allocObject) { + const existing = this.alloc.find((el) => Object.keys(el).includes(address)) || null; + + return (existing === null) + && this.alloc.push(allocObject) + || Object.assign(existing[address], allocObject[address]); +}; diff --git a/lib/parser.js b/lib/parser.js deleted file mode 100644 index 9a00664..0000000 --- a/lib/parser.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Block state parser. - * - * @module lib/parser - */ - -'use strict'; - -/** - * Retrieve map of delegates from block state. - * - * @param {Object} state - * @return {Object} - */ -module.exports = function parse(state) { - let delegates = new Map(); - - let totalCertificates = 0; - - state.forEach(({certificates, votes, balance}) => { - totalCertificates += certificates.length; - - votes.forEach(delegate => { - const existingDelegate = delegates.get(delegate); - - return existingDelegate - && delegates.set(delegate, existingDelegate + balance) - || delegates.set(delegate, +balance); - }); - }); - - return { - delegates, - totalCertificates - }; -}; diff --git a/package.json b/package.json index 7952333..297e641 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Griffin is blockchain consensus based on a combination of Delegated Proof of Stake (DPoS) and Block Producers (PoS)", "main": "index.js", "scripts": { - "postinstall": "mkdir data", + "postinstall": "mkdir -p data", "test": "NODE_PATH=. mocha test" }, "repository": { diff --git a/test/unit/account.js b/test/unit/account.js index c8c8010..1634426 100644 --- a/test/unit/account.js +++ b/test/unit/account.js @@ -8,15 +8,15 @@ require('chai').should(); const Account = require('core/account'); const helpers = require('lib/helpers'); +const Genesis = require('lib/genesis'); const genesis = require('genesis'); -const parser = require('lib/parser'); /** * Secret key used for testing. * * @type {String} */ -const SECRET_KEY = Buffer.from('557dce58018cf502a32b9b7723024805399350d006a4f71c3b9f489f7085cb50', 'hex'); +const SECRET_KEY = Buffer.from('557dce58018cf502a32b9b7723024805399350d006a4f71c3b9f489f7085cb50', 'hex'); describe('Accounts', () => { let account = {}; @@ -88,12 +88,10 @@ describe('Accounts', () => { }); it('produce first block and verify state', () => { - block = account.produceBlock(genesis, transactions); + block = account.produceBlock(Genesis.genesisToBlock(genesis), transactions); const accountState = block.state[0]; - console.log('Account state:', accountState); - const currentAmount = INITIAL_AMOUNT - AMOUNT_TO_SEND - AMOUNT_TO_VOTE; accountState.balance.should.be.equal(currentAmount); @@ -101,18 +99,10 @@ describe('Accounts', () => { accountState.certificates.length.should.be.equal(1); }); - it('parse block state', () => { - const normalizedState = parser(block.state); - - console.log('Delegates:', normalizedState.delegates); - console.log('Amount of certificates:', normalizedState.totalCertificates); - }); - it('sign message with account secret key', () => { signature = account.signMessage(MESSAGE); signature.length.should.be.equal(64); - console.log('Signature: ', signature); }); it('verify signed message', () => { diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js index 6b92d93..63bf155 100644 --- a/test/unit/blockchain.js +++ b/test/unit/blockchain.js @@ -7,18 +7,18 @@ require('chai').should(); const Account = require('core/account'); -const genesis = require('genesis'); const blockchain = require('core/blockchain'); - +const Genesis = require('lib/genesis'); +const genesis = require('genesis'); /** * Secret key used for testing. * * @type {String} */ -const SECRET_KEY = Buffer.from('557dce58018cf502a32b9b7723024805399350d006a4f71c3b9f489f7085cb50', 'hex'); +const SECRET_KEY = Buffer.from('557dce58018cf502a32b9b7723024805399350d006a4f71c3b9f489f7085cb50', 'hex'); -describe('Chain', () => { +describe('Blockchain', () => { let account = {}; let delegate = {}; let delegates = []; @@ -28,6 +28,8 @@ describe('Chain', () => { let serializedTx = {}; const transactions = []; + const genesisBlock = Genesis.genesisToBlock(genesis); + account = Account(SECRET_KEY); delegate = Account(); @@ -40,29 +42,25 @@ describe('Chain', () => { serializedTx = account.stake(100); transactions.push(serializedTx); - account.produceBlock(genesis, transactions); + account.produceBlock(genesisBlock, transactions); }); it('get account balance', async () => { const balance = await blockchain.getBalance(account.getHexAddress()); balance.should.be.a('number'); - - console.log('Balance:', balance); }); xit('get account stake', async () => { const stake = await blockchain.getStake(account.getHexAddress()); stake.should.be.a('number'); - - console.log('Stake:', stake); }); it('get account votes', async () => { const votes = await blockchain.getVotesFor(delegate.getHexAddress()); - console.log('Votes:', votes); + // TODO: finish test }); it('get all delegates', async () => { @@ -70,19 +68,19 @@ describe('Chain', () => { delegates.should.be.a('array'); - console.log('Delegates:', delegates); + // TODO: TEST }); it('get active delegates', async () => { const activeDelegates = await blockchain.getActiveDelegates(); - console.log('Active delegates:', activeDelegates); + // TODO: TEST }); it('get successor delegates', async () => { const successorDelegates = await blockchain.getSuccessorDelegates(); - console.log('Successor delegates:', successorDelegates); + // TODO: TEST }); it('get delegates count', async () => { @@ -90,28 +88,24 @@ describe('Chain', () => { count.should.be.a('number'); - console.log('Count:', count); + // TODO: TEST }); it('check if account is delegate', async () => { const isDelegate = await blockchain.isDelegate(delegates[0]); isDelegate.should.be.true; - - console.log('is delegate:', isDelegate); }); it('get block producers', async () => { blockProducers = await blockchain.getBlockProducers(); - console.log('block producers:', blockProducers); + // TODO: TEST }); - it('check if account is block producer', async () => { + xit('check if account is block producer', async () => { const isBlockProducer = await blockchain.isBlockProducer(blockProducers[0].address); - isBlockProducer.should.be.true; - - console.log('is block producer:', isBlockProducer); + // TODO: TEST }); }); diff --git a/test/unit/genesis.js b/test/unit/genesis.js index 08d8de8..5ea244e 100644 --- a/test/unit/genesis.js +++ b/test/unit/genesis.js @@ -8,7 +8,6 @@ const chai = require('chai'); chai.use(require('chai-like')); require('chai').should(); -const fs = require('fs'); const rb = require('crypto').randomBytes; const Genesis = require('lib/genesis'); @@ -32,7 +31,7 @@ describe('Create genesis', () => { const allocObject = findAllocatedObject(genesis, address); allocObject.certificates.length.should.be.equal(amount); - allocObject.balance.should.be.equal(0); + // allocObject.balance.should.be.equal(0); QUESTION: should balance be there or not? }); it('add delegate', () => { @@ -51,18 +50,6 @@ describe('Create genesis', () => { allocObject.balance.should.be.equal(amount); }); - - it('save as file', () => { - const path = 'test/genesis.test.json'; - - genesis.writeToFile(path); - - const genesisFromFile = require(path); - - genesisFromFile.number.should.be.like(genesis.number); - - fs.unlinkSync(path); - }); }); /** @@ -73,5 +60,5 @@ describe('Create genesis', () => { * @return {Object} Found account. */ function findAllocatedObject(genesis, address) { - return (genesis.alloc.find(el => (Object.keys(el)[0] === address)))[address]; + return (genesis.alloc.find(el => (Object.keys(el).includes(address))))[address]; } From aababd1da2646da5615c9d08362e353cdf853020 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 22 Apr 2019 08:53:03 +0300 Subject: [PATCH 08/25] Changed clients to roles - role is an assignment for one round defined in start of it - both delegate and producer are just roles from now on - index.js is a key file which listens to all the events and plays both roles - no more /docs directory --- bp.js | 8 +++-- core/account.js | 10 +++++- core/blockchain.js | 32 ++++++++++------- core/db.js | 2 +- docs/EVENTS.md | 16 --------- docs/NEW_TX_EVENT.md | 0 docs/SYNC_MECHANICS.md | 20 ----------- index.js | 26 +++++++++++--- lib/genesis.js | 10 +++--- roles/bank.js | 48 +++++++++++++++++++++++++ {clients => roles}/block-producer.js | 35 ++++++------------ {clients => roles}/delegate.js | 53 ++++++++++------------------ runner.js | 15 ++++---- services/sync.js | 8 +++++ services/wallet.js | 17 +++++++-- 15 files changed, 171 insertions(+), 129 deletions(-) delete mode 100644 docs/EVENTS.md delete mode 100644 docs/NEW_TX_EVENT.md delete mode 100644 docs/SYNC_MECHANICS.md create mode 100644 roles/bank.js rename {clients => roles}/block-producer.js (84%) rename {clients => roles}/delegate.js (86%) diff --git a/bp.js b/bp.js index 5080835..c1a343b 100644 --- a/bp.js +++ b/bp.js @@ -38,16 +38,20 @@ require('services/observer'); const parentBlock = await chaindata.getLatest(); const transactions = await pool.drain(); + console.log(parentBlock.number); + const block = producer.produceBlock(parentBlock, transactions); - await chaindata.add(block); + await chaindata.add(block.number, block); await streamBlock(block); + console.log('NEW BLOCK', block.number, block.hash); + // transport.send(events.NEW_BLOCK, block); return wait(TIMEOUT).then(newBlock); -})(); +})().catch(console.error); (async function newTx() { diff --git a/core/account.js b/core/account.js index 40f7894..0f94802 100644 --- a/core/account.js +++ b/core/account.js @@ -79,10 +79,18 @@ Account.prototype.tx = function tx(to, value, data='0x00') { return '0x' + tx.serialize().toString('hex'); }; +/** + * 0x-prefixed hex representation on address. + * @property {String} + */ +Object.defineProperty(Account.prototype, 'hexAddress', { + get() { return '0x' + this.address.toString('hex'); } +}); + /** * Get address of account as hex string. * - * @returns {string} Address as hex string. + * @returns {String} Address as hex string. */ Account.prototype.getHexAddress = function getHexAddress() { return '0x' + this.address.toString('hex'); diff --git a/core/blockchain.js b/core/blockchain.js index 10606a7..1731c2a 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -115,9 +115,9 @@ exports.getDelegatesCount = async function getDelegatesCount() { * @returns {Promise.} True/false depends is delegate or not. */ exports.isDelegate = async function isDelegate(address) { - const rawDelegates = await getRawDelegates(); + const delegates = await getRawDelegates(); - return rawDelegates[address] == undefined; + return delegates.has(address); }; /** @@ -303,19 +303,27 @@ function getBlockProducer(block, finalRandomNumber) { async function getRawDelegates() { const latestBlock = await chainData.getLatest(); - const rawDelegates = latestBlock.state.reduce((rawDelegates, account) => { - for (let v of account.votes) { - if (!rawDelegates[v.delegate]) { - rawDelegates[v.delegate] = 0; - } + const delegates = latestBlock.state + .filter((record) => (!!record.votes.length)) // Leave only delegates first + .map((account) => [account.address, account]); - rawDelegates[v.delegate] += parseInt(v.amount); - } - return rawDelegates; - }, {}); + return new Map(delegates); + - return rawDelegates; + // .reduce((rawDelegates, account) => { + // for (let v of account.votes) { + // if (!rawDelegates[v.delegate]) { + // rawDelegates[v.delegate] = 0; + // } + // + // rawDelegates[v.delegate] += parseInt(v.amount); + // } + // + // return rawDelegates; + // }, {}); + // + // return rawDelegates; } /** diff --git a/core/db.js b/core/db.js index b98dd54..e0343fe 100644 --- a/core/db.js +++ b/core/db.js @@ -133,7 +133,7 @@ class Chain extends DB { * @param {String} block Block to store */ add(number, block) { - return this.db.put(number, block); + return this.db.put(number, block.constructor === String && block || JSON.stringify(block)); } /** diff --git a/docs/EVENTS.md b/docs/EVENTS.md deleted file mode 100644 index 0c13123..0000000 --- a/docs/EVENTS.md +++ /dev/null @@ -1,16 +0,0 @@ -# Network Events - -## New Block - -**Event name:** ```NewBlock``` - -**Actions:** -- Remove transactions present in this block from local pool -- Add new block to chaindata - -## New TX - -**Event name:** ```NewTransaction``` - -**Actions:** -- Add transaction to pool diff --git a/docs/NEW_TX_EVENT.md b/docs/NEW_TX_EVENT.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/SYNC_MECHANICS.md b/docs/SYNC_MECHANICS.md deleted file mode 100644 index 5e965c3..0000000 --- a/docs/SYNC_MECHANICS.md +++ /dev/null @@ -1,20 +0,0 @@ -# Pool/Chain sync mechanics - -To provide fast setup of new node in this PoC application while keeping decentralisation. - -1. New node emits event **REQUEST_CHAIN** -2. All the nodes in network receive it and emit number of blocks (or txs count in pool case) using **SHARE_CHAIN** event -3. Newbie decides from which node to pull data and sends **CREATE_CHAINDATA_SERVER** -4. Node with info sets up one-time HTTP server and sends **CHAINDATA_SERVER_CREATED** directly to newbie -5. New node pulls Chain or Pool data via HTTP and is now good to go - -Described sequence is the same with pool, but events named differently: - -``` -CHAINDATA | POOL --------------------------+---------------------- -REQUEST_CHAIN | REQUEST_POOL -SHARE_CHAIN | SHARE_POOL -CREATE_CHAINDATA_SERVER | CREATE_POOL_SERVER -CHAINDATA_SERVER_CREATED | POOL_SERVER_CREATED -``` diff --git a/index.js b/index.js index 0cc2f36..6bfdcbd 100644 --- a/index.js +++ b/index.js @@ -11,11 +11,12 @@ process.stdin.resume(); const wait = require('util').promisify(setTimeout); const tp = require('core/transport'); const sync = require('services/sync'); +const evt = require('lib/events'); +const me = require('services/wallet'); +const repl = require('repl'); (async function initServices() { - await wait(3500); - // More than one node in network if (tp.knownNodes.size > 1) { @@ -23,8 +24,6 @@ const sync = require('services/sync'); // sync.chain(), sync.pool() ]).catch(console.error); - - console.log('Synced'); } })().then(async function runClient() { @@ -32,4 +31,23 @@ const sync = require('services/sync'); console.log('Starting observer'); require('services/observer'); + + // Attach both roles by default + require('roles/block-producer').attach(); + require('roles/delegate').attach(); + + console.log('Starting prompt...'); + + const tty = repl.start('> '); + + Object.assign(tty.context, {tp, evt, me}); }); + +// QUESTION +// ON START ROUND EVENT +// AM I DELEGATE? +// ATTACH LISTENERS FOR DELEGATE FOR 1 ROUND +// AM I PRODUCER? +// ATTACH LISTENERS FOR PRODUCER FOR 1 ROUND +// +// ON ROUND END REMOVE ALL LISTENERS AND LIVE FREE diff --git a/lib/genesis.js b/lib/genesis.js index 1d16fcf..0c38d04 100644 --- a/lib/genesis.js +++ b/lib/genesis.js @@ -30,11 +30,11 @@ function Genesis() { } Object.defineProperties(this, { - number: {value: 0, writable: true}, - hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, - parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, - timestamp: {value: '0x00', writable: true}, - producer: {value: '0x0000000000000000000000000000000000000000', writable: true}, + number: {value: 0, writable: true, enumerable: true}, + hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, + parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, + timestamp: {value: '0x00', writable: true, enumerable: true}, + producer: {value: '0x0000000000000000000000000000000000000000', writable: true, enumerable: true}, alloc: {value: [], writable: true, enumerable: true} }); } diff --git a/roles/bank.js b/roles/bank.js new file mode 100644 index 0000000..0783170 --- /dev/null +++ b/roles/bank.js @@ -0,0 +1,48 @@ +/** + * Dummy-account with a lot of funds in it to share them with others and make txs and + * make interactive voting/tx-sending from future REPL + * + * This one requires ENV setting with SECRET_KEY to have funds to send + * + * @module clients/bank + */ + +'use strict'; + +const evt = require('lib/events'); +const tp = require('core/transport'); +const sync = require('services/sync'); +const acc = require('services/wallet'); + +/** + * Amount to send when requested + * @type {Number} + */ +const AMOUNT = '0x64'; // 100 + +(async function init() { + + // Sync with other nodes if there are + if (tp.knownNodes.size > 1) { + await Promise.all([sync.pool(), sync.chain()]); + } + +})().then(async function startListening() { + + // Start listening to network events right after + require('services/observer'); + + tp.on(evt.GIMME_COINS, listenAndSend); +}); + +/** + * Simply listen to GIMME_COINS event and send TX with 100 coins in response + * + * @listens events.GIMME_COINS + * @emits events.NEW_TRANSACTION + * + * @param {String} addr 0x-prefixed address to send coins to + */ +function listenAndSend(addr) { + tp.send(evt.NEW_TRANSACTION, acc.tx(addr, AMOUNT)); +} diff --git a/clients/block-producer.js b/roles/block-producer.js similarity index 84% rename from clients/block-producer.js rename to roles/block-producer.js index 9ae3b15..b882c63 100644 --- a/clients/block-producer.js +++ b/roles/block-producer.js @@ -12,31 +12,18 @@ const pool = require('core/db').pool; const tp = require('core/transport'); const chaindata = require('core/db').chain; const peer = require('core/file-peer'); -const sync = require('services/sync'); const waiter = require('services/waiter'); const blockProducer = require('services/wallet'); -/** - * Number of Delegates in network to wait for - * @type {Number} - */ -const DELEGATES = +process.env.DELEGATES || 33; - -(async function init() { - - // Sync with other nodes if there are - if (tp.knownNodes.size > 1) { - await Promise.all([sync.pool(), sync.chain()]); - } +exports.attach = function attach() { -})().then(async function main() { + tp.on(events.START_ROUND, waitAndProduce); +}; - // Start observing network events - require('services/observer'); +exports.detach = function detach() { - // Attach block producer event listener - tp.on(events.START_ROUND, waitAndProduce); -}); + tp.off(events.START_ROUND, waitAndProduce); +}; /** * This function is a generic listener for START_ROUND event from @@ -56,13 +43,13 @@ async function waitAndProduce() { // // QUESTION: Should we do a check somewhere for round definition or smth. Number of retries mb? // We want to let BP know whether round has been restarted so he can drop this listener - const randoms = await waiter.waitForAll(events.BP_CATCH_IT, DELEGATES, Infinity); + const random = await waiter.waitFor(events.BP_CATCH_IT, Infinity); // On each round every block producer checks whether he should produce this block. // We may want every bp to produce block every round. // TODO: remember about backup block producer, as he have to produce as well in order to get block reward. // FIXME Supply real FRN. - const isProducer = await isMyRound(randoms[0].data); + const isProducer = await isMyRound(random.data); if (!isProducer) { console.log('I AM NO PRODUCER'); @@ -74,11 +61,11 @@ async function waitAndProduce() { const transactions = await pool.drain().catch(console.error); const block = blockProducer.produceBlock(parentBlock, transactions); - block.randomNumber = randoms[0].data; + block.randomNumber = random; // Share block with delegates // TODO: think of verification: do it in UDP short block or HTTP or both - const {port} = peer.peerString(block, DELEGATES); + const {port} = peer.peerString(block, ); // Send event so delegates know where to get block tp.send(events.VERIFY_BLOCK, { @@ -130,8 +117,6 @@ async function isMyRound(frn) { return true; } - console.log(block, typeof block); - // get all certificates from latest block block.state.forEach((account) => { if (activeProducers.includes(account.address)) { diff --git a/clients/delegate.js b/roles/delegate.js similarity index 86% rename from clients/delegate.js rename to roles/delegate.js index f29a9f4..e04ad41 100644 --- a/clients/delegate.js +++ b/roles/delegate.js @@ -13,7 +13,6 @@ const Delegate = require('core/account'); const tp = require('core/transport'); const chaindata = require('core/db').chain; const waiter = require('services/waiter'); -const sync = require('services/sync'); const peer = require('core/file-peer'); const delegate = require('services/wallet'); @@ -39,43 +38,20 @@ const delegate = require('services/wallet'); */ const DELEGATES = '*'; -/** - * Initialize pool and chain on first client start. - * @todo We may want to move this logic elsewhere as it's the same in bp, index and here. - */ -(async function init() { - - // Sync with other nodes if there are - if (tp.knownNodes.size > 1) { - await Promise.all([sync.pool(), sync.chain()]); - } - -})().then(async function waitForDelegates() { +exports.attach = function attach() { - tp.groups.add(DELEGATES); - - tp.delegates = new Map(); - Object.defineProperty(tp, 'knownDelegates', {get: () => tp.delegates.size}); - - tp.on(events.HELLO_DUDE, (data, msg, meta) => tp.delegates.set(msg.sender, meta)); - tp.on(events.I_AM_HERE, (data, msg, meta) => { - tp.delegates.set(msg.sender, meta); - tp.send(events.HELLO_DUDE, null, msg.sender); - }); + tp.on(events.START_ROUND, exchangeRandoms); + tp.on(events.VERIFY_BLOCK, blockVerification); +}; - tp.send(events.I_AM_HERE); +exports.detach = function detach() { - while (tp.knownDelegates < +process.env.DELEGATES) { - await waiter.wait(500); - } + tp.off(events.START_ROUND, exchangeRandoms); + tp.off(events.VERIFY_BLOCK, blockVerification); +}; -}).then(async function startClient() { - require('services/observer'); - tp.on(events.START_ROUND, exchangeRandoms); - tp.on(events.VERIFY_BLOCK, blockVerification); -}); /** * Do generate final random number and broadcast it to network using following steps: @@ -99,7 +75,7 @@ const DELEGATES = '*'; async function exchangeRandoms() { // Let's use this variable as if it existed - const numDelegates = tp.knownDelegates || 33; + const numDelegates = 1 || 33; const myRandomNum = math.random().toString(10); // sign message @@ -111,6 +87,8 @@ async function exchangeRandoms() { tp.send(events.RND_EVENT, messageWithRandom, DELEGATES); + console.log('SENDING MY RANDOM', myRandomNum); + const responses = await waiter.waitForAll(events.RND_EVENT, numDelegates, Infinity); const responseMessages = responses.map((r) => r.data); const verifiedMessages = responseMessages.filter(msg => Delegate.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); @@ -165,9 +143,14 @@ async function blockVerification({port, block: short}, msg, meta) { const block = JSON.parse(rawData); if (block && await isValidBlock(block) && isValidBlockProducer(block)) { + console.log('streaming block over network'); return streamBlock(block); } + console.log(!!block, await isValidBlock(block), isValidBlockProducer(block)); + + console.log('block is invalid'); + // TODO Case when block is invalid. return null; } @@ -202,7 +185,7 @@ async function streamBlock(block) { signature }, DELEGATES); - const numDelegates = tp.knownDelegates || 33; + const numDelegates = 1 || 33; const responses = await waiter.waitForAll(events.BLOCK_EVENT, numDelegates, Infinity); const responseMessages = responses.map((r) => r.data); const verifiedMessages = responseMessages.filter(msg => Delegate.verifyMessage(msg.hashedBlock, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); @@ -260,6 +243,8 @@ async function isValidBlock(producedBlock) { const parentBlock = await chaindata.getLatest(); const block = delegate.produceBlock(parentBlock, producedBlock.transactions); + console.log(block, producedBlock); + return producedBlock.stateRoot === block.stateRoot && producedBlock.receiptsRoot === block.receiptsRoot; } diff --git a/runner.js b/runner.js index d315730..69ec123 100644 --- a/runner.js +++ b/runner.js @@ -44,15 +44,16 @@ for (let i = 0; i < num; i++) { delegates.push(account); genesis.addDelegate(account.address.toString('hex'), DELEGATE_BALANCE); -} - -for (let i = 0; i < num; i++) { - const account = Account(); - - producers.push(account); genesis.addProducer(account.address.toString('hex'), 1); } +// for (let i = 0; i < num; i++) { +// const account = Account(); +// +// producers.push(account); +// genesis.addProducer(account.address.toString('hex'), 1); +// } + genesis.writeToFile(GENESIS_PATH); const kids = [] @@ -107,7 +108,7 @@ function spawnDelegate(e, i) { fs.mkdirSync(datadir); const stream = fs.createWriteStream(datadir + '/out.log', {flags: 'w'}); - const child = cp.fork('clients/delegate.js', [], options); + const child = cp.fork('index.js', [1], options); child.stdout.pipe(stream); child.stderr.pipe(stream); diff --git a/services/sync.js b/services/sync.js index cadfefe..cb38504 100644 --- a/services/sync.js +++ b/services/sync.js @@ -36,6 +36,10 @@ exports.pool = async function syncPool() { const nodes = await waiter.waitForAll(events.SHARE_POOL, 10, WAIT_FOR); const myNode = nodes.sort((a, b) => a.data - b.data)[0]; + if (!myNode) { + return; + } + tp.send(events.CREATE_POOL_SERVER, null, myNode.msg.sender); const peerData = await waiter.waitFor(events.POOL_SERVER_CREATED, WAIT_FOR); @@ -61,6 +65,10 @@ exports.chain = async function syncChain() { const responses = await waiter.waitForAll(events.SHARE_CHAIN, 10, 3000); const oneAndOnly = responses[0]; + if (!oneAndOnly) { + return; + } + tp.send(events.CREATE_CHAINDATA_SERVER, null, oneAndOnly.msg.sender); const peerData = await waiter.waitFor(events.CHAINDATA_SERVER_CREATED, WAIT_FOR); diff --git a/services/wallet.js b/services/wallet.js index 60612bb..b09815c 100644 --- a/services/wallet.js +++ b/services/wallet.js @@ -9,7 +9,8 @@ 'use strict'; -const Account = require('core/account'); +const Account = require('core/account'); +const blockchain = require('core/blockchain'); /** * Secret key parsed from ENV when provided. @@ -19,4 +20,16 @@ const Account = require('core/account'); */ const SECRET_KEY = process.env.SECRET_KEY && Buffer.from(process.env.SECRET_KEY, 'hex') || null; -module.exports = exports = new Account(SECRET_KEY); +const me = module.exports = exports = new Account(SECRET_KEY); + +/** + * Check whether me (account i) + * @return {[type]} [description] + */ +exports.isDelegate = function () { + return blockchain.isDelegate(me.address.toString('hex')); +}; + +exports.isProducer = function () { + +}; From 62530df790e0f6fd37e8a6903db9d25e4d94616a Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 22 Apr 2019 14:00:08 +0300 Subject: [PATCH 09/25] Temporarily disabled bank, added isProducer check --- core/blockchain.js | 4 ++-- index.js | 3 +++ lib/genesis.js | 14 ++++++++------ runner.js | 4 ++-- services/wallet.js | 12 +++++++++--- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/core/blockchain.js b/core/blockchain.js index 1731c2a..6797917 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -135,12 +135,12 @@ exports.getCertificates = async function getCertificates(address) { /** * Check if account is a block producer. * - * @param address Address of account to check. + * @param {String} address Address of account to check (without 0x prefix). * @returns {Promise.} */ exports.isBlockProducer = async function isBlockProducer(address) { const latestBlock = await chainData.getLatest(); - const account = latestBlock.state.find(account => account.address == address); + const account = latestBlock.state.find((account) => (account.address === address)); return !!account.certificates.length; }; diff --git a/index.js b/index.js index 60ad1c0..22993e4 100644 --- a/index.js +++ b/index.js @@ -27,6 +27,9 @@ const repl = require('repl'); })().then(async function runClient() { + console.log('PRODUCER?', await me.isProducer()); + console.log('DELEGATE?', await me.isDelegate()); + console.log('Starting observer'); require('services/observer'); diff --git a/lib/genesis.js b/lib/genesis.js index dc85aa3..768632c 100644 --- a/lib/genesis.js +++ b/lib/genesis.js @@ -30,12 +30,12 @@ function Genesis() { } Object.defineProperties(this, { - number: {value: 0, writable: true}, - hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, - parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true}, - timestamp: {value: '0x00', writable: true}, - producer: {value: '0x0000000000000000000000000000000000000000', writable: true}, - alloc: {value: [], writable: true} + number: {value: 0, writable: true, enumerable: true}, + hash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, + parentHash: {value: '0x0000000000000000000000000000000000000000000000000000000000000000', writable: true, enumerable: true}, + timestamp: {value: '0x00', writable: true, enumerable: true}, + producer: {value: '0x0000000000000000000000000000000000000000', writable: true, enumerable: true}, + alloc: {value: [], writable: true, enumerable: true} }); } @@ -48,6 +48,8 @@ function Genesis() { Genesis.genesisToBlock = function genesisToBlock(genesis) { genesis.state = []; + console.log(genesis.alloc); + for (const allocObject of genesis.alloc) { const address = Object.keys(allocObject)[0]; const allocatedAccount = allocObject[address]; diff --git a/runner.js b/runner.js index ddab346..a5f80f9 100644 --- a/runner.js +++ b/runner.js @@ -59,7 +59,7 @@ const kids = [] .concat(delegates.map(spawnDelegate)) .concat(producers.map(spawnProducer)); -const bankKiddo = cp.fork('clients/bank.js', ['bank'], {env: Object.assign(env, {SECRET_KEY: bank.secretKey.toString('hex')})}); +// const bankKiddo = cp.fork('clients/bank.js', ['bank'], {env: Object.assign(env, {SECRET_KEY: bank.secretKey.toString('hex')})}); const repl = require('repl').start('> '); repl.context.kids = kids; @@ -139,7 +139,7 @@ function spawnProducer(e, i) { function finish(...args) { return console.log('Cleaning up:', args) - || bankKiddo.kill() + // || bankKiddo.kill() || true && kids.map((kid) => kid.kill()) && process.exit(0); diff --git a/services/wallet.js b/services/wallet.js index b09815c..90d8151 100644 --- a/services/wallet.js +++ b/services/wallet.js @@ -23,13 +23,19 @@ const SECRET_KEY = process.env.SECRET_KEY && Buffer.from(process.env.SECRET_KEY, const me = module.exports = exports = new Account(SECRET_KEY); /** - * Check whether me (account i) - * @return {[type]} [description] + * Check whether me (process account) is delegate + * + * @return {Promise} Whether account is delegate */ exports.isDelegate = function () { return blockchain.isDelegate(me.address.toString('hex')); }; +/** + * Check whether me (process account) is delegate + * + * @return {Promise} Whether account is block producer + */ exports.isProducer = function () { - + return blockchain.isBlockProducer(me.address.toString('hex')); }; From 41fd023445c52c246221bbcfc40801e2c6459791 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 22 Apr 2019 23:07:58 +0300 Subject: [PATCH 10/25] Added account record check in state --- core/blockchain.js | 2 +- lib/genesis.js | 2 -- lib/helpers.js | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/blockchain.js b/core/blockchain.js index 6797917..e3c6a87 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -142,7 +142,7 @@ exports.isBlockProducer = async function isBlockProducer(address) { const latestBlock = await chainData.getLatest(); const account = latestBlock.state.find((account) => (account.address === address)); - return !!account.certificates.length; + return !!account && !!account.certificates.length; }; /** diff --git a/lib/genesis.js b/lib/genesis.js index 768632c..0c38d04 100644 --- a/lib/genesis.js +++ b/lib/genesis.js @@ -48,8 +48,6 @@ function Genesis() { Genesis.genesisToBlock = function genesisToBlock(genesis) { genesis.state = []; - console.log(genesis.alloc); - for (const allocObject of genesis.alloc) { const address = Object.keys(allocObject)[0]; const allocatedAccount = allocObject[address]; diff --git a/lib/helpers.js b/lib/helpers.js index ca02438..38b336b 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -4,7 +4,7 @@ 'use strict'; -const {randomBytes} = require('crypto'); +const rb = require('crypto').randomBytes; const secp256k1 = require('secp256k1'); const rlp = require('rlp'); const keccak256 = require('keccak256'); @@ -132,7 +132,7 @@ exports.generateSecretKey = function generateSecretKey() { let secretKey; while (!secretKey || !secp256k1.privateKeyVerify(secretKey)) { - secretKey = randomBytes(32); + secretKey = rb(32); } return secretKey; From a01ff9abdcde004942651e5a31d2186c0b0b8e2b Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 22 Apr 2019 23:10:28 +0300 Subject: [PATCH 11/25] Removed producers listing in runner --- runner.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runner.js b/runner.js index a5f80f9..a639519 100644 --- a/runner.js +++ b/runner.js @@ -72,8 +72,7 @@ tp.on(evt.PONG, (data) => console.log('Yup, dude, %s', data)); tp.delegates = new Map(); tp.on(evt.I_AM_HERE, (data, msg) => tp.delegates.set(msg.sender, msg)); -console.log('DELEGATES:') || delegates.map((e) => console.log('-', e.getHexAddress())); -console.log('PRODUCERS:') || producers.map((e) => console.log('-', e.getHexAddress())); +console.log('ACCOUNTS:') || delegates.map((e) => console.log('-', e.getHexAddress())); tp.on(evt.START_ROUND, function () { tp.once(evt.NEW_BLOCK, function ({block}) { From a42f39dc30151c32f503f7feeb6346f27c595f37 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 22 Apr 2019 23:17:12 +0300 Subject: [PATCH 12/25] Question everything, replaced getHexAddress with getter --- core/account.js | 2 +- runner.js | 2 +- services/observer.js | 11 ++++++++--- services/sync.js | 7 ++++++- test/unit/blockchain.js | 10 +++++----- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/core/account.js b/core/account.js index 0f94802..89102ef 100644 --- a/core/account.js +++ b/core/account.js @@ -160,7 +160,7 @@ Account.prototype.produceBlock = function produceBlock(parentBlock, transactions block.number = parentBlock.number + 1; block.parentHash = parentBlock.hash; block.hash = '0x' + keccak256(parentBlock.hash).toString('hex'); - block.producer = this.getHexAddress(); + block.producer = this.hexAddress; block.state = parentBlock.state || []; block.transactions = transactions; block.receipts = parentBlock.receipts || []; diff --git a/runner.js b/runner.js index a639519..152b0d6 100644 --- a/runner.js +++ b/runner.js @@ -72,7 +72,7 @@ tp.on(evt.PONG, (data) => console.log('Yup, dude, %s', data)); tp.delegates = new Map(); tp.on(evt.I_AM_HERE, (data, msg) => tp.delegates.set(msg.sender, msg)); -console.log('ACCOUNTS:') || delegates.map((e) => console.log('-', e.getHexAddress())); +console.log('ACCOUNTS:') || delegates.map((e) => console.log('-', e.hexAddress)); tp.on(evt.START_ROUND, function () { tp.once(evt.NEW_BLOCK, function ({block}) { diff --git a/services/observer.js b/services/observer.js index c9b5dbb..37ac4cd 100644 --- a/services/observer.js +++ b/services/observer.js @@ -29,10 +29,15 @@ require('core/transport') const newBlock = await peer.pullString(meta.address, port); await chaindata.add(block.number, newBlock); + + // QUESTION: should definition of role happen here? Or we could use same + // event in another module/service to attach/detach listeners? }); }) .on(events.NEW_TRANSACTION, function newTx(tx) { + // QUESTION: should there be some sort of filter of txs in PoC? + // QUESTION: how do we get tx hash and should it be extracted and written into pool here? pool.add(tx); }) @@ -65,12 +70,12 @@ require('core/transport') }) .on(events.PING, function areYouLookingForMe(data, msg) { - const addr = wallet.getHexAddress(); + // QUESTION: okay, we have PING event, what's next? How should it be used? + + const addr = wallet.hexAddress; if (data === addr) { this.send(events.PONG, addr, msg.sender); } }) - - ; diff --git a/services/sync.js b/services/sync.js index cb38504..797ebd2 100644 --- a/services/sync.js +++ b/services/sync.js @@ -34,6 +34,9 @@ exports.pool = async function syncPool() { tp.send(events.REQUEST_POOL); const nodes = await waiter.waitForAll(events.SHARE_POOL, 10, WAIT_FOR); + + // QUESTION: what principle should lay below sync server choosing? + // Currently 'just first' one is taken const myNode = nodes.sort((a, b) => a.data - b.data)[0]; if (!myNode) { @@ -60,8 +63,10 @@ exports.pool = async function syncPool() { */ exports.chain = async function syncChain() { - tp.send(events.REQUEST_CHAIN, null, '*'); + tp.send(events.REQUEST_CHAIN); + // QUESTION: what principle should lay below sync server choosing? + // Currently 'just first' one is taken const responses = await waiter.waitForAll(events.SHARE_CHAIN, 10, 3000); const oneAndOnly = responses[0]; diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js index 7ad3263..ad9210b 100644 --- a/test/unit/blockchain.js +++ b/test/unit/blockchain.js @@ -33,10 +33,10 @@ describe('Blockchain', () => { account = Account(SECRET_KEY); delegate = Account(); - serializedTx = account.tx(delegate.getHexAddress(), 100); + serializedTx = account.tx(delegate.hexAddress, 100); transactions.push(serializedTx); - serializedTx = account.vote(delegate.getHexAddress()); + serializedTx = account.vote(delegate.hexAddress); transactions.push(serializedTx); serializedTx = account.stake(100); @@ -46,19 +46,19 @@ describe('Blockchain', () => { }); it('get account balance', async () => { - const balance = await blockchain.getBalance(account.getHexAddress()); + const balance = await blockchain.getBalance(account.hexAddress); balance.should.be.a('number'); }); xit('get account stake', async () => { - const stake = await blockchain.getStake(account.getHexAddress()); + const stake = await blockchain.getStake(account.hexAddress); stake.should.be.a('number'); }); it('get account votes', async () => { - const votes = await blockchain.getVotesFor(delegate.getHexAddress()); + const votes = await blockchain.getVotesFor(delegate.hexAddress); // TODO: finish test }); From c8839a54ef6b1c79abd2404ab0b2b9ef8873d639 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 22 Apr 2019 23:24:15 +0300 Subject: [PATCH 13/25] Removed merge conflict in tests --- core/blockchain.js | 3 +++ test/unit/blockchain.js | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/blockchain.js b/core/blockchain.js index e3c6a87..b60eb8e 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -358,6 +358,9 @@ async function getAccount(address, block) { /** * Get all delegates sorted by their votes. * + * TODO: Update this method to match Map in getRawDelegates() + * TODO: Redesign this and active/successor methods to match Map type as well + * * @returns {Promise.|Object[]} Delegates array. */ async function getDelegates() { diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js index ad9210b..bbbfcc4 100644 --- a/test/unit/blockchain.js +++ b/test/unit/blockchain.js @@ -92,6 +92,8 @@ describe('Blockchain', () => { }); it('check if account is delegate', async () => { + console.log(delegates[0]) + const isDelegate = await blockchain.isDelegate(delegates[0]); isDelegate.should.be.true; @@ -106,11 +108,6 @@ describe('Blockchain', () => { xit('check if account is block producer', async () => { const isBlockProducer = await blockchain.isBlockProducer(blockProducers[0].address); -<<<<<<< HEAD isBlockProducer.should.be.true; - -======= ->>>>>>> master - // TODO: TEST }); }); From e9974b02c0c20650b7bd720defa2e726160e5e8c Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Wed, 24 Apr 2019 14:14:39 +0300 Subject: [PATCH 14/25] Improved waiter to allow more options to listen - 2 new methods in services/waiter - constants are now able to define through env setting --- lib/constants.js | 25 ++++++++++++-------- services/waiter.js | 58 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index e4051fd..b06e99f 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,5 +1,6 @@ /** - * Constants. + * Application-level constants. + * In PoC variable and changeable with ENV. * * @module lib/constants */ @@ -13,9 +14,7 @@ */ exports.ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; -/** - * Method signatures. - */ +/* Vote and stake method signatures */ exports.VOTE_METHOD_SIG = 'vote(address)'; exports.STAKE_METHOD_SIG = 'stake(uint256)'; @@ -27,15 +26,21 @@ exports.STAKE_METHOD_SIG = 'stake(uint256)'; exports.CERTIFICATE_PRICE = 100; /** - * Active delegates amount. + * Active delegates count. + * Total number of delegates equals this + number of 'active successor' delegates. * - * @type {number} + * ENV: ACTIVE_DELEGATES_COUNT + * @default 31 + * @type {Number} */ -exports.ACTIVE_DELEGATES_COUNT = 31; +exports.ACTIVE_DELEGATES_COUNT = process.env.ACTIVE_DELEGATES_COUNT || 31; /** - * Successor delegates amount. + * Number of successor delegates to participate in consensus. + * Total number of delegates equals this + number of active delegates. * - * @type {number} + * ENV: ACTIVE_SUCCESSOR_DELEGATES_COUNT + * @default 2 + * @type {Number} */ -exports.SUCCESSOR_DELEGATES_COUNT = 31; +exports.ACTIVE_SUCCESSOR_DELEGATES_COUNT = process.env.ACTIVE_SUCCESSOR_DELEGATES_COUNT || 2; diff --git a/services/waiter.js b/services/waiter.js index a522ba9..ea6c9d1 100644 --- a/services/waiter.js +++ b/services/waiter.js @@ -7,9 +7,59 @@ const msg = require('core/transport'); const wait = require('util').promisify(setTimeout); -exports.wait = wait; -exports.waitFor = waitFor; -exports.waitForAll = waitForAll; +exports.wait = wait; +exports.collect = collect; +exports.waitFor = waitFor; +exports.waitForAll = waitForAll; +exports.waitForCond = waitForCond; + +/** + * Collect as many events as possible in 'wait' amount of time. + * + * @param {String} evt Name of the event to collect. + * @param {Number} [wait=1000] Number of milliseconds to collect + * @return {Promise} Promise with collected data + */ +async function collect(evt, wait = 1000) { + const result = []; + + return new Promise((resolve) => { + + msg.on(evt, listener); + + setTimeout(function success() { + msg.off(evt, listener); + resolve(result); + }, wait); + + function listener(data, msg, meta) { + result.push({data, msg, meta}); + } + }); +} + +/** + * Listen to specific event and apply callback to it. + * When cb(data, msg, meta) returns 'truthy value' resolve Promise with that data. + * + * @param {String} evt Event to listen to and whose results to filter + * @param {Function} cb Callback to check results + * @param {Number} [wait=1000] Number of milliseconds to wait + * @return {Promise} Promise with result when cb returned true or null when timeout reached + */ +async function waitForCond(evt, cb, wait = 1000) { + return new Promise(async function (resolve) { + + function listener(data, msg, meta) { + if (cb(data, msg, meta)) { + msg.off(evt, listener); + resolve(data, msg, meta); + } + } + + setTimeout(() => msg.off(evt, listener) && resolve(null), wait); + }); +} /** * Wait for N number of emitted events or for K milliseconds. @@ -25,7 +75,7 @@ async function waitForAll(evt, count = 1, wait = 1000) { const finite = (wait !== Infinity); return new Promise((resolve) => { - const success = () => { msg.removeListener(evt, listener); resolve(result); }; + const success = () => { msg.off(evt, listener); resolve(result); }; function listener(data, msg, meta) { (result.push({data, msg, meta}) === count) && success(); From 3c9abaa5b1a9eb84ef29e5794ec3099b6f5f00f7 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Tue, 30 Apr 2019 08:19:25 +0300 Subject: [PATCH 15/25] Reorganized state-parsing logic - new lib/block-state util for parsing block state - in genesis and everywhere else 0x-prefixed addresses (yay!) - few changes around tx handling - we don't want errors - instead of certificates there's locked in genesis - we don't issue certificates from now on, we only lock coins --- core/account.js | 4 +- core/blockchain.js | 241 +--------------------------------------- lib/block-state.js | 142 +++++++++++++++++++++++ lib/constants.js | 7 +- lib/genesis.js | 13 ++- roles/block-producer.js | 4 +- roles/delegate.js | 52 +++++++-- runner.js | 14 ++- services/wallet.js | 9 +- test/unit/blockchain.js | 2 +- 10 files changed, 219 insertions(+), 269 deletions(-) create mode 100644 lib/block-state.js diff --git a/core/account.js b/core/account.js index 89102ef..7144a76 100644 --- a/core/account.js +++ b/core/account.js @@ -169,9 +169,9 @@ Account.prototype.produceBlock = function produceBlock(parentBlock, transactions for (let txIndex = 0; txIndex < transactions.length; txIndex++) { const serializedTx = block.transactions[txIndex]; const tx = helpers.toTxObject(serializedTx); - const sender = block.state.find(account => account.address === tx.from); + const sender = block.state.find(account => account.address === tx.from.slice(2)); - if (!sender) { throw 'Sender account doesn\'t exist'; } + if (!sender) { console.log('Sender account doesn\'t exist'); continue; } block = blockchain.handleTransaction(tx, block); diff --git a/core/blockchain.js b/core/blockchain.js index b60eb8e..f835c28 100644 --- a/core/blockchain.js +++ b/core/blockchain.js @@ -11,11 +11,8 @@ const keccak256 = require('keccak256'); const ethRpc = require('eth-json-rpc')('http://localhost:8545'); const helpers = require('lib/helpers'); const constants = require('lib/constants'); -const chainData = require('core/db').chain; -exports.generateReceipt = generateReceipt; -exports.getBlockProducer = getBlockProducer; -exports.getDelegates = getDelegates; +exports.generateReceipt = generateReceipt; /** * Transaction handling entrypoint. @@ -30,133 +27,6 @@ exports.handleTransaction = function handleTransaction(tx, block) { } }; - -/** - * Get balance of account by address. - * - * @param address Address of account. - * @returns {Promise.} Balance of account. - */ -exports.getBalance = async function getBalance(address) { - const account = await getAccountFromLatestBlock(address); - return account.balance; -}; - -/** - * Get stake of account by address. - * - * @param address Address of account. - * @returns {Promise.} Stake of account. - */ -exports.getStake = async function getStake(address) { - const account = await getAccountFromLatestBlock(address); - - if (!account.certificates.length) { - return 0; - } - - return account.locked; -}; - -/** - * Get votes for account (delegate) by address. - * - * @param address Address of delegates. - * @returns {Promise.} All votes for delegate. - */ -exports.getVotesFor = async function getVotesFor(address) { - const latestBlock = await chainData.getLatest(); - - const votes = latestBlock.state.filter(account => { - return account.votes.findIndex(delegate => delegate == address) >= 0; - }); - - return votes; -}; - -/** - * Returns 31 active delegates. - * - * @returns {Promise.} - */ -exports.getActiveDelegates = async function getActiveDelegates() { - const delegates = await getDelegates(); - - return delegates.slice(0, constants.ACTIVE_DELEGATES_COUNT); -}; - -/** - * Returns 31 successor delegates. - * - * @returns {Promise.} - */ -exports.getSuccessorDelegates = async function getSuccessorDelegates() { - const delegates = await getDelegates(); - const delegatesListEnd = constants.ACTIVE_DELEGATES_COUNT + constants.SUCCESSOR_DELEGATES_COUNT; - - return delegates.slice(constants.ACTIVE_DELEGATES_COUNT, delegatesListEnd); -}; - -/** - * Returns delegates count. - * - * @returns {Promise.} - */ -exports.getDelegatesCount = async function getDelegatesCount() { - const rawDelegates = await getRawDelegates(); - - return Object.keys(rawDelegates).length; -}; - -/** - * Check if account is delegate. - * - * @param address Address of account. - * @returns {Promise.} True/false depends is delegate or not. - */ -exports.isDelegate = async function isDelegate(address) { - const delegates = await getRawDelegates(); - - return delegates.has(address); -}; - -/** - * Get certificates of account. - * - * @param address Address of account. - * @returns {Promise.} Array with certificates. - */ -exports.getCertificates = async function getCertificates(address) { - const account = await getAccountFromLatestBlock(address); - - return account.certificates; -}; - -/** - * Check if account is a block producer. - * - * @param {String} address Address of account to check (without 0x prefix). - * @returns {Promise.} - */ -exports.isBlockProducer = async function isBlockProducer(address) { - const latestBlock = await chainData.getLatest(); - const account = latestBlock.state.find((account) => (account.address === address)); - - return !!account && !!account.certificates.length; -}; - -/** - * Get all block producers. - * - * @returns {Promise.} List of accounts who has certificates as block producers. - */ -exports.getBlockProducers = async function getBlockProducers() { - const latestBlock = await chainData.getLatest(); - const blockProducers = latestBlock.state.filter(account => account.certificates.length); - - return blockProducers; -}; - /** * Handle standard transaction. * @@ -171,6 +41,7 @@ function handleStandardTransaction(tx, block) { if (value < 0) { throw 'Invalid value.'; } + // QUESTION what do we do when transaction has failed by any reason? if (sender.balance < value) { throw 'Sender doesn\'t have enough coin in his purse.'; } if (!receiver) { @@ -269,111 +140,3 @@ function isVote(data) { function isStake(data) { return data.slice(0, 2 + helpers.METHOD_SIGNATURE_LENGTH) === helpers.encodeTxData(constants.STAKE_METHOD_SIG); } - -/** - * Get block producer by certificate number. - * - * @param {Object} block - * @param {Number} finalRandomNumber - * @return {String} Address of block producer. - */ -function getBlockProducer(block, finalRandomNumber) { - let i = 0; - - for (const account of block.state) { - for (let j = 0; j < account.certificates.length; j++) { - if (i++ === finalRandomNumber) { - return account.address; - } - } - } -} - -/** - * Get delegates packed into object. - * - * @example - * { - * '0x00...': 1000, - * '0x01...': 2000 - * } - * - * @returns {Promise.} Object contains delegates and their votes. - */ -async function getRawDelegates() { - const latestBlock = await chainData.getLatest(); - - const delegates = latestBlock.state - .filter((record) => (!!record.votes.length)) // Leave only delegates first - .map((account) => [account.address, account]); - - - return new Map(delegates); - - - // .reduce((rawDelegates, account) => { - // for (let v of account.votes) { - // if (!rawDelegates[v.delegate]) { - // rawDelegates[v.delegate] = 0; - // } - // - // rawDelegates[v.delegate] += parseInt(v.amount); - // } - // - // return rawDelegates; - // }, {}); - // - // return rawDelegates; -} - -/** - * Get account by address from latest block. - * - * @param address Address of account to return. - * @returns {Promise.} Account. - */ -async function getAccountFromLatestBlock(address) { - const latestBlock = await chainData.getLatest(); - - return getAccount(address, latestBlock); -} - -/** - * Finds account by address in the provided block. - * - * @param address Address of account to find. - * @param block Block to find account. - * @returns {Promise.} Account. - */ -async function getAccount(address, block) { - const account = block.state.find(account => account.address == address); - - if (!account) { - throw `Account '0x${address.toString('hex')}' not found`; - } - - return account; -} - -/** - * Get all delegates sorted by their votes. - * - * TODO: Update this method to match Map in getRawDelegates() - * TODO: Redesign this and active/successor methods to match Map type as well - * - * @returns {Promise.|Object[]} Delegates array. - */ -async function getDelegates() { - let delegates = await getRawDelegates(); - - delegates = Object.keys(delegates).map(delegate => { - return { - delegate, - voteAmount: delegates[delegate] - }; - }); - - delegates.sort((a,b) => b.voteAmount - a.voteAmount); - - return delegates; -} diff --git a/lib/block-state.js b/lib/block-state.js new file mode 100644 index 0000000..707e44a --- /dev/null +++ b/lib/block-state.js @@ -0,0 +1,142 @@ +/** + * Important to note: + * + * - voting power: sum of balances of voters + * + * @module core/state-parser + */ + +'use strict'; + +const config = require('lib/constants'); +const utils = require('lib/helpers'); + +/** + * Local import of CERTIFICATE_PRICE variable + * @type {Number} + */ +const CERT_PRICE = config.CERTIFICATE_PRICE; + +/** + * This module can be either used as set of exported functions or as a wrapper for + * one block's state. + * + * @param {State} state Block state to wrap + * @return {Object} Wrapped state with same methods as others + */ +module.exports = exports = function parser(state) { + const wrapper = {}; + Object.keys(exports).forEach((key) => (wrapper[key] = exports[key].bind(null, state))); + return wrapper; +}; + +exports.getAccount = getAccount; +exports.getVotesMap = getVotesMap; + +/** + * Get balance of address from current state. 0 if acccount does not exist. + * + * @param {State} [state=[]] Block state to parse + * @param {Strgin} address Address to get balance of + * @return {NUmber} Balance of account or 0 + */ +exports.getBalance = function getBalance(state = [], address) { + return getAccount(state, address).balance; +}; + +/** + * Get array of delegates addresses (even those who didn't choose themselves). + * + * @param {State} [state=[]] Block state to parse + * @return {String[]} Array of addresses of delegates + */ +exports.getDelegates = function getDelegates(state = []) { + return Array.from(getVotesMap(state).keys()); +}; + +/** + * Get amount of voting power a delegate has. + * + * @param {State} [state=[]] Block state to parse + * @param {Strgin} address Address to get power of + * @return {Number} Amount of voting power + */ +exports.getVotesFor = function getVotesFor(state = [], address) { + return getVotesMap(state).get(address) || 0; +}; + +/** + * Check whether address is delegate (i.e. has any votes). + * + * @param {State} [state=[]] Block state to parse + * @param {String} address Address to check + * @return {Boolean} Whether someone voted + */ +exports.isDelegate = function isDelegate(state = [], address) { + return getVotesMap(state).has(address); +}; + +/** + * Get array of delegates addresses (even those who didn't choose themselves). + * + * @param {State} [state=[]] Block state to parse + * @return {String[]} Array of addresses of delegates + */ +exports.getBlockProducers = function getBlockProducers(state = []) { + return state + .filter((account) => account.locked > CERT_PRICE) + .map((account) => account.address); +}; + +/** + * Get number of certificates of account. + * + * @param {State} [state=[]] Block state to parse + * @param {String} address Address to check + * @return {Number} Number of certificates account has + */ +exports.getCertificatesCount = function getCertificatesCount(state = [], address) { + return Math.floor(getAccount(state, address).locked / CERT_PRICE); +}; + +/** + * Check whether address is delegate (i.e. has at least 1 certificate). + * + * @param {State} [state=[]] Block state to parse + * @param {String} address Address to check + * @return {Boolean} Whether account is block producer or not + */ +exports.isBlockProducer = function isBlockProducer(state = [], address) { + return getAccount(state, address).locked > CERT_PRICE; +}; + +/** + * Get account from blockchain state or create empty when there's no. + * + * @param {State} state Block state to parse + * @param {String} address Address to access account + * @return {Object} State record for account + */ +function getAccount(state, address) { + return Object.assign(utils.emptyAccount(), state.find((account) => (address === account.address))); +} + +/** + * Build Map: address -> sum of the balances of voters for this address. + * + * @param {State} state Block state to parse + * @return {Map} Map with each delegate's votes + */ +function getVotesMap(state) { + const delegates = new Map(); + + for (let account of state) { + account.votes.forEach((voteFor) => { + delegates.has(voteFor) + && delegates.set(voteFor, delegates.get(voteFor) + account.balance) + || delegates.set(voteFor, account.balance); + }); + } + + return delegates; +} diff --git a/lib/constants.js b/lib/constants.js index b06e99f..4b633df 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -19,11 +19,14 @@ exports.VOTE_METHOD_SIG = 'vote(address)'; exports.STAKE_METHOD_SIG = 'stake(uint256)'; /** - * Certificate price. + * Certificate price. In PoC we hardcode this value, whereas in Blockchain it will + * be dynamic and will be recalculated on every block. * + * ENV: CERTIFICATE_PRICE + * @default 10 * @type {Number} */ -exports.CERTIFICATE_PRICE = 100; +exports.CERTIFICATE_PRICE = process.env.CERTIFICATE_PRICE || 10; /** * Active delegates count. diff --git a/lib/genesis.js b/lib/genesis.js index 0c38d04..4f0153b 100644 --- a/lib/genesis.js +++ b/lib/genesis.js @@ -6,7 +6,6 @@ 'use strict'; -const rb = require('crypto').randomBytes; const fs = require('fs'); const helpers = require('lib/helpers'); @@ -55,9 +54,9 @@ Genesis.genesisToBlock = function genesisToBlock(genesis) { const account = helpers.emptyAccount(address); Object.assign(account, { - balance: allocatedAccount.balance || 0, - votes: allocatedAccount.votes || [], - certificates: allocatedAccount.certificates || [] + locked: allocatedAccount.locked || 0, + balance: allocatedAccount.balance || 0, + votes: allocatedAccount.votes || [] }); genesis.state.push(account); @@ -72,10 +71,12 @@ Genesis.genesisToBlock = function genesisToBlock(genesis) { * @param {String} address Account address. * @param {Number} nCertificates Number of certificates to generate. */ -Genesis.prototype.addProducer = function addProducer(address, nCertificates) { +Genesis.prototype.addProducer = function addProducer(address, locked) { return this.writeOrExtend(address, { [address]: { - certificates: Array.apply(null, {length: nCertificates}).map(() => '0x' + rb(32).toString('hex')), + locked, + balance: locked + // certificates: Array.apply(null, {length: nCertificates}).map(() => '0x' + rb(32).toString('hex')), // balance: 0 // TODO: find out whether this line is required } }); diff --git a/roles/block-producer.js b/roles/block-producer.js index 01037fc..744f358 100644 --- a/roles/block-producer.js +++ b/roles/block-producer.js @@ -110,15 +110,13 @@ async function isMyRound(frn) { const block = await chaindata.getLatest(); const orderedCertificates = []; - if (block.number === 0) { + if (block.number === 0 || true) { // TODO First round scenario. // Next line causes EventEmitter memory leak. // Object.assign(block, blockchain.initiateGenesisState(block, {state: []})); return true; } - console.log(block, typeof block); - // get all certificates from latest block block.state.forEach((account) => { if (activeProducers.includes(account.address)) { diff --git a/roles/delegate.js b/roles/delegate.js index e04ad41..190341b 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -16,6 +16,8 @@ const waiter = require('services/waiter'); const peer = require('core/file-peer'); const delegate = require('services/wallet'); +const conf = require('lib/constants'); + // NOTE Random number distribution. // // 1. When random number is generated @@ -38,21 +40,36 @@ const delegate = require('services/wallet'); */ const DELEGATES = '*'; +// /** +// * Total number of responses to await. +// * @type {Number} +// */ +// const DELEGATES_COUNT = conf.ACTIVE_DELEGATES_COUNT + conf.ACTIVE_SUCCESSOR_DELEGATES_COUNT - 1; + +/** + * Number of ms to wait to receive other delegates' randoms. + * @type {Number} + */ +const WAIT_TIME = conf.DELEGATE_WAIT_TIME || 3000; + +/** + * Attach event listeners to current transport. + */ exports.attach = function attach() { tp.on(events.START_ROUND, exchangeRandoms); tp.on(events.VERIFY_BLOCK, blockVerification); }; +/** + * Detach event listeners from current transport. + */ exports.detach = function detach() { tp.off(events.START_ROUND, exchangeRandoms); tp.off(events.VERIFY_BLOCK, blockVerification); }; - - - /** * Do generate final random number and broadcast it to network using following steps: * @@ -89,10 +106,10 @@ async function exchangeRandoms() { console.log('SENDING MY RANDOM', myRandomNum); - const responses = await waiter.waitForAll(events.RND_EVENT, numDelegates, Infinity); + const responses = await waiter.collect(events.RND_EVENT, WAIT_TIME); const responseMessages = responses.map((r) => r.data); - const verifiedMessages = responseMessages.filter(msg => Delegate.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); - const randomNumbers = verifiedMessages.map(msg => +msg.random); + const verifiedMessages = responseMessages.filter((msg) => Delegate.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); + const randomNumbers = verifiedMessages.map((msg) => +msg.random); const finalRandomNum = math.finalRandom(randomNumbers); @@ -117,6 +134,10 @@ async function exchangeRandoms() { console.log('ROUND UNSUCCESSFUL: ', mostCommon.count, parseInt(numDelegates / 2)); + // QUESTION: what should we do programmatically when round is unsucceful? + // I mean should we restart everything? Or only this function? Consensus to re-roll + // has to be reached somehow. Think about it + return waiter.wait(2000).then(exchangeRandoms); } @@ -242,8 +263,23 @@ function isValidBlockProducer(block, finalRandomNumber) { async function isValidBlock(producedBlock) { const parentBlock = await chaindata.getLatest(); const block = delegate.produceBlock(parentBlock, producedBlock.transactions); - - console.log(block, producedBlock); + const oneMore = delegate.produceBlock(parentBlock, producedBlock.transactions); + + console.log('STATE', producedBlock.stateRoot === block.stateRoot, producedBlock.stateRoot, block.stateRoot); + console.log('RECEIPTS', producedBlock.receiptsRoot === block.receiptsRoot, block.receiptsRoot, block.receiptsRoot); + + console.log('---------'); + console.log('---------'); + console.log('---------'); + console.log(producedBlock.state); + console.log('---------'); + console.log(block.state); + console.log('---------'); + console.log(oneMore.state); + console.log('---------'); + console.log('---------'); + console.log('---------'); + console.log(producedBlock); return producedBlock.stateRoot === block.stateRoot && producedBlock.receiptsRoot === block.receiptsRoot; diff --git a/runner.js b/runner.js index 152b0d6..bc9bbfa 100644 --- a/runner.js +++ b/runner.js @@ -19,7 +19,13 @@ const Account = require('core/account'); * * @type {Number} */ -const DELEGATE_BALANCE = 100; +const DELEGATE_BALANCE = 100000000; + +/** + * Initial staked amount for block producers. + * @type {Number} + */ +const STAKED_AMOUNT = 100; /** * Path to genesis file. @@ -36,14 +42,14 @@ const bank = Account(); const genesis = Genesis(); -genesis.addAccount(bank.address.toString('hex'), 1000000); +genesis.addAccount(bank.hexAddress, 1000000); for (let i = 0; i < num; i++) { const account = Account(); delegates.push(account); - genesis.addDelegate(account.address.toString('hex'), DELEGATE_BALANCE); - genesis.addProducer(account.address.toString('hex'), 1); + genesis.addDelegate(account.hexAddress, DELEGATE_BALANCE); + genesis.addProducer(account.hexAddress, STAKED_AMOUNT); } // for (let i = 0; i < num; i++) { diff --git a/services/wallet.js b/services/wallet.js index 90d8151..fcbea2e 100644 --- a/services/wallet.js +++ b/services/wallet.js @@ -9,8 +9,9 @@ 'use strict'; -const Account = require('core/account'); -const blockchain = require('core/blockchain'); +const Account = require('core/account'); +const chain = require('core/db').chain; +const state = require('lib/block-state'); /** * Secret key parsed from ENV when provided. @@ -28,7 +29,7 @@ const me = module.exports = exports = new Account(SECRET_KEY); * @return {Promise} Whether account is delegate */ exports.isDelegate = function () { - return blockchain.isDelegate(me.address.toString('hex')); + return chain.getLatest().then((block) => console.log(block.state) || state.isDelegate(block.state, me.hexAddress)); }; /** @@ -37,5 +38,5 @@ exports.isDelegate = function () { * @return {Promise} Whether account is block producer */ exports.isProducer = function () { - return blockchain.isBlockProducer(me.address.toString('hex')); + return chain.getLatest().then((block) => console.log(block.state) || state.isBlockProducer(block.state, me.hexAddress)); }; diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js index bbbfcc4..7632dc6 100644 --- a/test/unit/blockchain.js +++ b/test/unit/blockchain.js @@ -18,7 +18,7 @@ const genesis = require('genesis'); */ const SECRET_KEY = Buffer.from('557dce58018cf502a32b9b7723024805399350d006a4f71c3b9f489f7085cb50', 'hex'); -describe('Blockchain', () => { +xdescribe('Blockchain', () => { let account = {}; let delegate = {}; let delegates = []; From d3694ebe08b4586b9d2bfc679b42c9513da357ab Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Wed, 1 May 2019 19:16:37 +0300 Subject: [PATCH 16/25] Improved block-state, now state() returns instance - use block-parser(block.state) for easier access - started implementing state support and usage in delegates role --- lib/block-state.js | 52 ++++++++++++++++++++++++++++++++++------------ roles/delegate.js | 43 +++++++++++++++----------------------- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/lib/block-state.js b/lib/block-state.js index 707e44a..5a3823f 100644 --- a/lib/block-state.js +++ b/lib/block-state.js @@ -26,12 +26,24 @@ const CERT_PRICE = config.CERTIFICATE_PRICE; */ module.exports = exports = function parser(state) { const wrapper = {}; - Object.keys(exports).forEach((key) => (wrapper[key] = exports[key].bind(null, state))); + + Object.keys(exports).forEach((key) => { + + wrapper[key] = exports[key].bind(null, state); + + if (key.slice(0, 3) === 'get' && exports[key].length === 1) { + Object.defineProperty(wrapper, key.replace(/get([A-Z])/, (m, l) => l.toLowerCase()), { + get: exports[key].bind(null, state) + }); + } + }); + return wrapper; }; -exports.getAccount = getAccount; -exports.getVotesMap = getVotesMap; +exports.getAccount = getAccount; +exports.getVotesMap = getVotesMap; +exports.getProducersMap = getProducersMap; /** * Get balance of address from current state. 0 if acccount does not exist. @@ -40,7 +52,7 @@ exports.getVotesMap = getVotesMap; * @param {Strgin} address Address to get balance of * @return {NUmber} Balance of account or 0 */ -exports.getBalance = function getBalance(state = [], address) { +exports.getBalance = function getBalance(state, address) { return getAccount(state, address).balance; }; @@ -50,7 +62,7 @@ exports.getBalance = function getBalance(state = [], address) { * @param {State} [state=[]] Block state to parse * @return {String[]} Array of addresses of delegates */ -exports.getDelegates = function getDelegates(state = []) { +exports.getDelegates = function getDelegates(state) { return Array.from(getVotesMap(state).keys()); }; @@ -61,7 +73,7 @@ exports.getDelegates = function getDelegates(state = []) { * @param {Strgin} address Address to get power of * @return {Number} Amount of voting power */ -exports.getVotesFor = function getVotesFor(state = [], address) { +exports.getVotesFor = function getVotesFor(state, address) { return getVotesMap(state).get(address) || 0; }; @@ -72,7 +84,7 @@ exports.getVotesFor = function getVotesFor(state = [], address) { * @param {String} address Address to check * @return {Boolean} Whether someone voted */ -exports.isDelegate = function isDelegate(state = [], address) { +exports.isDelegate = function isDelegate(state, address) { return getVotesMap(state).has(address); }; @@ -82,10 +94,8 @@ exports.isDelegate = function isDelegate(state = [], address) { * @param {State} [state=[]] Block state to parse * @return {String[]} Array of addresses of delegates */ -exports.getBlockProducers = function getBlockProducers(state = []) { - return state - .filter((account) => account.locked > CERT_PRICE) - .map((account) => account.address); +exports.getBlockProducers = function getBlockProducers(state) { + return Array.from(getProducersMap(state).keys()); }; /** @@ -95,7 +105,7 @@ exports.getBlockProducers = function getBlockProducers(state = []) { * @param {String} address Address to check * @return {Number} Number of certificates account has */ -exports.getCertificatesCount = function getCertificatesCount(state = [], address) { +exports.getCertificatesCount = function getCertificatesCount(state, address) { return Math.floor(getAccount(state, address).locked / CERT_PRICE); }; @@ -106,7 +116,7 @@ exports.getCertificatesCount = function getCertificatesCount(state = [], address * @param {String} address Address to check * @return {Boolean} Whether account is block producer or not */ -exports.isBlockProducer = function isBlockProducer(state = [], address) { +exports.isBlockProducer = function isBlockProducer(state, address) { return getAccount(state, address).locked > CERT_PRICE; }; @@ -121,6 +131,22 @@ function getAccount(state, address) { return Object.assign(utils.emptyAccount(), state.find((account) => (address === account.address))); } +/** + * Build Map: address -> number of certificates address has from his locked funds. + * + * @param {State} state Block state to parse + * @return {Map} Map with certificates by address + */ +function getProducersMap(state) { + const producers = new Map(); + + for (let account of state) { + producers.set(account.balance, Math.floor(account.locked / CERT_PRICE)); + } + + return producers; +} + /** * Build Map: address -> sum of the balances of voters for this address. * diff --git a/roles/delegate.js b/roles/delegate.js index 190341b..363dbb8 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -11,10 +11,11 @@ const math = require('lib/math'); const events = require('lib/events'); const Delegate = require('core/account'); const tp = require('core/transport'); -const chaindata = require('core/db').chain; +const chain = require('core/db').chain; const waiter = require('services/waiter'); const peer = require('core/file-peer'); -const delegate = require('services/wallet'); +const parseState = require('lib/block-state'); +const me = require('services/wallet'); const conf = require('lib/constants'); @@ -91,15 +92,22 @@ exports.detach = function detach() { */ async function exchangeRandoms() { + const currentBlock = await chain.getLatest(); + const state = parseState(currentBlock.state); + + // Get list of delegates from state + // const delegates = state.delegates; + // Let's use this variable as if it existed const numDelegates = 1 || 33; const myRandomNum = math.random().toString(10); // sign message const messageWithRandom = { + me: me.hexAddress, random: myRandomNum, - publicKey: delegate.publicKey.toString('hex'), - signature: delegate.signMessage(myRandomNum).toString('hex') + publicKey: me.publicKey.toString('hex'), + signature: me.signMessage(myRandomNum).toString('hex') }; tp.send(events.RND_EVENT, messageWithRandom, DELEGATES); @@ -197,12 +205,12 @@ async function streamBlock(block) { const {port, promise} = peer.peerString(block, nodesCount); const hashedBlock = keccak256(JSON.stringify(block)).toString('hex'); - const signature = delegate.signMessage(hashedBlock).toString('hex'); + const signature = me.signMessage(hashedBlock).toString('hex'); tp.send(events.BLOCK_EVENT, { port, hashedBlock, - publicKey: delegate.publicKey.toString('hex'), + publicKey: me.publicKey.toString('hex'), signature }, DELEGATES); @@ -228,7 +236,7 @@ async function streamBlock(block) { random: block.randomNumber, producer: block.producer }, - publicKey: delegate.publicKey.toString('hex'), + publicKey: me.publicKey.toString('hex'), signature }); @@ -261,25 +269,8 @@ function isValidBlockProducer(block, finalRandomNumber) { * @return {Promise} Whether block is valid or not. */ async function isValidBlock(producedBlock) { - const parentBlock = await chaindata.getLatest(); - const block = delegate.produceBlock(parentBlock, producedBlock.transactions); - const oneMore = delegate.produceBlock(parentBlock, producedBlock.transactions); - - console.log('STATE', producedBlock.stateRoot === block.stateRoot, producedBlock.stateRoot, block.stateRoot); - console.log('RECEIPTS', producedBlock.receiptsRoot === block.receiptsRoot, block.receiptsRoot, block.receiptsRoot); - - console.log('---------'); - console.log('---------'); - console.log('---------'); - console.log(producedBlock.state); - console.log('---------'); - console.log(block.state); - console.log('---------'); - console.log(oneMore.state); - console.log('---------'); - console.log('---------'); - console.log('---------'); - console.log(producedBlock); + const parentBlock = await chain.getLatest(); + const block = me.produceBlock(parentBlock, producedBlock.transactions); return producedBlock.stateRoot === block.stateRoot && producedBlock.receiptsRoot === block.receiptsRoot; From f4a55e953fa0f62864bf64a512bdcde269449d17 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Wed, 1 May 2019 22:50:44 +0300 Subject: [PATCH 17/25] Added choosing block producer! Hooray! --- index.js | 3 --- lib/block-state.js | 2 +- roles/block-producer.js | 51 +++++++++++++---------------------------- roles/delegate.js | 13 +++++++---- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/index.js b/index.js index 22993e4..60ad1c0 100644 --- a/index.js +++ b/index.js @@ -27,9 +27,6 @@ const repl = require('repl'); })().then(async function runClient() { - console.log('PRODUCER?', await me.isProducer()); - console.log('DELEGATE?', await me.isDelegate()); - console.log('Starting observer'); require('services/observer'); diff --git a/lib/block-state.js b/lib/block-state.js index 5a3823f..88d8830 100644 --- a/lib/block-state.js +++ b/lib/block-state.js @@ -141,7 +141,7 @@ function getProducersMap(state) { const producers = new Map(); for (let account of state) { - producers.set(account.balance, Math.floor(account.locked / CERT_PRICE)); + producers.set(account.address, Math.floor(account.locked / CERT_PRICE)); } return producers; diff --git a/roles/block-producer.js b/roles/block-producer.js index 744f358..a4f367a 100644 --- a/roles/block-producer.js +++ b/roles/block-producer.js @@ -6,14 +6,15 @@ 'use strict'; -const math = require('lib/math'); -const events = require('lib/events'); -const pool = require('core/db').pool; -const tp = require('core/transport'); -const chaindata = require('core/db').chain; -const peer = require('core/file-peer'); -const waiter = require('services/waiter'); -const blockProducer = require('services/wallet'); +const math = require('lib/math'); +const events = require('lib/events'); +const pool = require('core/db').pool; +const tp = require('core/transport'); +const chaindata = require('core/db').chain; +const peer = require('core/file-peer'); +const waiter = require('services/waiter'); +const parseState = require('lib/block-state'); +const me = require('services/wallet'); exports.attach = function attach() { @@ -59,7 +60,7 @@ async function waitAndProduce() { // Drain a pool and create new block const parentBlock = await chaindata.getLatest(); const transactions = await pool.drain().catch(console.error); - const block = blockProducer.produceBlock(parentBlock, transactions); + const block = me.produceBlock(parentBlock, transactions); block.randomNumber = random; @@ -105,32 +106,12 @@ async function waitAndProduce() { * @return {Promise} Whether current account was chosen as a BP or not. */ async function isMyRound(frn) { - // TODO Get real array of active block producers. - const activeProducers = [blockProducer.address.toString('hex')]; - const block = await chaindata.getLatest(); - const orderedCertificates = []; - - if (block.number === 0 || true) { - // TODO First round scenario. - // Next line causes EventEmitter memory leak. - // Object.assign(block, blockchain.initiateGenesisState(block, {state: []})); - return true; - } - - // get all certificates from latest block - block.state.forEach((account) => { - if (activeProducers.includes(account.address)) { - orderedCertificates.push(...account.certificates); - } - }); - - const index = math.findCertificateIndex(frn, orderedCertificates.length); - const chosenCert = orderedCertificates[index]; - const chosenBp = block.state.find(el => el.certificates.includes(chosenCert)); + const block = await chaindata.getLatest(); + const state = parseState(block.state); + const producers = state.blockProducers; - console.log('PERC, FRN, TOTAL', index, frn, orderedCertificates.length); - console.log('CHOSEN', chosenBp); - console.log('MY IS', blockProducer.address.toString('hex')); + const index = math.findCertificateIndex(frn, producers.length); + const chosenBp = producers[index]; - return (chosenBp.address === blockProducer.address.toString('hex')); + return (chosenBp === me.hexAddress); } diff --git a/roles/delegate.js b/roles/delegate.js index 363dbb8..4518203 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -9,7 +9,7 @@ const keccak256 = require('keccak256'); const math = require('lib/math'); const events = require('lib/events'); -const Delegate = require('core/account'); +const Account = require('core/account'); const tp = require('core/transport'); const chain = require('core/db').chain; const waiter = require('services/waiter'); @@ -96,7 +96,7 @@ async function exchangeRandoms() { const state = parseState(currentBlock.state); // Get list of delegates from state - // const delegates = state.delegates; + const delegates = state.delegates; // Let's use this variable as if it existed const numDelegates = 1 || 33; @@ -104,7 +104,7 @@ async function exchangeRandoms() { // sign message const messageWithRandom = { - me: me.hexAddress, + sender: me.hexAddress, random: myRandomNum, publicKey: me.publicKey.toString('hex'), signature: me.signMessage(myRandomNum).toString('hex') @@ -116,7 +116,10 @@ async function exchangeRandoms() { const responses = await waiter.collect(events.RND_EVENT, WAIT_TIME); const responseMessages = responses.map((r) => r.data); - const verifiedMessages = responseMessages.filter((msg) => Delegate.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); + const verifiedMessages = responseMessages + .filter((msg) => delegates.includes(msg.sender)) + .filter((msg) => Account.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); + const randomNumbers = verifiedMessages.map((msg) => +msg.random); const finalRandomNum = math.finalRandom(randomNumbers); @@ -217,7 +220,7 @@ async function streamBlock(block) { const numDelegates = 1 || 33; const responses = await waiter.waitForAll(events.BLOCK_EVENT, numDelegates, Infinity); const responseMessages = responses.map((r) => r.data); - const verifiedMessages = responseMessages.filter(msg => Delegate.verifyMessage(msg.hashedBlock, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); + const verifiedMessages = responseMessages.filter(msg => Account.verifyMessage(msg.hashedBlock, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); const verifiedBlocks = verifiedMessages.map(msg => msg.hashedBlock); console.log('Verified blocks:', verifiedBlocks); From d1a0e5581a7c57e556fdd5ab300b102e7106b10b Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Thu, 2 May 2019 04:43:38 +0300 Subject: [PATCH 18/25] Added publicKeyToAddress function in Account - now delegates can know each other addresses from public keys --- core/account.js | 68 +++++++++++++++++++++++++---------------- roles/block-producer.js | 6 ++-- roles/delegate.js | 6 ++-- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/core/account.js b/core/account.js index 7144a76..f6f7ff0 100644 --- a/core/account.js +++ b/core/account.js @@ -50,6 +50,48 @@ function Account(secretKey) { }); } +/** + * Signing message by account private key. + * + * @param {String} message Text message to sign. + * @return {Buffer} Signature of message. + */ +Account.prototype.signMessage = function signMessage(message) { + const hash = keccak256(Buffer.from(message)); + + return secp256k1.sign(hash, this.secretKey).signature; +}; + +/** + * Verifying message signed by account. + * + * @param {String} message Message to verify. + * @param {Buffer} publicKey Public key of account that signed message. + * @param {Buffer} signature Signature of message. + * @return {Boolean} Result of signature verification. + */ +Account.verifyMessage = function verifyMessage(message, publicKey, signature) { + const hash = keccak256(Buffer.from(message)); + + return secp256k1.verify(hash, signature, Buffer.concat([SECP256K1_PREFIX, publicKey])); +}; + +/** + * Hashes publicKey with keccak256 and gets matching address. Adds 0x prefix! + * + * @param {Buffer|String} publicKey Public key as Buffer or 'hex' encoded String + * @param {String} [encoding=hex] Optional encoding of publicKey when pk is String + * @return {String} 0x-prefixed address + */ +Account.publicKeyToAddress = function publicKeyToAddress(publicKey, encoding) { + + if (publicKey.constructor !== Buffer) { + publicKey = Buffer.from(publicKey, encoding || 'hex'); + } + + return '0x' + keccak256(publicKey).slice(12).toString('hex'); +}; + /** * Creates new serialized signed transaction. * @@ -96,32 +138,6 @@ Account.prototype.getHexAddress = function getHexAddress() { return '0x' + this.address.toString('hex'); }; -/** - * Signing message by account private key. - * - * @param {String} message Text message to sign. - * @return {Buffer} Signature of message. - */ -Account.prototype.signMessage = function signMessage(message) { - const hash = keccak256(Buffer.from(message)); - - return secp256k1.sign(hash, this.secretKey).signature; -}; - -/** - * Verifying message signed by account. - * - * @param {String} message Message to verify. - * @param {Buffer} publicKey Public key of account that signed message. - * @param {Buffer} signature Signature of message. - * @return {Boolean} Result of signature verification. - */ -Account.verifyMessage = function verifyMessage(message, publicKey, signature) { - const hash = keccak256(Buffer.from(message)); - - return secp256k1.verify(hash, signature, Buffer.concat([SECP256K1_PREFIX, publicKey])); -}; - /** * Vote for delegate. * diff --git a/roles/block-producer.js b/roles/block-producer.js index a4f367a..20d19b7 100644 --- a/roles/block-producer.js +++ b/roles/block-producer.js @@ -62,11 +62,11 @@ async function waitAndProduce() { const transactions = await pool.drain().catch(console.error); const block = me.produceBlock(parentBlock, transactions); - block.randomNumber = random; + block.randomNumber = random.data; // Share block with delegates // TODO: think of verification: do it in UDP short block or HTTP or both - const {port} = peer.peerString(block, ); + const {port} = peer.peerString(block, tp.knownNodes, 5000); // Send event so delegates know where to get block tp.send(events.VERIFY_BLOCK, { @@ -103,7 +103,7 @@ async function waitAndProduce() { * happen frequently. * * @param {Number} frn Final Random Number. - * @return {Promise} Whether current account was chosen as a BP or not. + * @return {Promise} Whether current account is a BP in current block or not. */ async function isMyRound(frn) { const block = await chaindata.getLatest(); diff --git a/roles/delegate.js b/roles/delegate.js index 4518203..017177f 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -104,7 +104,6 @@ async function exchangeRandoms() { // sign message const messageWithRandom = { - sender: me.hexAddress, random: myRandomNum, publicKey: me.publicKey.toString('hex'), signature: me.signMessage(myRandomNum).toString('hex') @@ -116,8 +115,11 @@ async function exchangeRandoms() { const responses = await waiter.collect(events.RND_EVENT, WAIT_TIME); const responseMessages = responses.map((r) => r.data); + + responseMessages.forEach((msg) => console.log(Account.publicKeyToAddress(msg.publicKey))); + const verifiedMessages = responseMessages - .filter((msg) => delegates.includes(msg.sender)) + .filter((msg) => delegates.includes(Account.publicKeyToAddress(msg.publicKey))) .filter((msg) => Account.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); const randomNumbers = verifiedMessages.map((msg) => +msg.random); From f5c85d77f9c38d02d2a14bba4f1bdd7ae87aec07 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 6 May 2019 17:35:58 +0300 Subject: [PATCH 19/25] Added sync mechanics, fixed numDelegates issue - finally consensus with N delegates works as expected - fixed sync bug so new nodes could not connect - role assignment works from start and as expected - observer now has function .observe() - runner prints log directory for each account address - fixed N delegates bug which led to only 1 response awaiting --- bp.js => _bp.js | 0 core/db.js | 3 ++- index.js | 43 +++++++++++++++++++------------- lib/block-state.js | 4 +-- repl.js | 55 ----------------------------------------- roles/block-producer.js | 10 ++++++-- roles/delegate.js | 18 ++++++++------ runner.js | 2 +- services/observer.js | 17 ++----------- services/sync.js | 10 ++++++++ services/wallet.js | 4 +-- 11 files changed, 64 insertions(+), 102 deletions(-) rename bp.js => _bp.js (100%) delete mode 100644 repl.js diff --git a/bp.js b/_bp.js similarity index 100% rename from bp.js rename to _bp.js diff --git a/core/db.js b/core/db.js index e0343fe..7f7aa61 100644 --- a/core/db.js +++ b/core/db.js @@ -172,12 +172,13 @@ class Chain extends DB { * @return {stream.Writable} Writable stream */ createWriteStream() { + const db = this.db; const writeStream = new stream.Writable({ write(chunk, encoding, cb) { const string = chunk.toString(); const number = JSON.parse(string).number; - return this.db.put(number, string).then(() => cb(null, string)); + return db.put(number, string).then(() => cb(null, string)); } }); diff --git a/index.js b/index.js index 60ad1c0..84a041d 100644 --- a/index.js +++ b/index.js @@ -8,32 +8,41 @@ process.stdin.resume(); -const tp = require('core/transport'); -const sync = require('services/sync'); -const evt = require('lib/events'); -const me = require('services/wallet'); -const repl = require('repl'); +const tp = require('core/transport'); +const sync = require('services/sync'); +const evt = require('lib/events'); +const me = require('services/wallet'); +const chain = require('core/db').chain; +const repl = require('repl'); +const observer = require('services/observer'); (async function initServices() { - // More than one node in network - if (tp.knownNodes.size > 1) { - - await Promise.all([ - // sync.chain(), - sync.pool() - ]).catch(console.error); - } + // First of all sync data from existing node + await Promise.all([ + sync.chain(), + sync.pool() + ]).catch(console.error); })().then(async function runClient() { - console.log('Starting observer'); + const lastBlock = await chain.getLatest(); + + console.log('Last block is %d', lastBlock.number); + console.log('My address is %s', me.hexAddress); - require('services/observer'); + observer.observe(); // Attach both roles by default - require('roles/block-producer').attach(); - require('roles/delegate').attach(); + + const isDelegate = await me.isDelegate(); + const isProducer = await me.isProducer(); + + console.log('Delegate: %s', isDelegate); + console.log('Producer: %s', isProducer); + + isDelegate && require('roles/delegate').attach(); + isProducer && require('roles/block-producer').attach(); console.log('Starting prompt...'); diff --git a/lib/block-state.js b/lib/block-state.js index 88d8830..bdbf924 100644 --- a/lib/block-state.js +++ b/lib/block-state.js @@ -49,8 +49,8 @@ exports.getProducersMap = getProducersMap; * Get balance of address from current state. 0 if acccount does not exist. * * @param {State} [state=[]] Block state to parse - * @param {Strgin} address Address to get balance of - * @return {NUmber} Balance of account or 0 + * @param {String} address Address to get balance of + * @return {Number} Balance of account or 0 */ exports.getBalance = function getBalance(state, address) { return getAccount(state, address).balance; diff --git a/repl.js b/repl.js deleted file mode 100644 index 338f898..0000000 --- a/repl.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @module repl - */ - -'use strict'; - -// Define REPL as is -const repl = require('repl'); -const tty = repl.start('> '); -const ctx = tty.context; - -// Attach transport system to it -(function attachTransport(tty) { - - const transport = ctx.transport = require('core/transport'); - - tty.defineCommand('info', { - action() { - this.clearBufferedCommand(); - console.log('Transport ID is: %s', transport.transportId); - console.log('Your groups are: %s', transport.groups.concat(['*'])); - console.log('Known nodes are: %s', JSON.stringify([...transport.knownNodes.keys()], null, 2)); - this.displayPrompt(); - } - }); - - tty.defineCommand('join', { - action(group) { - this.clearBufferedCommand(); - transport.joinGroup(group); - console.log('You joined group %s', group); - console.log('Your groups are: %s', transport.groups.concat(['*'])); - this.displayPrompt(); - } - }); - - tty.defineCommand('send', { - action(message) { - this.clearBufferedCommand(); - transport.send(message); - console.log('You\'ve sent the message: %s', message); - this.displayPrompt(); - } - }); - - transport.on('message', function (message, meta) { - tty.clearBufferedCommand(); - console.log('New message!'); - console.log(JSON.stringify(message, null, 4)); - console.log(JSON.stringify(meta, null, 4)); - tty.displayPrompt(); - }); - - -})(tty); diff --git a/roles/block-producer.js b/roles/block-producer.js index 20d19b7..c56ea25 100644 --- a/roles/block-producer.js +++ b/roles/block-producer.js @@ -57,20 +57,24 @@ async function waitAndProduce() { return; } + console.log('I AM PRODUCER %s %s', random.data, me.hexAddress); + // Drain a pool and create new block const parentBlock = await chaindata.getLatest(); const transactions = await pool.drain().catch(console.error); const block = me.produceBlock(parentBlock, transactions); + // Assign random number to newly produced block block.randomNumber = random.data; // Share block with delegates // TODO: think of verification: do it in UDP short block or HTTP or both - const {port} = peer.peerString(block, tp.knownNodes, 5000); + const {port, promise} = peer.peerString(block, tp.knownNodes, 5000); // Send event so delegates know where to get block tp.send(events.VERIFY_BLOCK, { - port, block: { + port, + block: { number: block.number, hash: block.hash, parentHash: block.parentHash, @@ -78,6 +82,8 @@ async function waitAndProduce() { producer: block.producer } }); + + return promise; } /** diff --git a/roles/delegate.js b/roles/delegate.js index 017177f..d19dcaa 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -118,29 +118,33 @@ async function exchangeRandoms() { responseMessages.forEach((msg) => console.log(Account.publicKeyToAddress(msg.publicKey))); - const verifiedMessages = responseMessages + const randomNumbers = responseMessages .filter((msg) => delegates.includes(Account.publicKeyToAddress(msg.publicKey))) - .filter((msg) => Account.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); + .filter((msg) => Account.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))) + .map((msg) => +msg.random); - const randomNumbers = verifiedMessages.map((msg) => +msg.random); + // QUESTION: What should we do when number of ACTIVE_DELEGATES has not been reached? + // QUESTION: How do we sort delegates and fill missing from successors? + // QUESTION: How to test setups when there's only runner X count? We'd have to run some test-ready setting. + // if (randomNumbers.length < conf.ACTIVE_DELEGATES_COUNT) { } - const finalRandomNum = math.finalRandom(randomNumbers); + // Do the math + const finalRandomNum = math.finalRandom(randomNumbers); console.log('RANDOMS: ', randomNumbers); console.log('MY FINAL RANDOM IS: ', finalRandomNum); tp.send(events.FRND_EVENT, finalRandomNum, DELEGATES); - const finalResponses = await waiter.waitForAll(events.FRND_EVENT, numDelegates, Infinity); + const finalResponses = await waiter.collect(events.FRND_EVENT, 5000); const resolution = math.votingResults(finalResponses.map((r) => r.data)); // Most frequent final random number from delegates const mostCommon = resolution[0]; - console.log('FINAL RANDOMS: ', resolution); console.log('MOST COMMON IS: ', mostCommon); - if (mostCommon.count > (numDelegates / 2)) { + if (mostCommon.count > Math.floor(resolution.length / 2)) { console.log('ROUND SUCCESSFUL, SENDING VALUE TO BP: %s', mostCommon.value); return tp.send(events.BP_CATCH_IT, mostCommon.value, '*'); } diff --git a/runner.js b/runner.js index bc9bbfa..3f04c84 100644 --- a/runner.js +++ b/runner.js @@ -78,7 +78,7 @@ tp.on(evt.PONG, (data) => console.log('Yup, dude, %s', data)); tp.delegates = new Map(); tp.on(evt.I_AM_HERE, (data, msg) => tp.delegates.set(msg.sender, msg)); -console.log('ACCOUNTS:') || delegates.map((e) => console.log('-', e.hexAddress)); +console.log('ACCOUNTS:') || delegates.map((e, i) => console.log('del_' + (i + 1), '-', e.hexAddress)); tp.on(evt.START_ROUND, function () { tp.once(evt.NEW_BLOCK, function ({block}) { diff --git a/services/observer.js b/services/observer.js index 37ac4cd..0809392 100644 --- a/services/observer.js +++ b/services/observer.js @@ -9,8 +9,9 @@ const events = require('lib/events'); const peer = require('core/file-peer'); const chaindata = require('core/db').chain; const wallet = require('services/wallet'); +const tp = require('core/transport'); -require('core/transport') +exports.observe = () => tp /** * We don't want one to receive 33 new blocks from delegates so for now we stick @@ -55,20 +56,6 @@ require('core/transport') } }) - .on(events.CREATE_POOL_SERVER, function createServer(data, msg) { - if (msg.sender !== this.transportId) { - const {port} = peer.peer(pool.createReadStream()); - this.send(events.POOL_SERVER_CREATED, port, msg.sender); - } - }) - - .on(events.CREATE_CHAINDATA_SERVER, function createServer(data, msg) { - if (msg.sender !== this.transportId) { - const {port} = peer.peer(chaindata.createReadStream()); - this.send(events.CHAINDATA_SERVER_CREATED, port, msg.sender); - } - }) - .on(events.PING, function areYouLookingForMe(data, msg) { // QUESTION: okay, we have PING event, what's next? How should it be used? diff --git a/services/sync.js b/services/sync.js index 797ebd2..78b7eb6 100644 --- a/services/sync.js +++ b/services/sync.js @@ -46,6 +46,11 @@ exports.pool = async function syncPool() { tp.send(events.CREATE_POOL_SERVER, null, myNode.msg.sender); const peerData = await waiter.waitFor(events.POOL_SERVER_CREATED, WAIT_FOR); + + if (peerData === null) { + return; + } + const peerPort = peerData.data; // Clean up before syncing @@ -77,6 +82,11 @@ exports.chain = async function syncChain() { tp.send(events.CREATE_CHAINDATA_SERVER, null, oneAndOnly.msg.sender); const peerData = await waiter.waitFor(events.CHAINDATA_SERVER_CREATED, WAIT_FOR); + + if (peerData === null) { + return; + } + const peerPort = peerData.data; // Clean up before syncing diff --git a/services/wallet.js b/services/wallet.js index fcbea2e..1b9a087 100644 --- a/services/wallet.js +++ b/services/wallet.js @@ -29,7 +29,7 @@ const me = module.exports = exports = new Account(SECRET_KEY); * @return {Promise} Whether account is delegate */ exports.isDelegate = function () { - return chain.getLatest().then((block) => console.log(block.state) || state.isDelegate(block.state, me.hexAddress)); + return chain.getLatest().then((block) => state.isDelegate(block.state, me.hexAddress)); }; /** @@ -38,5 +38,5 @@ exports.isDelegate = function () { * @return {Promise} Whether account is block producer */ exports.isProducer = function () { - return chain.getLatest().then((block) => console.log(block.state) || state.isBlockProducer(block.state, me.hexAddress)); + return chain.getLatest().then((block) => state.isBlockProducer(block.state, me.hexAddress)); }; From cdd39a2d2ca6a6e86e459964c4f4ed698ed82a97 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 6 May 2019 18:20:51 +0300 Subject: [PATCH 20/25] Added internal NEW_BLOCK_RECEIVED event - new event NEW_BLOCK_RECEIVED emitted when block is downloaded - reduced delegate wait time to 1 second instead of 5 - now role is defined on every block, not only first one --- index.js | 38 +++++++++++++++++++++----------------- lib/events.js | 8 ++++---- roles/delegate.js | 2 +- services/observer.js | 2 ++ services/wallet.js | 18 ++++++++++++------ 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index 84a041d..4ceea23 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,11 @@ const chain = require('core/db').chain; const repl = require('repl'); const observer = require('services/observer'); +const roles = { + delegate: require('roles/delegate'), + producer: require('roles/block-producer') +}; + (async function initServices() { // First of all sync data from existing node @@ -33,16 +38,9 @@ const observer = require('services/observer'); observer.observe(); - // Attach both roles by default - - const isDelegate = await me.isDelegate(); - const isProducer = await me.isProducer(); - - console.log('Delegate: %s', isDelegate); - console.log('Producer: %s', isProducer); + await defineRoles(); - isDelegate && require('roles/delegate').attach(); - isProducer && require('roles/block-producer').attach(); + tp.on(evt.NEW_BLOCK_RECEIVED, defineRoles); console.log('Starting prompt...'); @@ -51,11 +49,17 @@ const observer = require('services/observer'); Object.assign(tty.context, {tp, evt, me}); }); -// QUESTION -// ON START ROUND EVENT -// AM I DELEGATE? -// ATTACH LISTENERS FOR DELEGATE FOR 1 ROUND -// AM I PRODUCER? -// ATTACH LISTENERS FOR PRODUCER FOR 1 ROUND -// -// ON ROUND END REMOVE ALL LISTENERS AND LIVE FREE +async function defineRoles(block) { + + roles.delegate.detach(); + roles.producer.detach(); + + const isDelegate = await me.isDelegate(block); + const isProducer = await me.isProducer(block); + + console.log('Delegate: %s', isDelegate); + console.log('Producer: %s', isProducer); + + isDelegate && roles.delegate.attach(); + isProducer && roles.producer.attach(); +} diff --git a/lib/events.js b/lib/events.js index 6249343..06bfe34 100644 --- a/lib/events.js +++ b/lib/events.js @@ -8,9 +8,10 @@ /* Blockchain layer events */ -exports.NEW_TRANSACTION = 'NewTransaction'; -exports.NEW_BLOCK = 'NewBlock'; -exports.RANDOM_NUMBER = 'RandomNumber'; +exports.NEW_TRANSACTION = 'NewTransaction'; +exports.NEW_BLOCK = 'NewBlock'; +exports.NEW_BLOCK_RECEIVED = 'NewBlockReceived'; // When new block is loaded and saved +exports.RANDOM_NUMBER = 'RandomNumber'; exports.BLOCK_EVENT = 'BlockEvent'; exports.RND_EVENT = 'RandomNumberBrotha'; @@ -20,7 +21,6 @@ exports.HELLO_DUDE = 'HelloThereDude!'; exports.START_ROUND = 'StartRoundGuys'; exports.BP_CATCH_IT = 'GoCatchItBoy'; - /* Infrastructure layer events */ exports.NODE_ONLINE = 'NodeOnline'; diff --git a/roles/delegate.js b/roles/delegate.js index d19dcaa..b232cd6 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -136,7 +136,7 @@ async function exchangeRandoms() { tp.send(events.FRND_EVENT, finalRandomNum, DELEGATES); - const finalResponses = await waiter.collect(events.FRND_EVENT, 5000); + const finalResponses = await waiter.collect(events.FRND_EVENT, 1000); const resolution = math.votingResults(finalResponses.map((r) => r.data)); // Most frequent final random number from delegates diff --git a/services/observer.js b/services/observer.js index 0809392..443690d 100644 --- a/services/observer.js +++ b/services/observer.js @@ -31,6 +31,8 @@ exports.observe = () => tp await chaindata.add(block.number, newBlock); + tp.emit(events.NEW_BLOCK_RECEIVED, JSON.parse(newBlock)); + // QUESTION: should definition of role happen here? Or we could use same // event in another module/service to attach/detach listeners? }); diff --git a/services/wallet.js b/services/wallet.js index 1b9a087..cb56b4a 100644 --- a/services/wallet.js +++ b/services/wallet.js @@ -26,17 +26,23 @@ const me = module.exports = exports = new Account(SECRET_KEY); /** * Check whether me (process account) is delegate * - * @return {Promise} Whether account is delegate + * @param {Object} [block=null] Optional: block to get info from + * @return {Promise} Whether account is delegate */ -exports.isDelegate = function () { - return chain.getLatest().then((block) => state.isDelegate(block.state, me.hexAddress)); +exports.isDelegate = function (block = null) { + return (block === null) + && chain.getLatest().then((block) => state.isDelegate(block.state, me.hexAddress)) + || Promise.resolve(state.isDelegate(block.state, me.hexAddress)); }; /** * Check whether me (process account) is delegate * - * @return {Promise} Whether account is block producer + * @param {Object} [block=null] Optional: block to get info from + * @return {Promise} Whether account is block producer */ -exports.isProducer = function () { - return chain.getLatest().then((block) => state.isBlockProducer(block.state, me.hexAddress)); +exports.isProducer = function (block = null) { + return (block === null) + && chain.getLatest().then((block) => state.isBlockProducer(block.state, me.hexAddress)) + || Promise.resolve(state.isBlockProducer(block.state, me.hexAddress)); }; From 314c2a881bb06aba016a3096eeed30e5e4caf6fd Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Mon, 6 May 2019 18:38:46 +0300 Subject: [PATCH 21/25] Optimized block-producer code, chain extraction - now bp and delegate pull block only once - started refactoring of delegate block - block-producer code is more opaque and simple --- lib/math.js | 10 ++++----- roles/block-producer.js | 45 ++++++----------------------------------- roles/delegate.js | 30 +++++++++++++-------------- 3 files changed, 26 insertions(+), 59 deletions(-) diff --git a/lib/math.js b/lib/math.js index 3851e67..5530ec7 100644 --- a/lib/math.js +++ b/lib/math.js @@ -53,10 +53,10 @@ exports.votingResults = function calculateResults(arr) { * To find certificate index we calculate FRN percent from MAX_RANDOM value * and multiply it by total number of issued certificates (which may be changed on each block). * - * @param {Number} frn Final random to use - * @param {Number} total Number of issued certificates - * @return {Number} Index of a matching certificate in ordered Array + * @param {Number} frn Final random to use + * @param {String[]} producers Array of producers' addresses to pick + * @return {String} Address of next block producer */ -exports.findCertificateIndex = function findCertificateIndex(frn, total) { - return Math.floor(frn / MAX_RANDOM * total); +exports.findProducer = function findProducer(frn, producers = []) { + return producers[Math.floor(frn / MAX_RANDOM * producers.length)]; }; diff --git a/roles/block-producer.js b/roles/block-producer.js index c56ea25..ed7fa4f 100644 --- a/roles/block-producer.js +++ b/roles/block-producer.js @@ -37,6 +37,10 @@ exports.detach = function detach() { */ async function waitAndProduce() { + // Get current block first + const currentBlock = await chaindata.getLatest(); + const state = parseState(currentBlock.state); + // This event can take forever. // TODO: think of time limitation for this part. // In async architecture it's also possible that BP will catch same events from different @@ -50,7 +54,7 @@ async function waitAndProduce() { // We may want every bp to produce block every round. // TODO: remember about backup block producer, as he have to produce as well in order to get block reward. // FIXME Supply real FRN. - const isProducer = await isMyRound(random.data); + const isProducer = me.hexAddress === math.findProducer(random.data, state.blockProducers); if (!isProducer) { console.log('I AM NO PRODUCER'); @@ -60,9 +64,8 @@ async function waitAndProduce() { console.log('I AM PRODUCER %s %s', random.data, me.hexAddress); // Drain a pool and create new block - const parentBlock = await chaindata.getLatest(); const transactions = await pool.drain().catch(console.error); - const block = me.produceBlock(parentBlock, transactions); + const block = me.produceBlock(currentBlock, transactions); // Assign random number to newly produced block block.randomNumber = random.data; @@ -85,39 +88,3 @@ async function waitAndProduce() { return promise; } - -/** - * Get current state. - * - * QUESTION Which scenario to choose? - * - * Scenario 1. - * We take FRN and take its percent from the total number of certificates - * and select one certificate at the same percentage from an array of certificates - * of online BPs. - * - * Cons: - * If list of BP changes dynamically this algorithm cannot be considered stable. - * - * Scenario 2. - * FRN is being generated in the range of certificates from the - * previous block. FRN generation occurs until selected - * certificate corresponds to one of online BPs. - * - * Cons: - * Many iterations. This works good when network is active or iterations - * happen frequently. - * - * @param {Number} frn Final Random Number. - * @return {Promise} Whether current account is a BP in current block or not. - */ -async function isMyRound(frn) { - const block = await chaindata.getLatest(); - const state = parseState(block.state); - const producers = state.blockProducers; - - const index = math.findCertificateIndex(frn, producers.length); - const chosenBp = producers[index]; - - return (chosenBp === me.hexAddress); -} diff --git a/roles/delegate.js b/roles/delegate.js index b232cd6..3a73fc1 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -92,6 +92,7 @@ exports.detach = function detach() { */ async function exchangeRandoms() { + // Get current block first const currentBlock = await chain.getLatest(); const state = parseState(currentBlock.state); @@ -131,9 +132,6 @@ async function exchangeRandoms() { // Do the math const finalRandomNum = math.finalRandom(randomNumbers); - console.log('RANDOMS: ', randomNumbers); - console.log('MY FINAL RANDOM IS: ', finalRandomNum); - tp.send(events.FRND_EVENT, finalRandomNum, DELEGATES); const finalResponses = await waiter.collect(events.FRND_EVENT, 1000); @@ -143,19 +141,21 @@ async function exchangeRandoms() { const mostCommon = resolution[0]; console.log('MOST COMMON IS: ', mostCommon); + console.log('OTHER RESULTS: ', resolution); - if (mostCommon.count > Math.floor(resolution.length / 2)) { - console.log('ROUND SUCCESSFUL, SENDING VALUE TO BP: %s', mostCommon.value); - return tp.send(events.BP_CATCH_IT, mostCommon.value, '*'); - } + if (mostCommon.count <= Math.floor(finalResponses.length / 2)) { - console.log('ROUND UNSUCCESSFUL: ', mostCommon.count, parseInt(numDelegates / 2)); + // QUESTION: what should we do programmatically when round is unsucceful? + // I mean should we restart everything? Or only this function? Consensus to re-roll + // has to be reached somehow. Think about it + + console.log('ROUND UNSUCCESSFUL: ', mostCommon.count, parseInt(numDelegates / 2)); + return waiter.wait(2000).then(exchangeRandoms); + } - // QUESTION: what should we do programmatically when round is unsucceful? - // I mean should we restart everything? Or only this function? Consensus to re-roll - // has to be reached somehow. Think about it + console.log('ROUND SUCCESSFUL, SENDING VALUE TO BP: %s', mostCommon.value); - return waiter.wait(2000).then(exchangeRandoms); + return tp.send(events.BP_CATCH_IT, mostCommon.value, '*'); } /** @@ -185,9 +185,7 @@ async function blockVerification({port, block: short}, msg, meta) { return streamBlock(block); } - console.log(!!block, await isValidBlock(block), isValidBlockProducer(block)); - - console.log('block is invalid'); + console.log('block is invalid', !!block, await isValidBlock(block), isValidBlockProducer(block)); // TODO Case when block is invalid. return null; @@ -265,6 +263,8 @@ async function streamBlock(block) { function isValidBlockProducer(block, finalRandomNumber) { // return (block.producer === blockchain.getBlockProducer(block, finalRandomNumber)); + + finalRandomNumber; block; From 5e2196784a997e16a6022469294b386873461c7b Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Tue, 7 May 2019 03:34:12 +0300 Subject: [PATCH 22/25] Fixed waiter.waitForCond - now works as expected --- services/waiter.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/waiter.js b/services/waiter.js index ea6c9d1..210edd4 100644 --- a/services/waiter.js +++ b/services/waiter.js @@ -50,14 +50,15 @@ async function collect(evt, wait = 1000) { async function waitForCond(evt, cb, wait = 1000) { return new Promise(async function (resolve) { - function listener(data, msg, meta) { - if (cb(data, msg, meta)) { + msg.on(evt, listener); + setTimeout(() => msg.off(evt, listener) && resolve(null), wait); + + function listener(data, message, meta) { + if (cb(data, message, meta)) { msg.off(evt, listener); - resolve(data, msg, meta); + resolve({data, msg: message, meta}); } } - - setTimeout(() => msg.off(evt, listener) && resolve(null), wait); }); } From 602e188c5f20b6b34b6baabf170fe01fd7302cd3 Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Tue, 7 May 2019 03:52:44 +0300 Subject: [PATCH 23/25] Shortened delegate role, improved block producer --- lib/events.js | 2 +- lib/math.js | 4 +- roles/block-producer.js | 17 ++-- roles/delegate.js | 202 ++++++++++++++++------------------------ 4 files changed, 94 insertions(+), 131 deletions(-) diff --git a/lib/events.js b/lib/events.js index 06bfe34..fb4ac38 100644 --- a/lib/events.js +++ b/lib/events.js @@ -10,10 +10,10 @@ exports.NEW_TRANSACTION = 'NewTransaction'; exports.NEW_BLOCK = 'NewBlock'; +exports.BLOCK_SIG = 'SignHereAndHerePlease'; exports.NEW_BLOCK_RECEIVED = 'NewBlockReceived'; // When new block is loaded and saved exports.RANDOM_NUMBER = 'RandomNumber'; -exports.BLOCK_EVENT = 'BlockEvent'; exports.RND_EVENT = 'RandomNumberBrotha'; exports.FRND_EVENT = 'HereGoesFinalFinally'; exports.I_AM_HERE = 'IamDelegate'; diff --git a/lib/math.js b/lib/math.js index 5530ec7..0605d51 100644 --- a/lib/math.js +++ b/lib/math.js @@ -16,7 +16,7 @@ const MAX_RANDOM = exports.MAX_RANDOM = 1000; * @return {Number} Random number in range 0-1000 */ exports.random = function random() { - return parseInt(Math.random() * MAX_RANDOM); + return parseInt(Math.random() * MAX_RANDOM).toString(10); }; /** @@ -55,7 +55,7 @@ exports.votingResults = function calculateResults(arr) { * * @param {Number} frn Final random to use * @param {String[]} producers Array of producers' addresses to pick - * @return {String} Address of next block producer + * @return {String} Address of next block producer */ exports.findProducer = function findProducer(frn, producers = []) { return producers[Math.floor(frn / MAX_RANDOM * producers.length)]; diff --git a/roles/block-producer.js b/roles/block-producer.js index ed7fa4f..b3f15a1 100644 --- a/roles/block-producer.js +++ b/roles/block-producer.js @@ -16,9 +16,12 @@ const waiter = require('services/waiter'); const parseState = require('lib/block-state'); const me = require('services/wallet'); +// How long to wait for FRN from delegates +const FRN_WAIT_TIME = 5000; + exports.attach = function attach() { - tp.on(events.START_ROUND, waitAndProduce); + tp.once(events.START_ROUND, waitAndProduce); }; exports.detach = function detach() { @@ -48,24 +51,25 @@ async function waitAndProduce() { // // QUESTION: Should we do a check somewhere for round definition or smth. Number of retries mb? // We want to let BP know whether round has been restarted so he can drop this listener - const random = await waiter.waitFor(events.BP_CATCH_IT, Infinity); + const random = await waiter.waitFor(events.BP_CATCH_IT, FRN_WAIT_TIME); // On each round every block producer checks whether he should produce this block. // We may want every bp to produce block every round. // TODO: remember about backup block producer, as he have to produce as well in order to get block reward. // FIXME Supply real FRN. - const isProducer = me.hexAddress === math.findProducer(random.data, state.blockProducers); + const nextProducer = math.findProducer(random.data, state.blockProducers); + const isProducer = (me.hexAddress === nextProducer); if (!isProducer) { - console.log('I AM NO PRODUCER'); + console.log('Next producer is: %s and I am %s', nextProducer, me.hexAddress); return; } - console.log('I AM PRODUCER %s %s', random.data, me.hexAddress); + console.log('I am producer %s %s', random.data, me.hexAddress); // Drain a pool and create new block const transactions = await pool.drain().catch(console.error); - const block = me.produceBlock(currentBlock, transactions); + const block = me.produceBlock(currentBlock, transactions || []); // Assign random number to newly produced block block.randomNumber = random.data; @@ -77,6 +81,7 @@ async function waitAndProduce() { // Send event so delegates know where to get block tp.send(events.VERIFY_BLOCK, { port, + publicKey: me.publicKey.toString('hex'), block: { number: block.number, hash: block.hash, diff --git a/roles/delegate.js b/roles/delegate.js index 3a73fc1..07f78ca 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -8,7 +8,7 @@ const keccak256 = require('keccak256'); const math = require('lib/math'); -const events = require('lib/events'); +const evt = require('lib/events'); const Account = require('core/account'); const tp = require('core/transport'); const chain = require('core/db').chain; @@ -41,12 +41,6 @@ const conf = require('lib/constants'); */ const DELEGATES = '*'; -// /** -// * Total number of responses to await. -// * @type {Number} -// */ -// const DELEGATES_COUNT = conf.ACTIVE_DELEGATES_COUNT + conf.ACTIVE_SUCCESSOR_DELEGATES_COUNT - 1; - /** * Number of ms to wait to receive other delegates' randoms. * @type {Number} @@ -58,8 +52,8 @@ const WAIT_TIME = conf.DELEGATE_WAIT_TIME || 3000; */ exports.attach = function attach() { - tp.on(events.START_ROUND, exchangeRandoms); - tp.on(events.VERIFY_BLOCK, blockVerification); + tp.on(evt.START_ROUND, exchangeRandoms); + // tp.on(evt.VERIFY_BLOCK, blockVerification); }; /** @@ -67,8 +61,8 @@ exports.attach = function attach() { */ exports.detach = function detach() { - tp.off(events.START_ROUND, exchangeRandoms); - tp.off(events.VERIFY_BLOCK, blockVerification); + tp.off(evt.START_ROUND, exchangeRandoms); + // tp.off(evt.VERIFY_BLOCK, blockVerification); }; /** @@ -95,31 +89,25 @@ async function exchangeRandoms() { // Get current block first const currentBlock = await chain.getLatest(); const state = parseState(currentBlock.state); - - // Get list of delegates from state const delegates = state.delegates; - // Let's use this variable as if it existed - const numDelegates = 1 || 33; - const myRandomNum = math.random().toString(10); - - // sign message - const messageWithRandom = { - random: myRandomNum, + // Start the delegate part + // 1. Generate and stream randmo number over network + const myRandom = math.random(); + const msgToSend = { + random: myRandom, publicKey: me.publicKey.toString('hex'), - signature: me.signMessage(myRandomNum).toString('hex') + signature: me.signMessage(myRandom).toString('hex') }; - tp.send(events.RND_EVENT, messageWithRandom, DELEGATES); - - console.log('SENDING MY RANDOM', myRandomNum); + const resPromise = waiter.collect(evt.RND_EVENT, WAIT_TIME); - const responses = await waiter.collect(events.RND_EVENT, WAIT_TIME); - const responseMessages = responses.map((r) => r.data); + tp.send(evt.RND_EVENT, msgToSend, DELEGATES); - responseMessages.forEach((msg) => console.log(Account.publicKeyToAddress(msg.publicKey))); - - const randomNumbers = responseMessages + // Message sent, other delegates did the same job + // 2. Wait for same action to be done by other delegates + const randomNumbers = (await resPromise) + .map((msg) => msg.data) .filter((msg) => delegates.includes(Account.publicKeyToAddress(msg.publicKey))) .filter((msg) => Account.verifyMessage(msg.random, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))) .map((msg) => +msg.random); @@ -129,66 +117,65 @@ async function exchangeRandoms() { // QUESTION: How to test setups when there's only runner X count? We'd have to run some test-ready setting. // if (randomNumbers.length < conf.ACTIVE_DELEGATES_COUNT) { } - // Do the math const finalRandomNum = math.finalRandom(randomNumbers); + const fresPromise = waiter.collect(evt.FRND_EVENT, 1000); // Collect FRN for a second - tp.send(events.FRND_EVENT, finalRandomNum, DELEGATES); + tp.send(evt.FRND_EVENT, finalRandomNum, DELEGATES); - const finalResponses = await waiter.collect(events.FRND_EVENT, 1000); + const finalResponses = await fresPromise; const resolution = math.votingResults(finalResponses.map((r) => r.data)); + const finalRandom = resolution[0].value; - // Most frequent final random number from delegates - const mostCommon = resolution[0]; - - console.log('MOST COMMON IS: ', mostCommon); - console.log('OTHER RESULTS: ', resolution); + console.log('Voting results are: ', finalRandom); + console.log('Other results for FRN: ', resolution); - if (mostCommon.count <= Math.floor(finalResponses.length / 2)) { + if (resolution[0].count <= Math.floor(finalResponses.length / 2)) { // QUESTION: what should we do programmatically when round is unsucceful? // I mean should we restart everything? Or only this function? Consensus to re-roll // has to be reached somehow. Think about it - console.log('ROUND UNSUCCESSFUL: ', mostCommon.count, parseInt(numDelegates / 2)); + console.log('Round unsucceful, retrying in 2 seconds'); + return waiter.wait(2000).then(exchangeRandoms); } - console.log('ROUND SUCCESSFUL, SENDING VALUE TO BP: %s', mostCommon.value); + console.log('Round successors, streaming: %s', finalRandom); - return tp.send(events.BP_CATCH_IT, mostCommon.value, '*'); -} + tp.send(evt.BP_CATCH_IT, finalRandom, '*'); -/** - * Verify block validity. - * - * This implies verification of: - * - state calculation - * - state root - * - receipts root - * - * @listens events.VERIFY_BLOCK - * - * @param {Number} port UDP port. - * @param {Object} msg Message description. - * @param {Object} meta UDP information. - * @return {Promise} - */ -async function blockVerification({port, block: short}, msg, meta) { + // X. Wait for producer to produce block (but also verify incoming blocks) + const nextProducer = math.findProducer(finalRandom, state.blockProducers); + const peerData = await waiter.waitForCond(evt.VERIFY_BLOCK, ({publicKey}) => { + return (Account.publicKeyToAddress(publicKey) === nextProducer); + }, 1000); - console.log('VERIFYING BLOCK: %s', JSON.stringify(short)); + console.log('Verifying received block'); - const rawData = await peer.pullString(meta.address, port).catch(console.error); - const block = JSON.parse(rawData); + const address = peerData.meta.address; + const port = peerData.data.port; + const block = await peer.pullString(address, port).then(JSON.parse).catch(console.error); - if (block && await isValidBlock(block) && isValidBlockProducer(block)) { - console.log('streaming block over network'); - return streamBlock(block); + if (!block || !isValidBlock(block, currentBlock)) { + return console.log('Block is invalid!'); } - console.log('block is invalid', !!block, await isValidBlock(block), isValidBlockProducer(block)); + console.log('Streaming block over the network'); + + return streamBlock(block); +} + +/** + * Validate block. + * + * @param {Object} producedBlock Block produced by BP. + * @return {Promise} Whether block is valid or not. + */ +async function isValidBlock(producedBlock, parentBlock) { + const block = me.produceBlock(parentBlock, producedBlock.transactions); - // TODO Case when block is invalid. - return null; + return (producedBlock.stateRoot === block.stateRoo) + && (producedBlock.receiptsRoot === block.receiptsRoot); } /** @@ -196,7 +183,7 @@ async function blockVerification({port, block: short}, msg, meta) { * * @todo create Block type definition somewhere * - * @emits events.NEW_BLOCK + * @emits evt.NEW_BLOCK * * @param {Block} block Block to stream over network. * @return {Promise} Promise that ends with peering result or null when 0 nodes were online. @@ -204,37 +191,43 @@ async function blockVerification({port, block: short}, msg, meta) { async function streamBlock(block) { const nodesCount = tp.knownNodes.size - 1; + // QUESTION - need to rethink this function - rn it's unclear what's happening + // ALSO - decide something about signatures from delegates and include them into block + // If there's no one to share - why bother? if (nodesCount === 0) { return null; } - const {port, promise} = peer.peerString(block, nodesCount); + const {port, promise} = peer.peerString(block, nodesCount, 5000); + const hashedBlock = keccak256(JSON.stringify(block)).toString('hex'); + const signature = me.signMessage(hashedBlock).toString('hex'); - const hashedBlock = keccak256(JSON.stringify(block)).toString('hex'); - const signature = me.signMessage(hashedBlock).toString('hex'); + const otherSinatures = waiter.collect(evt.BLOCK_SIG, 1000); - tp.send(events.BLOCK_EVENT, { + tp.send(evt.BLOCK_SIG, { port, hashedBlock, publicKey: me.publicKey.toString('hex'), signature }, DELEGATES); - const numDelegates = 1 || 33; - const responses = await waiter.waitForAll(events.BLOCK_EVENT, numDelegates, Infinity); - const responseMessages = responses.map((r) => r.data); - const verifiedMessages = responseMessages.filter(msg => Account.verifyMessage(msg.hashedBlock, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); - const verifiedBlocks = verifiedMessages.map(msg => msg.hashedBlock); - - console.log('Verified blocks:', verifiedBlocks); - - if (verifiedBlocks.length < numDelegates) { - // TODO Case when not enough delegates verified block. - console.log('VERIFIED < NUMBER OF DELEGATES'); - } - - tp.send(events.NEW_BLOCK, { + const signatures = (await otherSinatures).map((msg) => msg.data); + + // const numDelegates = 1 || 33; + // const responses = await waiter.waitForAll(evt.BLOCK_SIG, numDelegates, Infinity); + // const responseMessages = responses.map((r) => r.data); + // const verifiedMessages = responseMessages.filter(msg => Account.verifyMessage(msg.hashedBlock, Buffer.from(msg.publicKey, 'hex'), Buffer.from(msg.signature, 'hex'))); + // const verifiedBlocks = verifiedMessages.map(msg => msg.hashedBlock); + // + // console.log('Verified blocks:', verifiedBlocks); + // + // if (verifiedBlocks.length < numDelegates) { + // // TODO Case when not enough delegates verified block. + // console.log('VERIFIED < NUMBER OF DELEGATES'); + // } + + tp.send(evt.NEW_BLOCK, { port, block: { number: block.number, @@ -244,43 +237,8 @@ async function streamBlock(block) { producer: block.producer }, publicKey: me.publicKey.toString('hex'), - signature + signatures }); return promise; } - - -/** - * Validate block producer. - * - * TODO Implement this function. - * - * @param {Object} block Block produced by BP. - * @param {Number} finalRandomNumber Final random number of current round. - * @return {Boolean} Whether block producer is a valid next BP or not. - */ -function isValidBlockProducer(block, finalRandomNumber) { - // return (block.producer === blockchain.getBlockProducer(block, finalRandomNumber)); - - - - finalRandomNumber; - block; - - return true; -} - -/** - * Validate block. - * - * @param {Object} producedBlock Block produced by BP. - * @return {Promise} Whether block is valid or not. - */ -async function isValidBlock(producedBlock) { - const parentBlock = await chain.getLatest(); - const block = me.produceBlock(parentBlock, producedBlock.transactions); - - return producedBlock.stateRoot === block.stateRoot - && producedBlock.receiptsRoot === block.receiptsRoot; -} From 37752e610752ece063f85fbe68e10e17513b69ed Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Tue, 7 May 2019 12:17:51 +0300 Subject: [PATCH 24/25] Fixed issue with no certificates block producers --- lib/block-state.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/block-state.js b/lib/block-state.js index bdbf924..c5b723c 100644 --- a/lib/block-state.js +++ b/lib/block-state.js @@ -141,7 +141,8 @@ function getProducersMap(state) { const producers = new Map(); for (let account of state) { - producers.set(account.address, Math.floor(account.locked / CERT_PRICE)); + const certs = Math.floor(account.locked / CERT_PRICE); + (certs > 0) && producers.set(account.address, certs); } return producers; From 4bab871685d9338eec36f2d28efc36ad1725e5ff Mon Sep 17 00:00:00 2001 From: "@damndam" Date: Tue, 7 May 2019 12:57:13 +0300 Subject: [PATCH 25/25] Fixed 10nth block issue with leveldb Leveldown uses first char to define 'last record' so 9th block was always a last one. From now on we store 'latest' block. --- core/account.js | 4 ++-- core/db.js | 39 ++++++++++++++++++++++++++------------- roles/delegate.js | 15 +++++++-------- services/observer.js | 14 ++++++++++++++ 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/core/account.js b/core/account.js index f6f7ff0..5eeb3bd 100644 --- a/core/account.js +++ b/core/account.js @@ -194,8 +194,8 @@ Account.prototype.produceBlock = function produceBlock(parentBlock, transactions block.receipts.push(blockchain.generateReceipt(block, txIndex, serializedTx, tx)); } - block.stateRoot = helpers.merkleRoot(block.state.map(account => JSON.stringify(account))); - block.receiptsRoot = helpers.merkleRoot(block.receipts.map(receipt => JSON.stringify(receipt))); + block.stateRoot = helpers.merkleRoot(block.state.map((account) => JSON.stringify(account))); + block.receiptsRoot = helpers.merkleRoot(block.receipts.map((receipt) => JSON.stringify(receipt))); return block; }; diff --git a/core/db.js b/core/db.js index 7f7aa61..6c1fcd9 100644 --- a/core/db.js +++ b/core/db.js @@ -133,7 +133,12 @@ class Chain extends DB { * @param {String} block Block to store */ add(number, block) { - return this.db.put(number, block.constructor === String && block || JSON.stringify(block)); + const toPut = (block.constructor === String) && block || JSON.stringify(block); + + return Promise.all([ + this.db.put(number, toPut), + this.db.put('latest', toPut) + ]); } /** @@ -155,15 +160,17 @@ class Chain extends DB { * @return {Promise} Promise with latest block or genesis */ getLatest() { - return new Promise((resolve, reject) => { - return this.db - .iterator({reverse: true, limit: 1}) - .next((err, key, value) => { - return (err) - && reject(err) - || resolve(value && JSON.parse(value.toString()) || this.genesis); - }); - }); + return this.db.get('latest').then(JSON.parse).catch(() => this.genesis); + + // return new Promise((resolve, reject) => { + // return this.db + // .iterator({reverse: true, limit: 1}) + // .next((err, key, value) => { + // return (err) + // && reject(err) + // || resolve(value && JSON.parse(value.toString()) || this.genesis); + // }); + // }); } /** @@ -172,13 +179,19 @@ class Chain extends DB { * @return {stream.Writable} Writable stream */ createWriteStream() { + let latest = 0; const db = this.db; const writeStream = new stream.Writable({ write(chunk, encoding, cb) { - const string = chunk.toString(); - const number = JSON.parse(string).number; + const string = chunk.toString(); + const number = JSON.parse(string).number; + const promises = [db.put(number, string)]; + + if (number > latest) { + promises.push(db.put('latest', string)); + } - return db.put(number, string).then(() => cb(null, string)); + return Promise.all(promises).then(() => cb(null, string)); } }); diff --git a/roles/delegate.js b/roles/delegate.js index 07f78ca..297ac6d 100644 --- a/roles/delegate.js +++ b/roles/delegate.js @@ -52,8 +52,7 @@ const WAIT_TIME = conf.DELEGATE_WAIT_TIME || 3000; */ exports.attach = function attach() { - tp.on(evt.START_ROUND, exchangeRandoms); - // tp.on(evt.VERIFY_BLOCK, blockVerification); + tp.on(evt.START_ROUND, exchangeRandoms); }; /** @@ -61,8 +60,7 @@ exports.attach = function attach() { */ exports.detach = function detach() { - tp.off(evt.START_ROUND, exchangeRandoms); - // tp.off(evt.VERIFY_BLOCK, blockVerification); + tp.off(evt.START_ROUND, exchangeRandoms); }; /** @@ -148,7 +146,7 @@ async function exchangeRandoms() { const nextProducer = math.findProducer(finalRandom, state.blockProducers); const peerData = await waiter.waitForCond(evt.VERIFY_BLOCK, ({publicKey}) => { return (Account.publicKeyToAddress(publicKey) === nextProducer); - }, 1000); + }, 2000); console.log('Verifying received block'); @@ -189,7 +187,7 @@ async function isValidBlock(producedBlock, parentBlock) { * @return {Promise} Promise that ends with peering result or null when 0 nodes were online. */ async function streamBlock(block) { - const nodesCount = tp.knownNodes.size - 1; + const nodesCount = 5 + tp.knownNodes.size; // QUESTION - need to rethink this function - rn it's unclear what's happening // ALSO - decide something about signatures from delegates and include them into block @@ -202,13 +200,14 @@ async function streamBlock(block) { const {port, promise} = peer.peerString(block, nodesCount, 5000); const hashedBlock = keccak256(JSON.stringify(block)).toString('hex'); const signature = me.signMessage(hashedBlock).toString('hex'); + const publicKey = me.publicKey.toString('hex'); const otherSinatures = waiter.collect(evt.BLOCK_SIG, 1000); tp.send(evt.BLOCK_SIG, { port, hashedBlock, - publicKey: me.publicKey.toString('hex'), + publicKey, signature }, DELEGATES); @@ -236,7 +235,7 @@ async function streamBlock(block) { random: block.randomNumber, producer: block.producer }, - publicKey: me.publicKey.toString('hex'), + publicKey, signatures }); diff --git a/services/observer.js b/services/observer.js index 443690d..d648127 100644 --- a/services/observer.js +++ b/services/observer.js @@ -58,6 +58,20 @@ exports.observe = () => tp } }) + .on(events.CREATE_POOL_SERVER, function createServer(data, msg) { + if (msg.sender !== this.transportId) { + const {port} = peer.peer(pool.createReadStream()); + this.send(events.POOL_SERVER_CREATED, port, msg.sender); + } + }) + + .on(events.CREATE_CHAINDATA_SERVER, function createServer(data, msg) { + if (msg.sender !== this.transportId) { + const {port} = peer.peer(chaindata.createReadStream()); + this.send(events.CHAINDATA_SERVER_CREATED, port, msg.sender); + } + }) + .on(events.PING, function areYouLookingForMe(data, msg) { // QUESTION: okay, we have PING event, what's next? How should it be used?