├── dev.js
├── Chainpro.png
├── .npmignore
├── .env.example
├── src
├── p2p
│ ├── types.js
│ ├── actions.js
│ ├── sockets.js
│ ├── index.js
│ └── handlers.js
├── http
│ ├── index.js
│ └── routes.js
├── index.js
├── config
│ └── index.js
├── block.js
└── chain.js
├── CHANGELOG.md
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── Chainpro.xml
├── package.json
└── CONTRIBUTING.md
/dev.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 | require('./src');
--------------------------------------------------------------------------------
/Chainpro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stremann/chainpro/HEAD/Chainpro.png
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Intellij Idea project files
2 | .idea
3 |
4 | # Project oncfiguration files
5 | .env
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | HTTP_PORT=3001
2 | P2P_PORT=6001
3 | P2P_PEERS=ws://localhost:6001
4 | NODE_ENV=development
--------------------------------------------------------------------------------
/src/p2p/types.js:
--------------------------------------------------------------------------------
1 | export const MessageType = { // eslint-disable-line import/prefer-default-export
2 | QUERY_LATEST: 0,
3 | QUERY_ALL: 1,
4 | RESPONSE_BLOCKCHAIN: 2
5 | };
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ======
3 |
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 | Every release is documented on the GitHub [Releases](https://github.com/stremann/chainpro/releases) page.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij Idea project files
2 | .idea
3 |
4 | # Project oncfiguration files
5 | .env
6 |
7 | # Nodejs installed packages
8 | node_modules
9 |
10 | # Nodejs log file
11 | npm-debug.log
12 |
13 | # Build artifacts
14 | dist
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '8'
4 | git:
5 | depth: 5
6 | script:
7 | - npm run lint
8 | - npm run clean
9 | - npm run build
10 | branches:
11 | only:
12 | - master
13 | cache:
14 | directories:
15 | - node_modules
--------------------------------------------------------------------------------
/src/http/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import bodyParser from 'body-parser';
3 |
4 | import routes from './routes';
5 |
6 | const app = express();
7 |
8 | app.use(bodyParser.json());
9 | app.use(bodyParser.urlencoded({extended: true}));
10 |
11 | app.use('/', routes);
12 |
13 | export default app;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import config from './config';
2 | import app from './http';
3 | import p2p, { initConnection, connectToPeers } from './p2p/index';
4 |
5 | app.listen(config.http.port, () => console.info(`HTTP server has been started on port: ${config.http.port} (${config.env})`));
6 |
7 | p2p.on('connection', ws => initConnection(ws));
8 | console.info(`P2P server has been started on port: ${config.p2p.port} (${config.env})`);
9 |
10 | connectToPeers(config.p2p.peers);
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import dotenv from 'dotenv';
3 |
4 | dotenv.config({
5 | path: path.join(__dirname, '../../.env')
6 | });
7 |
8 | const config = {
9 | env: process.env.NODE_ENV,
10 | http: {
11 | port: process.env.HTTP_PORT
12 | },
13 | p2p: {
14 | port: process.env.P2P_PORT,
15 | peers: process.env.P2P_PEERS ? process.env.P2P_PEERS.split(',') : []
16 | }
17 | };
18 |
19 | export default config;
--------------------------------------------------------------------------------
/src/p2p/actions.js:
--------------------------------------------------------------------------------
1 | import chain from '../chain';
2 | import { MessageType } from './types';
3 |
4 | export const queryChainLengthMsg = () => ({
5 | type: MessageType.QUERY_LATEST
6 | });
7 |
8 | export const queryAllMsg = () => ({
9 | type: MessageType.QUERY_ALL
10 | });
11 |
12 | export const responseChainMsg = () =>({
13 | type: MessageType.RESPONSE_BLOCKCHAIN,
14 | data: JSON.stringify(chain.get())
15 | });
16 |
17 | export const responseLatestMsg = () => ({
18 | type: MessageType.RESPONSE_BLOCKCHAIN,
19 | data: JSON.stringify([chain.last()])
20 | });
--------------------------------------------------------------------------------
/src/p2p/sockets.js:
--------------------------------------------------------------------------------
1 | const Sockets = (function () { // eslint-disable-line func-names
2 | let instance;
3 | const sockets = [];
4 |
5 | function get() {
6 | return sockets;
7 | }
8 |
9 | function update(ws) {
10 | return sockets.push(ws);
11 | }
12 |
13 |
14 | function remove(ws) {
15 | return sockets.splice(sockets.indexOf(ws), 1);
16 | }
17 |
18 | function create() {
19 | return {
20 | get,
21 | update,
22 | remove
23 | };
24 | }
25 |
26 | return {
27 | init() {
28 | if (!instance) {
29 | instance = create();
30 | }
31 | return instance;
32 | }
33 | };
34 | }());
35 |
36 | export default Sockets.init();
37 |
--------------------------------------------------------------------------------
/src/p2p/index.js:
--------------------------------------------------------------------------------
1 | import WebSocket from 'ws';
2 |
3 | import config from '../config/index';
4 | import sockets from './sockets';
5 | import { onMessage, onError, write } from './handlers';
6 | import { queryChainLengthMsg } from './actions';
7 |
8 | const p2p = new WebSocket.Server({
9 | port: config.p2p.port
10 | });
11 |
12 | export const initConnection = (ws) => {
13 | sockets.update(ws);
14 | onMessage(ws);
15 | onError(ws);
16 | write(ws, queryChainLengthMsg());
17 | };
18 |
19 | export const connectToPeers = (newPeers) => {
20 | newPeers.forEach((peer) => {
21 | const ws = new WebSocket(peer);
22 | ws.on('open', () => {
23 | console.log('Connection received to peer: ', peer);
24 | initConnection(ws);
25 | });
26 | ws.on('error', () => {
27 | console.log('Connection failed to peer: ', peer);
28 | });
29 | });
30 | };
31 |
32 | export default p2p;
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017-present Roman Stremedlovskyi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/block.js:
--------------------------------------------------------------------------------
1 | import CryptoJS from 'crypto-js';
2 |
3 | import chain from './chain';
4 |
5 | export const calcHash = ({index, prevHash, timestamp, data}) => CryptoJS.SHA256(index + prevHash + timestamp + data).toString();
6 |
7 | export const create = (data) => {
8 | const prev = chain.last();
9 | const index = prev.index + 1;
10 | const timestamp = new Date().getTime();
11 | const prevHash = prev.hash;
12 | const hash = calcHash({
13 | index,
14 | prevHash,
15 | timestamp,
16 | data
17 | });
18 |
19 | return {
20 | index,
21 | timestamp,
22 | data,
23 | prevHash,
24 | hash
25 | };
26 | };
27 |
28 | export const isNewBlockValid = (newBlock, prevBlock = chain.last()) => {
29 | let isValid = true;
30 |
31 | if (prevBlock.index + 1 !== newBlock.index) {
32 | console.log('New block has invalid index');
33 | isValid = false;
34 | } else if (prevBlock.hash !== newBlock.prevHash) {
35 | console.log('New block has invalid prevHash');
36 | isValid = false;
37 | } else if (calcHash(newBlock) !== newBlock.hash) {
38 | console.log('New block has invalid hash');
39 | isValid = false;
40 | }
41 |
42 | return isValid;
43 | };
44 |
--------------------------------------------------------------------------------
/src/http/routes.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 |
3 | import chain from '../chain';
4 | import { create } from '../block';
5 | import sockets from '../p2p/sockets';
6 | import { connectToPeers } from '../p2p/index';
7 | import { broadcast } from '../p2p/handlers';
8 | import { responseLatestMsg } from '../p2p/actions';
9 |
10 | const router = express.Router();
11 |
12 | router.get('/health-check', (req, res) => res.send('OK'));
13 |
14 | router.get('/chain', (req, res) => {
15 | res.setHeader('Content-Type', 'application/json');
16 | res.send(JSON.stringify(chain.get()))
17 | });
18 |
19 | router.post('/mine', (req, res) => {
20 | const block = create(req.body.data);
21 | chain.update(block);
22 | broadcast(responseLatestMsg());
23 | console.log('New block in chain has been added: ', block);
24 | res.send(block);
25 | });
26 |
27 | router.get('/peers', (req, res) => {
28 | res.send(sockets.get().map(s => `${s._socket.remoteAddress}:${s._socket.remotePort}`)); // eslint-disable-line no-underscore-dangle
29 | });
30 |
31 | router.post('/connect', (req, res) => {
32 | const { peer } = req.body;
33 | connectToPeers([peer]);
34 | console.log('New peer in p2p websocket has been added: ', peer);
35 | res.send(peer);
36 | });
37 |
38 | export default router;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Chainpro
2 | ========
3 | Easy Blockchain implementation for JavaScript apps.
4 |
5 | [](https://travis-ci.org/stremann/chainpro)
6 | [](https://www.npmjs.com/package/chainpro)
7 | [](https://www.npmjs.com/package/chainpro)
8 |
9 | ### Installation
10 |
11 | To install the stable version:
12 |
13 | ```
14 | npm install --save chainpro
15 | ```
16 |
17 | This assumes you are using [npm](https://www.npmjs.com/) as your package manager.
18 | If you don't, you can [access these files on unpkg](https://unpkg.com/chainpro/), download them, or point your package manager to them.
19 |
20 | ### Quick Start
21 |
22 | Set up two connected nodes:
23 |
24 | ```
25 | HTTP_PORT=3001 P2P_PORT=6001 npm run dev
26 | HTTP_PORT=3002 P2P_PORT=6002 P2P_PEERS=ws://localhost:6001 npm run dev
27 | ```
28 |
29 | #### HTTP API
30 |
31 | - `GET: /chain` - return current chain of your application.
32 | - `POST: /mine --data {"data": "Some block data"}` - create new block into the chain.
33 | - `GET: /peers` - return current peer list of your application.
34 | - `POST: /connect --data {"peer" : "ws://localhost:6002"}` - add peer to you application.
35 |
36 | ### Architecture
37 |
38 | 
39 |
40 | To get more details check the post on [Medium](https://medium.com/@stremann/blockchain-in-100-lines-of-code-50186a9a230).
41 |
42 | ### Change Log
43 |
44 | This project adheres to [Semantic Versioning](http://semver.org/).
45 | Every release is documented on the GitHub [Releases](https://github.com/stremann/chainpro/releases) page.
46 |
47 | ### License
48 |
49 | MIT
50 |
--------------------------------------------------------------------------------
/Chainpro.xml:
--------------------------------------------------------------------------------
1 |
2 | 7VpLk6M2EP41rsoeZgsQYHy057F7SKqmMlPZzVEGAaoREpHlsZ1fHwlJvIw9zhSeTDl2+SB1i1ZL39cNapiA22L7jcMy/40liEw8J9lOwN3E8yLPm6i/k+y0YOr5WpBxnGiR2wie8N/ICB0jXeMErToDBWNE4LIrjBmlKBYdGeScbbrDUka6s5YwQ3uCpxiSfekPnIjcSF3HaRTfEc5yM3UUGMUSxi8ZZ2tq5pt4IK1+Wl1Aa8uMX+UwYZuWCNxPwC1nTOhWsb1FRG2t3TZ93cMBbe03R1SccgGYuYGDIj9K3SDynfjGWHiFZI3sEipHxc5uziviAsu9mhOcUSkSrJyABTQ9glI582JVwhjT7Fnp7qJG8GulvvMaye9mG91KlsNSTROvl0h1NTMkLcAiwVxCjZmaZMXWaiMXKaPiyXjmy34uCqLGy2YFAlLLdLRdvc+qE7MCx6ZN4BKRRY3aLSOMSxVltJpfcPaCrFCC6VS/WmPJoSZMMSGtkQZ27eMDLDBRsfAH4gmk0LpulueZ/tBE+4haiCQMaNsSGYS/IVYgwXdyiNVattloNN1NQ20AjCxvsbpmKTThlNWmG0rJhmHViQzzrgy7OIYF4DMxDFwZdnEMA9Muw3x3n2JeOEAxNzgHxfwBioVEkWYpG5lqfH9+frRCab+WW1mCX2vRrTeZLzAViKcwRqq7WOwZrAW8LxkyL2XtGfoBcIEss9EbI7WR49DO6yU219+nnesO0O4srAvfTmzSgnxIVohscizQU6n4BO428jH9MnPLWVCfBh3U69tUO9kM3c7OAXr0dqr5gZYrFr8gMUa+uSaOMSgU+p8pccyuieNjUI8+UeKw7GqhviAyTcQ5xHQP//qplRWlxEhtwYHnYfP4C8ILIkOL3yPQwO8Fvx8OBP8QD8Kz8MDdD/YkQ/bMYUDoItnajwSu8krungAk5GKu6m892QNW7mqOqL6BRJ2pEE3sFaxEVEta42XPAhiejrPEju9+Gqerzp/K3lc3qrWPiGO5uSr0744eUuRBjcfojSCTq8qQODRIj1G7fpQ+HBEo8Gu3CDkuFwYS/6Vz4X3AgrExM5c+Mvnc1bpjuN1U4c16KUATy1zVQ752451kAEfJgMiSbe4bwWFanJUJ5opRs4LzNWjnhaNp/yhLZidEf/RBTOofVWfBxzHJ/58x6X1sCU9gi/957hXTo6AyLnKWMQrJFVl7tziKrHeWPCC3AO5aA0oV36sjaWLWPaL45sjSsERbHC83RFcajUmj0R8m30UjLzovjYZKnbo+JU+htEOn8K+1el0uD5wVF+Zq7dnyF3k3lH85kdNqfVFNhY6jALtJDZDzakQIi7JSAqBexLwacPvyrgn9ekcZcL1y29NpH5WSMl5A0lVvzMlvQE+QkCeTG3PiHhwiz6LixpzQ59UlqegqsQwhauw7Ld8qpeCQrlJp0hqX1LcDqgpQNXntfVUHqvWMJ13f9swnKGYc6lddaoAKZ05wa5IEr0oCzeZj2tGlhEEx4FjzecVND20vCGqUe+0vDUVsVVOue1J9PSI4I0StQrqdo2q6BLUqoZpsB0qhaqFDtYO9QsFAIjhce3Z7JSTf9Nu1A3+gdgBGqB1Mr1F3jbozR13plVXsFcWa4lg7q9gtye05TEYhN2G4+k/jMJiCXhwOlHJtbHbiMPjXcSi7zSdf+mbZfFYH7v8B
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chainpro",
3 | "version": "0.1.0",
4 | "description": "Chainpro",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "clean": "rimraf ./dist",
8 | "lint": "eslint ./src",
9 | "start": "node dist/index.js",
10 | "dev": "node dev.js",
11 | "build": "babel -d ./dist ./src -s",
12 | "lint-staged": "lint-staged",
13 | "prepublish": "npm run clean && npm run lint && npm run build"
14 | },
15 | "pre-commit": "lint-staged",
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/stremann/chainpro.git"
19 | },
20 | "keywords": [
21 | "chainpro",
22 | "blockchain",
23 | "database",
24 | "maining",
25 | "bitcoin"
26 | ],
27 | "author": "Roman Stremedlovskyi (https://github.com/stremann)",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/stremann/chainpro/issues"
31 | },
32 | "homepage": "https://github.com/stremann/chainpro#readme",
33 | "eslintConfig": {
34 | "root": true,
35 | "extends": [
36 | "airbnb-base",
37 | "prettier"
38 | ],
39 | "rules": {
40 | "no-console": 0
41 | },
42 | "env": {
43 | "node": true
44 | }
45 | },
46 | "babel": {
47 | "presets": [
48 | [
49 | "env",
50 | {
51 | "targets": {
52 | "node": "current"
53 | }
54 | }
55 | ]
56 | ]
57 | },
58 | "lint-staged": {
59 | "src/*.js": "eslint"
60 | },
61 | "dependencies": {
62 | "body-parser": "^1.18.2",
63 | "crypto-js": "^3.1.9-1",
64 | "dotenv": "^4.0.0",
65 | "express": "^4.16.2",
66 | "ws": "^3.2.0"
67 | },
68 | "devDependencies": {
69 | "babel-cli": "^6.26.0",
70 | "babel-core": "^6.26.0",
71 | "babel-preset-env": "^1.6.0",
72 | "eslint": "^4.9.0",
73 | "eslint-config-airbnb-base": "^12.0.2",
74 | "eslint-config-prettier": "^2.6.0",
75 | "eslint-plugin-import": "^2.7.0",
76 | "lint-staged": "^4.2.3",
77 | "pre-commit": "^1.2.2"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/chain.js:
--------------------------------------------------------------------------------
1 | import { calcHash, isNewBlockValid } from './block';
2 |
3 | const Chain = (function () { // eslint-disable-line func-names
4 | let instance;
5 | const origin = {
6 | index: 0,
7 | timestamp: 0,
8 | data: 'Hello Blockchain!',
9 | prevHash: 0,
10 | hash: calcHash({
11 | index: 0,
12 | prevHash: 0,
13 | timestamp: 0,
14 | data: 'Hello Blockchain!'
15 | })
16 | };
17 | const chain = [origin];
18 |
19 | function isChainValid(newChain) {
20 | let isValid = true;
21 |
22 | if (JSON.stringify(newChain[0]) !== JSON.stringify(origin)) {
23 | console.log('Received chain is invalid. Origin block does not coincide');
24 | isValid = false;
25 | return isValid;
26 | }
27 |
28 | const tempChain = [newChain[0]];
29 | for (let i = 1; i < newChain.length; i += 1) {
30 | if (isNewBlockValid(newChain[i], tempChain[i - 1])) {
31 | tempChain.push(newChain[i]);
32 | } else {
33 | isValid = false;
34 | return isValid;
35 | }
36 | }
37 |
38 | return isValid;
39 | }
40 |
41 | function get() {
42 | return chain;
43 | }
44 |
45 | function update(block) {
46 | if (isNewBlockValid(block)) {
47 | chain.push(block);
48 | }
49 | }
50 |
51 | function last() {
52 | return chain.slice().pop();
53 | }
54 |
55 | function replace(newChain) {
56 | if (isChainValid(newChain) && newChain.length > chain.length) {
57 | console.log('Received chain is valid. Replacing current chain with received chain');
58 | chain.length = 0; // clear current chain
59 | chain.push(...newChain); // fill current already empty chain with the new one if valid
60 | } else {
61 | console.log('Received chain is invalid');
62 | }
63 | }
64 |
65 | function create() {
66 | return {
67 | get,
68 | update,
69 | last,
70 | replace
71 | };
72 | }
73 |
74 | return {
75 | init() {
76 | if (!instance) {
77 | instance = create();
78 | }
79 | return instance;
80 | }
81 | };
82 | }());
83 |
84 | export default Chain.init();
85 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ======
3 |
4 | Chainpro is open to, and grateful for, any contributions made by the community.
5 |
6 | ## Reporting Issues and Asking Questions
7 |
8 | Before opening an issue, please search the [issue tracker](https://github.com/stremann/chainpro/issues) to make sure your issue hasn't already been reported.
9 |
10 | ### Bugs and Improvements
11 |
12 | Chainpro uses the issue tracker to keep track of bugs and improvements to Chainpro itself, its examples, and the documentation. I encourage you to open issues to discuss improvements, architecture, theory, internal implementation, etc. If a topic has been discussed before, please join the previous discussion.
13 |
14 | ### Help Us Help You
15 |
16 | It is a good idea to structure your code and question in a way that is easy to read to entice people to answer it. For example, use syntax highlighting, indentation, and split text in paragraphs.
17 |
18 | Please keep in mind that people spend their free time trying to help you. You can make it easier for them if you provide versions of the relevant libraries and a runnable small project reproducing your issue. You can put your code on [JSBin](http://jsbin.com) or, for bigger projects, on GitHub. Make sure all the necessary dependencies are declared in `package.json` so anyone can run `npm install && npm start` and reproduce your issue.
19 |
20 | ## Development
21 |
22 | Visit the [issue tracker](https://github.com/stremann/chainpro/issues) to find a list of open issues that need attention.
23 |
24 | Fork, then clone the repo:
25 |
26 | ```
27 | git clone https://github.com/your-username/chainpro.git
28 | ```
29 |
30 | Running the `build` task will create a build version of Chainpro.
31 |
32 | ```
33 | npm start
34 | ```
35 |
36 | ### Sending a Pull Request
37 |
38 | For non-trivial changes, please open an issue with a proposal for a new feature or refactoring before starting on the work. I don't want you to waste your efforts on a pull request that will not be accepted.
39 |
40 | On the other hand, sometimes the best way to start a conversation *is* to send a pull request. Use your best judgement!
41 |
42 | In general, the contribution workflow looks like this:
43 |
44 | * Open a new issue in the [issue tracker](https://github.com/stremann/chainpro/issues).
45 | * Fork the repo.
46 | * Create a new feature branch based on the `master` branch.
47 | * Make sure all tests pass and there are no linting errors.
48 | * Submit a pull request, referencing any issues it addresses.
49 |
50 | Please try to keep your pull request focused in scope and avoid including unrelated commits.
51 |
52 | After you have submitted your pull request, I'll try to get back to you as soon as possible.
53 |
54 | Thank you for contributing!
--------------------------------------------------------------------------------
/src/p2p/handlers.js:
--------------------------------------------------------------------------------
1 | import chain from '../chain';
2 | import sockets from './sockets';
3 | import { MessageType } from './types';
4 | import { queryAllMsg, responseChainMsg, responseLatestMsg } from './actions';
5 |
6 | export const write = (ws, message) => {
7 | console.log('Write message data to p2p socket: ', message);
8 | ws.send(JSON.stringify(message));
9 | };
10 |
11 | export const broadcast = (message) => {
12 | console.log('Broadcast message data to p2p socket: ', message);
13 | sockets.get().map(socket => write(socket, message));
14 | };
15 |
16 | const handleChainResponse = (message) => {
17 | const receivedBlocks = JSON.parse(message.data).sort((b1, b2) => (b1.index - b2.index));
18 | const latestBlockReceived = receivedBlocks[receivedBlocks.length - 1];
19 | const latestBlockHeld = chain.last();
20 |
21 | if (latestBlockReceived.index === latestBlockHeld.index) {
22 | console.log('Received chain is no longer than hold chain. Do nothing');
23 | return;
24 | }
25 |
26 | console.log(`Chain is possibly behind. We got: ${latestBlockHeld.index} Peer got: ${latestBlockReceived.index}`);
27 |
28 | if (latestBlockHeld.hash === latestBlockReceived.prevHash) {
29 | console.log('We can append the received block to our chain');
30 | chain.update(latestBlockReceived);
31 | broadcast(responseLatestMsg());
32 | } else if (receivedBlocks.length === 1) {
33 | console.log('We have to query the chain from our peer');
34 | broadcast(queryAllMsg());
35 | } else {
36 | console.log('Received chain is longer than current chain. Replace chain');
37 | chain.replace(receivedBlocks);
38 | }
39 | };
40 |
41 | export const onMessage = (ws) => {
42 | ws.on('message', (data) => {
43 | const message = JSON.parse(data);
44 | console.log(`Received message: ${JSON.stringify(message)}`);
45 |
46 | switch (message.type) {
47 | case MessageType.QUERY_LATEST:
48 | write(ws, responseLatestMsg());
49 | break;
50 | case MessageType.QUERY_ALL:
51 | write(ws, responseChainMsg());
52 | break;
53 | case MessageType.RESPONSE_BLOCKCHAIN:
54 | handleChainResponse(message);
55 | break;
56 | default:
57 | console.log('Received message type is out of scope');
58 | break;
59 | }
60 | });
61 | };
62 |
63 | export const onError = (ws) => {
64 | const closeConnection = (peer) => {
65 | console.log(`Close connection to peer: ${peer.url}`);
66 | sockets.remove(peer);
67 | };
68 | ws.on('close', () => closeConnection(ws));
69 | ws.on('error', () => closeConnection(ws));
70 | };
71 |
72 |
--------------------------------------------------------------------------------