├── 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 | [![Build Status](https://travis-ci.org/stremann/chainpro.svg?branch=master)](https://travis-ci.org/stremann/chainpro) 6 | [![NPM Version](https://img.shields.io/npm/v/chainpro.svg)](https://www.npmjs.com/package/chainpro) 7 | [![NPM Downloads](https://img.shields.io/npm/dm/chainpro.svg?style=flat-square)](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 | ![Architecture](Chainpro.png) 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 | --------------------------------------------------------------------------------